# Canvas language

The visual grammar of a Visualli map. Every rule carries one tag:

- **SPEC** — what a `.visualli` file holds and how *any* viewer renders and browses it, **read-only**. Normative and exact: a conforming viewer looks and moves like Visualli, pixel for pixel and millisecond for millisecond. The open React SDK implements SPEC, the desktop viewer is SPEC and nothing else, and **the Visualli apps import that same SDK** — the experience outside developers get is the one we use ourselves. Candidates for the open standard at spec.visualli.ai.
- **PRODUCT** — what the Visualli apps add on top: AI (Go deeper, Map it, live generation), editing and moving ideas, sources, chat, vault, export and app chrome. Product data may travel in a file as extensions; SPEC viewers ignore extensions they don't know and never drop them.

The `.visualli` terms are used as-is: *layer*, *level*, *node*, *connection*, *container*, *extension*.

## The split at a glance
| SPEC — file + read-only rendering (open SDK) | PRODUCT — the Visualli apps |
| --- | --- |
| Layers, levels, layouts; nodes with `label`, `summary`, `color`, `position`; connections; containers — including everything the AI added with Map it, once it's in the file | Live generation: streaming a file while the AI writes it, ThinkingTree |
| The look: six blob shapes, one per level; depth rings; sizes; labels and fonts; connectors and arrowheads; containers; chromatic immersion | Go deeper (the button, the facets panel and the `facets` extension), *Visualize it* / Map it as actions |
| The 8 themes (Light, Dark, Focus, Color-safe, High contrast) with their exact values, plus reduced motion, readable type and larger text | Sources: the Sources panel, source chips and the `sources` extension |
| Motion: bloom, connector draw, dive in, back out, hover and select, reduced motion | Edit mode, the edit operations (`applyEdit`), undo |
| Read-only browsing: peek, term cards (`semantic-anchors`), step inside / back out, depth trail, zoom / pan / fit, touch rules, keyboard | Moving ideas and Reset layout (writing `position`) |
| Components: `VisualMap` (read-only), `Node`, `Edge`, `DepthTrail`, `CanvasControls`, `SemanticPeek`, `SemanticAnchor` / `AnchorCard`, `blobPath` / `drawBlob` / `RINGS` / `edgePath`, theme tokens | Components: `GoDeeper`, `SourcesPanel`, `EditBanner` / `SelectionBar` / `InlineText` / `SummaryEditor`, `Composer`, `ChatTurn`, `Vault`, `ComfortPanel`, `ThinkingTree` / `TreeMark`, `ExtensionPanel`, export |

## Principles
1. **Visuals first, text last.** A node is a short label (≤ 6 words). Detail lives in a deeper layer or a peek — never on the canvas. **SPEC**
2. **Depth is spatial.** You go *into* an idea, not to another page. **SPEC**
3. **Reveal gradually.** A layer arrives one idea at a time, center first. **SPEC**
4. **Only connect what is insightful.** A connector exists only when the relationship itself teaches something, and it always has a label. Hierarchy alone (parent → child) is shown by layers, not by lines. **SPEC**

## Node anatomy — SPEC
| Part | Rule |
| --- | --- |
| Outline | One of six hand-authored organic blobs (`BLOB_SHAPES`, drawn with midpoint-quadratic smoothing). All nodes in a layer share one shape; consecutive levels never repeat a shape (`shapeForLevel`). |
| Size | `node-width` 200 × `node-height` 148 at zoom 1; the level-0 center node is 240 wide (`size="root"`). |
| Fill | `node.data.color` — a topic name (`teal` … `stone`) or any CSS color. Absent: topics are assigned round-robin by sibling order. |
| Outline stroke | `topic-*-ring`, 3px (`node-stroke`), 4px when selected. |
| Label | `node-label` (Kalam 700, 22px) in `node-ink`, centered, ≤ 3 lines then ellipsis. The center node uses `node-root`. Labels scale inversely with zoom up to 1.3× so they stay legible when zoomed out; hidden below `zoom-min`. |
| **Depth rings** | Rings say *"there is more inside"*. Count = number of levels beneath the node, capped at 3. Ring 1 at 1.10× (2px, 50%, −5°), ring 2 at 1.20× (1px, 40%, −10°), ring 3 at 1.30× (3px dashed 6 4, 30%, −15°). A node without rings is a leaf and is not clickable. |

