# DataFlow Animator — authoring guide DataFlow Animator compiles a **JSON spec** into a deterministic, step-by-step animated diagram: packets travelling between servers, a protocol handshake, a tree rebalancing, current flowing through a circuit. The player adds play/pause, a scrub bar, Previous/Next step navigation, a text transcript and video export. You write the spec; the engine does the layout, the routing and the timing. This guide is what you need to write a GOOD spec. The exhaustive field list is the field reference (`types.md`); circuit terminal names are in `pins.md`; real specs are in the examples. ## 1. The model in one minute A spec has five parts. Only `nodes`, `packets` and `timeline` are required. | Part | What it is | | ------------- | -------------------------------------------------------------- | | `nodes` | The static cast: servers, users, databases, shapes, components | | `packets` | The payloads that travel. Invisible until a `move` carries one | | `connections` | Permanent links drawn from the start (the diagram's decor) | | `zones` | Background rectangles grouping nodes (a network, a cluster) | | `timeline` | The story: an ordered list of actions | Plus root options: `direction` (the layout), `description` (one or two sentences on what the animation shows — always write it), `pace`, `tree`, `diagonal_wires`. **Every ROOT action of `timeline` is one navigable step.** Previous/Next stop between them. A `parallel` action is one step whose children play together. **You never give pixel coordinates.** Nodes are placed from `direction` plus `lane` (flow layouts), `main` (circular), the `tree` block (tree), or automatically (graph, circuit); `x`/`y` fractions only pin nodes in `graph` and `circuit`. ## 2. How to write a good animation 1. **Decide the story before the JSON.** Write the 4–12 steps as sentences first: "the browser sends GET /users", "the server queries the database"… One idea per step. More than ~15 steps is a sign to split into two animations. 2. **Pick the layout** from the table in section 3. 3. **Cast the nodes**: one node per actor, with a short `text` label (2–3 words). Add an `icon` badge when a technology is involved (`postgres`, `react`, `nginx`, `docker`, `aws`…; free text up to 4 characters works too). 4. **Declare a packet per distinct payload**, with the text the reader should see on it (`GET /users`, `SELECT * FROM users`, `200 OK`). A packet can be moved several times (there and back, then onward). 5. **Narrate with `comment`.** A short sentence on the node it concerns, at the moment it matters. Omit `object` for a stage-wide caption. 6. **Let the engine time it.** Leave `duration` out almost everywhere: a `comment` or `set_content` gets a reading time from its text, a `move` a travel time from its distance. Use `pace` (e.g. `1.3`) to slow everything down at once. Write `duration` only for a deliberate effect. 7. **Show state, not just traffic**: `set_content` puts code, a web page, a table or rich HTML on a node; `set_color` and `highlight` mark what changed; `set_icon` keeps a value (a distance, a counter) visible on a node; `set_visible` makes a component appear when the story introduces it. 8. **Write every visible string in the user's language** (labels, comments, packet text). Technical tokens (`GET`, `SQL`, `200 OK`, identifiers) stay as they are. ## 3. Choosing `direction` | `direction` | Use it for | Placement fields | Closest examples | | ---------------------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------ | | `left-to-right` (default), `right-to-left`, `top-to-bottom`, `bottom-to-top` | Request/response flows, pipelines, protocols, architectures | `lane` (1, 2, 3… along the flow), `align_with` | `clientServer`, `loadBalancer`, `tls`, `microservices` | | `circular` | A hub and its spokes, a cluster of peers | `main: true` on the centre node | `signalr`, `raft`, `circular` | | `tree` | Binary trees (BST, AVL, red-black), with rotations | the `tree` block; no `connections` | `bstInsert`, `avlTree`, `redBlackRotation` | | `graph` | Weighted/arbitrary graphs: Dijkstra, A\*, spanning trees | none (auto); `x`/`y` to pin a node | `mst`, `dijkstra`, `astar` | | `circuit` | Electrical schematics and logic diagrams | none for a loop or feed-forward logic; else `x`/`y` | `circuit`, `halfAdder`, `rcCircuit`, `srLatch` | **Flow layouts.** `lane` is the position along the flow. Nodes sharing a lane stack across it (two backends side by side = same lane). Lanes are integers ≥ 1; keep them contiguous. `align_with: ""` lines a node up with another one in a different lane. **Tree.** `tree: { root: "8", children: { "8": { left: "3", right: "13" }, … } }`. Node ids are the tree's keys. Edges are drawn for you — style them with `tree.edge_style` / `tree.edges` (keyed by CHILD id), never with `connections`. `rotate_subtree` performs a rotation (`object` = pivot, `rotation`: `left` | `right`). To insert a node, declare it with `visible: false` AND already place it in `tree.children`, then reveal it with `set_visible` when the story inserts it. **Graph.** Edges are `connections` with `arrow_head: "none"`, `path: "straight"`, the weight in `text`, and an `id` so `set_color` / `highlight` can light them up. Nodes are usually `circle`s with the name in `body`. **Circuit.** See section 7. ## 4. Nodes `id` (unique, short, snake*case) and `type` are required. `text` is the label UNDER the node; it supports inline math between `$…$` (`"$V*{out}$"`; in JSON a backslash is doubled: `"$\\overline{A}$"`). Node type families (full enum: `NodeType` in the reference): - **Pictograms**: `desktop`, `laptop`, `mobile`, `client`, `server`, `cloud`, `database`, `user`, `admin`, `users`, and the characters `alice`, `bob`, `eve` (for cryptography and protocol stories). - **Text panels**: `simple_node` (a box showing `body`) and `complex_node` (`header` + `body`, looks like an HTTP message). `language` syntax-highlights both (`json`, `sql`, `http`, `javascript`, `python`…, enum `HighlightLanguage`). - **Shapes**: `square`, `circle`, `diamond`, `triangle`, `parallelogram`, `width_rectangle`, `height_rectangle`, `star`. `body` is a SHORT centred text (a word, a number) — longer text is cropped; use a `simple_node` instead. - **Circuit components and logic**: resistors, sources, transistors, gates, flip-flops, `signal` pads, `junction`s, and `block` (a box with declared `pins`). Terminals in `pins.md`. Useful node fields: `icon` (corner badge), `background_color` / `border_color` / `text_color` (CSS names or hex; border and text derive from the background if omitted), `content` (an initial panel, same shape as `set_content`), `visible: false` (hidden until `set_visible`), `rotation` (degrees), `url` (clickable), `merge_edges: false` (fan out several links on one face instead of converging), `value` + `unit` (component labels: `220` + `Ω` → "220 Ω"). ## 5. Packets | `kind` | Shows | Content field(s) | | -------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------- | | `http_packet` | Request/response envelope | `packet_content: { header, body?: { type: "text" \| "image", value, language? } }` | | `sql_request` | A query | `request_content: "SELECT …"` | | `sql_response` | A result set | `response_content: { header?, rows?, body?: { type: "table", columns, rows_data } \| { type: "text", value } }` | | `simple_node` | A travelling text box | `body`, `language` | | `complex_node` | A travelling header + body | `header`, `body`, `language` | | `subicon` | A travelling tech badge | `icon` | Use the field that matches the kind — `packet_content` on anything but an `http_packet` is ignored. Keep packet text to a few lines: it is read while the packet is in flight. ## 6. The timeline Each action has a `type` and its own fields (reference: `MoveAction`, `CommentAction`…), plus the common timing fields. | `type` | Does | Key fields | | ---------------- | ---------------------------------------------- | ---------------------------------------------------------- | | `move` | Carries a packet between two nodes | `object` (a PACKET id), `from`, `to` (node ids) | | `comment` | Speech bubble near a node, or a stage caption | `text`, `object?` | | `arrow` | Draws an arrow at that moment | `from`, `to`, `text?`, `style?`, `path?` | | `loading` | Spinner on a node ("processing") | `object` | | `set_content` | Puts a panel on a node | `object`, `content: { type, … }` | | `highlight` | Pulsing halo on a node or connection | `object` (node or connection id) | | `set_color` | Recolours a node or a connection, persistently | `object`, `background_color?`… or `color` for a connection | | `set_icon` | Changes a node's badge, persistently | `object`, `icon` (`""` clears) | | `set_visible` | Shows / hides a node, persistently | `object`, `visible` | | `rotate` | Rotates a node (`to` degrees, or `spin` deg/s) | `object`, `to?`, `spin?` | | `rotate_subtree` | Tree rotation (tree layout only) | `object` (pivot), `rotation` | | `flow` | Current flowing along wires (circuit) | `route` (ids / `"node:pin"`), `color?`, `loop?` | | `toggle` | Opens / closes a `switch` / `push_button` | `object`, `closed` | | `parallel` | Plays its children together (one step) | `actions` | | `wait` | A pause | `duration` | `content` of `set_content` (and of a node's `content`): `{ type: "code", value, language }`, `{ type: "text", value, url? }` (a browser window), `{ type: "table", columns, rows_data }`, `{ type: "image", value }` (a URL or `data:` URI), or `{ type: "html", value, url?, screen_width? }` (sanitized markup with inline `style` — no scripts, no classes). **Coordinating actions** - Sequence is the default: root actions play one after another. - `parallel` for things that happen at once (a request leaves while a spinner starts). Add `delay_ms` to children to stagger them (a fan-out). - `wait_for: ""` starts an action when another ends — typically a response that waits for a `loading`. - **Persistence.** A `move` disappears on arrival; `comment`, `arrow`, `set_content` and `highlight` stay until the next root step. Extend with `keep_until: ""` (until that action starts) or `keep_until_end: true`. `set_color`, `set_icon`, `set_visible`, `toggle`, `rotate` are STATE: they persist until changed, no flag needed. ## 7. Circuits - Wire to named terminals with `"node:pin"` (`"R1:a"`, `"batt:+"`, `"g1:y"`) — ONLY names listed in `pins.md` for that type. A `signal`, `junction` or other type without terminals is wired by its bare id. - `connections` are orthogonal wires with no arrow head by default; do not set `arrow_head`/`path` on them. - **No coordinates needed** for a single loop (battery → switch → resistor → LED → back) or a feed-forward logic network (signals → gates → signals). Anything else (parallel branches, feedback, mixed) needs `x`/`y` (0..1) on EVERY node, as the gallery's hand-placed circuits do: keep `x` within ~0.1–0.9 and `y` within ~0.15–0.9, space components ≥ 0.12 apart, line up the terminals a wire joins so it runs straight, and use `junction` nodes for wire corners and T-splits. Start from the closest example (`srLatch`, `cmosNand`, `mux4to1`). - Animate with `toggle` (close a switch), then `flow` along a `route` that follows real wires (a loop repeats its first element at the end). For logic, show bits with `set_icon` on `signal` pads (`"1"` / `"0"`) and light the wires carrying a 1 with `set_color` (`color`) on connection ids. - `diagonal_wires: true` gives 45° wires where they help (feedback loops). ## 8. Mistakes that break or spoil a spec - **Wrong id kind**: `move.object` must be a PACKET id; `from`/`to`, `comment`, `loading`, `set_content`… target NODE ids; `highlight`/`set_color` accept a node or a connection `id` (so give such connections an `id`). - **Invented fields.** The schema rejects unknown fields. It is `icon`, not `subicon`; `main`, not `is_main`; `object`, not `target`/`node`; `text`, not `label`; `packets`, not `messages`. - **Coordinates in a flow layout** (`x`/`y` are ignored outside `graph` and `circuit`), or `lane` in `circular`/`tree`/`graph` (ignored). - **`connections` in a tree** — the tree draws its own edges. - **A packet never moved** — it is never shown. - **Long text in a shape's `body`** — cropped. Use a `simple_node`. - **`wait` after a `comment`** to "give time to read" — remove both the `wait` and any `duration`: the comment already gets its reading time. - **Everything in one `parallel`** — it becomes one step and loses the step-by-step navigation. - **Invalid JSON**: no comments, no trailing commas, no `…` placeholders, double quotes only. A newline inside a string is `"\n"`; a LaTeX backslash is doubled (`"$\\alpha$"`). --- # DataFlowSpec — field reference Generated from the JSON Schema. `field?` is optional; `A | B` is a union; a name in backticks refers to the section of the same name. `Action` is a discriminated union on `type`. ## DataFlowSpec - `direction?`: `Direction` — Node placement direction. For the flow (`left-to-right` …), `circular` and `tree` modes you provide **no coordinates** — the engine arranges nodes from `lane` / `main` / the `TreeSpec`. Default: 'left-to-right'. Use `'tree'` to lay out a binary tree (in-order rank → horizontal, depth → vertical) and enable the `RotateSubtreeAction`. Use `'graph'` for an arbitrary graph (Dijkstra, A\*, minimum spanning tree…): nodes are auto-placed to minimize edge crossings, and a node MAY pin itself with `x` / `y`. Use `'circuit'` for an electrical schematic: `connections` drawn as orthogonal **wires** (no arrow head) by default, edges anchored on the components' **named terminals** (`"node:pin"`); a single loop or a feed-forward logic network is auto-placed, anything else is positioned with `x` / `y`. - `diagonal_wires?`: `boolean` — (`direction: 'circuit'` only) Draw wires **octilinearly**: their corners are mitered into exact 45° segments (only 45 / 135 / 225 / 315°), so long L-shapes and staircases collapse into clean diagonal runs, while wires that are already straight (aligned terminals) stay straight. A short perpendicular stub is kept at each component pin, and a miter that would cross a body falls back to a right angle. Override per wire with `Connection.diagonal`. Default: false (strict horizontal/vertical wires). Ignored by every other direction. - `nodes`: `Node[]` — Fixed elements of the scene (servers, clients, databases…). They form the permanent decor and are placed automatically according to `direction` and their `lane`. - `tree?`: `TreeSpec` — Binary-tree topology, **required when `direction` is `'tree'`** (ignored otherwise). Drives the layout and the auto-drawn parent/child edges, and is the structure mutated by `rotate_subtree`. See `TreeSpec`. - `packets`: `Packet[]` — Mobile elements (requests, responses, messages). Declared here, then moved from one node to another by a `move` action in the `timeline`. - `connections?`: `Connection[]` — Permanent arrows/links (decor) displayed upon initialization. - `zones?`: `Zone[]` — Rectangular regions displayed in the background around a group of nodes. - `timeline`: `Action[]` — Animated scenario: ordered list of actions (moves, arrows, comments…) played sequentially. Each root action becomes a navigable step. - `description?`: `string` — What this animation shows, in one or two sentences — the opening line of the player's text description, which is what a screen-reader user hears before any step. The rest of that description (the cast of elements and every step) is generated from the spec, so omitting this still produces a usable one; it loses only the part no structure can supply, which is what the animation is ABOUT. Per-step wording is `Action.description`. - `pace?`: `number` (0.1..10) — Scales the reading time the engine DERIVES for actions that carry something to read — a `comment`'s text, a `set_content`'s panel — when they declare no `duration` of their own. Above 1 leaves more time (1.25 = a quarter longer), below 1 moves faster. Default: 1. It is the single knob for the whole animation's pace: derive everything, then nudge it in one place if the result reads too fast or too slow for your audience. It deliberately does NOT touch a `duration` you wrote yourself — that is an explicit intent, not an estimate to be scaled. ## Direction How nodes are placed: along a flow (`left-to-right`, `right-to-left`, `top-to-bottom`, `bottom-to-top`, positioned by `lane`), on a ring around the `main` node (`circular`), as an auto-placed arbitrary graph (`graph`), as a binary tree (`tree`, driven by the `tree` block) or as an electrical schematic (`circuit`). `"left-to-right" | "right-to-left" | "top-to-bottom" | "bottom-to-top" | "circular" | "graph" | "tree" | "circuit"` ## Node - `id`: `string` — Unique node identifier (e.g., 'web_server'). - `type`: `NodeType` — Node appearance: pictogram (server, client…), text node (panel) or geometric shape. See previews of each value. - `text?`: `string` — Label displayed below the node. Supports inline LaTeX between `$…$` — `"$B_{in}$"` renders B with an "in" subscript (see the "Math notation" page). - `visible?`: `boolean` — Initial visibility of the node. Default: true. A hidden node can be displayed via the `set_visible` action. - `icon?`: `string` — Overlaid badge: name of a known technology (e.g., 'react', 'postgres'), name of a registered icon, or short free text (e.g., 'v2', 'API'). - `lane?`: `number` — Placement row/column (positive integer). Default: 1. - `main?`: `boolean` — (circular) Marks the node as a central node. Default: false. - `align_with?`: `string` — Aligns this node on the transverse axis of another node (by ID): useful for aligning two nodes from different lanes. Ignored in circular layout. - `x?`: `number` (0..1) — (`graph`, `circuit`) Horizontal position as a fraction of the Stage, from 0 (left edge) to 1 (right edge). Used **only** when `direction` is `'graph'` or `'circuit'`. **Optional:** omit it and the node is placed AUTOMATICALLY (in `graph`, the layout minimizes edge crossings; in `circuit`, a single loop or a feed-forward logic network is auto-arranged). Provide it to **pin** the node as a fixed anchor — handy to fix a source, a target, or the overall orientation, and required for a `circuit` that is neither a loop nor feed-forward. Ignored by every other direction (which derive positions from `lane` / `main` / the tree). - `y?`: `number` (0..1) — (`graph`, `circuit`) Vertical position as a fraction of the Stage, from 0 (top edge) to 1 (bottom edge). Companion of `Node.x`: used only when `direction` is `'graph'` or `'circuit'`, omit for automatic placement, provide to pin the node. Ignored otherwise. - `merge_edges?`: `boolean` — Edge convergence on the node's faces. When `true` (the default), all connections / arrows / moves attaching to the same face of this node meet at a **single anchor point**: a many-to-one flow (a flood, a load balancer, a hub) visually converges instead of spreading out. Set to `false` to **fan out** the edges instead — each pair gets its own attachment point along the face, ordered to reduce crossings. Useful when a node has several distinct neighbours on the same side and you want to tell the links apart. Independent from the spreading of multiple edges between the *same* two nodes (bidirectional request/response tracks stay distinct regardless). Default: true. - `ports?`: `"direct" | number` — Connection points on a **round** node's outline (`type: 'circle'`), so edges meet the circle wherever they naturally point instead of snapping to the four cardinal sides — the reason graphs and binary trees look more organic. - `'direct'` (**the default for a circle**): the edge aims at the node centre and attaches exactly where that straight line crosses the outline — an *infinite* set of possible points, i.e. the most direct path. - a positive integer `N`: exactly `N` attach points, spread evenly around the outline; each edge snaps to the nearest one (`4` reproduces N/E/S/W on the round contour). Edges landing on the same point merge, like `merge_edges`. Ignored on non-round node types (which keep cardinal-side anchoring). - `url?`: `string` — URL making the node clickable (opens in a new tab). - `background_color?`: `string` — Background color of the node: shape fill, panel background (`simple_node`/`complex_node`), pill behind a pictogram. Accepts a **predefined** CSS color (name: `tomato`, `steelblue`, `teal`…) or an **exact hex** value (`#3b82f6`). No effect on an active `set_content`. - `border_color?`: `string` — Node border / stroke color (shape stroke, panel borders, stroke color of a pictogram). Same format as `background_color`. If `background_color` is defined but not `border_color`, a coordinated border (darker variant of the background) is automatically generated. - `text_color?`: `string` — Color of the text displayed IN the node (shape body, panel header/body), **only when syntax highlighting is disabled** (no `language`: otherwise syntax colors take precedence). Same format as `background_color` (predefined name or hex). If not defined but a `background_color` is, a high-contrast color with the background (black or white) is automatically chosen. - `content?`: `ObjectContent` — Initial content displayed in the node (code terminal, window, etc.). - `body?`: `string` — Text displayed IN the node, as opposed to `text` which remains the label below the node. For `simple_node` / `complex_node`: panel body (line breaks respected, colored according to `language` if provided). For geometric shapes (`square` … `star`): short centered text in the shape (keep it brief so it doesn't overflow), which supports inline LaTeX between `$…$` (see the "Math notation" page). A panel body does NOT: `$` is a legitimate character there, and the text goes through syntax highlighting. - `header?`: `string` — (`complex_node`) Header displayed above the `body`, separated by a line — the node then looks like an HTTP packet. Ignored by `simple_node`. Colored according to `language` if provided. - `language?`: `HighlightLanguage` — (`simple_node` / `complex_node`) Syntax highlighting language applied to ALL text areas of the node (`header` and `body`). Recognized values: see `HighlightLanguage`. - `rotation?`: `number` — Clockwise rotation of the node visual, in degrees. The label below the node stays upright and arrow anchoring is unchanged (the layout box is not rotated). Can be animated with the `rotate` action. Default: 0. **Exception — electrical components:** a component's **named terminals** ( `NodeType`: `resistor`, `transistor_npn`…) DO rotate with the symbol, so a vertical resistor (`rotation: 90`) has its `a`/`b` terminals at the top/bottom. This is the intended behavior for circuits. - `pins?`: `BlockPin[]` — (`block`) Terminals of a generic functional block, in declaration order per face. The box SIZES ITSELF from them: its height follows the busiest of the `left` / `right` faces, its width the longest labels plus the `body` text, so a 4:1 multiplexer and an 8-bit register are the same node type at two different sizes. Wire a terminal by name like any other component (`"mux:i3"`, `"reg:clk"`). Ignored by every other node type — which carry their terminals in the library instead, `d_flip_flop` and `mux_2to1` included. - `closed?`: `boolean` — (Electrical `switch` / `push_button`) Initial state of the contact: `true` = closed (conducting), `false` = open (the default). Animate it with the `toggle` action — the lever swings and the state persists. Ignored by every other node type. - `value?`: `string | number` — Component value shown in the label (mainly for electrical components): a resistance, capacitance, voltage… Combined with `Node.unit` to form `" "`. If `Node.text` is also set, the value is appended to it (`"R1 · 10 kΩ"`). Purely a label convenience. - `unit?`: `string` — Unit appended after `Node.value` in the label (`kΩ`, `µF`, `V`, `mA`…). Ignored when `value` is absent. ## NodeType Node types (appearance). Decor arrows live in `connections`. Four families: - **Pictograms** (`desktop` … `cloud`): a fixed SVG icon. - **Text nodes** (`simple_node`, `complex_node`): a text box (body only, or header + body like an HTTP packet). - **Geometric shapes** (`square` … `star`): a drawn shape that can contain short centered text (`body` field). - **Electrical components** (`resistor` … `transformer`): schematic symbols with **named terminals** (see `Node.pins` — actually resolved from the type). A `Connection` targets a specific terminal with `"node:pin"` (e.g. `"R1:a"`, `"Q1:base"`). Best drawn in `direction: 'circuit'`. `"desktop" | "laptop" | "client" | "server" | "database" | "mobile" | "user" | "admin" | "users" | "cloud" | "alice" | "bob" | "eve" | "simple_node" | "complex_node" | "square" | "diamond" | "circle" | "triangle" | "parallelogram" | "height_rectangle" | "width_rectangle" | "star" | "resistor" | "potentiometer" | "capacitor" | "polarized_capacitor" | "inductor" | "fuse" | "battery" | "dc_source" | "ac_source" | "current_source" | "diode" | "led" | "transistor_npn" | "transistor_pnp" | "opamp" | "switch" | "push_button" | "lamp" | "motor" | "buzzer" | "ground" | "junction" | "signal" | "ammeter" | "voltmeter" | "antenna" | "transformer" | "mosfet_n" | "mosfet_p" | "transmission_gate" | "and_gate" | "or_gate" | "not_gate" | "nand_gate" | "nor_gate" | "xor_gate" | "xnor_gate" | "buffer_gate" | "and3_gate" | "or3_gate" | "nand3_gate" | "nor3_gate" | "xor3_gate" | "d_flip_flop" | "jk_flip_flop" | "t_flip_flop" | "sr_latch" | "mux_2to1" | "demux_1to2" | "half_adder" | "full_adder" | "block"` ## ObjectContent - `type?`: `ContentType` — Content display mode: `code` (colored terminal), `text` (browser-like window), `image` (illustration), `table` (data table) or `html` (rich markup, sanitized). - `value?`: `string` — Textual content according to `type`: source code (`code`), text (`text`), image path/URL (`image`) or markup (`html`). - `language?`: `HighlightLanguage` — Language for syntax highlighting. Recognized values: see `HighlightLanguage`. - `url?`: `string` — (`text` mode, and `html` when provided) URL displayed in the window's address bar. An `html` panel with no `url` renders bare, without chrome. - `screen_width?`: `number` — (`html` mode) Width, in px, of the DESIGN SPACE the markup is laid out in. Setting it turns the panel into a SCREEN: the layout is computed once at this width and then scaled uniformly to fit the room the node has, so the same arrangement is recognisable at every player size instead of re-flowing. Clamped to `[40, 4000]`. - `screen_height?`: `number` — (`html` mode) Height of the design space, in px. Default: `screen_width × 0.625` — a 16:10 lid. A screen is a fixed box: what does not fit is cropped, as it is on a real one. - `frames?`: `string[]` — (`image` mode) Frame sequence played from the ANIMATION's clock rather than the browser's: it pauses with the player, rewinds when scrubbed backwards and exports frame-accurately — none of which an animated GIF in `value` can do. Each entry is an image source, ideally a `data:` URI (a remote one cannot be rasterized by the video export). `value` is the still shown when the list is empty. - `fps?`: `number` — (`image` mode) Playback rate of `frames`, in frames per second. Clamped to `[0.1, 60]`. Default: 12. - `loop?`: `boolean` — (`image` mode) Whether `frames` repeats. `false` holds the last frame once the sequence has played through. Default: true. - `columns?`: `string[]` — (`table` mode) Column headers. - `rows_data?`: `((string | number)[])[]` — (`table` mode) Data rows. ## ContentType Content modes for `set_content` (action) and `content` (static object). `"image" | "text" | "code" | "table" | "html"` ## HighlightLanguage Languages supported by the syntax highlighting engine (Prism). `"javascript" | "js" | "typescript" | "ts" | "json" | "sql" | "bash" | "sh" | "shell" | "python" | "py" | "csharp" | "cs" | "html" | "xml" | "markup" | "css" | "jsx" | "tsx" | "http"` ## BlockPin One declared terminal of a `type: 'block'` node. The fixed-pin symbols (`d_flip_flop`, `mux_2to1`, `full_adder`…) carry their terminals in the library because their COUNT never varies. A block is the escape hatch for everything whose count does — a 4:1 multiplexer, an n→2ⁿ decoder, an N-bit register, an ALU — which would otherwise need one node type per size, forever. - `name`: `string` — Terminal name, used in `"node:pin"` endpoint references (`"mux:i3"`). Must be unique within the block. - `side?`: `BlockSide` — Face the terminal sits on. Terminals of the same face are spread evenly over it, in DECLARATION order (top to bottom on `left` / `right`, left to right on `top` / `bottom`). Default: `'left'`. - `label?`: `string` — Text drawn inside the box next to the terminal. Defaults to BlockPin.name — set it when the wiring name and the printed name differ (`name: 'i0'`, `label: 'D0'`). Supports inline LaTeX between `$…$`. ## BlockSide Face of a `block` a terminal sits on. `"left" | "right" | "top" | "bottom"` ## TreeSpec Binary-tree topology (used when `direction` is `'tree'`). Single source of truth for the structure: parent/child **edges are derived from it** (and drawn automatically — no `connections` to maintain), and the layout places each node by its **in-order rank** (horizontal) and **depth** (vertical). The `RotateSubtreeAction` mutates this topology at runtime and the engine re-lays-out and re-routes the edges from this same model. - `root`: `string` — ID of the root node. - `children`: `{ [id]: TreeChildren }` — Left/right child of each (internal) node, by node ID. A node absent from this map — or with an empty entry — is a leaf. - `edge_style?`: `TreeEdgeStyle` — Default styling applied to EVERY parent→child edge — the place to set the `path` (or the line style / color) once for the whole tree. Tree edges otherwise default to a `straight` path and no arrow head. Overridden per edge by `TreeSpec.edges`. See `TreeEdgeStyle`. - `edges?`: `{ [id]: TreeEdgeStyle }` — Per-edge styling override, keyed by the **child** node ID: since every node has exactly one parent, its id names the incoming edge unambiguously (and the style follows the node through `RotateSubtreeAction`). Each entry is merged OVER `TreeSpec.edge_style`, field by field. See `TreeEdgeStyle`. ## { [id]: TreeChildren } `{ [key]: TreeChildren }` ## TreeChildren Left/right children of a node in a binary tree. - `left?`: `string` — ID of the left child (smaller key), if any. - `right?`: `string` — ID of the right child (greater key), if any. ## TreeEdgeStyle Visual styling for the parent→child edges auto-drawn in a `'tree'` layout. Orthogonal to the topology (`children` stays a pure structure): it is applied at RENDER time, so it survives `RotateSubtreeAction` re-routing — a style keyed to a child follows that node as its depth changes. Used both as the tree-wide default ( `TreeSpec.edge_style`) and as a per-edge override ( `TreeSpec.edges`), the override merged over the default field by field. Same vocabulary as a `Connection`, but tree edges have their own defaults: a **`straight`** path (not `bezier`) and **no** arrow head (they are plain hierarchy links, not directed arrows). - `style?`: `LineStyle` — Line style. Default: 'solid'. - `path?`: `PathShape` — Shape of the edge path. Default (tree): 'straight'. See `PathShape`. - `arrow_head?`: `"forward" | "backward" | "both" | "none"` — Arrow head. Default (tree): 'none' — parent→child links carry no head. - `text?`: `string` — Optional median label drawn on the edge. Supports inline LaTeX between `$…$` (see the "Math notation" page). - `color?`: `string` — Line color (predefined CSS name or hex). Tints the whole path and its arrow head(s). Default: the theme's neutral connection color. - `highlighted?`: `boolean` — Emphasizes the edge permanently — accent color, thicker stroke and glow — like a statically `Connection.highlighted` link. Default: false. ## LineStyle Line style (SVG/CSS terminology). `full` is accepted as an alias for `solid`. `"solid" | "dotted" | "dashed" | "animated"` ## PathShape Shape of the arrow / connection path — orthogonal to `LineStyle`, which only sets the stroke pattern (solid, dotted…). Default: 'bezier'. - `bezier`: smooth S curve, handles along the dominant axis (default); - `simplebezier`: same idea, more subtle curvature; - `straight`: direct segment (bypasses sandwiched labels); - `step`: orthogonal path with right angles; - `smoothstep`: orthogonal path with rounded angles. On two perfectly aligned nodes (same row/column), all shapes merge into a straight line: curvature only appears in the presence of a transverse offset (different lanes, fan-out, bidirectional tracks). `"bezier" | "simplebezier" | "straight" | "step" | "smoothstep"` ## { [id]: TreeEdgeStyle } `{ [key]: TreeEdgeStyle }` ## Packet - `id`: `string` — Unique identifier of the packet. - `kind`: `PacketKind` — Packet category, which sets its appearance and expected content: `http_packet` (header + body via `packet_content`), `sql_request` (textual request via `request_content`), `sql_response` (response via `response_content`), a **text panel** (`simple_node` / `complex_node`) that travels: same look and content fields as the homonymous `NodeType` (`body`, plus `header` for `complex_node`, optionally syntax-highlighted via `language`), or a **tech badge** (`subicon`) that travels: the same `Node.icon` badge (known technology, registered icon, or short free text), via `icon`. - `request_content?`: `string` — Textual request (e.g., for sql_request). - `response_content?`: `SqlResponse` — Content of a `sql_response`: number of rows, header and body (text or table). - `packet_content?`: `PacketContent` — Content of an `http_packet`: header (e.g., 'GET /api') and optional body. - `body?`: `string` — (`simple_node` / `complex_node`) Panel body text (line breaks respected, colored according to `language` if provided). Same role as `Node.body`. - `header?`: `string` — (`complex_node`) Header displayed above the `body`, separated by a line — the packet then looks like an HTTP packet. Ignored by `simple_node`. Same role as `Node.header`. - `language?`: `HighlightLanguage` — (`simple_node` / `complex_node`) Syntax highlighting language applied to ALL text areas of the panel (`header` and `body`). Recognized values: see `HighlightLanguage`. Same role as `Node.language`. - `icon?`: `string` — (`subicon`) The tech badge that travels: name of a known technology (e.g., 'react', 'postgres'), name of a registered icon, or short free text (e.g., 'v2', 'API'). Same role as `Node.icon`. ## PacketKind `"http_packet" | "sql_request" | "sql_response" | "simple_node" | "complex_node" | "subicon"` ## SqlResponse - `rows?`: `number` — Number of rows returned. - `header?`: `string` — Optional header visible in the packet. - `body?`: `SqlResponseBody` — Response body (pure text or table). ## SqlResponseBody - `type?`: `"text" | "table"` - `value?`: `string` — Pure text if type is 'text' - `columns?`: `string[]` — Table columns if type is 'table' - `rows_data?`: `((string | number)[])[]` — Data rows if type is 'table' ## PacketContent - `header?`: `string` — Header visible in the packet (e.g., 'GET /api'). - `body?`: `PacketBody` ## PacketBody - `type?`: `"text" | "image"` - `value?`: `string` — Text or image path. - `language?`: `HighlightLanguage` — Language for text syntax highlighting (optional). Recognized values: see `HighlightLanguage`. ## Connection Permanent link/arrow (decor), displayed upon initialization. - `id?`: `string` — Optional identifier. - `from`: `string` — Source node ID. - `to`: `string` — Destination node ID. - `style?`: `LineStyle` — Line style. Default: 'solid'. - `path?`: `PathShape` — Shape of the link path. Default: 'bezier'. See `PathShape`. - `diagonal?`: `boolean` — (`direction: 'circuit'` only) Draw THIS wire octilinearly (`true`, 45° miters) or force it strictly orthogonal (`false`), **overriding** the circuit-wide `DataFlowSpec.diagonal_wires`. Unset = inherit that circuit default. Ignored outside `circuit`. - `arrow_head?`: `"forward" | "backward" | "both" | "none"` — Arrow head. Default: 'forward'. - `text?`: `string` — Optional median text. Supports inline LaTeX between `$…$` (see the "Math notation" page). - `color?`: `string` — Line color (predefined CSS name or hex). Tints the whole path and its arrow head(s); the median `text` label keeps the theme color for legibility. Default: the theme's neutral connection color. A runtime `set_color` on this connection's `id` recolors it, and an active `highlight` (accent) takes precedence over both. - `highlighted?`: `boolean` — Emphasizes the connection permanently, from initialization — the same accent color, thicker stroke and glow the `HighlightAction` applies, but static (no timeline action needed). Default: false. ## Zone Rectangular region enclosing a group of nodes and/or other zones. - `id?`: `string` — Optional identifier (required to be referenced in the `contains` of another zone). - `contains`: `string[]` — IDs of the enclosed nodes and/or zones. - `color?`: `string` — CSS color of the border and semi-transparent background. - `label?`: `string` — Label displayed at the top left of the zone. Supports inline LaTeX between `$…$` (see the "Math notation" page). ## Action Discriminated union of actions (by `type`). `MoveAction | ArrowAction | ParallelAction | LoadingAction | SetContentAction | CommentAction | HighlightAction | SetVisibleAction | SetColorAction | SetIconAction | RotateAction | RotateSubtreeAction | FlowAction | ToggleAction | WaitAction` ## Common action fields Every action accepts these, in addition to its own fields. - `id?`: `string` — Action ID to refer to it (wait_for / keep_until). - `description?`: `string` — What this step means, in one sentence, for the player's text description (see `DataFlowSpec.description`). The description is generated from the action itself when this is omitted — "GET /users travels from Browser to Web server" — so an animation is described whether or not anyone writes anything. Provide it when the generated sentence would be accurate and still miss the point: the structure knows a packet moved, only you know it moved BECAUSE the cache missed. A `comment` action needs it least of all: its `text` is already your own narration, and it is used verbatim. - `duration?`: `number` (≥ 1) — Animation duration in ms (default: 500, 1200 for loading). - `wait_for?`: `string` — ID of another action: this action starts at the end of that one. - `keep_until?`: `string` — ID of a future action: this action remains visible until its start. - `keep_until_next?`: `boolean` — Remains visible until the start of the next root step. Default: false for `move`/`loading`, true for `arrow`/`comment`/`set_content`. - `keep_until_end?`: `boolean` — If true, remains visible until the end of the timeline. - `delay_ms?`: `number` (≥ 0) — Start offset in ms, added after resolving `wait_for` and the step clamp. Mainly useful in a `parallel` block to stagger actions against each other (staggered sequence animations). Also applies to an entire `parallel` action to delay the whole group. - `fade_in_ms?`: `number` (≥ 0) — Fade-in duration in ms. Default: 250 ms — or the element's appearance pause when that pause is shorter. The fade never fills the whole pause: a packet held at its origin to be read appears first, then stays legible for the rest of it. 0 = instant appearance. - `fade_out_ms?`: `number` (≥ 0) — Fade-out duration in ms. Default: 250. 0 = instant disappearance. No effect if `keep_until_end` is true. ## MoveAction Moves a packet from `from` to `to`. - `type`: `"move"` - `object`: `string` — ID of the packet (declared in `packets`) to move. - `from`: `string` — Start node ID. - `to`: `string` — Arrival node ID. - …plus the common action fields. ## ArrowAction Draws an animated arrow between two nodes. - `type`: `"arrow"` - `from`: `string` — Start node ID of the arrow. - `to`: `string` — Arrival node ID of the arrow. - `text?`: `string` — Label displayed in the middle of the arrow. Supports inline LaTeX between `$…$` (see the "Math notation" page). - `style?`: `LineStyle` — Line style: solid, dotted, dashed or animated. Default: 'solid'. - `path?`: `PathShape` — Path shape: bezier, simplebezier, straight, step, smoothstep. Default: 'bezier'. - `arrow_head?`: `"forward" | "backward" | "both" | "none"` — Side(s) where to draw the arrow head. Default: 'forward'. - …plus the common action fields. ## ParallelAction Executes multiple actions at the same time. - `type`: `"parallel"` - `actions`: `Action[]` - …plus the common action fields. ## LoadingAction Displays a loading spinner on a node. - `type`: `"loading"` - `object`: `string` - …plus the common action fields. ## SetContentAction Mutates the content of a node (code, text, image). - `type`: `"set_content"` - `object`: `string` - `content`: `ObjectContent` - …plus the common action fields. ## CommentAction Displays a comment bubble near a node, or at the top of the stage if `object` is omitted. - `type`: `"comment"` - `object?`: `string` — ID of the node near which to display the comment. Omitted = omniscient comment (top of the stage). - `text`: `string` — Supports inline LaTeX between `$…$` (see the "Math notation" page). - …plus the common action fields. ## HighlightAction Highlights a static node or a connection (by ID). - `type`: `"highlight"` - `object`: `string` — ID of a static node OR a connection to highlight. - …plus the common action fields. ## SetVisibleAction Shows or hides a static node with a fade. - `type`: `"set_visible"` - `object`: `string` — ID of the node to show or hide. - `visible`: `boolean` — true = show, false = hide. - …plus the common action fields. ## SetColorAction Recolors a static node — or a permanent `Connection` — at runtime, over `duration` ms, with a deterministic eased cross-fade between the previous color and the new one (via CSS `color-mix`, so it stays scrubbable in both directions). What `object` refers to selects the channels that apply: a node uses `background_color` / `border_color` / `text_color`; a connection uses `color` (its single line color). Only the channels you provide change; the others keep their current value. Same value space as the static colors: a predefined CSS color name (`tomato`, `steelblue`…) or an exact hex (`#1a1a1a`). Node auto-derivations apply exactly as for a static node (a `background_color` without `border_color` derives a coordinated border; without `text_color`, a high-contrast ink — the latter only outside syntax-highlighted areas). The reached color persists until the end of the timeline, like `SetVisibleAction`. This is the core operation for algorithm visualizations that recolor elements — e.g. the red/black recoloring of a red-black tree, or lighting up the edge a traversal just followed. - `type`: `"set_color"` - `object`: `string` — ID of the node or connection to recolor. - `background_color?`: `string` — New background color (shape fill, panel background, pill behind a pictogram). Predefined CSS name or hex. Omit to leave it unchanged. - `border_color?`: `string` — New border / stroke color. Predefined CSS name or hex. Omit to leave it unchanged. If `background_color` is set without `border_color`, a coordinated border (darker background) is derived automatically. - `text_color?`: `string` — New color of the text inside the node (shape body, panel header/body), effective only when syntax highlighting is disabled. Predefined CSS name or hex. Omit to leave it unchanged. - `color?`: `string` — New line color when `object` is a `Connection` id (recolors the path and its arrow head). Predefined CSS name or hex. Ignored for a node — use the three channels above. Omit to leave it unchanged. - …plus the common action fields. ## SetIconAction Updates a node's corner **icon badge** at runtime — the small overlaid badge ( `Node.icon`: a known technology, a registered icon, or short free text). The badge swaps to the new value when the clip starts and, like `SetVisibleAction` / `SetColorAction`, the reached value persists until the end of the timeline; successive `set_icon` on the same node chain (each replaces the previous badge). It is scrubbable in both directions. This is how an algorithm visualization keeps a **per-node scalar that evolves** legible right on the node — the tentative distance of a Dijkstra node (∞ → 7 → 5…), the `f = g + h` of an A\* node — instead of relying only on a comment. - `type`: `"set_icon"` - `object`: `string` — ID of the node whose badge changes. - `icon`: `string` — New badge value: a known technology (e.g. 'react'), a registered icon name, or short free text (a number like '7', a symbol like '∞'). An empty string clears the badge. - …plus the common action fields. ## RotateAction Animates the visual rotation of a node. Two mutually exclusive modes: - **Target angle** (`to`): a single eased rotation toward an absolute angle. The starting angle is the node's current rotation (its static `rotation`, or the target of a previous `rotate`), so successive rotations chain. - **Continuous spin** (`spin`): the node turns at a constant speed (linear, no easing) for as long as the action lasts. How long is driven by the usual timing fields — `duration` (spin that long), `keep_until` (spin until another action starts), or `keep_until_end` (spin until the end of the timeline). Only the visual rotates; the label stays upright. The angle reached when the rotation ends persists until the end of the timeline. - `type`: `"rotate"` - `object`: `string` — ID of the node to rotate. - `to?`: `number` — Target angle in degrees (absolute, clockwise). Mutually exclusive with `spin` (which takes precedence if both are set). - `spin?`: `number` — Continuous spin speed in degrees per second: positive turns clockwise, negative counter-clockwise. Mutually exclusive with `to`. The spin lasts for `duration` ms (default 600), or until `keep_until` / `keep_until_end` when set; the angle reached then persists. - …plus the common action fields. ## RotateSubtreeAction Restructures a binary `TreeSpec` with a left or right **tree rotation** around a pivot node, then animates the nodes gliding to their new places while the parent/child edges re-route. Only valid when `direction` is `'tree'`. A rotation preserves the in-order traversal of the tree, so horizontal positions (assigned by in-order rank) stay put — the motion is essentially the pivot and the moved subtree changing depth. The engine recomputes the layout AND the edges from the single tree model, so positions and links can never disagree. Successive `rotate_subtree` actions chain (each starts from the topology left by the previous one), which is how AVL double rotations (LR / RL) are expressed: two `rotate_subtree` in a row. - `type`: `"rotate_subtree"` - `object`: `string` — ID of the pivot node. A **left** rotation requires it to have a right child; a **right** rotation requires a left child. - `rotation`: `"left" | "right"` — Rotation direction. - …plus the common action fields. ## FlowAction Animates an **electric current** circulating along a chain of wires — the signature animation of a schematic. A train of evenly-spaced charges rides the `route` (an ordered list of `node` / `node:pin` references forming a path, a branch or a closed loop), advancing one full lap per `duration` ms and, by default, looping continuously. Deterministic in `t` (the phase is a pure function of time), so it scrubs both ways like everything else. Each consecutive pair of the `route` must be joined by an actual wire (`Connection`) so the charges follow the real path. Use `keep_until_end` (or `keep_until_next`) to keep the current flowing across the whole step. - `type`: `"flow"` - `route`: `string[]` — Ordered wire path the current follows: node ids or `"node:pin"` terminal references. A closed loop repeats the first id at the end. - `reverse?`: `boolean` — Reverses the travel direction (e.g. electron flow − → + instead of conventional current + → −). Default: false. - `loop?`: `boolean` — Continuous circulation: the charges wrap around the `route` forever (until the clip ends). Set to `false` for a single pass. Default: true. - `count?`: `number` (≥ 1) — Number of charge dots spread along the route. Default: derived from the route length (roughly one per segment). - `color?`: `string` — Charge color (predefined CSS name or hex). Default: the theme's accent. - …plus the common action fields. ## ToggleAction Flips an electrical `switch` / `push_button` between open and closed, swinging the lever over `duration` ms. Like `SetVisibleAction` the reached state persists until the end of the timeline (or the next contrary `toggle`), and it is scrubbable in both directions. Pair it with a `flow` that starts once the contact is closed to show the circuit energizing. - `type`: `"toggle"` - `object`: `string` — ID of the `switch` / `push_button` node to actuate. - `closed`: `boolean` — Target state: `true` = close the contact, `false` = open it. - …plus the common action fields. ## WaitAction Dead time: nothing happens for `duration` ms (default: 1000). Does not produce any clip; simply inserts a pause between two steps (elements maintained via `keep_until_next` remain displayed during the wait). - `type`: `"wait"` - …plus the common action fields. --- # Component terminals In `direction: "circuit"`, a `connection` (or `arrow` / `move` / `flow` step) targets a terminal with `"node:pin"` — e.g. `"R1:a"`, `"batt:+"`. Names separated by `/` are aliases of the SAME terminal. Sides are given for the unrotated symbol; terminals rotate with `rotation`. A type absent from this table has no named terminals (wire it by its bare id). A `block` declares its own terminals in `pins`. | type | terminals | | --- | --- | | `resistor` | `a` (left); `b` (right) | | `potentiometer` | `a` (left); `b` (right); `wiper` / `w` (top) | | `capacitor` | `a` (left); `b` (right) | | `polarized_capacitor` | `-` / `a` (left); `+` / `b` (right) | | `inductor` | `a` (left); `b` (right) | | `fuse` | `a` (left); `b` (right) | | `diode` | `a` / `anode` (left); `b` / `cathode` (right) | | `led` | `a` / `anode` (left); `b` / `cathode` (right) | | `lamp` | `a` (left); `b` (right) | | `motor` | `a` (left); `b` (right) | | `buzzer` | `-` / `a` (left); `+` / `b` (right) | | `ammeter` | `a` (left); `b` (right) | | `voltmeter` | `a` (left); `b` (right) | | `switch` | `a` (left); `b` (right) | | `push_button` | `a` (left); `b` (right) | | `battery` | `-` / `a` (left); `+` / `b` (right) | | `dc_source` | `-` / `a` (left); `+` / `b` (right) | | `ac_source` | `a` (left); `b` (right) | | `current_source` | `-` / `a` (left); `+` / `b` (right) | | `transformer` | `p1` (left, upper); `p2` (left, lower); `s1` (right, upper); `s2` (right, lower) | | `transistor_npn` | `base` / `b` (left); `collector` / `c` (top); `emitter` / `e` (bottom) | | `transistor_pnp` | `base` / `b` (left); `collector` / `c` (top); `emitter` / `e` (bottom) | | `opamp` | `in_plus` / `+` (left, lower); `in_minus` / `-` (left, upper); `out` (right) | | `mosfet_n` | `gate` / `g` (left); `drain` / `d` (right, upper); `source` / `s` (right, lower) | | `mosfet_p` | `gate` / `g` (left); `source` / `s` (right, upper); `drain` / `d` (right, lower) | | `transmission_gate` | `a` / `in` (left); `b` / `out` (right); `en` (top); `enb` (bottom) | | `and_gate` | `a` (left, upper); `b` (left, lower); `y` / `out` (right) | | `or_gate` | `a` (left, upper); `b` (left, lower); `y` / `out` (right) | | `nand_gate` | `a` (left, upper); `b` (left, lower); `y` / `out` (right) | | `nor_gate` | `a` (left, upper); `b` (left, lower); `y` / `out` (right) | | `xor_gate` | `a` (left, upper); `b` (left, lower); `y` / `out` (right) | | `xnor_gate` | `a` (left, upper); `b` (left, lower); `y` / `out` (right) | | `not_gate` | `a` / `in` (left); `y` / `out` (right) | | `buffer_gate` | `a` / `in` (left); `y` / `out` (right) | | `and3_gate` | `a` (left, upper); `b` (left); `c` (left, lower); `y` / `out` (right) | | `or3_gate` | `a` (left, upper); `b` (left); `c` (left, lower); `y` / `out` (right) | | `nand3_gate` | `a` (left, upper); `b` (left); `c` (left, lower); `y` / `out` (right) | | `nor3_gate` | `a` (left, upper); `b` (left); `c` (left, lower); `y` / `out` (right) | | `xor3_gate` | `a` (left, upper); `b` (left); `c` (left, lower); `y` / `out` (right) | | `d_flip_flop` | `d` (left, upper); `clk` (left, lower); `q` (right, upper); `qn` / `q_bar` (right, lower) | | `jk_flip_flop` | `j` (left, upper); `clk` (left); `k` (left, lower); `q` (right, upper); `qn` / `q_bar` (right, lower) | | `t_flip_flop` | `t` (left, upper); `clk` (left, lower); `q` (right, upper); `qn` / `q_bar` (right, lower) | | `sr_latch` | `s` (left, upper); `r` (left, lower); `q` (right, upper); `qn` / `q_bar` (right, lower) | | `mux_2to1` | `i0` (left, upper); `i1` (left, lower); `sel` / `s` (bottom); `y` / `out` (right) | | `demux_1to2` | `i` / `in` (left); `sel` / `s` (bottom); `y0` (right, upper); `y1` (right, lower) | | `half_adder` | `a` (left, upper); `b` (left, lower); `s` / `sum` (right, upper); `c` / `cout` (right, lower) | | `full_adder` | `a` (left, upper); `b` (left); `cin` (left, lower); `s` / `sum` (right, upper); `cout` (right, lower) | | `ground` | `a` / `t` (top) | | `antenna` | `a` / `t` (bottom) | --- # Examples Real gallery demos. Copy the idiom of the closest one rather than inventing a structure. ### `clientServer` — Client-Server Client-server architecture ```json { "direction": "left-to-right", "nodes": [ { "id": "browser", "type": "laptop", "text": "Browser", "icon": "chrome", "lane": 1 }, { "id": "api", "type": "server", "text": "Web server", "icon": "dotnet", "lane": 2 }, { "id": "db", "type": "database", "text": "Database", "icon": "mssql", "lane": 3 } ], "connections": [ { "from": "browser", "to": "api", "arrow_head": "both", "style": "dashed" }, { "from": "api", "to": "db", "arrow_head": "both", "style": "dashed" } ], "packets": [ { "id": "req", "kind": "http_packet", "packet_content": { "header": "GET /users\nAccept: application/json" } }, { "id": "sql", "kind": "sql_request", "request_content": "SELECT * FROM users" }, { "id": "rows", "kind": "sql_response", "response_content": { "header": "2 rows", "body": { "type": "table", "columns": [ "id", "name" ], "rows_data": [ [ 1, "Alice" ], [ 2, "Bob" ] ] } } }, { "id": "res", "kind": "http_packet", "packet_content": { "header": "200 OK", "body": { "type": "text", "value": "
\n

