@pixerate/editor 0.280.0 → 0.282.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/animation-editor/index.cjs +59 -59
  2. package/dist/animation-editor/index.js +3 -3
  3. package/dist/brushes/index.cjs +76 -71
  4. package/dist/brushes/index.js +13 -8
  5. package/dist/brushes/schema.d.cts +1 -0
  6. package/dist/brushes/schema.d.ts +1 -0
  7. package/dist/brushes/types.d.cts +29 -3
  8. package/dist/brushes/types.d.ts +29 -3
  9. package/dist/{brushstrokeRender-EIK3625T.js → brushstrokeRender-4MA5CBW3.js} +3 -3
  10. package/dist/{brushstrokeRender-7HOMKZNO.cjs → brushstrokeRender-JVKVKWDA.cjs} +15 -15
  11. package/dist/canvas/edge-animation-tools.d.cts +115 -0
  12. package/dist/canvas/edge-animation-tools.d.ts +115 -0
  13. package/dist/canvas/edgeAnimation.d.cts +164 -0
  14. package/dist/canvas/edgeAnimation.d.ts +164 -0
  15. package/dist/canvas/edgePaths.d.cts +63 -0
  16. package/dist/canvas/edgePaths.d.ts +63 -0
  17. package/dist/canvas/index.cjs +691 -21
  18. package/dist/canvas/index.d.cts +3 -0
  19. package/dist/canvas/index.d.ts +3 -0
  20. package/dist/canvas/index.js +679 -9
  21. package/dist/{chunk-NJDEKU5D.js → chunk-2YGYYVSG.js} +1 -1
  22. package/dist/{chunk-JVFGK3DK.cjs → chunk-3JUKN3RM.cjs} +76 -53
  23. package/dist/{chunk-HN57I7KT.js → chunk-CG3FBNXE.js} +1 -1
  24. package/dist/{chunk-WFSYZO5Z.cjs → chunk-DFZ4I7EZ.cjs} +1 -1
  25. package/dist/{chunk-LZE2QZCT.js → chunk-IHMMHZGP.js} +1 -1
  26. package/dist/{chunk-K4ANWBF7.cjs → chunk-IJQ2YH2M.cjs} +35 -35
  27. package/dist/{chunk-HRUK5TFW.cjs → chunk-IUKPQCYQ.cjs} +164 -71
  28. package/dist/{chunk-WCC4NXWY.cjs → chunk-JD63B33V.cjs} +86 -86
  29. package/dist/{chunk-NJLPW4MM.js → chunk-KV7QHPKL.js} +27 -4
  30. package/dist/{chunk-H3EME3S5.cjs → chunk-MRIAR5DY.cjs} +2 -2
  31. package/dist/{chunk-ROURHJYT.js → chunk-NEDS6DSC.js} +1 -1
  32. package/dist/{chunk-FKO3CY2C.js → chunk-ULPXJ3NB.js} +1 -1
  33. package/dist/{chunk-WZYRYYXN.cjs → chunk-UOM5S4W5.cjs} +12 -12
  34. package/dist/{chunk-YOM76H2V.js → chunk-VEX6YVNG.js} +1 -1
  35. package/dist/{chunk-MNHP3AQT.cjs → chunk-WEXWDBUF.cjs} +23 -23
  36. package/dist/{chunk-PPDUCZZV.js → chunk-YRGTQLDN.js} +122 -29
  37. package/dist/image-editor/index.cjs +788 -788
  38. package/dist/image-editor/index.js +4 -4
  39. package/dist/paint-editor/antimatter.d.cts +154 -0
  40. package/dist/paint-editor/antimatter.d.ts +154 -0
  41. package/dist/paint-editor/brush-controls.d.cts +1 -0
  42. package/dist/paint-editor/brush-controls.d.ts +1 -0
  43. package/dist/paint-editor/brush-model.d.cts +9 -5
  44. package/dist/paint-editor/brush-model.d.ts +9 -5
  45. package/dist/paint-editor/controller.d.cts +23 -0
  46. package/dist/paint-editor/controller.d.ts +23 -0
  47. package/dist/paint-editor/index.cjs +1931 -891
  48. package/dist/paint-editor/index.d.cts +1 -0
  49. package/dist/paint-editor/index.d.ts +1 -0
  50. package/dist/paint-editor/index.js +1098 -58
  51. package/dist/paint-editor/mark-settings.d.cts +30 -0
  52. package/dist/paint-editor/mark-settings.d.ts +30 -0
  53. package/dist/paint-editor/stroke.d.cts +16 -0
  54. package/dist/paint-editor/stroke.d.ts +16 -0
  55. package/dist/paint-editor/strokes.d.cts +2 -0
  56. package/dist/paint-editor/strokes.d.ts +2 -0
  57. package/dist/paint-editor/ui.d.cts +4 -2
  58. package/dist/paint-editor/ui.d.ts +4 -2
  59. package/dist/vector/index.cjs +999 -999
  60. package/dist/vector/index.js +4 -4
  61. package/package.json +1 -1
  62. package/skills/pixerate-brushes/SKILL.md +1 -1
  63. package/skills/pixerate-canvas/SKILL.md +25 -2
  64. package/skills/pixerate-paint-editor/SKILL.md +8 -2
  65. package/skills/pixerate-paint-editor/references/antimatter.md +49 -0
  66. package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