### Topic colors and themes — SPEC
Eight pastels derived from the logo: `topic-teal`, `-harbor`, `-iris`, `-berry`, `-coral`, `-amber`, `-sun`, `-stone`. Siblings get different topics; a child layer may reuse its parent's topic for the idea that continues it. Colors **do not encode meaning** — no legend, no "red = risk". Meaning belongs in labels, rings and connector style.
- The **8 themes** are normative, with the exact values in `tokens/tokens.json`: Light and Dark, Focus (desaturated, for ADHD minds), Color-safe (Okabe & Ito-derived) and High contrast, each light and dark. Every viewer offers them. **SPEC**
- Comfort settings that change rendering are normative too: reduced motion, readable type (hand faces fall back to the UI face) and larger text. **SPEC** · the panel that sets them (`ComfortPanel`) is **PRODUCT**.
- A file's `themes` extension may *add* themes (`{ "id":"themes", "data":[{ "name":"…", "topics":{ "teal":"#…" } }] }`); it never replaces the eight. **SPEC**

## Connectors — SPEC
- A cubic curve that bows perpendicular to the chord — at most `edge-arc-max` (45px), flatter as distance grows (`edgePath`). Ends `edge-gap` (15px) outside the outermost ring. Open V arrowhead (11px arms at ~33°). Stroke `edge`, `edge-width` 2.5px, round caps.
- `style: "solid"` = a direct or causal relationship; `style: "dashed"` (7 7) = an associative one.
- The label rides the curve in `edge-label` (Caveat 500, 18px, lowercase, ≤ 4 words), set above the line with a `canvas` halo, always reading left-to-right.
- Connections stay within one layer.

## Containers — SPEC
A dashed rounded hull (`line-strong`, 10 6, radius 48) with its label in `note` 22px above the top-left corner. `style: "none"` draws nothing. Containers group; they never create depth.

## Layers, levels and layouts
- Level 0 is the overview; every child layer is attached to one node (`parentNodeId`). Sibling layers on one level show one at a time. **SPEC**
- `layout: radial` — center idea plus a ring of peers (the default). `linear-horizontal` — sequences and timelines, gently staggered up/down. `linear-vertical` — ranked steps. Layouts seed positions only for nodes that have none. **SPEC**
- **`position` is authoritative once set**: viewers render each node where the file puts it. **SPEC**
- **Moving ideas** (drag with mouse or finger, Option/Alt + arrows) writes the new `position` back to the file, so the arrangement is the person's own; *Reset layout* restores the generated one. A press that moves more than 4px (mouse) or 10px (finger) drags; a still press is a tap; the click that ends a drag is ignored. **PRODUCT**
- **Chromatic immersion**: inside a layer, the canvas takes the parent node's topic fill at `immersion-alpha` (15%), so you always know whose inside you're in. **SPEC**

## Navigation — SPEC
- **Step inside**: enter an idea's inner layer — click a ringed node (mouse), tap a selected ringed node a second time (touch), press Enter on it, or use *Step inside* on its peek. The camera dives into the node (scale ≈ 3.2 around it, `duration-zoom`, `ease-zoom`), fades at 150ms, swaps at ~320ms, and the new layer blooms.
- **Back out**: the depth trail, Escape or Backspace, or pinch out past `zoom` 0.4. The layer shrinks to 0.6 and fades (220ms), the parent settles in from 0.94.
- **Depth trail** (`DepthTrail`): a vertical stack top-left — one organic dot per level, in that level's shape and topic, joined by a dashed stem; the current level is a raised pill. The first item carries a home glyph and the Visualli's title.
- **Zoom, pan, fit** (`CanvasControls`): zoom −, %, +, and *Fit map to view*; ⌘+ / ⌘− / ⌘0; pinch and two-finger pan on touch.
- The interaction mode follows the **pointer in use, not the screen size**: a touchscreen laptop switches the moment someone taps, an iPad with a trackpad hovers. Double-tap is left to the system's zoom.
- On touch, a one-time Caveat note teaches the canvas: "tap an idea to peek · tap again to step inside".

