- name
- flywheel
- description
- Explain a business, finance or strategy concept (SaaS flywheel, unit economics, LTV:CAC, cash conversion cycle, marketplace liquidity, network effects, burn and runway, cohort retention, pricing, operating leverage, funnels) as a live isometric "how things work" machine in one self-contained HTML page. A small business model drives every moving part, sliders change its inputs, and hover/click shows what each part means. Use this whenever someone wants to explain, teach, visualise or animate how a business works, an interactive explainer for a metric or business model, or a figure for a deck, memo, course or landing page, even if they never say "isometric" or "machine".
- license
- MIT
- homepage
- https://abhione.github.io/flywheel/
- compatibility
- Plain HTML, SVG and vanilla JS with no build step. The verify script needs Node 18+ and Chrome or Chromium.
# Flywheel
A figure is a small machine on a bench: hoppers, pipes, gates, gears, valves, gauges and tanks. Tokens (customers, sellers, coins) flow through it. The reader understands the business because they can watch it run and break it with a slider. **Every number on screen comes from the same model that moves the parts.**
`examples/saas-flywheel/index.html` is the reference figure, and `figures/two-sided-marketplace.html` is a second, larger one (two inputs, a branch, an overhead loop). Open it before you start and copy its four-section layout (MODEL, SHELL, MACHINE, FLOWS).
## The one idea that matters
**The machine is the explanation.** Don't draw boxes, arrows and labels. Pick a mechanism whose behaviour *is* the concept, where a lever on the machine is a lever in the business:
| Concept | Machine | What moves, and what it means |
|---|---|---|
| SaaS flywheel | Pipe loop through a hopper, turnstile, flywheel, gear, drain and tank | Flywheel speed = MRR momentum; the drain valve = churn |
| LTV:CAC | Balance scale | CAC weights against stacked monthly margin coins; it tips at payback |
| Cash conversion cycle | Three-stage conveyor and a cash reservoir | Belt length = DIO + DSO − DPO; the reservoir drains while goods ride |
| Two-sided marketplace | Two hoppers, a matching chamber, a toll gate | The liquidity gauge reads the match rate; the toll is the take rate |
| Burn and runway | Fuel tank, gauge in months, refuel valve | The level falls at the burn rate; a raise refills it |
| Cohort retention | A chest of drawers | Each cohort is a drawer that empties month by month |
| Operating leverage | Lever on a fulcrum | Fixed cost is the long arm; small revenue moves amplify profit |
| Funnel | Stacked sieves | Mesh size = conversion at each stage |
Two failures seen in practice. **Too abstract:** labelled boxes joined by arrows. **Too flat:** one slab with a few lines. The fix for both: a recognisable machine of many small parts (posts, collars, screws, label plate, ruler ticks), with the money and the people as moving tokens.
## Workflow
Follow these steps in order. Steps 1–3 are text only; write them as a comment block at the top of the figure's script.
1. **Size it and say so.** A **Figure** has one mechanism, 5–8 inspectable parts and 3–6 sliders (about 45–90 minutes). Anything bigger (several linked machines, a whole P&L) is a **Hero**: propose the core Figure first.
2. **Model first.** Write the business as a pure monthly `step(state, params) → state` plus `derive(state, params) → readouts`, in about 20–40 lines. Use real formulas (LTV = ARPA × GM ÷ churn, GMV = transactions × AOV, …) and state every assumption in the figure's `note`. Wrap it in `/* MODEL START */ … /* MODEL END */` and define `INIT` and `DEFAULTS` in it. Then **run `node scripts/model-check.mjs figure.html`**: 36 months at the defaults, every slider's min and max, and all-min/all-max. It fails on NaN, Infinity or a negative stock. Then tune until the defaults show a healthy-but-improvable business (for example break-even inside 2–3 years), at least one extreme shows each failure the figure teaches, and growth saturates. Expect two or three tuning passes. Clamp where reality clamps. (You can write the model block before the HTML exists: the script only needs the block and the `controls` ranges.)
3. **Pick the machine and list the parts.** One sentence: "what moves, and what that motion means". Then write the parts list in four groups:
- **inspectable parts:** one per model variable, each with a part id and an `info` entry.
- **flows:** the token routes and token kinds, and which model quantity sets each spawn rate.
- **gauges/levels:** the readouts drawn as physical parts.
- **structure:** base, posts, collars, label plate.
Map **every slider** to a visible change in a part (valve size, gate height, window glow, spawn rate), not just to a number.
4. **Plan the world on paper.** Use world units with x and y on the floor and z up. The base is about 600 × 470 at z 0–16. Nearer is larger x + y + z, so the screen front is the +x+y corner. Lay pipes out **along the x and y axes only** (diagonals get fat depth boxes; see `references/depth.md`), at one working height (z ≈ 50–60). Keep about 1.5 × the pipe width between a pipe and any part it doesn't connect to. Then:
- **Return loops and overhead runs go along the back (−y) and left (−x) edges.** These sit far from the viewer, so they frame the machine instead of crossing in front of the subject. A return pipe across the front hides everything behind it.
- **Tall parts (tanks, bins) go behind the main line**, at smaller x + y than the subject and the gates. In front they hide the chamber window and the toll gate.
- **The subject goes near the middle.** Gauges sit on a face that is visible from the +x+y side (the +x or +y face).
- **Write the coordinates as named constants** (`CH`, `TX`, `XO` …), so re-laying a part is a one-line change. You will move things after the first screenshot.
5. **Build with the kit.** Copy the example's skeleton and keep the kit script and CSS as `<link>`/`<script src>` tags (`kit/flywheel-kit.js`, `kit/flywheel.css`). Every drawable is a scene item with a world box: `S.item(box, {part})`, `LA.pipe`, `LA.post`, `LA.base`, `LA.flow`. **Stack parts exactly** (the top of one box is the bottom of the next) so the depth sort can separate them on z. A part that wraps a pipe (a gate, a collar) is split into pieces that don't intersect it: pillars beside the pipe and a lintel above it. **Pipes that meet a face or another pipe stop at its surface.** Use `LA.pipe(..., { inset: { start, end } })` to pull an end back by w/2. A tee starts the branch at the trunk's wall (trunk w/2 + branch w/2 from its axis), and a riser that feeds an overhead pipe ends at that pipe's underside (also `inset`). Add a `LA.collar` at the joint to hide the seam. Moving parts redraw into their own item's `<g>` each frame; call `S.sort()` once per frame, last.
6. **Make it live.** One `LA.run(tick)`. The model steps about every 1.2 s of wall time, which is one "month". Flows spawn at rates taken from the model (use `LA.rate()`, and cap them so the screen never floods). Seed every flow with a few tokens so a still capture already shows it working. Place gates, drains and tees on a route with `f.uNear([x,y,z])`, never by arc-length arithmetic. Support `?months=N` by stepping the model `F.months` times before the first readout (see the examples), so a capture can show the mature state as well as the cold start. Readouts update every month step, with tone `good`/`bad` on the key ratio.
7. **Inspector copy.** For each part give a title and 1–3 sentences: what it is in the business, which slider moves it and what to look for. Use `<code>` for slider names.
8. **Verify like a critic.** Run `node scripts/verify.mjs path/to/figure.html`. It fails if the page threw, never ran, or has a part without inspector text. It prints `audit=N` (intersecting boxes whose order is a guess) and writes desktop and phone PNGs. Run `MONTHS=24 node scripts/verify.mjs …` as well, to see the grown-up state. If your image viewer caches by path, copy each new PNG to a fresh filename before you look at it, or you'll review a stale picture. Then:
- **Read both PNGs.** Is it instantly a machine? Does any token, pipe or part paint over something it should be behind? Is there any clipping at the card edge? Do the readouts and title overlap?
- Get `audit` to 0, or explain each remaining pair. Fix the geometry (split, stack, re-route), never with a bias.
- Sweep each slider to both extremes and check the readouts stay finite and the machine visibly reacts. Use `?p=key:value,…` (see below) to capture a non-default state.
- Hover each part once and check every part has inspector text.
Iterate until a stranger could explain the concept from the picture.
## Kit at a glance
See `references/kit.md` for every signature.
- `LA.camera({ox, oy, scale})` → `P(x,y,z)` returns screen coordinates. Use one fixed azimuth (45°) per figure.
- `LA.scene(svg, P)` → `S.item(box, {part, dynamic, bias, name})` and `S.sort()`, the exact depth sort described in `references/depth.md`.
- Parts: `slab`, `cyl`, `frustum` (hopper), `gear`, `spokes`, `gauge`, `screws`, `ticks`, `labelPlate`, `halo`, `base`, `pipe`, `collar`, `post`.
- Materials: `paper steel dark blue amber green red gold glass accent`, each with three tones (top lit, left mid, right shade).
- `LA.flow(S, route, {speed})` → `spawn(kind,u)`, `update(dt)`, `at(u)`, `uAt(i)`, `uNear(pt)`, `setKind`, `kill`. Tokens ride on top of the pipe, so they sort correctly. Set `t.free=[x,y,z]` to make a token leave the route.
- `LA.figure({...})` builds the card, sliders, readout, legend, inspector and hover/click. `F.params()` reads the sliders.
- `LA.audit(S)`, `LA.run(tick)`, `LA.rate()`, `LA.money`, `LA.pct`, `LA.clamp`. URL overrides: `?p=take:30,subsidy:0` (sliders) and `?months=24` (fast-forward).
## Rules of the look
- Light paper theme by default. The subject (the matching chamber, the flywheel) gets the one strong material; everything else is paper and steel.
- Words stay off the drawing except tiny engraved labels on gauges and tanks. Explanations live in the inspector.
- Keep motion calm: the dt-based loop clamps dt, space pauses, it stops offscreen, and under reduced motion it draws one frame per input.
- Each token colour means one thing across the figure. Show it in the legend.
- Captions and the note must be true to the model. Mark illustrative numbers as illustrative.
## References
- `references/model.md`: modelling patterns and the formula crib (SaaS, marketplace, unit economics, cash cycle), plus sanity checks.
- `references/metaphors.md`: concept→machine table with parts lists for 16 concepts.
- `references/depth.md`: how the depth sort works, the rules that keep it exact, and how to read `audit`.
- `references/kit.md`: kit API.
- `references/verify.md`: verify script, review checklist, common faults.
Auf GitHub ansehen