@@ -35,7 +35,7 @@ import {
35
35
  traceField,
36
36
  traceVectorImage,
37
37
  vectorTraceContours
38
- } from "../chunk-HN57I7KT.js";
38
+ } from "../chunk-CG3FBNXE.js";
39
39
  import {
40
40
  rasterizeCoverage
41
41
  } from "../chunk-ADMRNWR7.js";
@@ -66,7 +66,7 @@ import {
66
66
  reverseMorphContour,
67
67
  subpathToMorphContour,
68
68
  turnMorphContour
69
- } from "../chunk-FKO3CY2C.js";
69
+ } from "../chunk-ULPXJ3NB.js";
70
70
  import {
71
71
  DEFAULT_VECTOR_FONT,
72
72
  IDENTITY_MATRIX,
@@ -556,10 +556,10 @@ import {
556
556
  withVectorStrokes,
557
557
  withVectorTargetPaint,
558
558
  zigzagPaths
559
- } from "../chunk-YOM76H2V.js";
559
+ } from "../chunk-VEX6YVNG.js";
560
560
  import {
561
561
  snapshotBrushLibrary
562
- } from "../chunk-PPDUCZZV.js";
562
+ } from "../chunk-YRGTQLDN.js";
563
563
  import "../chunk-JL6YH5WD.js";