## Understanding: peek, term, go deeper
Every idea can be understood without leaving the map. **Step inside** moves you *through* the map; **Go deeper** explains *one thing* in depth. They never share a name.

| Depth | Component | What it shows | Mouse / keyboard | Touch | Tag |
| --- | --- | --- | --- | --- | --- |
| 1 · Peek | `SemanticPeek` | The idea's `summary` (1–2 sentences) with its terms, and *Step inside* if it has rings | Hover or focus the idea → card beside it, never over it | First tap → bottom sheet; the idea lifts, others dim, the canvas nudges it above the sheet | **SPEC** |
| 2 · Term | `SemanticAnchor` / `AnchorCard` | One underlined term's definition and *Learn more ↗* | Hover the term 250ms (stays while hovered, closes 300ms after leaving), or Tab + Enter → card layered over the peek; Escape returns focus | Tap the term → the sheet swaps to its definition, *‹ back* to the idea | **SPEC** |
| 3 · Go deeper | `GoDeeper` | Five facets — *What it means · How it works · What it's like · A worked example · What it's not* — streamed in, then *Visualize it* | Docked panel on the right (380px); the map makes room, the source idea stays highlighted | Sheet up to just below the top bar, *‹ back* to the peek | **PRODUCT** |

- **Terms** come from the `semantic-anchors` extension. They are underlined dotted in `link` (1.5px, offset 3px) wherever they appear in a summary — real buttons, so keyboards and screen readers reach them. A term's own card never underlines further terms. **SPEC**
- The peek's **Go deeper** button, its **source** line and the term card's **Go deeper** are added by the product (`VisualMap` `deeper`, `source`); a SPEC viewer's peek has summary, terms and *Step inside* only. **PRODUCT**
- **Go deeper works for terms and for whole ideas.** From a peek it explains the idea; from a term card it explains the term, with "in ‹idea›" as context. Facets are AI-written text, streamed one by one: queued, *writing* (spinner), ready (opens itself if nothing else is open), or *Retry*. Icons are Lucide — lightbulb, cog, shapes, flask, ban — never emoji. A polite live region counts "3 of 5 ready". Saved facets may travel in a `facets` extension, which SPEC viewers ignore. **PRODUCT**
- **Visualize it** closes the panel: "*‹subject›* could use a map of its own." The button **Map ‹subject›** asks where — *Inside this map* (a new layer under the idea; a ring appears and you can step inside) or *As a new Visualli* (its own map in the vault). **PRODUCT**
- **What Map it creates is SPEC content.** The AI's expansion is written as ordinary `.visualli` data — a child layer under the idea (or a new file) with nodes, `summary` texts, connections and `semantic-anchors` terms. Once saved or exported, every SPEC viewer renders and browses it like any other layer: the new ring, step inside, peeks, terms. Only the *act* of generating (the button, the panel, streaming) is product. **SPEC** (the result) · **PRODUCT** (generating it)
- **Layouts by surface** for Go deeper and the Sources panel (`VisualMap` picks automatically): web app → *panel*; phones, tablets and narrow canvases → *sheet*; short embeds under 460px tall → *stack* (replaces the map view, *‹ back* returns). The MCP card turns Go deeper off (`deeper={false}`) and hands it to *Open in Visualli*. **PRODUCT**
- **Sources** — what a Visualli is made from, passages linked to ideas — live in the product's `SourcesPanel` and its `sources` extension; SPEC viewers ignore them. **PRODUCT**

## Editing — PRODUCT
Reading is the default; changing a Visualli is a deliberate mode, and only the Visualli apps have it. **Edit** (pencil + word) in the top toolbar turns it on; **Done** in the editing bar turns it off. While editing, everything about browsing still works — the trail, stepping inside (from the selection bar), peeks on hover — so people edit any layer as they move through it. A quiet dot grid on the canvas says "you can change this".

