diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index b2bd921..0e28335 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -1,7 +1,7 @@
{
"name": "diagram-design",
"metadata": {
- "description": "Editorial-quality technical and product diagrams — 26 types rendered as standalone HTML with inline SVG, skinnable to match your brand"
+ "description": "Editorial-quality technical and product diagrams — 27 types rendered as standalone HTML with inline SVG, skinnable to match your brand"
},
"owner": {
"name": "Cathryn Lavery"
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index 71e67b2..cdc047a 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "diagram-design",
- "description": "Editorial-quality technical and product diagrams — 26 types rendered as standalone HTML with inline SVG, skinnable to match your brand",
+ "description": "Editorial-quality technical and product diagrams — 27 types rendered as standalone HTML with inline SVG, skinnable to match your brand",
"version": "2.0.0",
"homepage": "https://github.com/cathrynlavery/diagram-design",
"repository": "https://github.com/cathrynlavery/diagram-design",
diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json
index 74ca72f..40a0dc8 100644
--- a/.codex-plugin/plugin.json
+++ b/.codex-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "diagram-design",
- "description": "Editorial-quality technical and product diagrams - 26 types rendered as standalone HTML with inline SVG, skinnable to match your brand",
+ "description": "Editorial-quality technical and product diagrams - 27 types rendered as standalone HTML with inline SVG, skinnable to match your brand",
"version": "2.0.0",
"author": {
"name": "Cathryn Lavery",
diff --git a/README.md b/README.md
index 504114a..b6dda5f 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,11 @@

-26 types. One Claude Code skill. Your brand in 60 seconds — the skill reads your website and maps colors + fonts to every diagram.
+
+
+*New in 2.0 — the Loop: flywheels with a shared-memory hub. The dashed lines are the write-backs.*
+
+27 types. One Claude Code skill. Your brand in 60 seconds — the skill reads your website and maps colors + fonts to every diagram.
No Figma. No generic rounded boxes. No 30-minute color-picking sessions.
@@ -14,7 +18,7 @@ No Figma. No generic rounded boxes. No 30-minute color-picking sessions.
I write at [littlemight.com](https://littlemight.com?utm_source=diagram-design&utm_medium=readme&utm_campaign=github&utm_content=intro) (and run [BestSelf.co](https://bestself.co?utm_source=diagram-design&utm_medium=readme&utm_campaign=github&utm_content=intro) on the side). Every time I needed a diagram — an architecture sketch, a flowchart, a pyramid of what matters most — I'd ask Claude and get back a generic rounded-box thing that looked nothing like the rest of the site. I'd either fight with Figma for 30 minutes or just skip the diagram.
-So I built a Claude Code skill for it. Twenty-six types, editorial quality, matches your brand in 60 seconds by reading your website.
+So I built a Claude Code skill for it. Twenty-seven types, editorial quality, matches your brand in 60 seconds by reading your website.
> *The highest-quality move is usually deletion.* Every node earns its place. The accent color is reserved for the 1–2 things the reader should look at first. Target density: 4/10.
@@ -22,7 +26,7 @@ So I built a Claude Code skill for it. Twenty-six types, editorial quality, matc
## What it makes
-All 26 diagrams ship in three variants: minimal light, minimal dark, and full-editorial. Open any of them directly in a browser — no build step, no JS, no external images.
+All 27 diagrams ship in three variants: minimal light, minimal dark, and full-editorial. Open any of them directly in a browser — no build step, no JS, no external images.
@@ -52,27 +56,32 @@ All 26 diagrams ship in three variants: minimal light, minimal dark, and full-ed
 Radar / Spider Multi-axis comparison |
+  Loop Flywheel · stations around a hub |
 IT current-state Legacy landscape · modernization |
-  High-Level End-to-end stack on a cluster |
+  High-Level End-to-end stack on a cluster |
 Bar chart Categorical comparison |
 Line chart Trends over time |
-  Gantt Tasks and phases on a timeline |
+  Gantt Tasks and phases on a timeline |
 Scatter plot Distribution and correlation |
 Process Multi-actor sequential workflow |
-  Medallion Multi-tier data storage |
+  Medallion Multi-tier data storage |
 Data flow Role-scoped pipeline steps |
 DP integration Sources → core → consumers |
+
+
 DP security matrix Per-role access permissions |
+ |
+ |
-**Browse the live gallery:** open [`skills/diagram-design/assets/index.html`](skills/diagram-design/assets/index.html) in your browser to flip through all 26 diagrams with light / dark / full-editorial tabs.
+**Browse the live gallery:** open [`skills/diagram-design/assets/index.html`](skills/diagram-design/assets/index.html) in your browser to flip through all 27 diagrams with light / dark / full-editorial tabs.
---
@@ -149,7 +158,7 @@ Before writing tokens, the skill verifies WCAG AA contrast on `ink` over `paper`
### Manual override
-Prefer to set tokens by hand? Open [`skills/diagram-design/references/style-guide.md`](skills/diagram-design/references/style-guide.md) and edit the table. Everything downstream reads from there — all 26 diagrams, the annotation primitive, and the gallery all inherit semantic role names (`accent`, not `#eb6c36`).
+Prefer to set tokens by hand? Open [`skills/diagram-design/references/style-guide.md`](skills/diagram-design/references/style-guide.md) and edit the table. Everything downstream reads from there — all 27 diagrams, the annotation primitive, and the gallery all inherit semantic role names (`accent`, not `#eb6c36`).
### First-run gate
@@ -164,7 +173,7 @@ See [`skills/diagram-design/references/onboarding.md`](skills/diagram-design/ref
## Quickstart
```bash
-# Open the gallery to see all 26 diagrams
+# Open the gallery to see all 27 diagrams
open ~/.claude/skills/diagram-design/assets/index.html
# In Claude Code, just ask:
@@ -235,16 +244,21 @@ diagram-design/
├── assets/
│ ├── index.html — live gallery, tabbed
│ ├── template*.html — scaffolds for new diagrams
-│ ├── example-.html — 3 variants × 26 types
+│ ├── example-.html — 3 variants × 27 types
│ └── example-quadrant-consultant.html — consultant-special 2×2 scenario matrix
└── docs/screenshots/ — the images in this README
```
-This keeps Claude's working context tight (only load what you need) and makes the skill easy to extend — drop a new `type-.md` and wire it into the selection guide. The skill ships with 32 reference files covering every diagram type, primitive, and utility.
+This keeps Claude's working context tight (only load what you need) and makes the skill easy to extend — drop a new `type-.md` and wire it into the selection guide. The skill ships with 33 reference files covering every diagram type, primitive, and utility.
+
+### Contributing / skin lint
+
+Before submitting a new example, run `python3 scripts/lint-skin.py `.
+The repository-wide check `python3 scripts/lint-skin.py --all --baseline` must stay green.
### What loads when
-The top-level `SKILL.md` is always in context. Everything else is pulled in only when relevant — this is what keeps the skill fast even with 32 reference files.
+The top-level `SKILL.md` is always in context. Everything else is pulled in only when relevant — this is what keeps the skill fast even with 33 reference files.
| You ask for… | Claude loads |
|---|---|
@@ -253,7 +267,7 @@ The top-level `SKILL.md` is always in context. Everything else is pulled in only
| "Onboard this skill to my site" | `SKILL.md` + `references/onboarding.md` + `references/style-guide.md` |
| "Add an editorial callout to this diagram" | `SKILL.md` + `references/primitive-annotation.md` |
| "Give me a hand-drawn version" | `SKILL.md` + `references/primitive-sketchy.md` |
-| Routine diagram-making (any of the 26 diagrams) | Only `SKILL.md` + that one type's reference |
+| Routine diagram-making (any of the 27 diagrams) | Only `SKILL.md` + that one type's reference |
No matter how many types exist, Claude only reads the one you need. Add a new type tomorrow and nothing else changes.
diff --git a/docs/screenshots/data-flow.png b/docs/screenshots/data-flow.png
index 7699eee..76286f8 100644
Binary files a/docs/screenshots/data-flow.png and b/docs/screenshots/data-flow.png differ
diff --git a/docs/screenshots/dp-integration.png b/docs/screenshots/dp-integration.png
index d74ea10..7718872 100644
Binary files a/docs/screenshots/dp-integration.png and b/docs/screenshots/dp-integration.png differ
diff --git a/docs/screenshots/it-state.png b/docs/screenshots/it-state.png
index 69d70f6..f96dfeb 100644
Binary files a/docs/screenshots/it-state.png and b/docs/screenshots/it-state.png differ
diff --git a/docs/screenshots/loop.png b/docs/screenshots/loop.png
new file mode 100644
index 0000000..4e64e4c
Binary files /dev/null and b/docs/screenshots/loop.png differ
diff --git a/scripts/lint-skin-baseline.txt b/scripts/lint-skin-baseline.txt
new file mode 100644
index 0000000..e6ece33
--- /dev/null
+++ b/scripts/lint-skin-baseline.txt
@@ -0,0 +1,20 @@
+example-architecture-dark.html
+example-data-flow-dark.html
+example-data-flow-full.html
+example-data-flow.html
+example-dp-integration-dark.html
+example-dp-integration-full.html
+example-dp-integration.html
+example-high-level-dark.html
+example-high-level-full.html
+example-high-level-vertical-dark.html
+example-high-level-vertical-full.html
+example-high-level-vertical.html
+example-high-level.html
+example-it-state-dark.html
+example-it-state-full.html
+example-it-state.html
+example-layers-dark.html
+example-sequence-dark.html
+example-timeline-dark.html
+example-venn-dark.html
diff --git a/scripts/lint-skin.py b/scripts/lint-skin.py
new file mode 100644
index 0000000..f7fa95c
--- /dev/null
+++ b/scripts/lint-skin.py
@@ -0,0 +1,267 @@
+#!/usr/bin/env python3
+"""Lint NEW and CHANGED diagram examples against the current visual skin.
+
+Pre-2.0 examples may legitimately fail because they were built against an older
+skin. Use ``--all --baseline`` to skip those documented legacy files.
+"""
+
+import argparse
+import re
+import sys
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parent.parent
+STYLE_GUIDE = ROOT / "skills/diagram-design/references/style-guide.md"
+ASSET_DIR = ROOT / "skills/diagram-design/assets"
+BASELINE = ROOT / "scripts/lint-skin-baseline.txt"
+
+HEX_RE = re.compile(
+ r"(?]*>", re.IGNORECASE | re.DOTALL)
+HREF_RE = re.compile(r"\bhref\s*=\s*(['\"])(.*?)\1", re.IGNORECASE | re.DOTALL)
+FONT_CSS_RE = re.compile(r"font-family\s*:\s*([^;}]+)", re.IGNORECASE)
+FONT_ATTR_RE = re.compile(
+ r"\bfont-family\s*=\s*(['\"])(.*?)\1", re.IGNORECASE | re.DOTALL
+)
+
+ALLOWED_FONTS = {
+ "instrument serif",
+ "geist",
+ "geist mono",
+ "system-ui",
+ "sans-serif",
+ "serif",
+ "monospace",
+ "ui-monospace",
+}
+CSS_FONT_KEYWORDS = {"inherit", "initial", "revert", "revert-layer", "unset"}
+
+
+def normalize_hex(value):
+ value = value.lower()
+ if len(value) == 4:
+ return "#" + "".join(character * 2 for character in value[1:])
+ return value
+
+
+def table_hexes(markdown, heading):
+ """Return hex colors from the first Markdown table after *heading*."""
+ start = markdown.find(heading)
+ if start < 0:
+ raise ValueError(f"missing {heading!r} in {STYLE_GUIDE}")
+
+ table_started = False
+ colors = set()
+ for line in markdown[start:].splitlines()[1:]:
+ if line.startswith("|"):
+ table_started = True
+ colors.update(normalize_hex(match.group()) for match in HEX_RE.finditer(line))
+ elif table_started:
+ break
+ return colors
+
+
+def allowed_colors():
+ markdown = STYLE_GUIDE.read_text(encoding="utf-8")
+ colors = table_hexes(markdown, "### Semantic roles")
+ colors.update(table_hexes(markdown, "### Series palette"))
+ colors.update({"#fff", "#ffffff"})
+
+ rgb_triplets = set()
+ for color in colors:
+ expanded = normalize_hex(color)
+ if len(expanded) == 7:
+ rgb_triplets.add(
+ tuple(int(expanded[offset : offset + 2], 16) for offset in (1, 3, 5))
+ )
+ return colors, rgb_triplets
+
+
+def line_number(text, offset):
+ return text.count("\n", 0, offset) + 1
+
+
+def display_path(path):
+ try:
+ return path.resolve().relative_to(ROOT).as_posix()
+ except ValueError:
+ return str(path)
+
+
+def named_families(value):
+ families = []
+ for raw_family in value.split(","):
+ family = raw_family.strip().strip("'\"").strip()
+ lowered = family.casefold()
+ if not family or lowered in CSS_FONT_KEYWORDS or lowered.startswith("var("):
+ continue
+ if lowered not in ALLOWED_FONTS:
+ families.append(family)
+ return families
+
+
+def lint_text(text, colors, rgb_triplets):
+ findings = []
+
+ def add(offset, category, message):
+ findings.append((line_number(text, offset), offset, category, message))
+
+ for match in HEX_RE.finditer(text):
+ value = match.group()
+ normalized = normalize_hex(value)
+ if normalized == "#000000":
+ add(match.start(), "pure-black", f"pure black {value} is not allowed")
+ elif normalized not in colors:
+ add(match.start(), "color", f"{value} is not in the style-guide palette")
+
+ for match in RGBA_RE.finditer(text):
+ rgb = tuple(int(match.group(index)) for index in (1, 2, 3))
+ if rgb not in rgb_triplets:
+ add(
+ match.start(),
+ "color",
+ f"{match.group()} is not derived from an allowed palette color",
+ )
+
+ for match in BLACK_RGB_RE.finditer(text):
+ add(match.start(), "pure-black", "pure black rgb(0,0,0) is not allowed")
+
+ for match in SCRIPT_RE.finditer(text):
+ add(match.start(), "script", "