**DELIVERY SKILL** — Produce polished diagrams, slides and social cards as crisp PNGs, JPEGs or PDFs by authoring HTML/SVG and rendering it with a local headless Chromium. Eight themes (ember, slate, paper, terminal, blueprint, frost, neon, sepia), one flag. Lays mermaid flowchart text out into a figure, deterministically. Also frames screenshots and photos in a browser, phone or terminal frame, or cropped to a shape. USE FOR: architecture and flow diagrams, slide decks, og/social cards, blog and README figures, any designed image a doc needs. DO NOT USE FOR: data charts from datasets (use a plotting library), capturing a live UI, photo retouching, or any surface without a shell — write inline SVG. TRIGGERS: mermaid, flowchart TD, graph TD, diagram, flow chart, architecture visual, swimlane, tree view, cluster, deployment, mind map, slide deck, presentation, og card, social card, banner, blog figure, browser frame, device mockup, render a png, export a pdf or jpeg, make an image, visual.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Der Befehl bleibt in einer Zeile. Scrollen Sie horizontal, um ihn vor dem Kopieren vollständig zu prüfen.
Sie bevorzugen eine lokale Kopie? Laden Sie die Dateien herunter, die SkillsMP derzeit vorliegen.
Datei-Explorer
92 Dateien
SKILL.md wird angezeigt
SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
render-visual
description
**DELIVERY SKILL** — Produce polished diagrams, slides and social cards as crisp PNGs, JPEGs or PDFs by authoring HTML/SVG and rendering it with a local headless Chromium. Eight themes (ember, slate, paper, terminal, blueprint, frost, neon, sepia), one flag. Lays mermaid flowchart text out into a figure, deterministically. Also frames screenshots and photos in a browser, phone or terminal frame, or cropped to a shape. USE FOR: architecture and flow diagrams, slide decks, og/social cards, blog and README figures, any designed image a doc needs. DO NOT USE FOR: data charts from datasets (use a plotting library), capturing a live UI, photo retouching, or any surface without a shell — write inline SVG. TRIGGERS: mermaid, flowchart TD, graph TD, diagram, flow chart, architecture visual, swimlane, tree view, cluster, deployment, mind map, slide deck, presentation, og card, social card, banner, blog figure, browser frame, device mockup, render a png, export a pdf or jpeg, make an image, visual.
license
MIT
compatibility
Requires shell command execution, Node 18+, and a local Chromium-based browser (Chrome, Chromium, Brave, or Edge); set CHROME_PATH if it is installed somewhere unusual. Theme fonts load from fonts.googleapis.com, so renders without network access fall back to system fonts. Cannot run where there is no shell or no browser: claude.ai chat, the Skills API, Cowork and cloud sessions, and most CI images.
metadata
{"version":"1.0.5"}
render-visual — designed PNGs from HTML, no design tool
Every image this skill produces is a small HTML/SVG page screenshotted by headless Chrome:
fully versioned, diffable, regenerable in seconds when the copy changes, and styled by a theme
file instead of hand-picked colors.
Template
Canvas
For
$SKILL/templates/diagram.html
1360×740
Architecture and flow figures: nodes, labeled arrows, a return path, a line-crossing bridge
$SKILL/templates/slide.html
1920×1080
Presentation slides: kicker, headline with a gradient phrase, up to three points, footer
Sequence diagrams: lifelines, calls and returns, activations — static, or animated step by step
$SKILL/templates/swimlane.html
1360×740
Cross-functional flows: lanes that own the steps, labelled handoffs, an exception path that never leaves its lane
$SKILL/templates/tree.html
1360×740
Hierarchies, both idioms: an indented tree view (files, nav, an outline) beside the same tree drawn node-link
$SKILL/templates/cluster.html
1360×740
What runs inside a boundary: control plane, worker nodes, pods, and the state that outlives a rebuild
$SKILL/templates/deployment.html
1360×740
Which artifact lands on which machine, over which protocol: el-cube nodes, «stereotypes», artifact chips — see §5
$SKILL/templates/flowchart.html
computed
Mermaid flowcharts. A vertically long process chart — decisions, branch labels, subgraph boundaries, retry loops. Never hand-authored: scripts/mermaid.mjs generates it from mermaid text — see §10
$SKILL/templates/mindmap.html
1360×740
A question in the middle and the answers around it: six branches, two leaves each, one branch emphasised
$SKILL/templates/code.html
1360×740
Code snippets in a themed window (Carbon-style): hand-highlighted tokens, line numbers, a highlight line, diff rows — see §7
$SKILL/templates/charts.html
parts sheet
Browsable catalogue of the chart and BI parts — pie, donut, bar, line, area, stacked, scatter, funnel, gauge, heatmap, sparkline, KPI tile, table, dashboard. Schematic figures, not plotted data — see §5
$SKILL/templates/palette.html
1360×980
Theme specimen: the tokens, both fonts, the gradient, the alpha ladders, the parts dressed by that theme. A catalogue, never a deliverable — see §4
$SKILL/templates/elements.html
parts sheet
Browsable catalogue of the frames, infrastructure shapes, deployment node and icon glyphs in $SKILL/parts/. Reference them, don't copy them. Never a deliverable — see §5
Theme
Mood
Fonts
ember
Warm dark — amber-hued neutrals, cyan/ember/magenta accents
Inter Tight + JetBrains Mono
slate
Cool dark — violet-leaning neutrals, jewel accents
Every path in this file is relative to the directory holding this SKILL.md, not to the
working directory. You are almost never invoked from inside the skill — it is installed
somewhere central and you are working in someone's project — so resolve that directory once
and prefix every command below with it. $SKILL is written for it throughout.
If you have lost that path, find it — the layout is always <skill>/scripts/render.mjs:
ls -d ~/.claude/skills/render-visual ~/.agents/skills/render-visual \
./.claude/skills/render-visual ./.agents/skills/render-visual 2>/dev/null
Nothing here needs the working directory to be the skill. Outputs go wherever you say.
1. Workflow
Settle theme, template and format before touching a file. Three choices shape
everything after, and none of them is yours to default silently:
Theme. A theme named in the request wins. Otherwise use the one recorded in
.render-visual.json (below). Failing both, ask — offer the theme table above by
mood and let the user pick. Never choose silently: the wrong palette is a re-render
at best and an off-brand deliverable at worst.
Template. Read the task against the template table's "For" column and shortlist
what fits. A request that names one — "a swimlane", "an og card" — is the answer.
One clear match: take it and say which in the handoff. Several plausible: ask,
naming the shortlist and what each would emphasise. Mermaid flowchart text settles
this on its own: generate it, do not pick a template and draw it by hand — §10.
Format. PNG, JPEG or PDF. A request that names one — "as a PDF", "make it a
jpg" — is the answer, and so is an output path that already ends in .pdf or
.jpg. Otherwise use the format recorded in .render-visual.json. Failing both,
ask, naming what each is for: PNG for anything going into a README, a web page
or a social card, and the only one that can carry transparency; JPEG for a
photo-heavy figure or an attachment where weight is the constraint, at a third to a
half the bytes; PDF for print, for a figure that will be zoomed, or for a deck
that wants vector. The destination usually settles it — ask about the destination
rather than the file extension if that is easier to answer.
Where nobody can answer — CI, a scripted run — fall back in the same order: the file,
then the closest match, then PNG, stated plainly in the output.
Remember the answers. A project that has decided once should not be asked twice.
Standing choices live in .render-visual.json at the project root — theme, where
finished images land, scale, anything settled that would otherwise be re-asked:
Read it before asking anything; after a first round of answers, offer to write it —
ask before creating it, it lives in the user's project. The prompt always beats the
file. The renderer itself never reads it: pass --theme explicitly on every command,
so a typo still fails loud instead of falling back to a wrong palette.
Copy the nearest template into the working directory (never edit the templates in
place — they are the exemplars):
cp "$SKILL/templates/diagram.html" my-figure.html
Nothing else needs copying. --theme inlines the theme's CSS at render time, so no
themes/ directory has to sit beside your figure.
Replace the placeholder content. Structure before prose: nodes and arrows for a
diagram, one claim per slide, one key fact per chip.
Look at the PNG. Read it back and inspect it before calling it done — every first
render has a defect a human spots instantly: a label overlapping a node, a line bisecting
text, a heading a word too long for its box. Fix and re-render; iteration is cheap.
2. The renderer
$SKILL/scripts/render.mjs — plain Node, zero dependencies.
Size is read from the width: Npx; height: Mpx on the input's <body> — keep that
declaration intact. --size overrides.
Scale defaults to 2: a 1360×740 canvas becomes a 2720×1480 PNG, right for retina,
Medium, and READMEs.
--format png|jpeg|pdf picks the output format. Leave it off and the output file's
own extension decides, so card.jpg and poster.pdf need no flag. The two never
disagree quietly: out.png --format pdf is fatal rather than resolved, because either
way of guessing writes a file whose name lies about its contents.
PNG is the master and the default — lossless, the only one that can carry alpha.
JPEG is transcoded from a master PNG that has already passed the content guard,
so it costs one extra Chrome pass and lands at roughly half the bytes. It has no
alpha channel, so --transparent is refused rather than silently blackened.
PDF is printed in the same launch as a proof screenshot, at exactly the canvas
size: an injected @page rule pins the page box and the box is checked afterwards.
Without that rule Chrome quietly lays the figure out on US Letter and exits 0, which
is why the input needs a <head> to inject into. PDF is vector, so --scale is
refused — it is already resolution-independent at any zoom.
--theme inlines one of the skill's own themes into a temp copy of the page, so one
source file renders in any theme and the figure needs no themes/ directory beside it.
An unknown name is fatal and lists what exists; a page with no theme stylesheet to
replace is fatal too, because rendering it unthemed would quietly give you the wrong
colours.
--transparent renders with a real alpha channel: the body's ground is unpainted and
the glow/dots furniture is stripped, so the figure drops onto any surface — docs and
slides with their own backgrounds, web pages, video overlays. Composes with --theme
(pick paper over light surroundings, a dark theme over dark). Note --surface keeps its
8% translucency, so a busy backdrop shows through faintly — a frosted-glass look; if that
fights the content, place the PNG on a calmer area.
<g data-part="..."> is replaced with that part's markup from $SKILL/parts/ before
the render, so a figure references shapes instead of carrying copies of them. See §5.
data-image="./shot.png" puts an image on the page — inside a device frame, cropped to
a shape, or as a background. Local files only; they are read and inlined before the render,
and anything unreadable is fatal rather than an invisible hole. See §6.
A missing local stylesheet is fatal, and checked before Chrome launches. Every color
is a theme token, so a broken <link> does not degrade the render — it empties it, into a
blank white page that screenshots as a perfect success. Passing --theme sidesteps this
entirely; without it, keep the page's own stylesheet resolvable.
A contentless render is fatal too, in every format. After the screenshot the PNG is
decoded and rejected if its luminance spread says nothing but background and furniture
got painted — the failure a resolved-but-empty stylesheet or content positioned outside
the body box produces, which Chrome otherwise reports as a perfect success. A truncated
PNG and one that came out the wrong size are rejected in the same pass. JPEG and PDF are
covered by the same guard rather than a weaker one, which is the reason for the shapes
above: a JPEG is only ever encoded from a master that passed, and a PDF is only ever
accepted beside a proof shot that passed. The rejected file is left in place so you can
look at it — including the proof or master, which stays in $TMPDIR/render-visual/ until
the next render reaps it.
Chrome discovery: standard install paths on macOS, Linux, and Windows are probed,
then PATH; CHROME_PATH overrides and is fatal if it does not resolve. Any Chromium
works (Chrome, Chromium, Brave, Edge). On Ubuntu the snap-packaged Chromium gets a
private /tmp and cannot see the paths written for it — prefer a deb Chrome or Brave
there, or set CHROME_PATH.
Chrome often hangs after writing the screenshot. The renderer waits on the output
file, not the process, then kills it — with SIGKILL, not SIGTERM: headless Chrome
swallows SIGTERM during startup and lives on holding its profile. Do the same in any
custom pipeline.
Everything temporary lives in one place, $TMPDIR/render-visual/: warm Chrome
profiles (profile, profile-1, …) claimed per process via pid lockfiles, the prepared
page copies, and animation frames. Concurrent renders never share a profile (Chrome would
refuse) and each stays warm; a cold one costs first-launch setup. Every run reaps Chromes
left on an unclaimed slot before it starts, and a render that still hits a bad slot
retries on another, so an interrupted run cannot wedge the next one. Nothing is written
into the directory you are rendering from — a read-only input directory works fine.
Theme fonts come from fonts.googleapis.com. With no network the render still
succeeds, but falls back to system fonts and will not match the previews. If that matters,
render on a connected machine or accept the substitution — the layout is unaffected.
The harness
Two scripts back the guarantees above. Neither is needed for a normal render.
node "$SKILL/scripts/doctor.mjs" # preflight: reap orphans, clear stale locks, report
node "$SKILL/scripts/doctor.mjs" --prune # also delete idle warm profiles to reclaim disk
node "$SKILL/scripts/selftest.mjs" # ~2m: assert every concurrency and cleanup invariant
node "$SKILL/scripts/selftest.mjs" --quick # ~90s: same, minus the animation test
Reach for doctor.mjs when renders fail with "is another instance using profile" — that
is an orphaned Chrome holding a slot, and --prune also answers "why is my temp dir
hundreds of MB". Run selftest.mjs after changing anything in scripts/: it renders into
a throwaway TMPDIR, so it never touches the machine's warm profiles and can assert that
it left nothing behind. It covers concurrent renders agreeing byte for byte, overlapping
--theme runs not contaminating each other, interrupted runs leaving no orphan, and both
blank-render guards.
3. Animated sequence diagrams — no ffmpeg required
$SKILL/templates/sequence.html marks every message with data-step="N". Opened plain it renders
the complete diagram (a static figure). Opened with ?step=N it shows steps below N dimmed,
step N highlighted, and the rest hidden. ?f=0..1 is the tween fraction within that step's
reveal, and ?fx= picks the reveal preset — so every frame of a smooth animation is still
just a URL.
$SKILL/scripts/animate.mjs renders each step as a short tween (several frames at --fps, in
parallel Chrome instances) plus a dwell frame, and assembles a looping GIF in pure Node:
Chrome's PNG frames are decoded with the built-in zlib, median-cut quantized to a shared
palette with ordered dithering, and LZW-encoded by hand. Frames after the first store only
the changed region (transparent-pixel deltas), so tween frames cost little. GIF is a 1989
format; it does not need ffmpeg.
node "$SKILL/scripts/animate.mjs" my-sequence.html my-sequence.gif --theme ember --fx pop
Flag
Default
--fx
slide
reveal preset: slide (fade + drop-in), fade, pop (scale overshoot); data-fx="name" on any element overrides per element
--fps
25
tween frame rate; 20/25/50 play back exactly (GIF timing is 10ms units, 50fps is the format's ceiling)
--transition
450
ms of tween per step reveal; 0 = the old one-frame-per-step look
--delay
900
ms of dwell on each completed step
--hold
2600
ms on the final, complete frame before the loop restarts
--jobs
4
parallel Chrome instances (each keeps its own warm profile)
--scale
1
GIFs get heavy fast — stay at 1x unless the canvas is small
--theme / --size
—
as in render.mjs
--keep-frames
off
keep the per-frame PNGs for inspection
Images (§6) work here too and are read once for the whole run, not once per frame — but
every frame still carries them, so keep a source that appears in a GIF small.
Rules of thumb: steps are numbered 1..N with no gaps; give activations the step of the call
that starts them; a GIF that tells the story in under ~8 steps is one people actually watch.
GitHub READMEs autoplay GIFs; Medium accepts them as uploads.
4. The aesthetic rules
Themes define tokens; templates consume only tokens. Never hard-code a color in a template.
Accent semantics — one accent per meaning, never decoration:
Token
Meaning
--a1
The primary thing, forward paths
--a2
Actions, returns, what flows back
--a3
External parties, alternates, alerts
--a4
Success, confirmation
Alpha grades — every colour token has a component twin: --a1-raw…--a4-raw,
--ink-raw, --ground-raw, --surface-raw, each three bare OKLCH numbers. Ask them for any
transparency you need:
.badge { background: oklch(var(--a1-raw) / 12%); border: 1px solid oklch(var(--a1-raw) / 45%); color: var(--a1); }
.scrim { background: oklch(var(--ground-raw) / 72%); } /* a caption band over a photo */
The solid token is built from the same components (--a1: oklch(var(--a1-raw))), so a wash
can never drift from the colour it is a wash of. Grades that read: 6–12% a wash to sit
text on, 20–35% a fill meant to be seen, 45% an edge, 70%+ a scrim over an image.
A grade is still the accent — the semantics above hold, so a 12% --a3 wash means external,
not a nice pink. In SVG this needs a CSS class like every other var(); a presentation
attribute will not resolve it. templates/palette.html renders the whole ladder for whichever
theme you hand it, which is the fastest way to pick a grade.
Gradient text (--grad) is for one phrase per image, on the headline. The ramp is
tuned per theme; do not compose your own across near-complementary hues — the midpoint
greys out.
Furniture stack, bottom to top: ground → blurred glow circles (--glow1/--glow2 at
--glow-opacity; paper and sepia set it to 0, frost keeps a pale one) → the dot lattice (masked radially so it
fades at the edges) → content. Do not add new furniture kinds; restraint is the style.
Diagrams: nodes are rounded rects (rx=14) in --surface with a --line border, a
small stroke glyph in the node's accent, a display-font title, and a mono subtitle. Arrows
are 1.8px with 7px triangle markers in the line's own color; labels sit above the line
in the same color. In SVG, set colors via CSS classes (.s1/.f1 etc.) — var() does not
resolve in presentation attributes.
Type: display font for titles only; mono for labels, chips, kickers, footers, and
anything technical. Kickers are small mono, letter-spaced, uppercase, --ink-faint.
5. The element library
$SKILL/parts/ holds 57 ready-made pieces, one .svg per part, each a single <g> anchored
at its own top-left and built purely from theme tokens. Reference one instead of copying
it — a data-part attribute is replaced with the part's markup at render time:
cat "$SKILL/parts/el-server.svg" shows one part's footprint and which coordinates move
together when you stretch it. To see them drawn, render templates/elements.html (frames,
shapes, the deployment cube, glyphs) or templates/charts.html (charts and BI) — catalogues, never deliverables,
and both built from the same includes.
data-accent="1..4" recolors the part's accent pieces to --a1…--a4 (§4). Anything
the part colors deliberately — el-server's health LEDs are f4 — is left alone, as is
the neutral frame. Putting an accent class in class= instead is fatal: which one won
would be CSS source order rather than intent.
An accent the part already uses is fatal.data-accent="4" on el-server would paint
the rack the same colour as its health LEDs, and on el-stat-tile the same colour as its
delta chip. Every other part spends exactly one accent, so data-accent works on all of
them.
Call-site attributes win over the part's own, and anything you nest inside the call
site is kept after the part's markup — so a caption can ride along with the shape:
The page must define the element utilities — .node, .s1–.s4, .f1–.f4,
.sline .fline .sfaint .ffaint .fground .glyph. diagram.html, sequence.html and
elements.html already carry the block. A page missing them is fatal before Chrome
launches, because parts carry no color of their own and undefined utilities render as
invisible or unstroked shapes on an otherwise perfect PNG.
Includes are not recursive. A data-part inside another part's call site is skipped
and then fatal — the substitution runs one pass. Place the part, then draw its contents in a
sibling<g> on the same origin; templates/deployment.html fills el-cube that way.
An unknown part id is fatal too, and lists what exists.
Frames are nodes in their own right: caption them with a .title/.sub pair beneath, or
replace the faint skeleton bars with real mono <text> when the content matters. They also
take a real screenshot — data-image fills the frame's screen, see §6. Glyphs drop
into a standard node card at transform="translate(20,30)" in place of the stock circle
glyph; they inherit stroke 1.6 and round caps from .glyph.
The chart parts are schematics of charts, not charts. Every proportion in them is fixed
and arbitrary — el-chart-pie is always 40/25/20/15, el-chart-gauge always reads 68%. They
exist to say "a dashboard goes here" in an architecture figure, the way el-database says
"a database goes here". Never present one as data. If the numbers are the point, edit the
geometry to the real values and say so, or plot the dataset with a real charting library —
that is still the answer this skill's own description gives for data charts.
Charts hold to the same accent discipline as everything else (§4): one accent per part,
graded by opacity where categories have to read apart — pie wedges, funnel stages, stack
levels, heatmap cells. el-chart-scatter separates its two series by solid dots against
hollow rings rather than by spending a second hue. Nothing here treats an accent as
decoration, so a chart sits inside a figure without competing with the arrows around it.
Copying a part's geometry into your figure still works and renders identically — reach for it
only when you need to edit the shape itself.
6. Images
Any image on disk can go into a figure: a screenshot inside a device frame, a photo cropped
to a circle, a logo on a card, a photograph behind a headline. One attribute does all of it.
On a framing part, data-image fills its screen.el-browser, el-window,
el-terminal, el-phone and el-dashboard each declare the rectangle their content sits
in; the image is cropped to the shell's own corners and covers the skeleton bars the frame
ships with. Every other part refuses data-image and lists the ones that take it — for any
other shape, place an <image> yourself.
Knob
Default
data-fit
cover
cover fills the box and crops the overflow · contain fits the whole image and letterboxes it · stretch distorts to fill
data-align
center
which part survives a cover crop: centertopbottomleftright. top is what a long page screenshot wants
data-shape
rect
on <image> and <img> only: rectroundedcirclehex
data-radius
16
corner radius for data-shape="rounded"
Sources are local files. A relative path resolves against the HTML file's own
directory, not the working directory; absolute paths and file: URLs work too. A remote
URL is refused: one that 404s or loads slowly leaves an invisible hole in a figure that
still screenshots as a success. curl -fsSL -o image.png "…" first.
The bytes are inlined as data: URIs into the temp copy before Chrome launches, so the
prepared page is self-contained and your source file is untouched. PNG, JPEG, GIF, WebP,
AVIF, BMP and SVG. HEIC and TIFF are refused with the command to convert them — Chrome
decodes neither.
Every failure is fatal and pre-launch, naming the path it resolved: a missing file, a
directory, an empty file, a file that is not an image whatever its extension, one over
12 MB, or a page whose images total more than 32 MB.
An <image> needs an explicit width and height. Sized only by its intrinsic
dimensions it is a coin flip across Chrome builds, and the losing side is an invisible
image inside a frame that drew perfectly. The error reports the image's natural size so you
can paste it in.
url() in a <style> block or a style= attribute is inlined too, so
background-image: url(./hero.jpg) works as written. A remote url() is passed through to
Chrome untouched, like the theme's font import.
A plain <img src> is inlined and otherwise left exactly as written. The data-*
vocabulary is opt-in; nothing here restyles markup that did not ask for it.
The render reports what it placed — name, pixel size, format, weight. Read that line:
it is the cheapest way to notice you just framed last week's screenshot.
Discipline: an image is content, not furniture. One per figure carries a point; three compete
for it. Crop to what matters with data-align rather than shrinking the whole thing so it
fits, and caption a frame the way you would any other node. A raw rectangle of UI dropped on
a themed ground reads as a mistake — put screenshots in a frame.
7. Code snippets
$SKILL/templates/code.html is a Carbon-style code window — surface, traffic dots, filename, a
language chip — floating on the themed ground with a --shadow (a token, like every other
color). The window's width is the one layout knob; height follows the lines.
You are the highlighter. There is no highlighting library; wrap tokens by hand in
exactly these six classes, and no others:
Class
Token kind
Color
.kw
keywords, control flow
--a3
.fn
functions and methods
--a1
.ty
types, classes
--a1, weight 500
.str
strings
--a2
.num
numbers, booleans, constants
--a4
.com
comments
--ink-faint, italic
Everything else (variables, properties, punctuation) stays --ink. The mapping is fixed
across themes — don't invent per-language variations.
Line rows: one <div class="line"> per line (empty divs render as blank lines; numbers
come from a CSS counter). .hl marks the line the figure is about — one per figure, like
the gradient phrase. .add/.del rows make a diff; their gutter sign replaces the line
number. Escape &, <, > in the code text.
Discipline: ~18 lines is the ceiling — a snippet that needs more is two figures. Keep
2-space indents so long lines survive. Real code beats lorem: verify the snippet compiles
in your head before shipping it. Rendered with --transparent, the window becomes a
shadowed sticker for embedding anywhere.
8. Layout discipline
Check these before rendering, not after:
Measure labels against gaps. Mono runs ≈ 0.6 em per character; a 12.5px label needs
~7.5px per character, and centred between two nodes it must clear both edges.
Crossings get a bridge. Where a vertical line must cross a horizontal one, hop it
with a small half-circle: V y+9 A 9 9 0 0 0 x y-9 V … — never just draw through.
The body is the canvas. Its CSS width/height must equal the render size exactly,
with overflow: hidden. Anything positioned outside is silently cropped.
Three points per slide is the ceiling. A diagram that needs more than ~7 nodes is two
diagrams.
9. Presentations
A deck is one HTML file per slide, numbered so order is explicit:
deck/
├── 01-title.html (copy of slide.html, headline only, points removed)
├── 02-problem.html
├── 03-how.html (a diagram canvas resized to 1920×1080 works as a full-bleed slide)
└── ...
Render the deck:
mkdir -p out
for s in deck/*.html; do
node "$SKILL/scripts/render.mjs" "$s" "out/$(basename "${s%.html}").png" --theme ember
done
Keep one theme per deck. The footer's NN / NN is manual — update it per slide. To ship a
single file, combine the PNGs into a PDF with whatever the machine has
(magick out/*.png deck.pdf, or print the folder from any viewer).
10. Mermaid flowcharts — generated, not drawn
If the flow arrives as mermaid, do not translate it by hand. A fenced ```mermaid
block, a .mmd file, or any text opening flowchart TD / graph LR is the trigger, and
$SKILL/scripts/mermaid.mjs turns it into a laid-out page. Transcribing one into SVG
yourself is slow, and it silently loses steps — which is a flowchart missing a box, in a PNG
that renders perfectly.
node "$SKILL/scripts/mermaid.mjs" flow.mmd flow.html --theme slate \
--title "How a request is served" --kicker "edge tier" --note "One sentence of context."
node "$SKILL/scripts/render.mjs" flow.html flow.png --theme slate
- reads the source from stdin, so pasted text needs no file:
printf 'flowchart TD\n A[Start] --> B{Ready?}\n B -->|yes| C([Ship])\n' \
| node "$SKILL/scripts/mermaid.mjs" - flow.html --theme ember
Two steps, not one, and the middle file is the point.flow.html is ordinary themed
SVG built from $SKILL/templates/flowchart.html — so reword a label, nudge a node, or drop
in a data-part glyph before the shot, and re-render it in any theme with --theme. The
generator prints the exact render command to run next.
What it draws. The flowchart subset of mermaid, and only that:
-->----.->==>--o--x<-->, labelled as -->|text| or -- text -->
Statements
chains A --> B --> C, fan-out A & B --> C, subgraph … end, classDef, class, :::name, %% comments, --- front matter (its title: becomes the figure's)
Directions
TDTBBTLRRL, overridable with --direction
Labels
<br/> breaks a line; anything longer wraps by itself
What it decides for you, so the figure obeys §4 without your help:
Layout is layered — ranks down the flow axis, barycentre ordering across it, one lane
per elbow so two turns in the same gap never draw as one line, and a bridge wherever one
edge crosses another (§8). Long edges route through their own channel rather than over whatever is
underneath. A cycle becomes a return path down the nearer side of the figure.
Accents carry meaning, never variety: --a1 the main line, --a2 returns and loops,
--a3 the branches off the main line, --a4 where the flow stops. Where a decision
splits, the branch continuing down the longest remaining path keeps --a1. A classDef
takes an accent in declaration order — printed, so you can see which — and a class named
a1…a4 takes the one it names. Five classes is fatal: there are four accents, and a
fifth would merge two things the source draws apart.
The canvas is computed from the geometry, including the header, the footnote and the
return channels — so a TD chart comes out tall and narrow, which is what it should be.
What it refuses, loudly and before writing anything: another mermaid diagram kind
(sequence, class, state, gantt, ER, pie — for sequence diagrams use §3), style and
linkStyle (colour is the theme's, §4), click, nested subgraphs, a per-subgraph
direction, the A@{ shape: … } syntax, unpaired brackets, and a fifth class. Every message
quotes the line and names what to write instead. Unimplemented syntax is never skipped: a
dropped statement is a step missing from the picture, and nothing downstream can see it.
Flag
--theme name
which theme the generated page links (default ember); render.mjs --theme still overrides at shot time
--direction TD|TB|BT|LR|RL
override the direction in the source
--title--kicker--note
the header and the footnote — all optional, all sized into the canvas
--template file
a different shell; it needs the FLOW:BEGIN/FLOW:END markers and every class the generator emits
11. QA checklist
Before delivering any image:
Rendered and viewed — not assumed
Code figures: highlighting spot-checked token by token; at most one .hl line
Mermaid figures: generated, never transcribed — and every node in the source is on the page
Images: the file the log named is the one you meant, and the crop kept the point
Transparent renders: viewed composited over a sample background, not just alone
No text touches a node edge, a line, or the canvas edge
Every line crossing is bridged
One gradient phrase at most; accents used by meaning, not variety
Facts in the copy verified against the project (counts, versions, names)
Output at 2x unless the destination demands otherwise (GIFs: 1x; PDFs take no scale)
Format matches the destination, and was chosen rather than defaulted into
Animations: every step present in order, and the final frame holds long enough to read
Every output opened and looked at — the renderer catches a blank or mis-sized
figure in any format, but only you catch an ugly one