Add Loop type (flywheel) + palette linter
- New type #27: Loop — ring of 5-8 stations around exactly one hub, solid flow arrows around the ring, dashed write-back spokes into the hub. Reference doc with ring layout math, budget, and anti-patterns; flagship example "The self-improving loop" in light/dark/full variants; gallery tab; README showcase under the hero image. - scripts/lint-skin.py — contributor palette linter: reads allowed tokens from style-guide.md at runtime, flags off-palette hexes, pure black, external assets, scripts, and off-system fonts. Baseline file grandfathers pre-2.0 examples; --all --baseline is green. - Linter immediately caught 36 near-miss hexes in 9 of the newly regenerated examples; swapped to exact series-palette tokens and recaptured the affected screenshots.
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@@ -52,27 +56,32 @@ All 26 diagrams ship in three variants: minimal light, minimal dark, and full-ed
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><img src="docs/screenshots/radar.png" alt="Radar"><br><b>Radar / Spider</b><br><sub>Multi-axis comparison</sub></td>
|
||||
<td align="center"><img src="docs/screenshots/loop.png" alt="Loop"><br><b>Loop</b><br><sub>Flywheel · stations around a hub</sub></td>
|
||||
<td align="center"><img src="docs/screenshots/it-state.png" alt="IT current-state"><br><b>IT current-state</b><br><sub>Legacy landscape · modernization</sub></td>
|
||||
<td align="center"><img src="docs/screenshots/high-level.png" alt="High-Level"><br><b>High-Level</b><br><sub>End-to-end stack on a cluster</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><img src="docs/screenshots/high-level.png" alt="High-Level"><br><b>High-Level</b><br><sub>End-to-end stack on a cluster</sub></td>
|
||||
<td align="center"><img src="docs/screenshots/bar.png" alt="Bar chart"><br><b>Bar chart</b><br><sub>Categorical comparison</sub></td>
|
||||
<td align="center"><img src="docs/screenshots/line.png" alt="Line chart"><br><b>Line chart</b><br><sub>Trends over time</sub></td>
|
||||
<td align="center"><img src="docs/screenshots/gantt.png" alt="Gantt"><br><b>Gantt</b><br><sub>Tasks and phases on a timeline</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><img src="docs/screenshots/gantt.png" alt="Gantt"><br><b>Gantt</b><br><sub>Tasks and phases on a timeline</sub></td>
|
||||
<td align="center"><img src="docs/screenshots/scatter.png" alt="Scatter plot"><br><b>Scatter plot</b><br><sub>Distribution and correlation</sub></td>
|
||||
<td align="center"><img src="docs/screenshots/process.png" alt="Process"><br><b>Process</b><br><sub>Multi-actor sequential workflow</sub></td>
|
||||
<td align="center"><img src="docs/screenshots/medallion.png" alt="Medallion"><br><b>Medallion</b><br><sub>Multi-tier data storage</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><img src="docs/screenshots/medallion.png" alt="Medallion"><br><b>Medallion</b><br><sub>Multi-tier data storage</sub></td>
|
||||
<td align="center"><img src="docs/screenshots/data-flow.png" alt="Data flow"><br><b>Data flow</b><br><sub>Role-scoped pipeline steps</sub></td>
|
||||
<td align="center"><img src="docs/screenshots/dp-integration.png" alt="DP integration"><br><b>DP integration</b><br><sub>Sources → core → consumers</sub></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><img src="docs/screenshots/dp-security-matrix.png" alt="DP security matrix"><br><b>DP security matrix</b><br><sub>Per-role access permissions</sub></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
**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-<type>.html — 3 variants × 26 types
|
||||
│ ├── example-<type>.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-<name>.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-<name>.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 <your-new-example.html>`.
|
||||
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.
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 135 KiB |
@@ -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
|
||||
@@ -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"(?<![\w-])#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})(?![0-9a-fA-F])"
|
||||
)
|
||||
RGBA_RE = re.compile(
|
||||
r"rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([^)]+)\)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
BLACK_RGB_RE = re.compile(r"rgb\(\s*0\s*,\s*0\s*,\s*0\s*\)", re.IGNORECASE)
|
||||
SCRIPT_RE = re.compile(r"<script\b", re.IGNORECASE)
|
||||
SRC_HTTP_RE = re.compile(r"\bsrc\s*=\s*(['\"])\s*https?://", re.IGNORECASE)
|
||||
IMPORT_HTTP_RE = re.compile(
|
||||
r"@import\s+(?:url\(\s*)?(?:['\"]\s*)?https?://", re.IGNORECASE
|
||||
)
|
||||
URL_HTTP_RE = re.compile(r"url\(\s*(?:['\"]\s*)?https?://", re.IGNORECASE)
|
||||
LINK_RE = re.compile(r"<link\b[^>]*>", 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", "<script> tags are not allowed")
|
||||
|
||||
for match in SRC_HTTP_RE.finditer(text):
|
||||
add(match.start(), "external-asset", "external HTTP(S) src is not allowed")
|
||||
|
||||
import_spans = []
|
||||
for match in IMPORT_HTTP_RE.finditer(text):
|
||||
import_spans.append(match.span())
|
||||
add(match.start(), "external-asset", "external HTTP(S) @import is not allowed")
|
||||
|
||||
for match in URL_HTTP_RE.finditer(text):
|
||||
if any(start <= match.start() < end for start, end in import_spans):
|
||||
continue
|
||||
add(match.start(), "external-asset", "external HTTP(S) url() is not allowed")
|
||||
|
||||
for link_match in LINK_RE.finditer(text):
|
||||
href_match = HREF_RE.search(link_match.group())
|
||||
if not href_match:
|
||||
continue
|
||||
href = href_match.group(2).strip()
|
||||
if href.lower().startswith("https://fonts.googleapis.com"):
|
||||
continue
|
||||
if href.lower().startswith(("http://", "https://")):
|
||||
href_offset = link_match.start() + href_match.start(2)
|
||||
add(href_offset, "external-asset", "external HTTP(S) <link> is not allowed")
|
||||
|
||||
for match in FONT_CSS_RE.finditer(text):
|
||||
unsupported = named_families(match.group(1))
|
||||
if unsupported:
|
||||
add(
|
||||
match.start(),
|
||||
"font-family",
|
||||
"unsupported font family: " + ", ".join(unsupported),
|
||||
)
|
||||
|
||||
for match in FONT_ATTR_RE.finditer(text):
|
||||
unsupported = named_families(match.group(2))
|
||||
if unsupported:
|
||||
add(
|
||||
match.start(),
|
||||
"font-family",
|
||||
"unsupported font family: " + ", ".join(unsupported),
|
||||
)
|
||||
|
||||
findings.sort(key=lambda finding: (finding[0], finding[1], finding[2]))
|
||||
return findings
|
||||
|
||||
|
||||
def load_baseline():
|
||||
if not BASELINE.exists():
|
||||
return set()
|
||||
return {
|
||||
line.strip()
|
||||
for line in BASELINE.read_text(encoding="utf-8").splitlines()
|
||||
if line.strip() and not line.lstrip().startswith("#")
|
||||
}
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Lint diagram examples against the colors and fonts in style-guide.md."
|
||||
)
|
||||
parser.add_argument("files", nargs="*", type=Path, help="HTML examples to lint")
|
||||
parser.add_argument(
|
||||
"--all",
|
||||
action="store_true",
|
||||
help="lint every skills/diagram-design/assets/example-*.html file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--baseline",
|
||||
action="store_true",
|
||||
help="under --all, skip files listed in scripts/lint-skin-baseline.txt",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--quiet", action="store_true", help="print only the summary line"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if args.all == bool(args.files):
|
||||
parser.error("provide either file arguments or --all")
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
colors, rgb_triplets = allowed_colors()
|
||||
|
||||
skipped = 0
|
||||
if args.all:
|
||||
paths = sorted(ASSET_DIR.glob("example-*.html"))
|
||||
if args.baseline:
|
||||
baseline = load_baseline()
|
||||
selected = []
|
||||
for path in paths:
|
||||
relative = display_path(path)
|
||||
if path.name in baseline or relative in baseline:
|
||||
skipped += 1
|
||||
else:
|
||||
selected.append(path)
|
||||
paths = selected
|
||||
else:
|
||||
paths = args.files
|
||||
|
||||
total_findings = 0
|
||||
for path in paths:
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
findings = lint_text(text, colors, rgb_triplets)
|
||||
except OSError as error:
|
||||
findings = [(0, 0, "read-error", str(error))]
|
||||
|
||||
total_findings += len(findings)
|
||||
if not args.quiet:
|
||||
shown_path = display_path(path)
|
||||
for line, _offset, category, message in findings:
|
||||
print(f"{shown_path}:{line}: {category}: {message}")
|
||||
|
||||
print(
|
||||
f"Summary: {len(paths)} file(s) checked, {skipped} skipped, "
|
||||
f"{total_findings} finding(s)."
|
||||
)
|
||||
return 1 if total_findings else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: diagram-design
|
||||
description: Create technical and product diagrams — architecture, IT current-state, flowchart, sequence, state machine, ER / data model, timeline, swimlane, quadrant, radar / spider, nested, tree, org chart, layer stack, venn, pyramid / funnel, bar chart, line chart, Gantt, scatter plot, high-level, process, medallion, data flow, DP integration, DP security matrix — as standalone HTML files with inline SVG. Ships with a neutral editorial skin and a first-run gate that prompts users to customize the style guide (colors, fonts) from their own website before generating. Includes annotation-callout primitive and optional sketchy variant.
|
||||
description: Create technical and product diagrams — architecture, IT current-state, flowchart, sequence, state machine, ER / data model, timeline, swimlane, quadrant, radar / spider, loop / flywheel, nested, tree, org chart, layer stack, venn, pyramid / funnel, bar chart, line chart, Gantt, scatter plot, high-level, process, medallion, data flow, DP integration, DP security matrix — as standalone HTML files with inline SVG. Ships with a neutral editorial skin and a first-run gate that prompts users to customize the style guide (colors, fonts) from their own website before generating. Includes annotation-callout primitive and optional sketchy variant.
|
||||
license: MIT
|
||||
metadata:
|
||||
version: "2.0"
|
||||
@@ -10,7 +10,7 @@ metadata:
|
||||
|
||||
Create visual diagrams as self-contained HTML files with inline SVG and CSS, following an opinionated editorial design system.
|
||||
|
||||
Twenty-six diagram types. One shared design system, complexity budget, and taste gate. Type-specific conventions live in `references/` and are loaded only when you pick a type.
|
||||
Twenty-seven diagram types. One shared design system, complexity budget, and taste gate. Type-specific conventions live in `references/` and are loaded only when you pick a type.
|
||||
|
||||
---
|
||||
|
||||
@@ -55,7 +55,7 @@ Applied to schematics:
|
||||
|
||||
## 2. When to Use
|
||||
|
||||
Use for any of the 26 diagram types (§3) when a reader will learn more from a visual than from prose, a table, or a bulleted list.
|
||||
Use for any of the 27 diagram types (§3) when a reader will learn more from a visual than from prose, a table, or a bulleted list.
|
||||
|
||||
**Don't use for:**
|
||||
|
||||
@@ -84,6 +84,7 @@ Before drawing, ask: *Would the reader learn more from this than from a well-wri
|
||||
| Cross-functional process with handoffs | **Swimlane** | [type-swimlane.md](references/type-swimlane.md) |
|
||||
| Two-axis positioning / prioritization | **Quadrant** | [type-quadrant.md](references/type-quadrant.md) |
|
||||
| Multiple entities scored across 3–5 quantitative criteria | **Radar / Spider** | [type-radar.md](references/type-radar.md) |
|
||||
| Reinforcing cycle / flywheel where the last step feeds the first and a shared hub accumulates state | **Loop** | [type-loop.md](references/type-loop.md) |
|
||||
| Hierarchy through containment / scope | **Nested** | [type-nested.md](references/type-nested.md) |
|
||||
| Parent → children relationships | **Tree** | [type-tree.md](references/type-tree.md) |
|
||||
| Human/agent/team ownership, reporting, routing, escalation | **Org chart** | [type-org-chart.md](references/type-org-chart.md) |
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
<rect x="146" y="44" width="100" height="64" rx="6" fill="rgba(245,245,245,0.04)" stroke="rgba(245,245,245,0.20)"/>
|
||||
<rect x="150" y="48" width="18" height="10" rx="3" fill="rgba(245,245,245,0.12)"/><text x="159" y="53" class="role-text">ENG</text>
|
||||
<text x="196" y="67" class="node-title">Capture Events</text><text x="196" y="79" class="node-sub">shop events → batch</text><text x="196" y="91" class="node-tool">NiFi ingest</text>
|
||||
<rect x="150" y="98" width="16" height="8" rx="3" fill="#4a7c59"/><text x="158" y="104" class="chip-text">LS</text>
|
||||
<rect x="150" y="98" width="16" height="8" rx="3" fill="#7c8f6f"/><text x="158" y="104" class="chip-text">LS</text>
|
||||
<rect x="226" y="98" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="234" y="104" class="chip-text">DB</text>
|
||||
|
||||
<!-- Data Engineer: Land Records -->
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
<!-- Legend: data types -->
|
||||
<text x="164" y="314" class="legend-label">DATA TYPE</text>
|
||||
<rect x="180" y="306" width="16" height="8" rx="3" fill="#4a7c59"/><text x="188" y="312" class="chip-text">LS</text><text x="202" y="314" class="legend-text">Stream</text>
|
||||
<rect x="180" y="306" width="16" height="8" rx="3" fill="#7c8f6f"/><text x="188" y="312" class="chip-text">LS</text><text x="202" y="314" class="legend-text">Stream</text>
|
||||
<rect x="252" y="306" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="260" y="312" class="chip-text">DB</text><text x="274" y="314" class="legend-text">Dataset</text>
|
||||
<rect x="332" y="306" width="16" height="8" rx="3" fill="#b8915a"/><text x="340" y="312" class="chip-text">TB</text><text x="354" y="314" class="legend-text">Table</text>
|
||||
<rect x="404" y="306" width="16" height="8" rx="3" fill="#9c6b50"/><text x="412" y="312" class="chip-text">FL</text><text x="426" y="314" class="legend-text">Dashboard</text>
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
<rect x="146" y="44" width="100" height="64" rx="6" fill="#f5f5f5" stroke="rgba(45,49,66,0.25)"/>
|
||||
<rect x="150" y="48" width="18" height="10" rx="3" fill="rgba(45,49,66,0.12)"/><text x="159" y="53" class="role-text">ENG</text>
|
||||
<text x="196" y="67" class="node-title">Capture Events</text><text x="196" y="79" class="node-sub">shop events → batch</text><text x="196" y="91" class="node-tool">NiFi ingest</text>
|
||||
<rect x="150" y="98" width="16" height="8" rx="3" fill="#4a7c59"/><text x="158" y="104" class="chip-text">LS</text>
|
||||
<rect x="150" y="98" width="16" height="8" rx="3" fill="#7c8f6f"/><text x="158" y="104" class="chip-text">LS</text>
|
||||
<rect x="226" y="98" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="234" y="104" class="chip-text">DB</text>
|
||||
|
||||
<!-- Data Engineer: Land Records -->
|
||||
@@ -146,7 +146,7 @@
|
||||
|
||||
<!-- Legend: data types -->
|
||||
<text x="164" y="314" class="legend-label">DATA TYPE</text>
|
||||
<rect x="180" y="306" width="16" height="8" rx="3" fill="#4a7c59"/><text x="188" y="312" class="chip-text">LS</text><text x="202" y="314" class="legend-text">Stream</text>
|
||||
<rect x="180" y="306" width="16" height="8" rx="3" fill="#7c8f6f"/><text x="188" y="312" class="chip-text">LS</text><text x="202" y="314" class="legend-text">Stream</text>
|
||||
<rect x="252" y="306" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="260" y="312" class="chip-text">DB</text><text x="274" y="314" class="legend-text">Dataset</text>
|
||||
<rect x="332" y="306" width="16" height="8" rx="3" fill="#b8915a"/><text x="340" y="312" class="chip-text">TB</text><text x="354" y="314" class="legend-text">Table</text>
|
||||
<rect x="404" y="306" width="16" height="8" rx="3" fill="#9c6b50"/><text x="412" y="312" class="chip-text">FL</text><text x="426" y="314" class="legend-text">Dashboard</text>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
<rect x="146" y="44" width="100" height="64" rx="6" fill="#f5f5f5" stroke="rgba(45,49,66,0.25)"/>
|
||||
<rect x="150" y="48" width="18" height="10" rx="3" fill="rgba(45,49,66,0.12)"/><text x="159" y="53" class="role-text">ENG</text>
|
||||
<text x="196" y="67" class="node-title">Capture Events</text><text x="196" y="79" class="node-sub">shop events → batch</text><text x="196" y="91" class="node-tool">NiFi ingest</text>
|
||||
<rect x="150" y="98" width="16" height="8" rx="3" fill="#4a7c59"/><text x="158" y="104" class="chip-text">LS</text>
|
||||
<rect x="150" y="98" width="16" height="8" rx="3" fill="#7c8f6f"/><text x="158" y="104" class="chip-text">LS</text>
|
||||
<rect x="226" y="98" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="234" y="104" class="chip-text">DB</text>
|
||||
|
||||
<!-- Data Engineer: Land Records -->
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
<!-- Legend: data types -->
|
||||
<text x="164" y="314" class="legend-label">DATA TYPE</text>
|
||||
<rect x="180" y="306" width="16" height="8" rx="3" fill="#4a7c59"/><text x="188" y="312" class="chip-text">LS</text><text x="202" y="314" class="legend-text">Stream</text>
|
||||
<rect x="180" y="306" width="16" height="8" rx="3" fill="#7c8f6f"/><text x="188" y="312" class="chip-text">LS</text><text x="202" y="314" class="legend-text">Stream</text>
|
||||
<rect x="252" y="306" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="260" y="312" class="chip-text">DB</text><text x="274" y="314" class="legend-text">Dataset</text>
|
||||
<rect x="332" y="306" width="16" height="8" rx="3" fill="#b8915a"/><text x="340" y="312" class="chip-text">TB</text><text x="354" y="314" class="legend-text">Table</text>
|
||||
<rect x="404" y="306" width="16" height="8" rx="3" fill="#9c6b50"/><text x="412" y="312" class="chip-text">FL</text><text x="426" y="314" class="legend-text">Dashboard</text>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--paper:#f5f5f5;--paper-2:#ececec;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--accent:#eb6c36;--accent-tint:rgba(235,108,54,.08);--link:#2e5aa8;--rule:rgba(45,49,66,.12);--dot:rgba(45,49,66,.10);--side-fill:rgba(79,93,117,.06);--side-stroke:#7a8399;--zone-fill:rgba(45,49,66,.025);--zone-stroke:rgba(45,49,66,.32);--bar-fill:rgba(45,49,66,.05);--node-fill:#fff;--custom-red:#b85450;--custom-red-fill:rgba(184,84,80,.06);--custom-red-stroke:rgba(184,84,80,.35);--custom-blue:#5a7d9a;--custom-blue-fill:rgba(90,125,154,.06);--custom-blue-stroke:rgba(90,125,154,.35);--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
:root{--paper:#f5f5f5;--paper-2:#ececec;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--accent:#eb6c36;--accent-tint:rgba(235,108,54,.08);--link:#2e5aa8;--rule:rgba(45,49,66,.12);--dot:rgba(45,49,66,.10);--side-fill:rgba(79,93,117,.06);--side-stroke:#7a8399;--zone-fill:rgba(45,49,66,.025);--zone-stroke:rgba(45,49,66,.32);--bar-fill:rgba(45,49,66,.05);--node-fill:#fff;--custom-red:#b88670;--custom-red-fill:rgba(184,134,112,.06);--custom-red-stroke:rgba(184,134,112,.35);--custom-blue:#82a0c0;--custom-blue-fill:rgba(130,160,192,.06);--custom-blue-stroke:rgba(130,160,192,.35);--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
body{font-family:var(--sans);background:var(--paper);color:var(--ink);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:3rem 2rem}.frame{max-width:1200px;width:100%}.eyebrow{font:500 .66rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}h1{font:400 clamp(1.5rem,2.4vw + .75rem,2rem)/1.15 var(--serif);letter-spacing:-.02em;margin-bottom:1.5rem}svg{width:100%;min-width:1100px;display:block}svg text{font-family:var(--sans)}.mono{font-family:var(--mono)}.connector{fill:none;stroke:var(--muted);stroke-width:1.2}.connector.primary{stroke:var(--accent);stroke-width:1.4}.connector.trigger{stroke:var(--muted);stroke-width:1;stroke-dasharray:4 3}.connector.auth{stroke:var(--accent);stroke-width:1.2;stroke-dasharray:5 4}.label-mask{fill:var(--paper)}.edge-label{fill:var(--accent);font:8px var(--mono);letter-spacing:.06em}.zone{fill:var(--zone-fill);stroke:var(--zone-stroke)}.zone-mask,.node-mask{fill:var(--paper)}.zone-label{fill:var(--soft);font:500 8px var(--mono);letter-spacing:.18em}.side-node{fill:var(--side-fill);stroke:var(--side-stroke)}.core-node{fill:var(--accent-tint);stroke:var(--accent)}.bar{fill:var(--bar-fill);stroke:var(--zone-stroke)}.role-box{fill:transparent;stroke:var(--side-stroke);stroke-opacity:.5}.role-box.focal{stroke:var(--accent)}.role-text{fill:var(--side-stroke);font:500 7px var(--mono);letter-spacing:.08em}.role-text.focal{fill:var(--accent)}.node-name{fill:var(--ink);font:600 12px var(--sans)}.node-name.focal{fill:var(--ink)}.node-sub{fill:var(--muted);font:9px var(--mono)}.footer-red{fill:var(--custom-red-fill);stroke:var(--custom-red-stroke)}.footer-blue{fill:var(--custom-blue-fill);stroke:var(--custom-blue-stroke)}.footer-red-text{fill:var(--custom-red)}.footer-blue-text{fill:var(--custom-blue)}.legend-key{fill:var(--muted);font:500 8px var(--mono);letter-spacing:.14em}.legend-text{fill:var(--muted);font:8px var(--sans)}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--paper:#f5f5f5;--paper-2:#ececec;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--accent:#eb6c36;--accent-tint:rgba(235,108,54,.08);--link:#2e5aa8;--rule:rgba(45,49,66,.12);--dot:rgba(45,49,66,.10);--side-fill:rgba(79,93,117,.06);--side-stroke:#7a8399;--zone-fill:rgba(45,49,66,.025);--zone-stroke:rgba(45,49,66,.32);--bar-fill:rgba(45,49,66,.05);--node-fill:#fff;--custom-red:#b85450;--custom-red-fill:rgba(184,84,80,.06);--custom-red-stroke:rgba(184,84,80,.35);--custom-blue:#5a7d9a;--custom-blue-fill:rgba(90,125,154,.06);--custom-blue-stroke:rgba(90,125,154,.35);--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
:root{--paper:#f5f5f5;--paper-2:#ececec;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--accent:#eb6c36;--accent-tint:rgba(235,108,54,.08);--link:#2e5aa8;--rule:rgba(45,49,66,.12);--dot:rgba(45,49,66,.10);--side-fill:rgba(79,93,117,.06);--side-stroke:#7a8399;--zone-fill:rgba(45,49,66,.025);--zone-stroke:rgba(45,49,66,.32);--bar-fill:rgba(45,49,66,.05);--node-fill:#fff;--custom-red:#9c6b50;--custom-red-fill:rgba(156,107,80,.06);--custom-red-stroke:rgba(156,107,80,.35);--custom-blue:#5e7a9b;--custom-blue-fill:rgba(94,122,155,.06);--custom-blue-stroke:rgba(94,122,155,.35);--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
body{font-family:var(--sans);background:var(--paper);color:var(--ink);min-height:100vh;padding:3rem 2rem}.frame{max-width:1200px;width:100%;margin:0 auto}.eyebrow{font:500 .66rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}h1{font:400 clamp(1.5rem,2.4vw + .75rem,2rem)/1.15 var(--serif);letter-spacing:-.02em;margin-bottom:1.5rem}svg{width:100%;min-width:1100px;display:block}svg text{font-family:var(--sans)}.mono{font-family:var(--mono)}.connector{fill:none;stroke:var(--muted);stroke-width:1.2}.connector.primary{stroke:var(--accent);stroke-width:1.4}.connector.trigger{stroke:var(--muted);stroke-width:1;stroke-dasharray:4 3}.connector.auth{stroke:var(--accent);stroke-width:1.2;stroke-dasharray:5 4}.label-mask{fill:var(--paper)}.edge-label{fill:var(--accent);font:8px var(--mono);letter-spacing:.06em}.zone{fill:var(--zone-fill);stroke:var(--zone-stroke)}.zone-mask,.node-mask{fill:var(--paper)}.zone-label{fill:var(--soft);font:500 8px var(--mono);letter-spacing:.18em}.side-node{fill:var(--side-fill);stroke:var(--side-stroke)}.core-node{fill:var(--accent-tint);stroke:var(--accent)}.bar{fill:var(--bar-fill);stroke:var(--zone-stroke)}.role-box{fill:transparent;stroke:var(--side-stroke);stroke-opacity:.5}.role-box.focal{stroke:var(--accent)}.role-text{fill:var(--side-stroke);font:500 7px var(--mono);letter-spacing:.08em}.role-text.focal{fill:var(--accent)}.node-name{fill:var(--ink);font:600 12px var(--sans)}.node-name.focal{fill:var(--ink)}.node-sub{fill:var(--muted);font:9px var(--mono)}.footer-red{fill:var(--custom-red-fill);stroke:var(--custom-red-stroke)}.footer-blue{fill:var(--custom-blue-fill);stroke:var(--custom-blue-stroke)}.footer-red-text{fill:var(--custom-red)}.footer-blue-text{fill:var(--custom-blue)}.legend-key{fill:var(--muted);font:500 8px var(--mono);letter-spacing:.14em}.legend-text{fill:var(--muted);font:8px var(--sans)}
|
||||
.subtitle{max-width:62ch;color:var(--muted);font-size:1rem;line-height:1.55;margin:-.75rem 0 2rem}.diagram-container{background:var(--paper-2);border:1px solid var(--rule);border-radius:8px;padding:1.5rem;overflow-x:auto}.cards{display:grid;grid-template-columns:1.1fr 1fr .9fr;gap:1rem;margin-top:1.5rem}.card{background:#fff;border:1px solid var(--rule);border-radius:6px;padding:1.25rem}.card .card-label{font:500 .5rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}.card-header{display:flex;align-items:center;gap:.6rem;margin-bottom:.875rem;padding-bottom:.875rem;border-bottom:1px solid rgba(45,49,66,.08)}.card-dot{width:7px;height:7px;border-radius:50%;background:var(--muted)}.card-dot.coral{background:var(--accent)}.card-dot.ink{background:var(--ink)}.card h3{font-size:.875rem;font-weight:600}.card p,.card li{color:var(--muted);font-size:.8125rem;line-height:1.55}.card ul{list-style:none}.card li{padding-left:.875rem;position:relative}.card li::before{content:'—';position:absolute;left:0;color:rgba(45,49,66,.25)}.footer{display:flex;justify-content:space-between;gap:.5rem;flex-wrap:wrap;margin-top:2rem;padding-top:1.5rem;border-top:1px solid var(--rule);color:var(--soft);font:.72rem var(--mono);letter-spacing:.06em}@media(max-width:820px){.cards{grid-template-columns:1fr}}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--paper:#f5f5f5;--paper-2:#ececec;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--accent:#eb6c36;--accent-tint:rgba(235,108,54,.08);--link:#2e5aa8;--rule:rgba(45,49,66,.12);--dot:rgba(45,49,66,.10);--side-fill:rgba(79,93,117,.06);--side-stroke:#7a8399;--zone-fill:rgba(45,49,66,.025);--zone-stroke:rgba(45,49,66,.32);--bar-fill:rgba(45,49,66,.05);--node-fill:#fff;--custom-red:#b85450;--custom-red-fill:rgba(184,84,80,.06);--custom-red-stroke:rgba(184,84,80,.35);--custom-blue:#5a7d9a;--custom-blue-fill:rgba(90,125,154,.06);--custom-blue-stroke:rgba(90,125,154,.35);--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
:root{--paper:#f5f5f5;--paper-2:#ececec;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--accent:#eb6c36;--accent-tint:rgba(235,108,54,.08);--link:#2e5aa8;--rule:rgba(45,49,66,.12);--dot:rgba(45,49,66,.10);--side-fill:rgba(79,93,117,.06);--side-stroke:#7a8399;--zone-fill:rgba(45,49,66,.025);--zone-stroke:rgba(45,49,66,.32);--bar-fill:rgba(45,49,66,.05);--node-fill:#fff;--custom-red:#9c6b50;--custom-red-fill:rgba(156,107,80,.06);--custom-red-stroke:rgba(156,107,80,.35);--custom-blue:#5e7a9b;--custom-blue-fill:rgba(94,122,155,.06);--custom-blue-stroke:rgba(94,122,155,.35);--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
body{font-family:var(--sans);background:var(--paper);color:var(--ink);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:3rem 2rem}.frame{max-width:1200px;width:100%}.eyebrow{font:500 .66rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}h1{font:400 clamp(1.5rem,2.4vw + .75rem,2rem)/1.15 var(--serif);letter-spacing:-.02em;margin-bottom:1.5rem}svg{width:100%;min-width:1100px;display:block}svg text{font-family:var(--sans)}.mono{font-family:var(--mono)}.connector{fill:none;stroke:var(--muted);stroke-width:1.2}.connector.primary{stroke:var(--accent);stroke-width:1.4}.connector.trigger{stroke:var(--muted);stroke-width:1;stroke-dasharray:4 3}.connector.auth{stroke:var(--accent);stroke-width:1.2;stroke-dasharray:5 4}.label-mask{fill:var(--paper)}.edge-label{fill:var(--accent);font:8px var(--mono);letter-spacing:.06em}.zone{fill:var(--zone-fill);stroke:var(--zone-stroke)}.zone-mask,.node-mask{fill:var(--paper)}.zone-label{fill:var(--soft);font:500 8px var(--mono);letter-spacing:.18em}.side-node{fill:var(--side-fill);stroke:var(--side-stroke)}.core-node{fill:var(--accent-tint);stroke:var(--accent)}.bar{fill:var(--bar-fill);stroke:var(--zone-stroke)}.role-box{fill:transparent;stroke:var(--side-stroke);stroke-opacity:.5}.role-box.focal{stroke:var(--accent)}.role-text{fill:var(--side-stroke);font:500 7px var(--mono);letter-spacing:.08em}.role-text.focal{fill:var(--accent)}.node-name{fill:var(--ink);font:600 12px var(--sans)}.node-name.focal{fill:var(--ink)}.node-sub{fill:var(--muted);font:9px var(--mono)}.footer-red{fill:var(--custom-red-fill);stroke:var(--custom-red-stroke)}.footer-blue{fill:var(--custom-blue-fill);stroke:var(--custom-blue-stroke)}.footer-red-text{fill:var(--custom-red)}.footer-blue-text{fill:var(--custom-blue)}.legend-key{fill:var(--muted);font:500 8px var(--mono);letter-spacing:.14em}.legend-text{fill:var(--muted);font:8px var(--sans)}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
svg .node { fill:rgba(245,245,245,.04); stroke:rgba(245,245,245,.32); stroke-width:1; }
|
||||
svg .node.external { stroke:#bfc0c0; stroke-dasharray:4 3; }
|
||||
svg .node.focal { fill:rgba(240,138,89,.12); stroke:#f08a59; stroke-width:1.4; }
|
||||
svg .node.survivor { fill:rgba(154,172,103,.10); stroke:rgba(154,172,103,.55); }
|
||||
svg .node.survivor { fill:rgba(156,175,143,.10); stroke:rgba(156,175,143,.55); }
|
||||
svg .node-name { fill:#f5f5f5; font:600 12px 'Geist',sans-serif; }
|
||||
svg .node-name.survivor { fill:#9aac67; }
|
||||
svg .node-name.survivor { fill:#9caf8f; }
|
||||
svg .sub { fill:#bfc0c0; font:400 10px 'Geist Mono',monospace; } svg .sub.focal { fill:#f08a59; }
|
||||
svg .arrow-label { font-size:8px; font-weight:600; letter-spacing:.08em; } svg .legend-label { fill:#bfc0c0; font-size:8px; }
|
||||
</style>
|
||||
@@ -64,7 +64,7 @@
|
||||
<rect class="node-mask" x="36" y="256" width="216" height="56" rx="6"/><rect class="node external" x="36" y="256" width="216" height="56" rx="6"/><use href="#icon-file" x="48" y="272" width="24" height="24" color="#bfc0c0"/><text class="node-name" x="80" y="282">Supplier Price Lists</text><text class="sub" x="80" y="298">external · XLSX</text>
|
||||
<rect class="node-mask" x="340" y="80" width="264" height="68" rx="6"/><rect class="node focal" x="340" y="80" width="264" height="68" rx="6"/><use href="#icon-file" x="352" y="100" width="24" height="24" color="#f08a59"/><text class="node-name" x="384" y="108">Shared Drive</text><text class="sub focal" x="384" y="124">no version control</text><text class="sub" x="384" y="140">department folders</text>
|
||||
<rect class="node-mask" x="340" y="232" width="264" height="72" rx="6"/><rect class="node" x="340" y="232" width="264" height="72" rx="6"/><use href="#icon-screen" x="352" y="256" width="24" height="24" color="#f5f5f5"/><text class="node-name" x="384" y="262">Spreadsheet Handoffs</text><text class="sub" x="384" y="278">local workbooks · macros</text><text class="sub" x="384" y="292">manual reconciliation</text>
|
||||
<rect class="node-mask" x="340" y="344" width="264" height="56" rx="6"/><rect class="node survivor" x="340" y="344" width="264" height="56" rx="6"/><use href="#icon-db" x="352" y="360" width="24" height="24" color="#9aac67"/><text class="node-name survivor" x="384" y="370">On-prem RDBMS</text><text class="sub" x="384" y="386">inventory · finance</text>
|
||||
<rect class="node-mask" x="340" y="344" width="264" height="56" rx="6"/><rect class="node survivor" x="340" y="344" width="264" height="56" rx="6"/><use href="#icon-db" x="352" y="360" width="24" height="24" color="#9caf8f"/><text class="node-name survivor" x="384" y="370">On-prem RDBMS</text><text class="sub" x="384" y="386">inventory · finance</text>
|
||||
<rect class="node-mask" x="704" y="80" width="208" height="60" rx="6"/><rect class="node focal" x="704" y="80" width="208" height="60" rx="6"/><use href="#icon-screen" x="716" y="100" width="24" height="24" color="#f08a59"/><text class="node-name" x="748" y="108">Reporting Portal</text><text class="sub focal" x="748" y="124">manual refresh</text>
|
||||
<rect class="node-mask" x="704" y="216" width="208" height="56" rx="6"/><rect class="node" x="704" y="216" width="208" height="56" rx="6"/><use href="#icon-file" x="716" y="232" width="24" height="24" color="#f5f5f5"/><text class="node-name" x="748" y="242">Email Report Packs</text><text class="sub" x="748" y="258">weekly · PDF</text>
|
||||
<rect class="node-mask" x="704" y="344" width="208" height="56" rx="6"/><rect class="node external" x="704" y="344" width="208" height="56" rx="6"/><use href="#icon-users" x="716" y="360" width="24" height="24" color="#bfc0c0"/><text class="node-name" x="748" y="370">Regional Managers</text><text class="sub" x="748" y="386">12 store regions</text>
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
svg { display:block; width:100%; min-width:920px; }
|
||||
svg .zone { fill:rgba(45,49,66,.02); stroke:rgba(45,49,66,.10); stroke-width:.8; } svg .zone-mask,svg .node-mask,svg .label-mask { fill:#f5f5f5; }
|
||||
svg .zone-label,svg .arrow-label,svg .legend-label { font-family:'Geist Mono',monospace; } svg .zone-label { fill:rgba(45,49,66,.40); font-size:8px; letter-spacing:.14em; }
|
||||
svg .node { fill:#fff; stroke:#2d3142; stroke-width:1; } svg .node.external { stroke:#4f5d75; stroke-dasharray:4 3; } svg .node.focal { fill:rgba(235,108,54,.07); stroke:#eb6c36; stroke-width:1.4; } svg .node.survivor { fill:rgba(122,140,71,.06); stroke:rgba(122,140,71,.45); }
|
||||
svg .node-name { fill:#2d3142; font:600 12px 'Geist',sans-serif; } svg .node-name.survivor { fill:#7a8c47; } svg .sub { fill:#4f5d75; font:400 10px 'Geist Mono',monospace; } svg .sub.focal { fill:#eb6c36; }
|
||||
svg .node { fill:#fff; stroke:#2d3142; stroke-width:1; } svg .node.external { stroke:#4f5d75; stroke-dasharray:4 3; } svg .node.focal { fill:rgba(235,108,54,.07); stroke:#eb6c36; stroke-width:1.4; } svg .node.survivor { fill:rgba(124,143,111,.06); stroke:rgba(124,143,111,.45); }
|
||||
svg .node-name { fill:#2d3142; font:600 12px 'Geist',sans-serif; } svg .node-name.survivor { fill:#7c8f6f; } svg .sub { fill:#4f5d75; font:400 10px 'Geist Mono',monospace; } svg .sub.focal { fill:#eb6c36; }
|
||||
svg .arrow-label { font-size:8px; font-weight:600; letter-spacing:.08em; } svg .legend-label { fill:#4f5d75; font-size:8px; }
|
||||
.cards { display:grid; grid-template-columns:1.1fr 1fr .9fr; gap:1rem; margin-top:1.5rem; } @media(max-width:820px){.cards{grid-template-columns:1fr;}}
|
||||
.card { padding:1.25rem; background:#fff; border:1px solid var(--rule); border-radius:6px; } .card .eyebrow { margin-bottom:.5rem; font-size:.5rem; }
|
||||
@@ -49,7 +49,7 @@
|
||||
<rect class="node-mask" x="36" y="256" width="216" height="56" rx="6"/><rect class="node external" x="36" y="256" width="216" height="56" rx="6"/><use href="#icon-file" x="48" y="272" width="24" height="24" color="#4f5d75"/><text class="node-name" x="80" y="282">Supplier Price Lists</text><text class="sub" x="80" y="298">external · XLSX</text>
|
||||
<rect class="node-mask" x="340" y="80" width="264" height="68" rx="6"/><rect class="node focal" x="340" y="80" width="264" height="68" rx="6"/><use href="#icon-file" x="352" y="100" width="24" height="24" color="#eb6c36"/><text class="node-name" x="384" y="108">Shared Drive</text><text class="sub focal" x="384" y="124">no version control</text><text class="sub" x="384" y="140">department folders</text>
|
||||
<rect class="node-mask" x="340" y="232" width="264" height="72" rx="6"/><rect class="node" x="340" y="232" width="264" height="72" rx="6"/><use href="#icon-screen" x="352" y="256" width="24" height="24" color="#2d3142"/><text class="node-name" x="384" y="262">Spreadsheet Handoffs</text><text class="sub" x="384" y="278">local workbooks · macros</text><text class="sub" x="384" y="292">manual reconciliation</text>
|
||||
<rect class="node-mask" x="340" y="344" width="264" height="56" rx="6"/><rect class="node survivor" x="340" y="344" width="264" height="56" rx="6"/><use href="#icon-db" x="352" y="360" width="24" height="24" color="#7a8c47"/><text class="node-name survivor" x="384" y="370">On-prem RDBMS</text><text class="sub" x="384" y="386">inventory · finance</text>
|
||||
<rect class="node-mask" x="340" y="344" width="264" height="56" rx="6"/><rect class="node survivor" x="340" y="344" width="264" height="56" rx="6"/><use href="#icon-db" x="352" y="360" width="24" height="24" color="#7c8f6f"/><text class="node-name survivor" x="384" y="370">On-prem RDBMS</text><text class="sub" x="384" y="386">inventory · finance</text>
|
||||
<rect class="node-mask" x="704" y="80" width="208" height="60" rx="6"/><rect class="node focal" x="704" y="80" width="208" height="60" rx="6"/><use href="#icon-screen" x="716" y="100" width="24" height="24" color="#eb6c36"/><text class="node-name" x="748" y="108">Reporting Portal</text><text class="sub focal" x="748" y="124">manual refresh</text>
|
||||
<rect class="node-mask" x="704" y="216" width="208" height="56" rx="6"/><rect class="node" x="704" y="216" width="208" height="56" rx="6"/><use href="#icon-file" x="716" y="232" width="24" height="24" color="#2d3142"/><text class="node-name" x="748" y="242">Email Report Packs</text><text class="sub" x="748" y="258">weekly · PDF</text>
|
||||
<rect class="node-mask" x="704" y="344" width="208" height="56" rx="6"/><rect class="node external" x="704" y="344" width="208" height="56" rx="6"/><use href="#icon-users" x="716" y="360" width="24" height="24" color="#4f5d75"/><text class="node-name" x="748" y="370">Regional Managers</text><text class="sub" x="748" y="386">12 store regions</text>
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
svg .node { fill: #fff; stroke: #2d3142; stroke-width: 1; }
|
||||
svg .node.external { stroke: #4f5d75; stroke-dasharray: 4 3; }
|
||||
svg .node.focal { fill: rgba(235,108,54,0.07); stroke: #eb6c36; stroke-width: 1.4; }
|
||||
svg .node.survivor { fill: rgba(122,140,71,0.06); stroke: rgba(122,140,71,0.45); }
|
||||
svg .node.survivor { fill: rgba(124,143,111,0.06); stroke: rgba(124,143,111,0.45); }
|
||||
svg .node-name { fill: #2d3142; font: 600 12px 'Geist', sans-serif; }
|
||||
svg .node-name.survivor { fill: #7a8c47; }
|
||||
svg .node-name.survivor { fill: #7c8f6f; }
|
||||
svg .sub { fill: #4f5d75; font: 400 10px 'Geist Mono', monospace; }
|
||||
svg .sub.focal { fill: #eb6c36; }
|
||||
svg .arrow-label { font-size: 8px; font-weight: 600; letter-spacing: 0.08em; }
|
||||
@@ -94,7 +94,7 @@
|
||||
<rect class="node-mask" x="340" y="232" width="264" height="72" rx="6"/><rect class="node" x="340" y="232" width="264" height="72" rx="6"/>
|
||||
<use href="#icon-screen" x="352" y="256" width="24" height="24" color="#2d3142"/><text class="node-name" x="384" y="262">Spreadsheet Handoffs</text><text class="sub" x="384" y="278">local workbooks · macros</text><text class="sub" x="384" y="292">manual reconciliation</text>
|
||||
<rect class="node-mask" x="340" y="344" width="264" height="56" rx="6"/><rect class="node survivor" x="340" y="344" width="264" height="56" rx="6"/>
|
||||
<use href="#icon-db" x="352" y="360" width="24" height="24" color="#7a8c47"/><text class="node-name survivor" x="384" y="370">On-prem RDBMS</text><text class="sub" x="384" y="386">inventory · finance</text>
|
||||
<use href="#icon-db" x="352" y="360" width="24" height="24" color="#7c8f6f"/><text class="node-name survivor" x="384" y="370">On-prem RDBMS</text><text class="sub" x="384" y="386">inventory · finance</text>
|
||||
|
||||
<!-- Dissemination -->
|
||||
<rect class="node-mask" x="704" y="80" width="208" height="60" rx="6"/><rect class="node focal" x="704" y="80" width="208" height="60" rx="6"/>
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>The self-improving loop · Dark</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--paper: #2d3142; --ink: #f5f5f5; --muted: #bfc0c0; --soft: #8e98ac;
|
||||
--accent: #f08a59; --accent-tint: rgba(240,138,89,0.10); --link: #6a95d8;
|
||||
--sans: 'Geist', system-ui, sans-serif; --serif: 'Instrument Serif', serif;
|
||||
--mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; background: var(--paper); color: var(--ink); font-family: var(--sans); }
|
||||
.frame { width: 100%; max-width: 1200px; }
|
||||
.eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.66rem var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
h1 { margin-bottom: 1.5rem; color: var(--ink); font: 400 clamp(1.5rem, 2.4vw + 0.75rem, 2rem)/1.15 var(--serif); letter-spacing: -0.02em; }
|
||||
svg { display: block; width: 100%; min-width: 900px; }
|
||||
.ring { fill: none; stroke: var(--muted); stroke-width: 1.2; }
|
||||
.spoke { fill: none; stroke: var(--soft); stroke-width: 1; stroke-dasharray: 5 4; }
|
||||
.station { fill: var(--paper); stroke: var(--ink); stroke-width: 1; }
|
||||
.station.focal { fill: var(--accent-tint); stroke: var(--accent); stroke-width: 1.2; }
|
||||
.hub { fill: var(--ink); }
|
||||
.node-name { fill: var(--ink); font: 600 12px var(--sans); text-anchor: middle; }
|
||||
.focal-name { fill: var(--accent); }
|
||||
.sublabel { fill: var(--soft); font: 400 8px var(--mono); text-anchor: middle; }
|
||||
.hub-name { fill: var(--paper); font: 600 16px var(--sans); text-anchor: middle; }
|
||||
.hub-sub { fill: var(--paper); opacity: 0.72; font: 400 8px var(--mono); text-anchor: middle; }
|
||||
.arrow-label { fill: var(--soft); font: 400 8px var(--mono); letter-spacing: 0.06em; text-anchor: middle; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="frame">
|
||||
<p class="eyebrow">Loop · Diagram Design</p>
|
||||
<h1>The self-improving loop</h1>
|
||||
|
||||
<svg viewBox="0 0 1040 680" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="loop-title loop-desc">
|
||||
<title id="loop-title">The self-improving loop</title>
|
||||
<desc id="loop-desc">Six stations flow clockwise from Capture through Learn and back to Capture. Each station writes shared state into one central memory hub, with Decide highlighted as the human approval gate.</desc>
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r="0.9" fill="rgba(245,245,245,0.10)"/></pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#bfc0c0"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#f08a59"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#6a95d8"/></marker>
|
||||
<marker id="arrow-soft" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#8e98ac"/></marker>
|
||||
</defs>
|
||||
<rect width="1040" height="680" fill="#2d3142"/>
|
||||
<rect width="1040" height="680" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Solid clockwise ring: every endpoint lands on a station edge. -->
|
||||
<path class="ring" d="M600 92 C648 100 696 140 672 188" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M808 236 H824 Q832 236 832 244 V436 Q832 444 824 444 H808" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M672 492 C696 520 648 564 600 572" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M440 572 C392 564 344 520 368 492" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M232 444 H216 Q208 444 208 436 V244 Q208 236 216 236 H232" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M368 188 C344 140 392 100 440 92" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dashed write-backs: station inner edge to 6px before the hub stroke. -->
|
||||
<path class="spoke" d="M520 132 V280" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M672 252 L616 284" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M672 428 L616 396" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M520 548 V400" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M368 428 L424 396" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M368 252 L424 284" marker-end="url(#arrow-soft)"/>
|
||||
|
||||
<!-- Curated spoke labels sit beside the line with an 8px visible gap. -->
|
||||
<rect x="532" y="200" width="48" height="16" rx="4" fill="#2d3142"/>
|
||||
<text x="556" y="212" class="arrow-label">SIGNALS</text>
|
||||
<rect x="460" y="464" width="52" height="16" rx="4" fill="#2d3142"/>
|
||||
<text x="486" y="476" class="arrow-label">OUTCOMES</text>
|
||||
|
||||
<!-- Stations -->
|
||||
<rect class="station" x="440" y="68" width="160" height="64" rx="6"/>
|
||||
<text x="520" y="96" class="node-name">Capture</text><text x="520" y="116" class="sublabel">signals in / intake</text>
|
||||
|
||||
<rect class="station" x="648" y="188" width="160" height="64" rx="6"/>
|
||||
<text x="728" y="216" class="node-name">Research</text><text x="728" y="236" class="sublabel">evidence pulled</text>
|
||||
|
||||
<rect class="station focal" x="648" y="428" width="160" height="64" rx="6"/>
|
||||
<text x="728" y="456" class="node-name focal-name">Decide</text><text x="728" y="476" class="sublabel">human approves</text>
|
||||
|
||||
<rect class="station" x="440" y="548" width="160" height="64" rx="6"/>
|
||||
<text x="520" y="576" class="node-name">Act</text><text x="520" y="596" class="sublabel">work ships</text>
|
||||
|
||||
<rect class="station" x="232" y="428" width="160" height="64" rx="6"/>
|
||||
<text x="312" y="456" class="node-name">Measure</text><text x="312" y="476" class="sublabel">outcomes logged</text>
|
||||
|
||||
<rect class="station" x="232" y="188" width="160" height="64" rx="6"/>
|
||||
<text x="312" y="216" class="node-name">Learn</text><text x="312" y="236" class="sublabel">playbook updated</text>
|
||||
|
||||
<!-- The one shared-state hub. -->
|
||||
<rect class="hub" x="420" y="288" width="200" height="104" rx="8"/>
|
||||
<text x="520" y="336" class="hub-name">Shared memory</text>
|
||||
<text x="520" y="360" class="hub-sub">one record, every loop</text>
|
||||
</svg>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,137 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>The self-improving loop · Full</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--paper: #f5f5f5; --paper-2: #ececec; --ink: #2d3142; --muted: #4f5d75;
|
||||
--soft: #7a8399; --rule: rgba(45,49,66,0.12); --accent: #eb6c36;
|
||||
--accent-tint: rgba(235,108,54,0.08); --link: #2e5aa8;
|
||||
--sans: 'Geist', system-ui, sans-serif; --serif: 'Instrument Serif', serif;
|
||||
--mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body { min-height: 100vh; padding: 3rem 2rem; background: var(--paper); color: var(--ink); font-family: var(--sans); }
|
||||
.container { width: 100%; max-width: 1200px; margin: 0 auto; }
|
||||
.header { margin-bottom: 2.5rem; }
|
||||
.eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.66rem var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
h1 { margin-bottom: 0.5rem; color: var(--ink); font: 400 clamp(1.5rem, 2.4vw + 0.75rem, 2rem)/1.15 var(--serif); letter-spacing: -0.02em; }
|
||||
.subtitle { max-width: 62ch; color: var(--muted); font-size: 1rem; line-height: 1.55; }
|
||||
.diagram-container { overflow-x: auto; padding: 1.5rem; background: var(--paper-2); border: 1px solid var(--rule); border-radius: 8px; }
|
||||
svg { display: block; width: 100%; min-width: 1040px; }
|
||||
.ring { fill: none; stroke: var(--muted); stroke-width: 1.2; }
|
||||
.spoke { fill: none; stroke: var(--soft); stroke-width: 1; stroke-dasharray: 5 4; }
|
||||
.station { fill: var(--paper); stroke: var(--ink); stroke-width: 1; }
|
||||
.station.focal { fill: var(--accent-tint); stroke: var(--accent); stroke-width: 1.2; }
|
||||
.hub { fill: var(--ink); }
|
||||
.node-name { fill: var(--ink); font: 600 12px var(--sans); text-anchor: middle; }
|
||||
.focal-name { fill: var(--accent); }
|
||||
.sublabel { fill: var(--soft); font: 400 8px var(--mono); text-anchor: middle; }
|
||||
.hub-name { fill: var(--paper); font: 600 16px var(--sans); text-anchor: middle; }
|
||||
.hub-sub { fill: var(--paper); opacity: 0.72; font: 400 8px var(--mono); text-anchor: middle; }
|
||||
.arrow-label { fill: var(--soft); font: 400 8px var(--mono); letter-spacing: 0.06em; text-anchor: middle; }
|
||||
.cards { display: grid; grid-template-columns: 1.1fr 1fr 0.9fr; gap: 1rem; margin-top: 1.5rem; }
|
||||
.card { padding: 1.25rem; background: #fff; border: 1px solid var(--rule); border-radius: 6px; }
|
||||
.card-eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.5rem var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
.card-header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.875rem; padding-bottom: 0.875rem; border-bottom: 1px solid rgba(45,49,66,0.08); }
|
||||
.card-dot { width: 7px; height: 7px; flex: 0 0 auto; border-radius: 50%; }
|
||||
.card-dot.coral { background: var(--accent); } .card-dot.ink { background: var(--ink); } .card-dot.muted { background: var(--muted); }
|
||||
.card h2 { font-size: 0.875rem; font-weight: 600; }
|
||||
.card p, .card ul { color: var(--muted); font-size: 0.8125rem; line-height: 1.55; list-style: none; }
|
||||
.card li { position: relative; margin-bottom: 0.3rem; padding-left: 0.875rem; }
|
||||
.card li::before { position: absolute; left: 0; color: rgba(45,49,66,0.25); content: '—'; }
|
||||
.footer { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.5rem; margin-top: 2rem; padding-top: 1.5rem; color: var(--soft); border-top: 1px solid rgba(45,49,66,0.10); font: 400 0.72rem var(--mono); letter-spacing: 0.06em; }
|
||||
@media (max-width: 820px) { .cards { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<header class="header">
|
||||
<p class="eyebrow">Loop · Diagram Design</p>
|
||||
<h1>The self-improving loop</h1>
|
||||
<p class="subtitle">A company operating rhythm where work moves clockwise, every pass writes back to one shared record, and a human stays accountable for the decision.</p>
|
||||
</header>
|
||||
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1040 680" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="loop-title loop-desc">
|
||||
<title id="loop-title">The self-improving loop</title>
|
||||
<desc id="loop-desc">Six stations flow clockwise from Capture through Learn and back to Capture. Each station writes shared state into one central memory hub, with Decide highlighted as the human approval gate.</desc>
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/></pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#2e5aa8"/></marker>
|
||||
<marker id="arrow-soft" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#7a8399"/></marker>
|
||||
</defs>
|
||||
<rect width="1040" height="680" fill="#f5f5f5"/>
|
||||
<rect width="1040" height="680" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Solid clockwise ring: every endpoint lands on a station edge. -->
|
||||
<path class="ring" d="M600 92 C648 100 696 140 672 188" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M808 236 H824 Q832 236 832 244 V436 Q832 444 824 444 H808" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M672 492 C696 520 648 564 600 572" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M440 572 C392 564 344 520 368 492" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M232 444 H216 Q208 444 208 436 V244 Q208 236 216 236 H232" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M368 188 C344 140 392 100 440 92" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dashed write-backs: station inner edge to 6px before the hub stroke. -->
|
||||
<path class="spoke" d="M520 132 V280" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M672 252 L616 284" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M672 428 L616 396" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M520 548 V400" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M368 428 L424 396" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M368 252 L424 284" marker-end="url(#arrow-soft)"/>
|
||||
|
||||
<!-- Curated spoke labels sit beside the line with an 8px visible gap. -->
|
||||
<rect x="532" y="200" width="48" height="16" rx="4" fill="#f5f5f5"/>
|
||||
<text x="556" y="212" class="arrow-label">SIGNALS</text>
|
||||
<rect x="460" y="464" width="52" height="16" rx="4" fill="#f5f5f5"/>
|
||||
<text x="486" y="476" class="arrow-label">OUTCOMES</text>
|
||||
|
||||
<!-- Stations -->
|
||||
<rect class="station" x="440" y="68" width="160" height="64" rx="6"/>
|
||||
<text x="520" y="96" class="node-name">Capture</text><text x="520" y="116" class="sublabel">signals in / intake</text>
|
||||
<rect class="station" x="648" y="188" width="160" height="64" rx="6"/>
|
||||
<text x="728" y="216" class="node-name">Research</text><text x="728" y="236" class="sublabel">evidence pulled</text>
|
||||
<rect class="station focal" x="648" y="428" width="160" height="64" rx="6"/>
|
||||
<text x="728" y="456" class="node-name focal-name">Decide</text><text x="728" y="476" class="sublabel">human approves</text>
|
||||
<rect class="station" x="440" y="548" width="160" height="64" rx="6"/>
|
||||
<text x="520" y="576" class="node-name">Act</text><text x="520" y="596" class="sublabel">work ships</text>
|
||||
<rect class="station" x="232" y="428" width="160" height="64" rx="6"/>
|
||||
<text x="312" y="456" class="node-name">Measure</text><text x="312" y="476" class="sublabel">outcomes logged</text>
|
||||
<rect class="station" x="232" y="188" width="160" height="64" rx="6"/>
|
||||
<text x="312" y="216" class="node-name">Learn</text><text x="312" y="236" class="sublabel">playbook updated</text>
|
||||
|
||||
<!-- The one shared-state hub. -->
|
||||
<rect class="hub" x="420" y="288" width="200" height="104" rx="8"/>
|
||||
<text x="520" y="336" class="hub-name">Shared memory</text>
|
||||
<text x="520" y="360" class="hub-sub">one record, every loop</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<section class="cards" aria-label="Loop summary">
|
||||
<article class="card">
|
||||
<p class="card-eyebrow">THE LOOP SIGNAL</p>
|
||||
<div class="card-header"><span class="card-dot coral"></span><h2>The dashed lines are the product</h2></div>
|
||||
<p>Shipping work matters once. Writing the result back makes the next pass faster, safer, and better informed.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-header"><span class="card-dot ink"></span><h2>Memory compounds</h2></div>
|
||||
<ul><li>Evidence survives the handoff</li><li>Outcomes update the playbook</li><li>One record stays shared</li></ul>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-header"><span class="card-dot muted"></span><h2>One human gate</h2></div>
|
||||
<p>Decide is the only focal station because accountability should remain visible inside an improving system.</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<span>company operations · reinforcing loop</span>
|
||||
<span>example · schematic skill</span>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>The self-improving loop</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--paper: #f5f5f5; --ink: #2d3142; --muted: #4f5d75; --soft: #7a8399;
|
||||
--accent: #eb6c36; --accent-tint: rgba(235,108,54,0.08); --link: #2e5aa8;
|
||||
--sans: 'Geist', system-ui, sans-serif; --serif: 'Instrument Serif', serif;
|
||||
--mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; background: var(--paper); color: var(--ink); font-family: var(--sans); }
|
||||
.frame { width: 100%; max-width: 1200px; }
|
||||
.eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.66rem var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
h1 { margin-bottom: 1.5rem; color: var(--ink); font: 400 clamp(1.5rem, 2.4vw + 0.75rem, 2rem)/1.15 var(--serif); letter-spacing: -0.02em; }
|
||||
svg { display: block; width: 100%; min-width: 900px; }
|
||||
.ring { fill: none; stroke: var(--muted); stroke-width: 1.2; }
|
||||
.spoke { fill: none; stroke: var(--soft); stroke-width: 1; stroke-dasharray: 5 4; }
|
||||
.station { fill: var(--paper); stroke: var(--ink); stroke-width: 1; }
|
||||
.station.focal { fill: var(--accent-tint); stroke: var(--accent); stroke-width: 1.2; }
|
||||
.hub { fill: var(--ink); }
|
||||
.node-name { fill: var(--ink); font: 600 12px var(--sans); text-anchor: middle; }
|
||||
.focal-name { fill: var(--accent); }
|
||||
.sublabel { fill: var(--soft); font: 400 8px var(--mono); text-anchor: middle; }
|
||||
.hub-name { fill: var(--paper); font: 600 16px var(--sans); text-anchor: middle; }
|
||||
.hub-sub { fill: var(--paper); opacity: 0.72; font: 400 8px var(--mono); text-anchor: middle; }
|
||||
.arrow-label { fill: var(--soft); font: 400 8px var(--mono); letter-spacing: 0.06em; text-anchor: middle; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="frame">
|
||||
<p class="eyebrow">Loop · Diagram Design</p>
|
||||
<h1>The self-improving loop</h1>
|
||||
|
||||
<svg viewBox="0 0 1040 680" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="loop-title loop-desc">
|
||||
<title id="loop-title">The self-improving loop</title>
|
||||
<desc id="loop-desc">Six stations flow clockwise from Capture through Learn and back to Capture. Each station writes shared state into one central memory hub, with Decide highlighted as the human approval gate.</desc>
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/></pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#2e5aa8"/></marker>
|
||||
<marker id="arrow-soft" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#7a8399"/></marker>
|
||||
</defs>
|
||||
<rect width="1040" height="680" fill="#f5f5f5"/>
|
||||
<rect width="1040" height="680" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Solid clockwise ring: every endpoint lands on a station edge. -->
|
||||
<path class="ring" d="M600 92 C648 100 696 140 672 188" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M808 236 H824 Q832 236 832 244 V436 Q832 444 824 444 H808" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M672 492 C696 520 648 564 600 572" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M440 572 C392 564 344 520 368 492" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M232 444 H216 Q208 444 208 436 V244 Q208 236 216 236 H232" marker-end="url(#arrow)"/>
|
||||
<path class="ring" d="M368 188 C344 140 392 100 440 92" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dashed write-backs: station inner edge to 6px before the hub stroke. -->
|
||||
<path class="spoke" d="M520 132 V280" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M672 252 L616 284" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M672 428 L616 396" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M520 548 V400" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M368 428 L424 396" marker-end="url(#arrow-soft)"/>
|
||||
<path class="spoke" d="M368 252 L424 284" marker-end="url(#arrow-soft)"/>
|
||||
|
||||
<!-- Curated spoke labels sit beside the line with an 8px visible gap. -->
|
||||
<rect x="532" y="200" width="48" height="16" rx="4" fill="#f5f5f5"/>
|
||||
<text x="556" y="212" class="arrow-label">SIGNALS</text>
|
||||
<rect x="460" y="464" width="52" height="16" rx="4" fill="#f5f5f5"/>
|
||||
<text x="486" y="476" class="arrow-label">OUTCOMES</text>
|
||||
|
||||
<!-- Stations -->
|
||||
<rect class="station" x="440" y="68" width="160" height="64" rx="6"/>
|
||||
<text x="520" y="96" class="node-name">Capture</text><text x="520" y="116" class="sublabel">signals in / intake</text>
|
||||
|
||||
<rect class="station" x="648" y="188" width="160" height="64" rx="6"/>
|
||||
<text x="728" y="216" class="node-name">Research</text><text x="728" y="236" class="sublabel">evidence pulled</text>
|
||||
|
||||
<rect class="station focal" x="648" y="428" width="160" height="64" rx="6"/>
|
||||
<text x="728" y="456" class="node-name focal-name">Decide</text><text x="728" y="476" class="sublabel">human approves</text>
|
||||
|
||||
<rect class="station" x="440" y="548" width="160" height="64" rx="6"/>
|
||||
<text x="520" y="576" class="node-name">Act</text><text x="520" y="596" class="sublabel">work ships</text>
|
||||
|
||||
<rect class="station" x="232" y="428" width="160" height="64" rx="6"/>
|
||||
<text x="312" y="456" class="node-name">Measure</text><text x="312" y="476" class="sublabel">outcomes logged</text>
|
||||
|
||||
<rect class="station" x="232" y="188" width="160" height="64" rx="6"/>
|
||||
<text x="312" y="216" class="node-name">Learn</text><text x="312" y="236" class="sublabel">playbook updated</text>
|
||||
|
||||
<!-- The one shared-state hub. -->
|
||||
<rect class="hub" x="420" y="288" width="200" height="104" rx="8"/>
|
||||
<text x="520" y="336" class="hub-name">Shared memory</text>
|
||||
<text x="520" y="360" class="hub-sub">one record, every loop</text>
|
||||
</svg>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -236,44 +236,47 @@
|
||||
<button class="tab new" data-type="radar">
|
||||
<span class="eyebrow">15</span>Radar
|
||||
</button>
|
||||
<button class="tab new" data-type="loop">
|
||||
<span class="eyebrow">16</span>Loop
|
||||
</button>
|
||||
<button class="tab new" data-type="datalake">
|
||||
<span class="eyebrow">16</span>Data lake
|
||||
<span class="eyebrow">17</span>Data lake
|
||||
</button>
|
||||
<button class="tab new" data-type="bar">
|
||||
<span class="eyebrow">17</span>Bar
|
||||
<span class="eyebrow">18</span>Bar
|
||||
</button>
|
||||
<button class="tab new" data-type="line">
|
||||
<span class="eyebrow">18</span>Line
|
||||
<span class="eyebrow">19</span>Line
|
||||
</button>
|
||||
<button class="tab new" data-type="gantt">
|
||||
<span class="eyebrow">19</span>Gantt
|
||||
<span class="eyebrow">20</span>Gantt
|
||||
</button>
|
||||
<button class="tab new" data-type="scatter">
|
||||
<span class="eyebrow">20</span>Scatter
|
||||
<span class="eyebrow">21</span>Scatter
|
||||
</button>
|
||||
<button class="tab new" data-type="medallion">
|
||||
<span class="eyebrow">21</span>Medallion
|
||||
<span class="eyebrow">22</span>Medallion
|
||||
</button>
|
||||
<button class="tab new" data-type="high-level">
|
||||
<span class="eyebrow">22</span>High-Level
|
||||
<span class="eyebrow">23</span>High-Level
|
||||
</button>
|
||||
<button class="tab new" data-type="process">
|
||||
<span class="eyebrow">23</span>Process
|
||||
<span class="eyebrow">24</span>Process
|
||||
</button>
|
||||
<button class="tab new" data-type="data-flow">
|
||||
<span class="eyebrow">25</span>Data flow
|
||||
<span class="eyebrow">26</span>Data flow
|
||||
</button>
|
||||
<button class="tab new" data-type="dp-integration">
|
||||
<span class="eyebrow">26</span>DP integration
|
||||
<span class="eyebrow">27</span>DP integration
|
||||
</button>
|
||||
<button class="tab new" data-type="high-level-vertical">
|
||||
<span class="eyebrow">27</span>High-Level / Parametric
|
||||
<span class="eyebrow">28</span>High-Level / Parametric
|
||||
</button>
|
||||
<button class="tab new" data-type="dp-security-matrix">
|
||||
<span class="eyebrow">31</span>DP security matrix
|
||||
<span class="eyebrow">32</span>DP security matrix
|
||||
</button>
|
||||
<button class="tab new" data-type="it-state">
|
||||
<span class="eyebrow">32</span>IT current-state
|
||||
<span class="eyebrow">33</span>IT current-state
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
# Loop
|
||||
|
||||
**Best for:** reinforcing cycles, flywheels, feedback loops, and operating loops — anything where the last step feeds the first and a shared hub accumulates state. Use Loop when the reader must see both motions at once: work advances clockwise around the ring, while each pass writes durable state back to one common center.
|
||||
|
||||
Prefer **Flowchart** when the path ends, branches toward an outcome, or never truly returns to its first step. Prefer **Cycle** when the center does not accumulate shared state. The dashed write-back spokes are the defining signal here: remove them and the figure is only a circular process.
|
||||
|
||||
This type is **parametric**. The inputs in §1 determine station count, angles, edge intersections, connector paths, and viewBox bounds. Identical inputs should produce identical geometry.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inputs — the parameter contract
|
||||
|
||||
```yaml
|
||||
title: "The self-improving loop"
|
||||
subtitle: "Every pass improves the shared operating record"
|
||||
|
||||
hub: # exactly one
|
||||
name: "Shared memory"
|
||||
sublabel: "one record, every loop"
|
||||
|
||||
stations: # 5..8, clockwise from top
|
||||
- { name: "Capture", sublabel: "signals in", spoke_label: "SIGNALS" }
|
||||
- { name: "Research", sublabel: "evidence pulled" }
|
||||
- { name: "Decide", sublabel: "human approves", focal: true }
|
||||
- { name: "Act", sublabel: "work ships", spoke_label: "OUTCOMES" }
|
||||
- { name: "Measure", sublabel: "outcomes logged" }
|
||||
- { name: "Learn", sublabel: "playbook updated" }
|
||||
|
||||
station_w: 160
|
||||
station_h: 64
|
||||
hub_w: 200
|
||||
hub_h: 104
|
||||
radius: 240
|
||||
margin: 64
|
||||
ring_style: "arc" # arc | rounded-orthogonal
|
||||
dark: false
|
||||
```
|
||||
|
||||
**Budget (hard):** **5–8 stations plus exactly one hub.** Above 8 stations, split the subject into an overview Loop and one or more detail diagrams. Exactly one hub — a loop with two hubs is two diagrams. At most one station may set `focal: true`; zero is allowed when no editorial gate deserves emphasis.
|
||||
|
||||
Station order is semantic. `stations[0]` is the top station, then entries proceed clockwise. The last station always connects back to station 0; if that return would be false, use a Flowchart instead.
|
||||
|
||||
---
|
||||
|
||||
## 2. Layout math — deterministic geometry
|
||||
|
||||
Use SVG coordinates, where positive y points downward. Let the hub center be `C = (cx, cy)`, station count be `N`, station ring radius be `R`, station half-size be `a = station_w/2`, `b = station_h/2`, and hub half-size be `A = hub_w/2`, `B = hub_h/2`.
|
||||
|
||||
### 2.1 Station centers
|
||||
|
||||
For zero-indexed station `k`:
|
||||
|
||||
```text
|
||||
theta_k = -90deg + k * (360deg / N)
|
||||
u_k = (cos(theta_k), sin(theta_k))
|
||||
P_k = C + R * u_k
|
||||
|
||||
station_center_x(k) = cx + R * cos(theta_k)
|
||||
station_center_y(k) = cy + R * sin(theta_k)
|
||||
station_x(k) = station_center_x(k) - station_w/2
|
||||
station_y(k) = station_center_y(k) - station_h/2
|
||||
```
|
||||
|
||||
Thus station 1 sits at the top, and increasing `k` moves clockwise. Round rendered coordinates to the nearest 4px grid point after computing the ideal geometry; preserve symmetry when rounding paired stations.
|
||||
|
||||
### 2.2 Solid ring-flow endpoints
|
||||
|
||||
Ring connectors travel from station `k` to station `j = (k + 1) mod N`. They terminate on box edges, never at box centers. The clockwise unit tangent at station `k` is:
|
||||
|
||||
```text
|
||||
t_k = (-sin(theta_k), cos(theta_k))
|
||||
```
|
||||
|
||||
The distance from the center of an axis-aligned station box to its boundary along a unit direction `v` is:
|
||||
|
||||
```text
|
||||
box_distance(v, a, b) = min(a / abs(v.x), b / abs(v.y))
|
||||
```
|
||||
|
||||
Ignore a division whose denominator is zero. Then:
|
||||
|
||||
```text
|
||||
d_exit = box_distance( t_k, a, b)
|
||||
d_entry = box_distance(-t_j, a, b)
|
||||
|
||||
ring_start(k) = P_k + d_exit * t_k
|
||||
ring_end(k) = P_j - d_entry * t_j
|
||||
```
|
||||
|
||||
For `ring_style: arc`, connect those edge points with a cubic whose control vectors follow the local tangents:
|
||||
|
||||
```text
|
||||
delta = 2pi / N
|
||||
lambda = 0.55 * R * delta
|
||||
|
||||
M ring_start
|
||||
C ring_start + lambda*t_k,
|
||||
ring_end - lambda*t_j,
|
||||
ring_end
|
||||
```
|
||||
|
||||
Reduce `lambda` only enough to keep the curve outside the hub and neighboring boxes. For `ring_style: rounded-orthogonal`, route both edge points to an outer rail and join them with `r=8` quarter-arcs. Every bend is rounded; no slanted straight segments. The closing connector from station `N-1` to station 0 uses the same formula and must remain visibly continuous with the other ring arrows.
|
||||
|
||||
### 2.3 Dashed write-back spoke endpoints
|
||||
|
||||
Each spoke runs inward from the station edge toward the hub. Use the same ray/box intersection formula, now on the radial vector `u_k`:
|
||||
|
||||
```text
|
||||
d_station = box_distance(u_k, a, b)
|
||||
d_hub = box_distance(u_k, A, B)
|
||||
marker_gap = 6 # 4..8px; 6px canonical
|
||||
|
||||
spoke_start(k) = P_k - d_station * u_k
|
||||
hub_edge(k) = C + d_hub * u_k
|
||||
spoke_end(k) = C + (d_hub + marker_gap) * u_k
|
||||
```
|
||||
|
||||
Because the arrow travels from the station toward `C`, adding `marker_gap` leaves the endpoint just outside the hub boundary. The lighter arrowhead stops before the hub stroke instead of colliding with it. Radial spokes are the type-specific exception to the general ban on slanted straight connectors; they must remain true radii, must not cross one another, and may touch only their source station and the hub.
|
||||
|
||||
Labels are optional when the station sublabel already names the write-back. When used, they follow the `arrow-label` role, stay to one side of the spoke, and receive an opaque `paper` mask with a visible 6–10px gap from the stroke. Label a curated subset rather than forcing six labels into the hub halo.
|
||||
|
||||
### 2.4 ViewBox sizing
|
||||
|
||||
The viewBox must include the full station rectangles, outer ring curves, arrowheads, and at least `margin` breathing room:
|
||||
|
||||
```text
|
||||
left <= cx - R - station_w/2 - margin
|
||||
right >= cx + R + station_w/2 + margin
|
||||
top <= cy - R - station_h/2 - margin
|
||||
bottom >= cy + R + station_h/2 + margin
|
||||
|
||||
viewBox_w = right - left
|
||||
viewBox_h = bottom - top
|
||||
```
|
||||
|
||||
If cubic controls or an outer orthogonal rail extend farther than those bounds, grow the corresponding margin. Never shrink the canvas until a station stroke, marker, or curved return clips. For the six-station canonical example, `viewBox="0 0 1040 680"`, `C=(520,340)`, `R=240`, station size `160×64`, and hub size `200×104` leave generous outer clearance.
|
||||
|
||||
---
|
||||
|
||||
## 3. Visual grammar
|
||||
|
||||
| Element | Treatment |
|
||||
|---|---|
|
||||
| Station | Standard node: `paper` fill, `ink` stroke, `radius-md`; name in `node-name`, sublabel in `sublabel` |
|
||||
| Hub | The one dark element: `ink` fill, `paper` text; slightly larger than a station |
|
||||
| Focal station | At most one: `accent-tint` fill, `accent` stroke; station name may use `accent` |
|
||||
| Ring flow | Solid `muted` stroke, default arrowhead at the destination; clockwise only |
|
||||
| Write-back spoke | Dashed `soft` stroke at reduced emphasis, `stroke-dasharray="5,4"`, with a `soft` arrowhead |
|
||||
| Spoke label | `arrow-label` role, `soft`, uppercase, paper mask, 6–10px clear of the connector |
|
||||
|
||||
Draw in this order: paper or optional dot grid → ring arrows → dashed spokes → spoke-label masks and labels → station boxes → hub → text. The nodes mask microscopic connector overshoot, while every intended endpoint still lands on an edge.
|
||||
|
||||
The hub is not a seventh process step. It is accumulated state: memory, standards, evidence, policy, or a shared operating record. Keep its copy to one name plus one short sublabel.
|
||||
|
||||
---
|
||||
|
||||
## 4. Connector rules (mandatory)
|
||||
|
||||
SKILL.md §6 applies in full, with the radial-spoke exception stated in §2.3:
|
||||
|
||||
- Ring arrows are solid and clockwise. Their destination markers land on station edges; no connector ends at a center point.
|
||||
- Spokes are dashed and point inward. A solid spoke destroys the visual distinction between operating flow and write-back.
|
||||
- Labels use opaque masks and maintain a visible 6–10px connector gap. Never place text on the stroke.
|
||||
- No ring connector or spoke may overlap another connector. Ring paths remain outside the hub; spokes occupy distinct radial routes.
|
||||
- When two spokes must leave the same station edge, fan their attach points by the §6 formula with at least 12px separation. The normal Loop has one spoke per station; use a second only when the semantics cannot be merged.
|
||||
- If a ring route would cross the hub, increase `R`, move to the outer rail, or split the diagram. Do not thread flow through shared state.
|
||||
|
||||
---
|
||||
|
||||
## 5. Dark variant — token swap
|
||||
|
||||
Apply the style-guide inversion rule; do not invent a second palette.
|
||||
|
||||
| Role | Light | Dark |
|
||||
|---|---|---|
|
||||
| Canvas and station fill | `paper` | dark `paper` |
|
||||
| Primary text and station stroke | `ink` | inverted `ink` |
|
||||
| Hub fill / hub text | `ink` / `paper` | inverted `ink` / dark `paper` |
|
||||
| Ring flow | `muted` | dark `muted` |
|
||||
| Write-back spokes and labels | `soft` | dark `soft` |
|
||||
| Focal fill / stroke | `accent-tint` / `accent` | dark `accent-tint` / brighter dark `accent` |
|
||||
| Rule and dot grid | `rule` | inverted `rule` at the same opacity |
|
||||
|
||||
The semantic relationship stays unchanged in dark mode: one `ink`-filled hub, one optional `accent` station, neutral solid ring arrows, and lighter dashed write-backs.
|
||||
|
||||
---
|
||||
|
||||
## 6. Reproducibility checklist
|
||||
|
||||
1. Station count is 5–8 and hub count is exactly one.
|
||||
2. Station 0 is at `-90deg`; all others use equal `360/N` steps clockwise.
|
||||
3. Every solid ring arrow connects adjacent stations and the last returns to the first.
|
||||
4. Every ring marker lands on a station edge, not its center.
|
||||
5. Every dashed spoke begins on a station's inner edge and stops `marker_gap` before the hub stroke.
|
||||
6. Ring connectors stay outside the hub; spokes do not cross or overlap.
|
||||
7. At most one station uses `accent-tint` + `accent`; the hub alone uses the dark `ink` fill.
|
||||
8. Spoke labels, if present, use `arrow-label`, an opaque mask, and a 6–10px gap.
|
||||
9. The viewBox includes station boxes, strokes, curves, markers, and margins without clipping.
|
||||
|
||||
---
|
||||
|
||||
## 7. Anti-patterns
|
||||
|
||||
| Anti-pattern | Why it fails / correction |
|
||||
|---|---|
|
||||
| Two hubs | Two accumulated states create two systems. Draw two diagrams. |
|
||||
| Solid spokes | They look like primary flow and kill the dashed return signal. Use dashed `soft` write-backs. |
|
||||
| Stations at uneven angles without reason | The ring stops reading as one operating cadence. Use equal `360/N` spacing unless a documented phase grouping requires a deliberate gap. |
|
||||
| Connectors crossing the hub | Flow becomes confused with state. Route the ring outside or enlarge the radius. |
|
||||
| Accent on multiple stations | The editorial gate disappears. Keep one focal station at most. |
|
||||
| More than 8 stations | Labels and spokes crowd the hub. Split into overview + detail. |
|
||||
| A cycle that never actually returns | That is a Flowchart arranged in a circle. Use Flowchart and show the real endpoint. |
|
||||
|
||||
---
|
||||
|
||||
## 8. Examples
|
||||
|
||||
- `assets/example-loop.html` — minimal light: six-station self-improving operating loop.
|
||||
- `assets/example-loop-dark.html` — the same geometry under the dark token inversion.
|
||||
- `assets/example-loop-full.html` — editorial page with the flagship loop, three summary cards, and colophon.
|
||||