🤖 Restructure SVG skill: extract references, add legibility rules

Extract inline SVG rules to references/inline-svg.md for progressive
disclosure. Add references/layout-legibility.md with 7 rules for
readable SVG output (text sizing, contrast, margins, chart patterns,
palettes) based on concrete failure modes from issue #4.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 20:17:25 +02:00
parent f7e1b96930
commit 61af13521c
3 changed files with 193 additions and 96 deletions
@@ -0,0 +1,83 @@
# Layout and Legibility Rules for SVG Diagrams
These rules prevent the most common legibility failures in agent-generated SVG: text overflow, poor contrast, and missing margins. They apply to both external `.svg` files and inline SVG.
## Rule 1 — Estimate max-text-width before sizing a container
For any text that lives **inside** a bounded shape (cell, box, button), estimate the rendered width of the longest string:
- Rough bound: `text_width_px ≈ chars × font_size × 0.6` for proportional fonts at common weights.
- If the container is narrower than the longest text, choose one of:
- **Widen the container** (preferred when there's space).
- **Truncate to a short label** (e.g. a count, a code, or an abbreviation) and put the full text outside (legend, footnote table, sidebar).
- **Wrap to multiple `<tspan>` lines** when truncation loses meaning.
- SVG doesn't clip text by default — never assume CSS overflow rules apply.
Default to **counts + short labels inside the chart, full names in a legend below**. For dense categorical charts this is almost always the right answer.
## Rule 2 — Text-over-background needs a contrast contract
If a `<text>` element renders on top of an arbitrary background, declare which class of background it's designed for:
- **Light-bg text** (`fill: #1a1a1a` or similar): legible on whites, light grays, light pastels.
- **Dark-bg text** (`fill: #ffffff` or similar): legible on saturated mid-to-dark colors only.
- **High-contrast halo** (`paint-order="stroke fill"` + `stroke-width="3"` + `stroke="#fff"`): legible on anything; use when the background is unpredictable or varies.
Never apply one fill class across multiple background colors without verifying contrast for each. WCAG large-text minimum is **3:1**; for text-in-data-viz aim for **4.5:1** to leave margin.
When colors are picked by category, group them into a "dark fills" palette (use white text) and a "light fills" palette (use dark text). Don't mix.
## Rule 3 — Backgrounds for text on uncertain surfaces
When text is placed near or over a colored region whose color depends on data, give the text its own background:
- A `<rect>` behind the text, sized to its bounding box (compute as `chars × font_size × 0.6 + padding`), with `fill="#fff"` and a small `rx` for softness.
- Or a halo via `paint-order="stroke fill"` with a white stroke.
For category legends, axis labels, and chart titles — anything that sits in the chart's "frame" rather than inside a data shape — always give them a clean background or place them in the margin.
## Rule 4 — Reserve margin for rotated labels
Rotated text (typically column headers at `-30°` to `-45°`) extends beyond its anchor in both x and y. Rule of thumb: an N-character rotated header at θ degrees and font-size F needs:
- horizontal reach: `cos(θ) × chars × F × 0.6`
- vertical reach: `sin(θ) × chars × F × 0.6`
Plus the anchor offset. Always add 20% padding. If the rotated headers extend into the chart body, increase the chart's top margin until they don't.
## Rule 5 — Default chart pattern: sparse cells, full legend below
For categorical density / heatmap / matrix charts:
```
[ Title ]
[ Rotated category headers (with margin) ]
[ Row label │ count │ count │ . │ count │ count │ . ] (cells: just the number, color-coded)
[ Row label │ . │ count │ count │ . │ count │ . ]
[ ... ]
[ ]
[ Legend / details table: ]
[ Row × Category → action_a, action_b, action_c ]
[ Row × Category → action_d ]
[ ... ]
```
Counts inside cells; names in the table below. This pattern composes well across renderers and prevents the overflow trap entirely.
## Rule 6 — Stick to a small palette with declared roles
Categorical palettes should be picked once, with their **role** declared:
- "Dark fills" (use **white** text): `#1f77b4`, `#d62728`, `#9467bd`, `#8c564b`
- "Light fills" (use **dark** text): `#aec7e8`, `#ffbb78`, `#98df8a`
Avoid mid-tones (`#ff7f0e`, `#2ca02c`, `#e377c2`) unless the text color is matched per-color. If the chart needs more than 6 categories, that's usually a sign the categorisation is too fine — group first.
## Rule 7 — Sanity check the rendered output
Before saving, mentally render at the target display width (typically 600750px effective in Markdown), and ask:
- Can I read every label?
- Does any text overlap another shape?
- Is there a category whose color makes its text invisible?
- Are empty cells distinguishable from filled ones at a glance?
If any answer is "no", iterate before checking in.