# Gritmind - Canvas illustrations & animation > File paths in this reference (`src/...`) point into the `gritmind-website-v2` > repo, which holds the reference implementations. The house illustration style: small animated diagrams drawn on 2D canvas (or build-time SVG), used for section graphics, capability tiles, and the hero. Everything here exists to make one idea legible - **quiet ink structure, one live accent element**. If a diagram wouldn't read as a still frame in a print deck, the animation won't save it. Reference implementations, one per tier: | Tier | Example | File | |---|---|---| | Static build-time SVG | insight dial | `src/components/InsightDial.astro` | | Scroll-gated ambient diagram | team loop | `src/components/TeamLoop.tsx` | | Hover-gated glyph | capability/value glyphs | `src/lib/glyphs.ts` + `src/components/CapabilityGlyph.tsx` | | Pointer-driven showpiece | hero signal | `src/components/HeroSignal.tsx` | ## Picking a tier - **Deterministic and non-looping?** Render it at build time as inline SVG with `currentColor` / CSS-var strokes - no JS at all. (InsightDial.) - **One narrative diagram per section?** Scroll-gated: an intersection-gated draw-in resolves the structure once, then a calm ambient loop runs. At most one of these per view. - **A grid of many?** Hover-gated: still until the card is hovered or focused. Seven simultaneously looping canvases are visual noise - the opposite of the point. The resting frame must read on its own. - The pointer-driven hero treatment is a one-off; don't repeat it elsewhere. ## Vocabulary The recurring marks and what they mean. Reuse these before inventing new ones. | Mark | Spec | Means | |---|---|---| | Anchor node | navy dot, 7px | the fixed thing (a lead agent, an endpoint) | | Entity node | slate/navy dot, 4.5–5px | a participant (role, model, step) | | The pulse | signal dot, 4–5px, glow 8–14 | the live work item moving through the system | | Path / chord | 1–1.5px stroke, low alpha | a relationship or route | | Ring / loop | 1px circle, navy 12–14% | a cycle, an iteration | | Target ring | stroked circle r 8–10 | a goal to reach | | Box | `rrect` radius 5–6, stroked 1.4px | a system, model, or step | | Dashed line | `setLineDash([6,5])` | a limit or threshold (guardrail, start line) | | Label | JetBrains Mono 10–12px/500, uppercase, navy 40–55% | naming, always mono | Opacity encodes attention, in ink (navy) or slate: - gridlines / graticule - navy 5–7% - baselines, rings, hairline rules - navy 12–18% - passive structure & inactive candidates - slate 25–50% (higher end in small glyphs, lower in large diagrams) - active/selected element - signal at 85–100%; its siblings drop back, they never disappear **The accent is the scalpel here too.** Exactly one signal-coloured element per canvas, and it is always the *live* one - the moving pulse, the selected route, the resolved line. Structure is never accent-coloured. A node may flash to signal only as the pulse passes through it (mix slate→signal by proximity) - that reads as the same signal, not a second one. Glow (`shadowBlur` 8–16 at 35–55%) belongs to accent elements only; ink never glows. ## Motion & timing Calm is the default; the numbers are slow on purpose. | What | Value | |---|---| | Draw-in (scroll-gated) | ~1.6s total, staggered eased ramps: chords → ring → nodes → labels | | Ambient loop period | 8–10s per lap | | Glyph loop period | 2.2–4s resting; hover shortens it ~30–40% | | Hover blend | exponential ease-in: `f += (target - f) * min(dt * 6, 1)` | | Pulse trail | ~0.9 rad of arc, alpha falling off as (i/n)² | | Stagger helpers | `ramp(p, a, b)` = clamped linear window; `easeOut` = 1−(1−x)³ | Draw-in pattern: accumulate progress from frame `dt` (`p += dt / 1.6`, clamp to 1), gate every element on its own `ramp(p, start, end)` window. Because `useCanvas` pauses offscreen, the ramp simply starts on the first visible frame - no IntersectionObserver of your own. Hover pattern: the clock only advances while hovered (`if (hover) t += dt`), so the glyph freezes in place rather than resetting; the hover blend `hb` eases activity in and accelerates the period. ## Reduced motion `prefers-reduced-motion` renders the **resolved end state**, never frame zero: - draw-ins force `p = 1` - looping clocks freeze (`t` stays 0) - so design the t=0 frame to read as a complete still (phase-shift the loop if needed; see `simple` in glyphs.ts) ## Code contract Every canvas uses the shared plumbing - never hand-roll RAF/DPR/observers: - `useCanvas` (`src/hooks/useCanvas.ts`): DPR clamped to 2, ResizeObserver, offscreen pause, `visibilitychange`, theme resync, reduced-motion `t` freeze. - `paletteFor(canvasEl)` (`src/lib/palette.ts`): resolves `{navy, slate, steel, sage, signal}` RGB triplets - accent read from the element's own computed `--signal`, so scoped `data-accent` subtrees recolour the canvas. Cache in a ref; null it in `onThemeChanged`. Values mirror `assets/tokens.json` → `canvas` (navy inverts to light in dark mode). - Helpers: `rgba(c, a)` and `mix(a, b, t)` from palette.ts; `dot`/`rrect`/ `bez` in glyphs.ts for glyph-scale work. - Feather edges with `destination-out` gradients **only when lines run off the canvas** (hero). Self-contained diagrams that end inside the frame don't feather. - Sizing comes from CSS (`.team-canvas`, `.svc-screen` in global.css); the hook reads the rect. Glyph tiles are 196px tall on `--surface-2`. ## Accessibility - Meaningful canvas: `role="img"` + `aria-label` describing what it depicts ("four cross-functional roles on one iteration loop…"), not that it moves. - Decorative backdrop: `aria-hidden="true"`. - Hover-gated glyphs also activate on `onFocus`/`onBlur`, not just pointer. - Never let colour alone carry the meaning - the accent element also differs by position, motion, or weight. ## Checklist for a new canvas 1. Sketch the still end frame first; if it doesn't explain the idea, stop. 2. Pick the tier (static / scroll-gated / hover-gated). 3. Build from the vocabulary table; one accent element, structure in ink. 4. Wire through `useCanvas` + `paletteFor`; check dark mode and a scoped `data-accent` subtree. 5. Check reduced motion shows the resolved state. 6. Write the `aria-label`.