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:
@@ -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.
|
||||
Reference in New Issue
Block a user