**One grammar for every construct**
1. **Select** — click or tap any construct: an idea, a connector, a group (and whatever comes next). It takes a `focus` outline and its **selection bar** appears beside it.
2. **Verbs** — the selection bar lists that construct's verbs in a fixed order: *text* (Rename, Summary, Edit label) → *structure* (Connect, Group, Edit ideas) → *navigate* (Step inside) → *delete* (Delete, Ungroup). The order never changes, so a new construct is learned at a glance.
3. **Add** — the editing bar's **Idea** and **Group** (future constructs join this list); double-click empty canvas adds an idea where you clicked.
4. **Choose…** — multi-step verbs (Connect: *choose the idea to connect to*; Group: *choose ideas*) put their instruction in the editing bar with **Cancel** (and **Done** when needed). The same on mouse, touch and keyboard.
5. **Every change is one op** (`node.rename`, `connection.add`, `container.delete`, …) — persisted by the app, undoable with **Undo/Redo** in the editing bar or ⌘Z / ⇧⌘Z.

| Construct | Select | Text | Structure | Delete |
| --- | --- | --- | --- | --- |
| Idea | click / tap | Rename (or click it again, double-click, F2) · Summary (in its peek; names any underlined terms the change would drop) | Connect · Group · Step inside | Delete — ideas with inner layers confirm first ("Its 2 inner layers and 6 ideas will be deleted too. You can undo this.") |
| Connector | click / tap the line | Edit label (required — a new connector asks "how are they related?") | — | Delete |
| Group | click its outline or name | Rename | Edit ideas (choose…) | Ungroup (keeps the ideas) |

- **Narrow maps** (phones, small embeds, under 560px): the editing bar moves to the bottom and its tools go icon-only (names stay for screen readers); the selection bar goes icon-only too. Hide the Composer while editing on phones so the bar has the bottom edge.
- **Moving ideas is layout, not editing**: it works in both modes and is saved per person. The first move in view mode invites "Layout saved. Want to edit the content too? **Edit**" once; apps may opt into switching modes automatically instead.
- **Keyboard**: Tab to an idea, Enter selects (Enter again renames), F2 renames, Delete removes, Escape cancels a step, then deselects. Inline editors: Enter saves, Escape cancels; summaries save with ⌘ Enter.
- **No AI in edit mode** — AI changes go through the chat and arrive as receipts with Undo.

## Choreography
| Moment | Motion | Tag |
| --- | --- | --- |
| Layer appears | Nodes bloom from 50% of the way to the center: scale .55 → 1, `duration-reveal` 420ms, `ease-bloom`, staggered `stagger-reveal` 70ms in sibling order. Then solid connectors draw from source to target (500ms), dashed ones fade in. | **SPEC** |
| Hover / select | Node lifts 2px; rings turn −2° and grow 1.5% (no overshoot — a nudge, not a wobble); Focus palette dims every other node to 40%. On touch the selected node also takes a 4px outline and others dim to 60%. | **SPEC** |
| Sheet (touch peek) | Slides up from the bottom edge (`duration-base`, `ease-standard`), follows the finger while dragged, snaps back unless pulled 80px. The canvas pans the selected idea into view with the same timing. | **SPEC** |
| Reduced motion / *Gradual reveal* off | Everything appears together with a 120ms fade; dives become cross-fades; sheets fade in 120ms. | **SPEC** |
| Live generation (the AI is still writing the file) | Nodes bloom as their lines arrive; a `ThinkingTree` sits in the chat dock. Never a spinner over the canvas. | **PRODUCT** |
| Drag | The held idea lifts (scale 1.04, deeper shadow or dark-mode glow) and follows the pointer with no easing; connectors redraw every frame; on drop it settles at once. No snapping grid — placement is personal. | **PRODUCT** |

## Canvas 2D / SDK notes — SPEC
Use `blobPath` (SVG) or `drawBlob(ctx, shape, rx, ry, cx, cy)` (Canvas 2D) — same points, same smoothing. `RINGS` holds the ring recipe, `edgePath` / `arrowPath` the connector geometry. Read colors from the CSS custom properties at draw time (`getComputedStyle(document.documentElement).getPropertyValue('--topic-teal')`) and redraw on a theme change so all eight themes stay correct. These are exported from `@visualli/design-system/spec` — the layer the open SDK and the Visualli apps share.