Alice

\n

Bob

\n
", "language": "html" } } } ], "timeline": [ { "type": "comment", "object": "browser", "text": "The user opens the page; the HTTP request is sent to the server" }, { "type": "parallel", "actions": [ { "type": "move", "object": "req", "from": "browser", "to": "api" }, { "type": "loading", "object": "browser", "keep_until": "display_html" } ] }, { "id": "dbwork", "type": "move", "object": "sql", "from": "api", "to": "db" }, { "type": "move", "object": "rows", "from": "db", "to": "api", "wait_for": "dbwork" }, { "type": "move", "object": "res", "from": "api", "to": "browser" }, { "type": "parallel", "actions": [ { "id": "display_html", "type": "set_content", "object": "browser", "keep_until_end": true, "content": { "url": "example.com/users", "type": "text", "value": "Alice\nBob" } }, { "type": "comment", "object": "browser", "text": "Page rendered 🎉", "keep_until_end": true } ] }, { "type": "wait", "delay_ms": 1000 } ] } ``` ### `loadBalancer` — Load balancer (round-robin) Three successive requests distributed to different backends; the fourth loops back to the first. ```json { "direction": "left-to-right", "nodes": [ { "id": "client", "type": "users", "text": "Clients", "lane": 1 }, { "id": "lb", "type": "server", "text": "Load Balancer", "icon": "nginx", "lane": 2 }, { "id": "b1", "type": "server", "text": "Backend 1", "icon": "node", "lane": 3 }, { "id": "b2", "type": "server", "text": "Backend 2", "icon": "node", "lane": 3 }, { "id": "b3", "type": "server", "text": "Backend 3", "icon": "node", "lane": 3 } ], "connections": [ { "from": "client", "to": "lb", "style": "dotted" }, { "from": "lb", "to": "b1", "style": "dotted" }, { "from": "lb", "to": "b2", "style": "dotted" }, { "from": "lb", "to": "b3", "style": "dotted" } ], "packets": [ { "id": "rq1", "kind": "http_packet", "packet_content": { "header": "GET /a" } }, { "id": "rs1", "kind": "http_packet", "packet_content": { "header": "200 OK" } }, { "id": "rq2", "kind": "http_packet", "packet_content": { "header": "GET /b" } }, { "id": "rs2", "kind": "http_packet", "packet_content": { "header": "200 OK" } }, { "id": "rq3", "kind": "http_packet", "packet_content": { "header": "GET /c" } }, { "id": "rs3", "kind": "http_packet", "packet_content": { "header": "200 OK" } } ], "timeline": [ { "type": "comment", "text": "The load balancer distributes requests in turn, to balance the load." }, { "type": "comment", "object": "lb", "text": "Request 1 → Backend 1" }, { "type": "move", "object": "rq1", "from": "client", "to": "lb", "duration": 1100 }, { "type": "move", "object": "rq1", "from": "lb", "to": "b1", "duration": 1100 }, { "type": "loading", "id": "w1", "object": "b1", "duration": 800 }, { "type": "move", "object": "rs1", "from": "b1", "to": "client", "wait_for": "w1" }, { "type": "comment", "object": "lb", "text": "Request 2 → Backend 2" }, { "type": "move", "object": "rq2", "from": "client", "to": "lb", "duration": 1100 }, { "type": "move", "object": "rq2", "from": "lb", "to": "b2", "duration": 1100 }, { "type": "loading", "id": "w2", "object": "b2", "duration": 800 }, { "type": "move", "object": "rs2", "from": "b2", "to": "client", "wait_for": "w2" }, { "type": "comment", "object": "lb", "text": "Request 3 → Backend 3" }, { "type": "move", "object": "rq3", "from": "client", "to": "lb", "duration": 1100 }, { "type": "move", "object": "rq3", "from": "lb", "to": "b3", "duration": 1100 }, { "type": "loading", "id": "w3", "object": "b3", "duration": 800 }, { "type": "move", "object": "rs3", "from": "b3", "to": "client", "wait_for": "w3" }, { "type": "comment", "text": "The 4th request would go back to Backend 1: the round-robin loops." } ] } ``` ### `signalr` — Full-duplex real-time (SignalR) Persistent WebSocket connection: we see the client code and the hub code, then a SendAll broadcasting the message to all connected clients. ```json { "direction": "circular", "nodes": [ { "id": "client1", "type": "laptop", "text": "Client 1", "icon": "react" }, { "id": "client2", "type": "laptop", "text": "Client 2", "icon": "react", "visible": false }, { "id": "client3", "type": "laptop", "text": "Client 3", "icon": "react", "visible": false }, { "id": "hub", "type": "server", "text": "SignalR Hub", "icon": "csharp", "main": true } ], "packets": [ { "id": "handshake", "kind": "http_packet", "packet_content": { "header": "WebSocket ⇄" } }, { "id": "send", "kind": "http_packet", "packet_content": { "header": "SendMessage(\"Hi\")" } }, { "id": "recv1", "kind": "http_packet", "packet_content": { "header": "ReceiveMessage" } }, { "id": "recv2", "kind": "http_packet", "packet_content": { "header": "ReceiveMessage" } }, { "id": "recv3", "kind": "http_packet", "packet_content": { "header": "ReceiveMessage" } } ], "timeline": [ { "type": "comment", "object": "client1", "text": "1. The client asks the server to open a connection" }, { "type": "set_content", "id": "clientCode", "object": "client1", "content": { "type": "code", "language": "typescript", "value": "const conn = new HubConnectionBuilder()\n .withUrl(\"/chatHub\").build();\nconn.on(\"ReceiveMessage\", render);\nawait conn.start();" }, "keep_until": "end" }, { "type": "move", "object": "handshake", "from": "client1", "to": "hub" }, { "type": "parallel", "actions": [ { "type": "comment", "object": "hub", "text": "2. Connection established" }, { "type": "arrow", "from": "client1", "to": "hub", "style": "dashed", "keep_until_end": true }, { "type": "arrow", "from": "hub", "to": "client1", "style": "dashed", "keep_until_end": true } ] }, { "type": "set_content", "id": "hubCode", "object": "hub", "content": { "type": "code", "language": "csharp", "value": "public async Task SendMessage(string m) =>\n await Clients.All\n .SendAsync(\"ReceiveMessage\", m);" }, "keep_until": "end" }, { "type": "parallel", "actions": [ { "type": "comment", "text": "Two other clients have already initiated their connection to the hub" }, { "type": "set_visible", "object": "client2", "visible": true }, { "type": "set_visible", "object": "client3", "visible": true }, { "type": "arrow", "from": "client2", "to": "hub", "style": "dashed", "keep_until_end": true }, { "type": "arrow", "from": "hub", "to": "client2", "style": "dashed", "keep_until_end": true }, { "type": "arrow", "from": "client3", "to": "hub", "style": "dashed", "keep_until_end": true }, { "type": "arrow", "from": "hub", "to": "client3", "style": "dashed", "keep_until_end": true } ] }, { "type": "comment", "object": "client1", "text": "3. The client sends a message" }, { "type": "move", "object": "send", "from": "client1", "to": "hub" }, { "type": "comment", "object": "hub", "text": "4. Clients.All: the hub broadcasts to all connected clients" }, { "type": "parallel", "id": "end", "actions": [ { "type": "move", "object": "recv1", "from": "hub", "to": "client1" }, { "type": "move", "object": "recv2", "from": "hub", "to": "client2" }, { "type": "move", "object": "recv3", "from": "hub", "to": "client3" } ] } ] } ``` ### `bstInsert` — Binary tree — insertion Inserting a key into a binary search tree: the new key arrives as an orphan and walks down step by step, comparing at each node, until the empty slot where the node is then created. ```json { "direction": "tree", "tree": { "root": "8", "children": { "4": { "left": "2", "right": "6" }, "6": { "right": "7" }, "8": { "left": "4", "right": "12" }, "12": { "left": "10", "right": "14" } } }, "nodes": [ { "id": "8", "type": "circle", "body": "8", "background_color": "steelblue", "text_color": "white" }, { "id": "4", "type": "circle", "body": "4", "background_color": "steelblue", "text_color": "white" }, { "id": "12", "type": "circle", "body": "12", "background_color": "steelblue", "text_color": "white" }, { "id": "2", "type": "circle", "body": "2", "background_color": "steelblue", "text_color": "white" }, { "id": "6", "type": "circle", "body": "6", "background_color": "steelblue", "text_color": "white" }, { "id": "10", "type": "circle", "body": "10", "background_color": "steelblue", "text_color": "white" }, { "id": "14", "type": "circle", "body": "14", "background_color": "steelblue", "text_color": "white" }, { "id": "7", "type": "circle", "body": "7", "background_color": "seagreen", "text_color": "white", "visible": false } ], "packets": [ { "id": "k", "kind": "subicon", "icon": "7" } ], "timeline": [ { "type": "comment", "text": "Inserting into a binary search tree: smaller keys go left, larger keys go right. We want to add the key 7." }, { "type": "comment", "object": "8", "text": "7 starts as an orphan, outside the tree, at the root. We compare it with each node to find its place." }, { "type": "parallel", "actions": [ { "type": "move", "object": "k", "from": "8", "to": "4", "duration": 900 }, { "type": "comment", "object": "8", "text": "Compare with 8: 7 < 8 → the new key belongs in the LEFT subtree.", "keep_until_next": true } ] }, { "type": "parallel", "actions": [ { "type": "move", "object": "k", "from": "4", "to": "6", "duration": 900 }, { "type": "comment", "object": "4", "text": "Compare with 4: 7 > 4 → go to the RIGHT.", "keep_until_next": true } ] }, { "type": "comment", "object": "6", "text": "Compare with 6: 7 > 6 → go right. But 6 has no right child — the search stops here." }, { "type": "parallel", "actions": [ { "type": "set_visible", "object": "7", "visible": true, "duration": 500 }, { "type": "comment", "object": "6", "text": "That empty spot is where 7 belongs: we place it there…", "duration": 1500 } ] }, { "type": "comment", "object": "7", "text": "…then connect it as the right child of 6." }, { "type": "comment", "object": "7", "text": "Done — 7 is now part of the tree, and it stays a valid binary search tree.", "keep_until_end": true } ] } ``` ### `mst` — Minimum spanning tree — Kruskal Kruskal's algorithm (direction:'graph'): sort the edges by weight and add the cheapest one that does not form a cycle, until all six nodes are linked by five edges. Accepted edges turn green; those that would close a cycle are skipped. ```json { "direction": "graph", "nodes": [ { "id": "A", "type": "circle", "body": "A" }, { "id": "B", "type": "circle", "body": "B" }, { "id": "C", "type": "circle", "body": "C" }, { "id": "D", "type": "circle", "body": "D" }, { "id": "E", "type": "circle", "body": "E" }, { "id": "F", "type": "circle", "body": "F" } ], "connections": [ { "id": "ab", "from": "A", "to": "B", "text": "1", "path": "straight", "arrow_head": "none" }, { "id": "bc", "from": "B", "to": "C", "text": "2", "path": "straight", "arrow_head": "none" }, { "id": "ac", "from": "A", "to": "C", "text": "3", "path": "straight", "arrow_head": "none" }, { "id": "cd", "from": "C", "to": "D", "text": "4", "path": "straight", "arrow_head": "none" }, { "id": "de", "from": "D", "to": "E", "text": "5", "path": "straight", "arrow_head": "none" }, { "id": "be", "from": "B", "to": "E", "text": "6", "path": "straight", "arrow_head": "none" }, { "id": "ef", "from": "E", "to": "F", "text": "7", "path": "straight", "arrow_head": "none" }, { "id": "cf", "from": "C", "to": "F", "text": "8", "path": "straight", "arrow_head": "none" } ], "packets": [], "timeline": [ { "type": "comment", "text": "Kruskal’s minimum spanning tree: sort the edges by weight, then take each in turn — ADD it if its ends are in different trees, SKIP it if they are already connected (that would close a cycle)." }, { "type": "parallel", "actions": [ { "type": "set_color", "object": "ab", "color": "#16a34a" }, { "type": "set_color", "object": "A", "background_color": "#0d9488", "text_color": "white" }, { "type": "set_color", "object": "B", "background_color": "#0d9488", "text_color": "white" }, { "type": "comment", "object": "A", "text": "1 — A–B links two lone nodes. Add it.", "keep_until_next": true } ] }, { "type": "parallel", "actions": [ { "type": "set_color", "object": "bc", "color": "#16a34a" }, { "type": "set_color", "object": "C", "background_color": "#0d9488", "text_color": "white" }, { "type": "comment", "object": "C", "text": "2 — B–C brings in C. Add it.", "keep_until_next": true } ] }, { "type": "parallel", "actions": [ { "type": "set_color", "object": "ac", "color": "#dc2626" }, { "type": "highlight", "object": "ab" }, { "type": "highlight", "object": "bc" }, { "type": "comment", "object": "A", "text": "3 — A–C: but A and C are already joined through A–B–C (highlighted). Adding it would close a cycle → skip.", "keep_until_next": true } ] }, { "type": "parallel", "actions": [ { "type": "set_color", "object": "cd", "color": "#16a34a" }, { "type": "set_color", "object": "D", "background_color": "#0d9488", "text_color": "white" }, { "type": "comment", "object": "D", "text": "4 — C–D brings in D. Add it.", "keep_until_next": true } ] }, { "type": "parallel", "actions": [ { "type": "set_color", "object": "de", "color": "#16a34a" }, { "type": "set_color", "object": "E", "background_color": "#0d9488", "text_color": "white" }, { "type": "comment", "object": "E", "text": "5 — D–E brings in E. Add it.", "keep_until_next": true } ] }, { "type": "parallel", "actions": [ { "type": "set_color", "object": "be", "color": "#dc2626" }, { "type": "highlight", "object": "bc" }, { "type": "highlight", "object": "cd" }, { "type": "highlight", "object": "de" }, { "type": "comment", "object": "E", "text": "6 — B–E: B and E are already connected via B–C–D–E (highlighted) → cycle → skip.", "keep_until_next": true } ] }, { "type": "parallel", "actions": [ { "type": "set_color", "object": "ef", "color": "#16a34a" }, { "type": "set_color", "object": "F", "background_color": "#0d9488", "text_color": "white" }, { "type": "comment", "object": "F", "text": "7 — E–F brings in the last node F. Five edges now join all six nodes: the tree is complete.", "keep_until_next": true } ] }, { "type": "parallel", "actions": [ { "type": "set_color", "object": "cf", "color": "#dc2626" }, { "type": "highlight", "object": "cd" }, { "type": "highlight", "object": "de" }, { "type": "highlight", "object": "ef" }, { "type": "comment", "object": "F", "text": "8 — C–F would also close a cycle (C–D–E–F) → skip. Kruskal stops once n−1 = 5 edges are chosen.", "keep_until_next": true } ] }, { "type": "comment", "text": "Minimum spanning tree = A–B, B–C, C–D, D–E, E–F, total weight 1+2+4+5+7 = 19.", "keep_until_end": true } ] } ``` ### `circuit` — Electrical circuit (current) A battery/switch/resistor/LED series loop on `direction: circuit`: orthogonal wires between named terminals, closing the switch (`toggle`), then animated current (`flow`) and the LED lighting up. ```json { "direction": "circuit", "nodes": [ { "id": "battery", "type": "battery", "text": "Battery", "value": 9, "unit": "V" }, { "id": "sw", "type": "switch", "text": "Switch", "closed": false }, { "id": "R1", "type": "resistor", "value": 220, "unit": "Ω" }, { "id": "led", "type": "led", "text": "LED" } ], "connections": [ { "from": "battery:+", "to": "sw:a" }, { "from": "sw:b", "to": "R1:a" }, { "from": "R1:b", "to": "led:a" }, { "from": "led:b", "to": "battery:-" } ], "packets": [], "timeline": [ { "type": "comment", "text": "A series loop: a 9 V battery, a switch, a 220 Ω resistor and an LED. No coordinates — the loop is auto-arranged. The switch is open, so no current flows." }, { "type": "parallel", "actions": [ { "type": "toggle", "object": "sw", "closed": true }, { "type": "comment", "object": "sw", "text": "Close the switch — the loop is now complete.", "keep_until_next": true } ] }, { "type": "parallel", "actions": [ { "type": "flow", "route": [ "battery:+", "sw:a", "sw:b", "R1:a", "R1:b", "led:a", "led:b", "battery:-" ], "color": "#f59e0b", "count": 8, "keep_until_end": true }, { "type": "highlight", "object": "led", "keep_until_end": true }, { "type": "comment", "text": "Conventional current flows + → − through the resistor and the LED, which lights up.", "keep_until_end": true } ] }, { "type": "wait", "duration": 3000 } ] } ``` ### `halfAdder` — Half-adder (wired gates) A complete digital diagram: two bits A and B added by a XOR (sum) and an AND (carry), stepping through all four combinations. ```json { "direction": "circuit", "diagonal_wires": true, "nodes": [ { "id": "A", "type": "signal", "text": "A" }, { "id": "B", "type": "signal", "text": "B" }, { "id": "xor", "type": "xor_gate", "text": "XOR" }, { "id": "and", "type": "and_gate", "text": "AND" }, { "id": "sumOut", "type": "signal", "text": "Sum" }, { "id": "carryOut", "type": "signal", "text": "Carry" } ], "connections": [ { "from": "A", "to": "xor:a" }, { "from": "A", "to": "and:a" }, { "from": "B", "to": "xor:b" }, { "from": "B", "to": "and:b" }, { "from": "xor:y", "to": "sumOut" }, { "from": "and:y", "to": "carryOut" } ], "packets": [], "timeline": [ { "type": "comment", "text": "A half-adder adds two bits. A and B fan out to two gates: XOR gives the Sum, AND gives the Carry. Watch every input combination." }, { "type": "parallel", "actions": [ { "type": "set_icon", "object": "A", "icon": "0" }, { "type": "set_icon", "object": "B", "icon": "0" }, { "type": "set_color", "object": "A", "background_color": "#e5e7eb", "text_color": "#334155" }, { "type": "set_color", "object": "B", "background_color": "#e5e7eb", "text_color": "#334155" }, { "type": "set_icon", "object": "sumOut", "icon": "0" }, { "type": "set_icon", "object": "carryOut", "icon": "0" }, { "type": "set_color", "object": "sumOut", "background_color": "#e5e7eb", "text_color": "#334155" }, { "type": "set_color", "object": "carryOut", "background_color": "#e5e7eb", "text_color": "#334155" }, { "type": "comment", "text": "0 + 0: XOR → Sum 0, AND → Carry 0. Result 00.", "keep_until_next": true } ] }, { "type": "parallel", "actions": [ { "type": "set_icon", "object": "A", "icon": "0" }, { "type": "set_icon", "object": "B", "icon": "1" }, { "type": "set_color", "object": "A", "background_color": "#e5e7eb", "text_color": "#334155" }, { "type": "set_color", "object": "B", "background_color": "#16a34a", "text_color": "white" }, { "type": "set_icon", "object": "sumOut", "icon": "1" }, { "type": "set_icon", "object": "carryOut", "icon": "0" }, { "type": "set_color", "object": "sumOut", "background_color": "#16a34a", "text_color": "white" }, { "type": "set_color", "object": "carryOut", "background_color": "#e5e7eb", "text_color": "#334155" }, { "type": "comment", "text": "0 + 1: the inputs differ, so XOR → Sum 1; AND → Carry 0. Result 01.", "keep_until_next": true } ] }, { "type": "parallel", "actions": [ { "type": "set_icon", "object": "A", "icon": "1" }, { "type": "set_icon", "object": "B", "icon": "0" }, { "type": "set_color", "object": "A", "background_color": "#16a34a", "text_color": "white" }, { "type": "set_color", "object": "B", "background_color": "#e5e7eb", "text_color": "#334155" }, { "type": "set_icon", "object": "sumOut", "icon": "1" }, { "type": "set_icon", "object": "carryOut", "icon": "0" }, { "type": "set_color", "object": "sumOut", "background_color": "#16a34a", "text_color": "white" }, { "type": "set_color", "object": "carryOut", "background_color": "#e5e7eb", "text_color": "#334155" }, { "type": "comment", "text": "1 + 0: same idea → Sum 1, Carry 0. Result 01.", "keep_until_next": true } ] }, { "type": "parallel", "actions": [ { "type": "set_icon", "object": "A", "icon": "1" }, { "type": "set_icon", "object": "B", "icon": "1" }, { "type": "set_color", "object": "A", "background_color": "#16a34a", "text_color": "white" }, { "type": "set_color", "object": "B", "background_color": "#16a34a", "text_color": "white" }, { "type": "set_icon", "object": "sumOut", "icon": "0" }, { "type": "set_icon", "object": "carryOut", "icon": "1" }, { "type": "set_color", "object": "sumOut", "background_color": "#e5e7eb", "text_color": "#334155" }, { "type": "set_color", "object": "carryOut", "background_color": "#16a34a", "text_color": "white" }, { "type": "comment", "text": "1 + 1: inputs are equal, so XOR → Sum 0; but AND → Carry 1. Result 10 — binary two.", "keep_until_end": true } ] }, { "type": "wait", "duration": 2500 } ] } ```