564
564
  import {
565
565
  createProvenance
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.280.0",
3
+ "version": "0.282.0",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -16,7 +16,7 @@ built-in presets (read-only) and the user's brushes, shared by every editor that
16
16
  | Medium and variety | Marks: what it lays (`paint`, `ink`, `dry`, `spray`) and what kind (`oil`, `watercolor`, `gouache`, `pencil`, `charcoal`, `airbrush`, `marker`, `highlighter`…). Descriptive, for finding brushes; a wet brush whose variety is `watercolor`, `gouache` or `oil` paints that medium. |
17
17
  | Tool | Tools: `eraser`, `blender`, `smudge`, and on wet paint `water`, `dryer`, `blow`, `blot`, `wet-blend`, `wet-drag`. |
18
18
  | Tip | `shape` (`round`, `flat`, `chisel` are ellipses set by `roundness` and `angle`; `bristle` has a tuft; `stamp` takes the shape of `stamps`: an editor stamp `editor:star`, any sticker ref from `find_stickers`, or an image source `source:<id>` from `list_brush_sources`), size in px, hardness (an image tip fades towards its edges below 1), spacing, opacity, flow, blend mode, and `grain`: texture inside each dab (`source`: a pattern such as `rough`, `canvas`, `sanded`, or `source:<id>`; `mode` `texturised` stays put on the canvas, `moving` rolls with the tip; `scale`, `depth`, `contrast`, `invert`). |
19
- | Marks | `continuous` dabs (default; on a stamp tip every dab is the first stamp's shape, in the paint colour), `scatter` (a spray: droplets across the tip, `density`, `size`), `particles` (strands, springs, ribbons, sparks; textured dots from the stamps) or `objects` (each mark an editable sticker, in its own colours). |
19
+ | Marks | `continuous` dabs (default; on a stamp tip every dab is the first stamp's shape, in the paint colour), `scatter` (a spray: droplets across the tip, `density`, `size`), `particles` (strands, springs, ribbons, sparks; textured dots from the stamps), `objects` (each mark an editable sticker, in its own colours) or `antimatter` (reactive blasts that invert, scorch and annihilate paint across every visible layer in the paint editor; presets `antimatter`, `antimatter-flare`, `antimatter-charge`). |
20
20
  | Dynamics | Pen feel: pressure to size and opacity, smoothing, tilt, velocity, jitter (`sizeJitter`, `angleJitter` for rotation, `opacityJitter`, `scatter`), rotation, `count` (dabs per spacing step, each jittered) and `flip` (mirror dabs at random: `x`, `y`, `xy`). |
21
21
  | Behaviour | `surface` `dry`, `wet` (`wet` settings) or `thick` (`thick` impasto settings); paper `grain`; `exchange` (pick-up and mix; a `reservoir` makes a Mixer Brush); tool `strength`. |
22
22
  | Light | Light the brush gives off (`amount`), plus suggested layer `spill` and `glow`. |
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  name: pixerate-canvas
3
- description: How to build and edit a Pixerate design canvas or node graph through its agent tools - drawing freehand pen, highlighter and dry-brush strokes (canvas.drawFreehand), annotating with hand-drawn circles, underlines, arrows, brackets, strikes, crosses and ticks (canvas.circle, canvas.arrow), adding, connecting, updating and laying out nodes (canvas_add_node, canvas_layout), reading state with get_editor_state and looking at it with view_editor, reusable components with variants, instances and overrides (create_component, add_variant, set_instance_properties), align, distribute, lock and layer order, shape boolean operations (unite, subtract, offset path, outline stroke), auto layout frames, and keyframe animation with easing and CSS export. Use when working on a canvas, whiteboard, flow chart, node graph, frame, design system, component library, button variants, layout, alignment, z-order or a canvas animation in a Pixerate editor.
3
+ description: How to build and edit a Pixerate design canvas or node graph through its agent tools - drawing freehand pen, highlighter and dry-brush strokes (canvas.drawFreehand), annotating with hand-drawn circles, underlines, arrows, brackets, strikes, crosses and ticks (canvas.circle, canvas.arrow), adding, connecting, updating and laying out nodes (canvas_add_node, canvas_layout), reading state with get_editor_state and looking at it with view_editor, reusable components with variants, instances and overrides (create_component, add_variant, set_instance_properties), align, distribute, lock and layer order, shape boolean operations (unite, subtract, offset path, outline stroke), auto layout frames, keyframe animation with easing and CSS export, animated edges (dots, dashes, beams: set_edge_animation). Use when working on a canvas, whiteboard, flow chart, node graph, frame, design system, component library, button variants, layout, alignment, z-order, a canvas animation or animated connections in a Pixerate editor.
4
4
  ---
5
5
 
6
6
  # Pixerate canvas
7
7
 
8
8
  The canvas is an xyflow (React Flow / SvelteFlow) graph: an array of **nodes** and **edges**.
9
9
  A node is `{ id, type, position: { x, y }, width?, height?, parentId?, zIndex?, data }`; edges are
10
- `{ id, source, target, label? }`. Everything a design feature knows lives in `node.data`, so it
10
+ `{ id, source, target, label?, data? }`, with `edge.data.animation` holding an edge's animation
11
+ (dots, dash, beam or pulse; not the keyframe tracks of a node's `data.animation`). Everything a design feature knows lives in `node.data`, so it
11
12
  survives copy, paste and JSON:
12
13
 
13
14
  - `data.label` / `data.name`: text content and layer name. `data.shape`: a shape node
@@ -161,6 +162,20 @@ undo step and is not saved with the document. The host decides what the children
161
162
  | `explode_node` | Explode `node` (an id from `list_explodable_nodes`). Unless `multiple`, the node exploded before collapses (it comes back in `collapsed`). Exploding an exploded node changes nothing (`changed: false`). Returns the ids now `exploded`. Then use `get_editor_state` or `capture_view` to read or see the children. |
162
163
  | `collapse_node` | Collapse an exploded `node`. Returns the ids still `exploded` and those still `collapsing` (their children leave when the collapse animation ends). Fails when the node is not exploded. |
163
164
 
165
+ ### Animate edges
166
+
167
+ Hosts that draw edges with `<AnimatedEdge>` (or `<EdgeAnimation>` in their own edge) add these.
168
+ An edge's animation is data (`edge.data.animation`): it saves, copies and undoes with the edge.
169
+ Use motion to say something: dots for data or messages flowing, dashes for "live" or "pending",
170
+ a beam for a request or trigger, a pulse for the active or highlighted path. Animate the few
171
+ edges that matter, not the whole graph.
172
+
173
+ | Tool | Use it to |
174
+ | --- | --- |
175
+ | `list_edge_animations` | List the edges (`id`, `source`, `target`, `label`, `animation` or null), the animation `kinds` with their `defaults`, and the `readOnly` reason if edges cannot change. Call it first: it is the source of edge ids. |
176
+ | `set_edge_animation` | Animate `edges` (ids from `list_edge_animations`) with one `animation`: `kind` (dots, dash, beam, pulse) and optional `speed` (canvas units per second), `duration` (ms per cycle), `direction` (forward = source to target, reverse, alternate), `color`, `size`, `count`, `length`, `gap`, `glow`, `trackOpacity`, `paused`. Replaces what those edges had; all of them change in one undo step. Returns the ids, the `animation` stored and `resolved`, with every default filled in. Then `capture_view` to see it. |
177
+ | `clear_edge_animation` | Stop the animation on `edges`, leaving the plain edge. One undo step. Returns the ids `cleared` and those `unchanged` (they had none). |
178
+
164
179
  ### Proposals (suggest, don't edit)
165
180
 
166
181
  Hosts that let agents suggest additions add these. A proposal is new nodes and edges shown as ghosts
@@ -354,6 +369,14 @@ effects provider (for example Flair); without one the read tools say so and the
354
369
 
355
370
  ## Pitfalls
356
371
 
372
+ - **Direction follows the edge, not the screen.** `forward` runs from the edge's `source` to its
373
+ `target`; read them from `list_edge_animations` before choosing, and use `reverse` rather than
374
+ re-connecting the edge to flip the flow.
375
+ - **Keep speeds comparable.** `speed` is in canvas units per second, so edges of different lengths
376
+ move at the same pace; `duration` makes a long edge move faster than a short one. Use the same
377
+ speed for edges that carry the same kind of flow. Users who ask for reduced motion see the first
378
+ frame, held still, so motion must not be the only sign of state: pair it with a colour or label.
379
+
357
380
  - **Proposals are not on the canvas.** Their ids are not in `get_editor_state` until accepted, and
358
381
  `canvas_connect` or `canvas_update_node` on them fails. Put the whole idea in one proposal (an
359
382
  edge between two proposed nodes goes in its `edges`), or wait for the user to accept it.
@@ -12,7 +12,7 @@ is never part of a layer. Coordinates are document pixels, (0, 0) top-left, y do
12
12
  `pixerate-agent-tools` for errors, namespacing and batching.
13
13
 
14
14
  - **Brush colours.** Choosing a brush with a default colour (highlighter yellow, ballpoint blue, chalk light or dark for the colour scheme) makes it the paint colour, unless the colour is locked (`set_color` `locked`). Set the colour after choosing the brush, or lock it first. Strokes painted in a light / dark colour (a brush's pair, or `set_color` `dualColor`) record the pair. Paint them on a stroke layer (`add_layer` type `strokes`) when they should follow the viewer's theme: the document keeps the light side and each viewer sees their own scheme's; a pixel layer keeps the side it was painted in.
15
- - **Brushes** come from the shared brush library (`pixerate-brushes`: `list_brush_presets`, `create_brush`, `preview_brush`…) and are made of facets: a tip (size, hardness, spacing, angle, roundness, opacity, flow, blend), dynamics (pressure, tilt, velocity, jitter, a **rotation** mode `fixed`, `stroke` or `barrel`), behaviour (paper **grain** response, pick-up, wet or thick surface), marks, light, ferrofluid, motion and colour. `set_brush` chooses one and lays facet changes over it; `get_brush` reads it with the engine it paints with: `dab` (most media), `eraser`, `mixer` (the Mixer Brush: a reservoir of paint plus paint it picks up), `blender` (mixes the colours under and near the tip) or `smudge` (drags paint along the stroke), `wet` (watercolour, gouache, oil and the wet tools, below), `impasto` (thick paint with relief and material, below), `particle` (procedural particle brushes), `bristle` (a tuft of bristles) or `sticker` (sprays editable stickers).
15
+ - **Brushes** come from the shared brush library (`pixerate-brushes`: `list_brush_presets`, `create_brush`, `preview_brush`…) and are made of facets: a tip (size, hardness, spacing, angle, roundness, opacity, flow, blend), dynamics (pressure, tilt, velocity, jitter, a **rotation** mode `fixed`, `stroke` or `barrel`), behaviour (paper **grain** response, pick-up, wet or thick surface), marks, light, ferrofluid, motion and colour. `set_brush` chooses one and lays facet changes over it; `get_brush` reads it with the engine it paints with: `dab` (most media), `eraser`, `mixer` (the Mixer Brush: a reservoir of paint plus paint it picks up), `blender` (mixes the colours under and near the tip) or `smudge` (drags paint along the stroke), `wet` (watercolour, gouache, oil and the wet tools, below), `impasto` (thick paint with relief and material, below), `particle` (procedural particle brushes), `bristle` (a tuft of bristles), `sticker` (sprays editable stickers) or `antimatter` (blasts that invert, scorch and annihilate paint across every visible layer).
16
16
  - **Wet media** (engine `wet`: `behaviour.surface` `wet`, and the wet tools; categories `watercolor` and `wet-tool`): a stroke lays water and pigment on the layer's **wet layer**, a fluid simulation that keeps running after the stroke: the water spreads until its edge is pinned by the paper, carries pigment out to the rim as it dries (edge darkening), drops pigment into the paper's valleys (granulation), pushes pigment back where new water flows in (blooms), bleeds between touching wet colours and runs downhill on a tilted canvas. Paint stays wet until it dries by itself (a few thousand steps) or `dry_paint` dries it; a dried wash is fixed, so the next wash **glazes** over it. A wet brush paints, or is a tool (`tool` `water`, `dryer`, `blow`, `blot`, `wet-blend`, `wet-drag`); `behaviour.wet` `water`, `pigment` (concentration), `body` (0 transparent watercolour .. 1 opaque gouache) and `rewet`, and `behaviour.strength`, set it up. Every result says whether paint is still wet (`wet` / `stillWet`).
17
17
  - **Behaviour** (`behaviour.exchange`, dab and mixer brushes) is how a brush treats paint already on the canvas: a **pick-up ratio** (how much paint each dab lifts and carries), a **mix ratio** (how much of the carried paint goes into what it lays down), **dirty** (keep the carried colour into the next stroke) and **multi-colour** (`tip`: several colours side by side across the tip; `dab`: the colour changes along the stroke). A Mixer Brush's `mixer.wet` / `mixer.mix` are the same two ratios. `get_mixer_brush` reads what the brush carries; `clean_mixer_brush` wipes it.
18
18
  - **Grain**: a brush's grain response times the paper's `depth` decides how much light pressure
@@ -121,6 +121,12 @@ inner glow, shadows, stroke, bevel, satin and overlays, as in the image editor
121
121
  with `marks` (kind `particles`): physics (string, spring, orbit, flow, elastic, fuzzy), render
122
122
  (dot, line, sequence, ribbon), waves and textured dots (`tip.stamps`); read [references/particle-brushes.md](references/particle-brushes.md).
123
123
  Draw slowly and with varying direction: the particles react to movement.
124
+ - **Antimatter paint** (`marks.kind: "antimatter"`, presets `antimatter`, `antimatter-flare`,
125
+ `antimatter-charge`): does nothing on bare canvas, and detonates wherever it touches paint across
126
+ every visible layer — inverting colours (`rgb` or `oklab`), removing alpha (`annihilate`), burning
127
+ a soot ring (`scorch`), carving craters into impasto relief (`crater`), clearing core light and
128
+ throwing sparks in the brush colour on the active layer, all in one undo step. Read
129
+ [references/antimatter.md](references/antimatter.md).
124
130
  - **Bristle brushes** (category `bristle`): `bristle-round`, `bristle-flat`, `dry-brush`. Every
125
131
  bristle draws its own line: press harder to splay the tuft, tilt to flatten it; soft bristles
126
132
  bend into curves at turns; a low `tip.bristle.load` runs dry into broken streaks.
@@ -257,7 +263,7 @@ Listing, saving, sharing and previewing brushes are the brush library's tools (`
257
263
 
258
264
  | Tool | Use |
259
265
  | --- | --- |
260
- | `paint_stroke` | Paint one stroke through `points` (document px; `pressure` 0..1, `t` ms, `tiltX` / `tiltY` or `altitude` / `azimuth`, `twist`) with `brushId` (default the active brush), optional per-stroke `brush` facet changes (as `set_brush` takes), `color` or `paint` (a gradient for this stroke), `layerId`, `seed`, `snapToPerspective`. The symmetry guide adds its copies; a sampling tracing image supplies the colour when `color` is missing. Returns `layerId`, `bounds`, `dabs`, `strokeId` and, for brushes that pick up paint, `mixerLoad` (what the brush carries now). Refused on locked, hidden or group layers, with the fix in the message. |
266
+ | `paint_stroke` | Paint one stroke through `points` (document px; `pressure` 0..1, `t` ms, `tiltX` / `tiltY` or `altitude` / `azimuth`, `twist`) with `brushId` (default the active brush), optional per-stroke `brush` facet changes (as `set_brush` takes), `color` or `paint` (a gradient for this stroke), `layerId`, `seed`, `snapToPerspective`. The symmetry guide adds its copies; a sampling tracing image supplies the colour when `color` is missing. Returns `layerId`, `bounds`, `dabs`, `strokeId`, `mixerLoad` (for brushes that pick up paint) and, for antimatter brushes, `antimatter` (`blasts`, `sparks`, `fuel`, `layers`, `skippedLayers`; `blasts: 0` and `bounds: null` over bare canvas). Refused on locked, hidden or group layers, with the fix in the message. |
261
267
  | `stroke_path` | Brush SVG path data (`path`, document px; M, L, C, Q, A, Z…) with the active brush or `brushId` / `brush`, as if drawn along it by hand: each subpath one recorded stroke, sampled every `spacing` px (default 2), with a `pressure` profile spread along each subpath (`[0.2, 1, 0.2]` tapers both ends) and optional `speed` (px/ms, stamps each sample with `t`). With an agent director, each subpath is painted live in turn. One undo step. Returns `layerId`, `bounds`, `dabs` and `strokeIds` (for `replay_strokes`, `update_stroke`). Use it for clean curves, outlines of shapes and lettering guides instead of working out points; use `paint_stroke` when the marks need variable timing or tilt. |
262
268
  | `list_strokes` | Read the recorded strokes, oldest first (default the last 100): id, layer, brush, colour, point count, seed, bounds, `live`. With `ids`, their points too; with `layerId`, one layer's. With `live: true`, the editable strokes of stroke layers as they are now, bottom first: the ids for the stroke layer tools. |
263
269
  | `get_live_stroke` | Read the stroke the user is painting right now (layer, brush, colour, samples so far; `live: false` when none). For painting along with the user; hosts stream the samples live. |
@@ -0,0 +1,49 @@
1
+ # Antimatter paint
2
+
3
+ Antimatter paint (`marks: { kind: "antimatter", ... }`, engine `antimatter`; presets `antimatter`,
4
+ `antimatter-flare`, `antimatter-charge`) is a reactive mark brush that does nothing over bare
5
+ canvas, and detonates wherever it touches existing paint across every visible layer.
6
+
7
+ - **Fuel and firing.** Each dab measures fuel (`0..1`) from the **pre-stroke** pixels of every
8
+ visible pixel layer (layers in hidden groups are ignored): tip coverage times the combined
9
+ visibility-weighted alpha $1 - \prod (1 - \alpha_i \cdot \text{opacity}_i)$, where each layer's
10
+ opacity is multiplied by its groups' opacities (paint in a half-opaque group is half the fuel). The first dab whose
11
+ fuel reaches `threshold` (default `0.1`) fires immediately so a single tap works; further blasts
12
+ fire every `blastSpacing` tip diameters (default `2`) of travel over paint. Over bare canvas,
13
+ `blasts` is `0`, `bounds` is `null`, and no undo step is recorded.
14
+ - **Heat field and single inversion.** Each blast writes a noise-edged radial profile (`0..1`) into
15
+ one stroke-wide `heat` field shared across all layers and combined by **maximum**, so overlapping
16
+ blasts in the same stroke invert the core once instead of cancelling back.
17
+ - **What changes on each visible editable layer.** Each target layer is recomposited from its own
18
+ pre-stroke pixels:
19
+ - **Core (`heat` > 0.55)**: inverts the layer's colours (`invert` `0..1`, `invertMode` `'rgb'` or
20
+ `'oklab'` — `oklab` flips lightness and hue so mid-grey `#808080` visibly changes too) and
21
+ destroys `annihilate` (`0..1`, default `0.25`) of its alpha (unless `locks.transparency` is on,
22
+ which preserves alpha while inverting colour).
23
+ - **Ring (`heat` 0.12..0.68)**: darkens and desaturates paint towards soot (`scorch` `0..1`,
24
+ default `0.6`).
25
+ - **Thick paint (`doc.thick`)**: on layers with impasto relief, the core lowers `height` and raises
26
+ a rim around the blast (`crater` `0..1`, default `0.5`).
27
+ - **Light paint (`doc.light`)**: blasts clear existing light in the core across target layers.
28
+ - Light and relief the blasts don't reach stay exactly as they were, including on a layer whose
29
+ pixels change elsewhere.
30
+ - **Active layer extras.** Bare-canvas soot fade and ballistic sparks (`sparks` `0..1`, `sparkReach`
31
+ `0.5..8` tip diameters) land on the **active layer only**, starting white-hot and cooling into the
32
+ stroke's paint colour (or gradient / effect paint). When `light.amount > 0` (`antimatter-flare`),
33
+ sparks also write glowing trails to the active layer's `doc.light`. Mask stencils contain the
34
+ blast and sparks.
35
+ - **Locked, stroke and sticker layers.** Uneditable visible layers (`locks.pixels`, stroke layers,
36
+ sticker layers, effect layers) still count as fuel so blasts fire over them, stay unchanged, and
37
+ are reported in `antimatter.skippedLayers`.
38
+ - **Single undo step and replay.** Any visible wet paint dries first in the same undo step, and all
39
+ changed layers commit together. `RecordedStroke.targets` records the stroke's **fuel layers** (every
40
+ visible pixel layer when it was painted, locked and stroke layers included). `replay_strokes` and
41
+ replays read fuel from exactly those layers, even if they are hidden now, and change the editable
42
+ ones. Layers added later are neither fuel nor changed. Passing `layerId` to `replay_strokes`
43
+ drops the recorded targets and uses the layers visible now.
44
+ - **Presets.**
45
+ - `antimatter`: balanced blast (`blastRadius` 2.5, `scorch` 0.6, `annihilate` 0.25, `sparks` 0.5).
46
+ - `antimatter-flare`: bright spark burst with glowing light trails (`sparks` 0.9, `sparkReach` 4.5,
47
+ `annihilate` 0.1, `light.amount` 0.7).
48
+ - `antimatter-charge`: wide, deep craters spaced farther apart (`blastRadius` 4, `blastSpacing` 3.5,
49
+ `annihilate` 0.45, `crater` 0.8, `invertMode: "oklab"`).
@@ -17,6 +17,7 @@ more; this is where each one went.
17
17
  | Impasto (thickness, gloss, metallic, rim, clarity…) | `behaviour.surface` `thick` and `behaviour.thick` |
18
18
  | Particle settings | `marks` `{ kind: 'particles', physics, render, count… }`; textured dots are `tip.stamps` on a `stamp` tip |
19
19
  | Sticker spray | `marks` `{ kind: 'objects', density… }` and `tip.stamps` (sticker refs) |
20
+ | Antimatter blasts | `marks` `{ kind: 'antimatter', blastRadius, blastSpacing, threshold, invert, invertMode, scorch, annihilate, sparks, sparkReach, crater, duration }` |
20
21
  | Bristles | `tip.shape` `bristle` and `tip.bristle` |
21
22
  | A brush's own gradient (paint) | `colour.default` (a gradient); `unset: ['colour']` drops it |
22
23
  | Light | `light` (`amount`, `pressure`) |