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.
This commit is contained in:
Cathryn Lavery
2026-07-14 19:41:17 -07:00
parent 2e9cefb9f3
commit e175eb723b
25 changed files with 912 additions and 50 deletions
@@ -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&amp;family=Geist:wght@400;500;600&amp;family=Geist+Mono:wght@400;500;600&amp;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&amp;family=Geist:wght@400;500;600&amp;family=Geist+Mono:wght@400;500;600&amp;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&amp;family=Geist:wght@400;500;600&amp;family=Geist+Mono:wght@400;500;600&amp;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&amp;family=Geist:wght@400;500;600&amp;family=Geist+Mono:wght@400;500;600&amp;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&amp;family=Geist:wght@400;500;600&amp;family=Geist+Mono:wght@400;500;600&amp;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&amp;family=Geist:wght@400;500;600&amp;family=Geist+Mono:wght@400;500;600&amp;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>
+16 -13
View File
@@ -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>