@pixerate/editor 0.275.1 → 0.276.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 (65) hide show
  1. package/dist/animation-editor/index.cjs +62 -62
  2. package/dist/animation-editor/index.js +6 -6
  3. package/dist/brushes/index.cjs +81 -70
  4. package/dist/brushes/index.js +17 -6
  5. package/dist/brushes/patch.d.cts +1 -1
  6. package/dist/brushes/patch.d.ts +1 -1
  7. package/dist/brushes/profiles.d.cts +2 -0
  8. package/dist/brushes/profiles.d.ts +2 -0
  9. package/dist/brushes/schema.d.cts +1 -0
  10. package/dist/brushes/schema.d.ts +1 -0
  11. package/dist/brushes/types.d.cts +22 -1
  12. package/dist/brushes/types.d.ts +22 -1
  13. package/dist/{brushstrokeRender-WGY4DZHF.js → brushstrokeRender-FK3ASUUJ.js} +5 -5
  14. package/dist/{brushstrokeRender-2RQL46WB.cjs → brushstrokeRender-I6MYFCOU.cjs} +21 -21
  15. package/dist/canvas/index.cjs +13 -13
  16. package/dist/canvas/index.js +1 -1
  17. package/dist/{chunk-Y2ISSVPH.js → chunk-4EM5NGCL.js} +167 -9
  18. package/dist/{chunk-Y354ETAE.cjs → chunk-5HR5CSLC.cjs} +49 -49
  19. package/dist/{chunk-LMG57L5A.js → chunk-CTHWXW5M.js} +121 -8
  20. package/dist/{chunk-43XJC3PP.js → chunk-CVBTJVRN.js} +1 -1
  21. package/dist/{chunk-RJGW3RQS.js → chunk-GHYJAMMS.js} +1 -1
  22. package/dist/{chunk-NIBFKXMD.cjs → chunk-IFWLZZCW.cjs} +619 -2
  23. package/dist/{chunk-G5TCMKX5.js → chunk-IJ6NSWKM.js} +2 -2
  24. package/dist/{chunk-C6VB7CPY.cjs → chunk-ISCAJBPQ.cjs} +1 -1
  25. package/dist/{chunk-W6JDUXP3.js → chunk-J23JOHPR.js} +1 -1
  26. package/dist/{chunk-IEEY5RJV.js → chunk-JNMNM3SL.js} +1 -1
  27. package/dist/{chunk-HVO6XCI4.js → chunk-JPB65KQ6.js} +1 -1
  28. package/dist/{chunk-ACTHSQOI.js → chunk-LDZOXESA.js} +618 -1
  29. package/dist/{chunk-DHC4E2DU.cjs → chunk-MITQGHN3.cjs} +121 -8
  30. package/dist/{chunk-STLTOISW.cjs → chunk-NPPMKMCU.cjs} +17 -17
  31. package/dist/{chunk-75SYKUOT.js → chunk-OS6OG7DN.js} +1 -1
  32. package/dist/{chunk-OLZXXAER.cjs → chunk-OY24XRYF.cjs} +408 -250
  33. package/dist/{chunk-6QNDGMK7.cjs → chunk-Q33V5PEF.cjs} +123 -42
  34. package/dist/{chunk-JDXWLCXF.cjs → chunk-QNNZA3V5.cjs} +2 -2
  35. package/dist/{chunk-T6KNJVBP.cjs → chunk-U26LUXCJ.cjs} +35 -35
  36. package/dist/{chunk-62MFUHSI.cjs → chunk-UQXULAMQ.cjs} +16 -16
  37. package/dist/{chunk-RAPNRK4R.cjs → chunk-X7XBEX3O.cjs} +88 -88
  38. package/dist/{chunk-MSWLJI4G.js → chunk-XMSH66VU.js} +2 -2
  39. package/dist/{chunk-ZM7IKQ4X.js → chunk-XVPQNX7O.js} +89 -8
  40. package/dist/{chunk-YYZC2EQO.cjs → chunk-ZIE6EJGR.cjs} +23 -23
  41. package/dist/image-editor/index.cjs +880 -852
  42. package/dist/image-editor/index.js +36 -8
  43. package/dist/paint/index.cjs +33 -5
  44. package/dist/paint/index.d.cts +1 -0
  45. package/dist/paint/index.d.ts +1 -0
  46. package/dist/paint/index.js +32 -4
  47. package/dist/paint/live-patterns.d.cts +149 -0
  48. package/dist/paint/live-patterns.d.ts +149 -0
  49. package/dist/paint/wet/world.d.cts +13 -0
  50. package/dist/paint/wet/world.d.ts +13 -0
  51. package/dist/paint-editor/brush-model.d.cts +7 -1
  52. package/dist/paint-editor/brush-model.d.ts +7 -1
  53. package/dist/paint-editor/controller.d.cts +52 -0
  54. package/dist/paint-editor/controller.d.ts +52 -0
  55. package/dist/paint-editor/index.cjs +850 -510
  56. package/dist/paint-editor/index.js +355 -15
  57. package/dist/paint-editor/ui.d.cts +5 -1
  58. package/dist/paint-editor/ui.d.ts +5 -1
  59. package/dist/paint-editor/wet-tools.d.cts +42 -0
  60. package/dist/paint-editor/wet-tools.d.ts +42 -0
  61. package/dist/paint-editor/wet.d.cts +22 -1
  62. package/dist/paint-editor/wet.d.ts +22 -1
  63. package/dist/vector/index.cjs +1002 -1002
  64. package/dist/vector/index.js +7 -7
  65. package/package.json +1 -1
@@ -18,7 +18,7 @@
18
18
 
19
19
 
20
20
 
21
- var _chunkY354ETAEcjs = require('../chunk-Y354ETAE.cjs');
21
+ var _chunk5HR5CSLCcjs = require('../chunk-5HR5CSLC.cjs');
22
22
  require('../chunk-PFHJS4HE.cjs');
23
23
 
24
24
 
@@ -27,7 +27,7 @@ require('../chunk-PFHJS4HE.cjs');
27
27
 
28
28
 
29
29
 
30
- var _chunkJDXWLCXFcjs = require('../chunk-JDXWLCXF.cjs');
30
+ var _chunkQNNZA3V5cjs = require('../chunk-QNNZA3V5.cjs');
31
31
 
32
32
 
33
33
 
@@ -102,7 +102,7 @@ var _chunkJDXWLCXFcjs = require('../chunk-JDXWLCXF.cjs');
102
102
 
103
103
 
104
104
 
105
- var _chunk6QNDGMK7cjs = require('../chunk-6QNDGMK7.cjs');
105
+ var _chunkQ33V5PEFcjs = require('../chunk-Q33V5PEF.cjs');
106
106
 
107
107
 
108
108
  var _chunkRDFHWAHLcjs = require('../chunk-RDFHWAHL.cjs');
@@ -113,7 +113,8 @@ require('../chunk-U67EN7GG.cjs');
113
113
  require('../chunk-WOFXZ3XU.cjs');
114
114
 
115
115
 
116
- var _chunkNIBFKXMDcjs = require('../chunk-NIBFKXMD.cjs');
116
+
117
+ var _chunkIFWLZZCWcjs = require('../chunk-IFWLZZCW.cjs');
117
118
  require('../chunk-3K6HWEXZ.cjs');
118
119
 
119
120
 
@@ -162,23 +163,24 @@ var ARIA = {
162
163
  "behaviour.thick.applicator": "Impasto tool",
163
164
  "light.pressure": "Light follows pressure",
164
165
  "tip.grain.source": "Tip grain",
165
- "dynamics.angleJitter": "Brush rotation jitter"
166
+ "dynamics.angleJitter": "Brush rotation jitter",
167
+ "livePatterns.pattern": "Live pattern"
166
168
  };
167
169
  var aria = (path, label) => _nullishCoalesce(ARIA[path], () => ( `Brush ${label.toLowerCase()}`));
168
- function brushEditorSections(brush, profile = _chunk6QNDGMK7cjs.PAINT_PROFILE, context = {}) {
169
- const resolved = _chunk6QNDGMK7cjs.resolveBrush.call(void 0, brush);
170
- const engine = _chunk6QNDGMK7cjs.resolveEngine.call(void 0, brush);
171
- const fieldOf = (facet) => _chunk6QNDGMK7cjs.BRUSH_FIELDS.find((f) => f.facet === facet && f.engines.includes(engine));
172
- const facetOf = (enginePath) => _optionalChain([(_nullishCoalesce(_chunk6QNDGMK7cjs.BRUSH_FIELDS.find((f) => f.engine === enginePath && f.engines.includes(engine)), () => ( _chunk6QNDGMK7cjs.BRUSH_FIELDS.find((f) => f.engine === enginePath)))), 'optionalAccess', _ => _.facet]);
170
+ function brushEditorSections(brush, profile = _chunkQ33V5PEFcjs.PAINT_PROFILE, context = {}) {
171
+ const resolved = _chunkQ33V5PEFcjs.resolveBrush.call(void 0, brush);
172
+ const engine = _chunkQ33V5PEFcjs.resolveEngine.call(void 0, brush);
173
+ const fieldOf = (facet) => _chunkQ33V5PEFcjs.BRUSH_FIELDS.find((f) => f.facet === facet && f.engines.includes(engine));
174
+ const facetOf = (enginePath) => _optionalChain([(_nullishCoalesce(_chunkQ33V5PEFcjs.BRUSH_FIELDS.find((f) => f.engine === enginePath && f.engines.includes(engine)), () => ( _chunkQ33V5PEFcjs.BRUSH_FIELDS.find((f) => f.engine === enginePath)))), 'optionalAccess', _ => _.facet]);
173
175
  const value = (path) => {
174
- const own = _chunk6QNDGMK7cjs.getPath.call(void 0, brush, path);
176
+ const own = _chunkQ33V5PEFcjs.getPath.call(void 0, brush, path);
175
177
  if (own !== void 0) return own;
176
178
  const field = fieldOf(path);
177
179
  if (!field) return void 0;
178
- const v = _chunk6QNDGMK7cjs.getPath.call(void 0, resolved, field.engine);
180
+ const v = _chunkQ33V5PEFcjs.getPath.call(void 0, resolved, field.engine);
179
181
  return field.map ? field.map.toFacet(v) : v;
180
182
  };
181
- const issues = _chunk6QNDGMK7cjs.checkBrush.call(void 0, brush, profile).issues;
183
+ const issues = _chunkQ33V5PEFcjs.checkBrush.call(void 0, brush, profile).issues;
182
184
  const warned = new Map(issues.filter((i) => i.severity === "warning").map((i) => [i.facet, i.reason]));
183
185
  const refused = new Set(issues.filter((i) => i.severity === "error").map((i) => i.facet));
184
186
  const full = (s) => s === "full";
@@ -191,7 +193,7 @@ function brushEditorSections(brush, profile = _chunk6QNDGMK7cjs.PAINT_PROFILE, c
191
193
  const slider = (c, path = facetOf(c.path)) => {
192
194
  if (!path) return null;
193
195
  const v = value(path);
194
- return withNote({ kind: "slider", path, label: c.label, ariaLabel: aria(path, c.label), min: c.min, max: path === "tip.size" ? Math.min(c.max, _chunkNIBFKXMDcjs.MAX_BRUSH_SIZE) : c.max, step: c.step, value: typeof v === "number" ? v : c.min });
196
+ return withNote({ kind: "slider", path, label: c.label, ariaLabel: aria(path, c.label), min: c.min, max: path === "tip.size" ? Math.min(c.max, _chunkIFWLZZCWcjs.MAX_BRUSH_SIZE) : c.max, step: c.step, value: typeof v === "number" ? v : c.min });
195
197
  };
196
198
  const choice = (path, label, opts, fallback = _nullishCoalesce(_optionalChain([opts, 'access', _5 => _5[0], 'optionalAccess', _6 => _6.value]), () => ( ""))) => withNote({ kind: "choice", path, label, ariaLabel: aria(path, label), options: opts, value: String(_nullishCoalesce(value(path), () => ( fallback))) });
197
199
  const toggle = (path, label) => withNote({ kind: "toggle", path, label, ariaLabel: aria(path, label), value: Boolean(value(path)) });
@@ -203,30 +205,30 @@ function brushEditorSections(brush, profile = _chunk6QNDGMK7cjs.PAINT_PROFILE, c
203
205
  };
204
206
  const isTool = brush.kind === "tool";
205
207
  const shape = brush.tip.shape;
206
- const sourceOptions = (_nullishCoalesce(context.sources, () => ( []))).map((x) => ({ value: _chunk6QNDGMK7cjs.brushSourceRef.call(void 0, x.id), label: x.name }));
208
+ const sourceOptions = (_nullishCoalesce(context.sources, () => ( []))).map((x) => ({ value: _chunkQ33V5PEFcjs.brushSourceRef.call(void 0, x.id), label: x.name }));
207
209
  const marks = _nullishCoalesce(_optionalChain([brush, 'access', _7 => _7.marks, 'optionalAccess', _8 => _8.kind]), () => ( "continuous"));
208
210
  const exchangeFull = full(_optionalChain([brush, 'access', _9 => _9.behaviour, 'optionalAccess', _10 => _10.exchange, 'optionalAccess', _11 => _11.reservoir]) ? profile.reservoir : profile.exchange);
209
211
  add("medium", "Medium", [
210
212
  profile.kinds.length > 1 && profile.kinds.includes(brush.kind) && choice("kind", "Kind", options(profile.kinds, { mark: "Lays a material", tool: "Works on paint" })),
211
- !isTool && choice("medium", "Medium", [{ value: "", label: "Any" }, ...options(_chunk6QNDGMK7cjs.BRUSH_MEDIA, MEDIUM_LABELS)], ""),
213
+ !isTool && choice("medium", "Medium", [{ value: "", label: "Any" }, ...options(_chunkQ33V5PEFcjs.BRUSH_MEDIA, MEDIUM_LABELS)], ""),
212
214
  !isTool && withNote({ kind: "text", path: "variety", label: "Variety", ariaLabel: "Brush variety", value: _nullishCoalesce(brush.variety, () => ( "")), placeholder: "pencil, oil, highlighter\u2026" }),
213
215
  isTool && !!brush.tool && profile.tools.includes(brush.tool) && choice("tool", "Tool", options(profile.tools, TOOL_LABELS))
214
216
  ]);
215
- const engineControls = _chunkY354ETAEcjs.brushEngineControls.call(void 0, resolved);
216
- const tipSliders = _chunkY354ETAEcjs.BRUSH_SLIDERS.filter((c) => c.path.startsWith("tip.") && c.path !== "tip.smoothing");
217
+ const engineControls = _chunk5HR5CSLCcjs.brushEngineControls.call(void 0, resolved);
218
+ const tipSliders = _chunk5HR5CSLCcjs.BRUSH_SLIDERS.filter((c) => c.path.startsWith("tip.") && c.path !== "tip.smoothing");
217
219
  add("shape", "Shape", [
218
220
  !refused.has("tip.shape") && choice("tip.shape", "Tip", options(profile.tipShapes.filter((s) => !isTool || s !== "stamp"), SHAPE_LABELS)),
219
221
  ...tipSliders.map((c) => slider(c)),
220
222
  engine === "dab" && choice("tip.blend", "Blend", _chunk2VX55NFMcjs.BLEND_MODES.map((m) => ({ value: m.id, label: m.label })), "normal"),
221
223
  ...shape === "bristle" && engineOk && engineControls ? engineControls.sliders.filter((c) => c.path.startsWith("bristle.")).map((c) => slider(c)) : [],
222
- shape === "stamp" && engineOk && withNote({ kind: "stamps", path: "tip.stamps", label: "Stamps", ariaLabel: "Brush stamps", value: [..._nullishCoalesce(value("tip.stamps"), () => ( []))], max: marks === "particles" ? _chunk6QNDGMK7cjs.MAX_DOT_TEXTURES : _chunk6QNDGMK7cjs.MAX_STICKER_STAMPS, options: marks === "objects" ? STAMP_OPTIONS : [...STAMP_OPTIONS, ...sourceOptions] })
224
+ shape === "stamp" && engineOk && withNote({ kind: "stamps", path: "tip.stamps", label: "Stamps", ariaLabel: "Brush stamps", value: [..._nullishCoalesce(value("tip.stamps"), () => ( []))], max: marks === "particles" ? _chunkQ33V5PEFcjs.MAX_DOT_TEXTURES : _chunkQ33V5PEFcjs.MAX_STICKER_STAMPS, options: marks === "objects" ? STAMP_OPTIONS : [...STAMP_OPTIONS, ...sourceOptions] })
223
225
  ]);
224
226
  const dabs = DAB_ENGINES.has(engine) && engineOk && shape !== "bristle" && marks !== "particles" && marks !== "objects";
225
227
  const grain = brush.tip.grain;
226
228
  if (dabs && full(_nullishCoalesce(profile.tipGrain, () => ( "none")))) {
227
229
  add("grain", "Tip grain", [
228
- choice("tip.grain.source", "Texture", [{ value: "", label: "None" }, ...options(_chunk6QNDGMK7cjs.TIP_GRAIN_PATTERNS), ...sourceOptions], ""),
229
- !!grain && choice("tip.grain.mode", "Grain moves", options(_chunk6QNDGMK7cjs.BRUSH_GRAIN_MODES, GRAIN_MODE_LABELS), "texturised"),
230
+ choice("tip.grain.source", "Texture", [{ value: "", label: "None" }, ...options(_chunkQ33V5PEFcjs.TIP_GRAIN_PATTERNS), ...sourceOptions], ""),
231
+ !!grain && choice("tip.grain.mode", "Grain moves", options(_chunkQ33V5PEFcjs.BRUSH_GRAIN_MODES, GRAIN_MODE_LABELS), "texturised"),
230
232
  !!grain && withNote({ kind: "slider", path: "tip.grain.scale", label: "Grain scale", ariaLabel: "Brush grain scale", min: 0.1, max: 8, step: 0.05, value: _nullishCoalesce(grain.scale, () => ( 1)) }),
231
233
  !!grain && withNote({ kind: "slider", path: "tip.grain.depth", label: "Grain depth", ariaLabel: "Brush grain depth", min: 0, max: 1, step: 0.01, value: _nullishCoalesce(grain.depth, () => ( 0.6)) }),
232
234
  !!grain && withNote({ kind: "slider", path: "tip.grain.contrast", label: "Grain contrast", ariaLabel: "Brush grain contrast", min: 0, max: 3, step: 0.05, value: _nullishCoalesce(grain.contrast, () => ( 1)) }),
@@ -239,28 +241,28 @@ function brushEditorSections(brush, profile = _chunk6QNDGMK7cjs.PAINT_PROFILE, c
239
241
  const render = value("marks.render");
240
242
  add("marks", "Marks", [
241
243
  choice("marks.kind", "Laid as", options(profile.marks, MARKS_LABELS), "continuous"),
242
- marks === "particles" && choice("marks.physics", "Particles", options(_chunk6QNDGMK7cjs.PARTICLE_KINDS, PHYSICS_LABELS)),
243
- marks === "particles" && choice("marks.render", "Drawn as", options(_chunk6QNDGMK7cjs.PARTICLE_RENDERS, RENDER_LABELS)),
244
- marks === "particles" && render === "ribbon" && choice("marks.ribbonPairs", "Ribbon joins", options(_chunk6QNDGMK7cjs.RIBBON_PAIRS, { adjacent: "Neighbours", "first-last": "Outermost" })),
245
- marks === "particles" && shape === "stamp" && choice("marks.stampOrder", "Texture order", options(_chunk6QNDGMK7cjs.DOT_TEXTURE_ORDERS, { sequence: "In turn", random: "Random" })),
246
- marks === "particles" && shape === "stamp" && choice("marks.stampRotation", "Texture turn", options(_chunk6QNDGMK7cjs.DOT_ROTATIONS, { follow: "Follow motion", random: "Random", fixed: "Tip angle" })),
244
+ marks === "particles" && choice("marks.physics", "Particles", options(_chunkQ33V5PEFcjs.PARTICLE_KINDS, PHYSICS_LABELS)),
245
+ marks === "particles" && choice("marks.render", "Drawn as", options(_chunkQ33V5PEFcjs.PARTICLE_RENDERS, RENDER_LABELS)),
246
+ marks === "particles" && render === "ribbon" && choice("marks.ribbonPairs", "Ribbon joins", options(_chunkQ33V5PEFcjs.RIBBON_PAIRS, { adjacent: "Neighbours", "first-last": "Outermost" })),
247
+ marks === "particles" && shape === "stamp" && choice("marks.stampOrder", "Texture order", options(_chunkQ33V5PEFcjs.DOT_TEXTURE_ORDERS, { sequence: "In turn", random: "Random" })),
248
+ marks === "particles" && shape === "stamp" && choice("marks.stampRotation", "Texture turn", options(_chunkQ33V5PEFcjs.DOT_ROTATIONS, { follow: "Follow motion", random: "Random", fixed: "Tip angle" })),
247
249
  ...particles.map((c) => slider(c)),
248
250
  ...objects.map((c) => slider(c))
249
251
  ]);
250
252
  }
251
- const feel = _chunkY354ETAEcjs.BRUSH_SLIDERS.filter((c) => ["tip.smoothing", "pressureSmoothing", "dynamics.minSize", "dynamics.tiltSize", "dynamics.velocitySize"].includes(c.path)).filter((c) => !(c.path === "dynamics.tiltSize" && !full(profile.tilt)) && !(c.path === "dynamics.velocitySize" && !full(profile.velocity)));
253
+ const feel = _chunk5HR5CSLCcjs.BRUSH_SLIDERS.filter((c) => ["tip.smoothing", "pressureSmoothing", "dynamics.minSize", "dynamics.tiltSize", "dynamics.velocitySize"].includes(c.path)).filter((c) => !(c.path === "dynamics.tiltSize" && !full(profile.tilt)) && !(c.path === "dynamics.velocitySize" && !full(profile.velocity)));
252
254
  const stampDabs = dabs && shape === "stamp";
253
255
  const jitter = (path, label, max = 1, step = 0.01) => withNote({ kind: "slider", path, label, ariaLabel: aria(path, label), min: 0, max, step, value: Number(_nullishCoalesce(value(path), () => ( 0))) });
254
256
  add("feel", "Feel", [
255
257
  ...feel.map((c) => slider(c)),
256
258
  toggle("dynamics.pressureSize", "Pressure \u2192 size"),
257
259
  toggle("dynamics.pressureOpacity", "Pressure \u2192 opacity"),
258
- choice("dynamics.rotation", "Rotation", options(_chunk6QNDGMK7cjs.BRUSH_ROTATIONS, ROTATION_LABELS)),
260
+ choice("dynamics.rotation", "Rotation", options(_chunkQ33V5PEFcjs.BRUSH_ROTATIONS, ROTATION_LABELS)),
259
261
  stampDabs && full(_nullishCoalesce(profile.dabCount, () => ( "none"))) && withNote({ kind: "slider", path: "dynamics.count", label: "Count", ariaLabel: "Brush dab count", min: 1, max: 16, step: 1, value: _nullishCoalesce(_optionalChain([brush, 'access', _12 => _12.dynamics, 'optionalAccess', _13 => _13.count]), () => ( 1)) }),
260
262
  stampDabs && jitter("dynamics.scatter", "Scatter", 4),
261
263
  stampDabs && jitter("dynamics.angleJitter", "Rotation jitter"),
262
264
  stampDabs && jitter("dynamics.sizeJitter", "Size jitter"),
263
- stampDabs && choice("dynamics.flip", "Flip", options(_chunk6QNDGMK7cjs.BRUSH_FLIPS, FLIP_LABELS), "none")
265
+ stampDabs && choice("dynamics.flip", "Flip", options(_chunkQ33V5PEFcjs.BRUSH_FLIPS, FLIP_LABELS), "none")
264
266
  ]);
265
267
  const surfaces = profile.surfaces;
266
268
  const engineSliders = (engineOk ? _nullishCoalesce(_optionalChain([engineControls, 'optionalAccess', _14 => _14.sliders]), () => ( [])) : []).filter((c) => !/^(bristle|particle|sticker|iridescence)\./.test(c.path) && c.path !== "light.amount" && c.path !== "impasto.rim" && c.path !== "impasto.clarity");
@@ -268,12 +270,16 @@ function brushEditorSections(brush, profile = _chunk6QNDGMK7cjs.PAINT_PROFILE, c
268
270
  add("behaviour", _nullishCoalesce(_optionalChain([engineControls, 'optionalAccess', _17 => _17.title]), () => ( "Behaviour")), [
269
271
  !isTool && surfaces.length > 1 && !refused.has("behaviour.surface") && choice("behaviour.surface", "Surface", options(surfaces, SURFACE_LABELS), "dry"),
270
272
  full(profile.grain) && slider({ path: "grain", label: "Grain", min: 0, max: 1, step: 0.01 }, "behaviour.grain"),
271
- engine === "wet" && engineOk && !isTool && choice("behaviour.wet.medium", "Medium", options(_chunk6QNDGMK7cjs.WET_MEDIA, WET_MEDIUM_LABELS), "watercolor"),
272
- engine === "impasto" && engineOk && choice("behaviour.thick.applicator", "Tool", options(_chunk6QNDGMK7cjs.IMPASTO_TOOLS, APPLICATOR_LABELS), "brush"),
273
+ engine === "wet" && engineOk && !isTool && choice("behaviour.wet.medium", "Medium", options(_chunkQ33V5PEFcjs.WET_MEDIA, WET_MEDIUM_LABELS), "watercolor"),
274
+ engine === "impasto" && engineOk && choice("behaviour.thick.applicator", "Tool", options(_chunkQ33V5PEFcjs.IMPASTO_TOOLS, APPLICATOR_LABELS), "brush"),
273
275
  ...engineSliders.map((c) => exchangeOk(facetOf(c.path)) ? slider(c) : null),
274
276
  engineOk && _optionalChain([engineControls, 'optionalAccess', _18 => _18.mixingPath]) && (engine === "wet" || exchangeFull) && fieldOf("behaviour.mixing") && choice("behaviour.mixing", "Mixing", options(["pigment", "rgb"], { pigment: "Pigment", rgb: "RGB" })),
275
277
  engineOk && _optionalChain([engineControls, 'optionalAccess', _19 => _19.dirty]) && exchangeFull && fieldOf("behaviour.exchange.dirty") && toggle("behaviour.exchange.dirty", "Dirty brush"),
276
- engine === "mixer" && engineOk && full(profile.reservoir) && toggle("behaviour.exchange.reservoir.refill", "Load each stroke")
278
+ engine === "mixer" && engineOk && full(profile.reservoir) && toggle("behaviour.exchange.reservoir.refill", "Load each stroke"),
279
+ engine === "wet" && engineOk && !isTool && full(_nullishCoalesce(profile.livePatterns, () => ( "none"))) && choice("livePatterns.pattern", "Live pattern", [{ value: "", label: "None" }, ...options(_chunkIFWLZZCWcjs.LIVE_PATTERN_KINDS)], ""),
280
+ engine === "wet" && engineOk && !isTool && full(_nullishCoalesce(profile.livePatterns, () => ( "none"))) && !!brush.livePatterns && withNote({ kind: "slider", path: "livePatterns.scale", label: "Pattern scale", ariaLabel: "Brush pattern scale", min: 0.5, max: 4, step: 0.1, value: _nullishCoalesce(brush.livePatterns.scale, () => ( 1)) }),
281
+ engine === "wet" && engineOk && !isTool && full(_nullishCoalesce(profile.livePatterns, () => ( "none"))) && !!brush.livePatterns && withNote({ kind: "slider", path: "livePatterns.contrast", label: "Pattern contrast", ariaLabel: "Brush pattern contrast", min: 0, max: 2, step: 0.05, value: _nullishCoalesce(brush.livePatterns.contrast, () => ( 1)) }),
282
+ engine === "wet" && engineOk && !isTool && full(_nullishCoalesce(profile.livePatterns, () => ( "none"))) && !!brush.livePatterns && withNote({ kind: "slider", path: "livePatterns.relief", label: "Pattern relief", ariaLabel: "Brush pattern relief", min: 0, max: 1, step: 0.05, value: _nullishCoalesce(brush.livePatterns.relief, () => ( 0)) })
277
283
  ]);
278
284
  if (!isTool && full(profile.light) && engineOk && _optionalChain([engineControls, 'optionalAccess', _20 => _20.light])) {
279
285
  add("light", "Light", [
@@ -307,7 +313,7 @@ function brushEditorSections(brush, profile = _chunk6QNDGMK7cjs.PAINT_PROFILE, c
307
313
  label: "Palette",
308
314
  ariaLabel: "Brush palette",
309
315
  value: (_nullishCoalesce(_optionalChain([brush, 'access', _32 => _32.colour, 'optionalAccess', _33 => _33.palette]), () => ( []))).flatMap((c) => _chunkMLLTWAXHcjs.isDualColor.call(void 0, c) ? [{ light: _chunkJBZRVLXVcjs.brushColourHex.call(void 0, c, "light"), dark: _chunkJBZRVLXVcjs.brushColourHex.call(void 0, c, "dark") }] : hex(c) ? [hex(c)] : []),
310
- max: _chunk6QNDGMK7cjs.MAX_BRUSH_PALETTE
316
+ max: _chunkQ33V5PEFcjs.MAX_BRUSH_PALETTE
311
317
  }),
312
318
  toggle("colour.own", "Always its own colour")
313
319
  ]);
@@ -338,6 +344,8 @@ function hiddenLabel(issue, b) {
338
344
  return "several dabs per step";
339
345
  case "light":
340
346
  return "light";
347
+ case "livePatterns":
348
+ return "live patterns";
341
349
  case "fluorescence":
342
350
  return "UV-fluorescent ink";
343
351
  case "thermochromic":
@@ -355,15 +363,15 @@ function hiddenLabel(issue, b) {
355
363
  case "dynamics.tilt":
356
364
  return "tilt dynamics";
357
365
  case "colour.default":
358
- return `a ${_chunk6QNDGMK7cjs.brushColourKind.call(void 0, b.colour.default)} default colour`;
366
+ return `a ${_chunkQ33V5PEFcjs.brushColourKind.call(void 0, b.colour.default)} default colour`;
359
367
  default:
360
368
  return null;
361
369
  }
362
370
  }
363
371
  var sentence = (words) => words.length < 2 ? _nullishCoalesce(words[0], () => ( "")) : `${words.slice(0, -1).join(", ")} and ${words.at(-1)}`;
364
- function brushEditorScope(brush, profile = _chunk6QNDGMK7cjs.PAINT_PROFILE) {
372
+ function brushEditorScope(brush, profile = _chunkQ33V5PEFcjs.PAINT_PROFILE) {
365
373
  const hidden = [];
366
- for (const issue of _chunk6QNDGMK7cjs.checkBrush.call(void 0, brush, profile).issues) {
374
+ for (const issue of _chunkQ33V5PEFcjs.checkBrush.call(void 0, brush, profile).issues) {
367
375
  const label = hiddenLabel(issue, brush);
368
376
  if (label && !hidden.some((h) => h.label === label)) hidden.push({ facet: issue.facet, label });
369
377
  }
@@ -376,17 +384,17 @@ function newBrushFor(profile, from, name = "New brush") {
376
384
  const id = brushIdFromName(name);
377
385
  const base = { id, name, kind: profile.kinds.includes("mark") ? "mark" : profile.kinds[0], ...profile.kinds.includes("mark") ? {} : { tool: profile.tools[0] }, tip: { shape: profile.tipShapes.includes("round") ? "round" : profile.tipShapes[0] } };
378
386
  if (from) {
379
- const stripped = _chunk6QNDGMK7cjs.degrade.call(void 0, from, profile, { ignored: true });
387
+ const stripped = _chunkQ33V5PEFcjs.degrade.call(void 0, from, profile, { ignored: true });
380
388
  if (!stripped.issues.length) return { ...stripped.brush, id, name };
381
389
  }
382
390
  return base;
383
391
  }
384
392
  async function saveBrushEdit(library, options2) {
385
393
  const { original, profile } = options2;
386
- const check = _chunk6QNDGMK7cjs.checkBrushEdit.call(void 0, original, options2.brush, profile);
394
+ const check = _chunkQ33V5PEFcjs.checkBrushEdit.call(void 0, original, options2.brush, profile);
387
395
  if (!check.ok) {
388
396
  const errors = check.issues.filter((i) => i.severity === "error").map((i) => `${i.facet}: ${i.reason} ${i.fix}`);
389
- throw new (0, _chunk6QNDGMK7cjs.BrushLibraryError)("invalid", `${profile.name} can't use this brush: ${errors.join(" ")} Nothing was saved.`);
397
+ throw new (0, _chunkQ33V5PEFcjs.BrushLibraryError)("invalid", `${profile.name} can't use this brush: ${errors.join(" ")} Nothing was saved.`);
390
398
  }
391
399
  const inPlace = !!original && options2.name === void 0 && !library.isBuiltin(original.id) && original.id === options2.brush.id && !!await library.get(original.id);
392
400
  let brush = options2.brush;
@@ -427,18 +435,20 @@ function setBrushEditorValue(brush, path, value) {
427
435
  else delete next.iridescence;
428
436
  } else if (path === "tip.grain.source" && (value === void 0 || value === "")) {
429
437
  delete next.tip.grain;
438
+ } else if (path === "livePatterns.pattern" && (value === void 0 || value === "")) {
439
+ delete next.livePatterns;
430
440
  } else if (path === "tip.shape" && value !== "stamp") {
431
441
  next.tip = { ...next.tip, shape: value, stamps: void 0 };
432
442
  if (value !== "bristle") delete next.tip.bristle;
433
443
  } else if (value === void 0 || value === "") {
434
444
  const keys = path.split(".");
435
- const parent = _chunk6QNDGMK7cjs.getPath.call(void 0, next, keys.slice(0, -1).join("."));
445
+ const parent = _chunkQ33V5PEFcjs.getPath.call(void 0, next, keys.slice(0, -1).join("."));
436
446
  if (keys.length === 1) delete next[keys[0]];
437
447
  else if (parent) delete parent[keys[keys.length - 1]];
438
- } else _chunk6QNDGMK7cjs.setPath.call(void 0, next, path, value);
448
+ } else _chunkQ33V5PEFcjs.setPath.call(void 0, next, path, value);
439
449
  const problems = [];
440
450
  try {
441
- return { brush: _chunk6QNDGMK7cjs.normalizeBrushSpec.call(void 0, JSON.parse(JSON.stringify(next)), problems), problems };
451
+ return { brush: _chunkQ33V5PEFcjs.normalizeBrushSpec.call(void 0, JSON.parse(JSON.stringify(next)), problems), problems };
442
452
  } catch (e) {
443
453
  return { brush, problems: [e.message] };
444
454
  }
@@ -454,7 +464,7 @@ function brushLibraryGroups(entries, filter = {}) {
454
464
  const text = words([b.id, b.name, _nullishCoalesce(b.variety, () => ( "")), _nullishCoalesce(b.medium, () => ( "")), _nullishCoalesce(b.tool, () => ( "")), ..._nullishCoalesce(b.tags, () => ( []))].join(" "));
455
465
  if (!wanted.every((w) => text.some((t) => t.startsWith(w)))) continue;
456
466
  }
457
- if (filter.profile && !_chunk6QNDGMK7cjs.checkBrush.call(void 0, b, filter.profile).ok) continue;
467
+ if (filter.profile && !_chunkQ33V5PEFcjs.checkBrush.call(void 0, b, filter.profile).ok) continue;
458
468
  const id = b.kind === "tool" ? "tools" : _nullishCoalesce(b.medium, () => ( "other"));
459
469
  let g = groups.get(id);
460
470
  if (!g) groups.set(id, g = { id, title: id === "tools" ? "Tools" : id === "other" ? "Other" : MEDIUM_LABELS[id], varieties: [] });
@@ -470,7 +480,7 @@ async function imageFileToPngDataUrl(file) {
470
480
  const bytes = new Uint8Array(await file.arrayBuffer());
471
481
  if (_chunkW5M3J7PVcjs.isPng.call(void 0, bytes)) return `data:image/png;base64,${_chunkW5M3J7PVcjs.bytesToBase64.call(void 0, bytes)}`;
472
482
  const g = globalThis;
473
- if (!g.createImageBitmap || !g.OffscreenCanvas) throw new (0, _chunk6QNDGMK7cjs.BrushSourceError)("Only PNG images can be read here; convert the image to PNG.");
483
+ if (!g.createImageBitmap || !g.OffscreenCanvas) throw new (0, _chunkQ33V5PEFcjs.BrushSourceError)("Only PNG images can be read here; convert the image to PNG.");
474
484
  const bitmap = await g.createImageBitmap(file);
475
485
  const canvas = new g.OffscreenCanvas(bitmap.width, bitmap.height);
476
486
  canvas.getContext("2d").drawImage(bitmap, 0, 0);
@@ -480,7 +490,7 @@ async function imageFileToPngDataUrl(file) {
480
490
  async function addBrushSourceFromFile(library, file, options2 = {}) {
481
491
  const name = (_nullishCoalesce(_nullishCoalesce(options2.name, () => ( _optionalChain([file, 'access', _38 => _38.name, 'optionalAccess', _39 => _39.replace, 'call', _40 => _40(/\.[a-z0-9]+$/i, "")]))), () => ( "Image"))).trim() || "Image";
482
492
  const result = await library.addSource({ name, image: await imageFileToPngDataUrl(file), origin: file.name, ...options2.channel ? { channel: options2.channel } : {} });
483
- return { ...result, ref: _chunk6QNDGMK7cjs.brushSourceRef.call(void 0, result.source.id) };
493
+ return { ...result, ref: _chunkQ33V5PEFcjs.brushSourceRef.call(void 0, result.source.id) };
484
494
  }
485
495
  function withBrushSource(brush, ref, as) {
486
496
  if (as === "grain") return setBrushEditorValue(brush, "tip.grain.source", ref);
@@ -498,11 +508,11 @@ var BRUSH_PROFILE_JSON_SCHEMA = {
498
508
  properties: {
499
509
  id: { type: "string", description: "paint, image, canvas, vector\u2026" },
500
510
  name: { type: "string", description: "Display name, used in messages." },
501
- kinds: { type: "array", items: { type: "string", enum: [..._chunk6QNDGMK7cjs.BRUSH_KINDS] }, description: "Brush kinds it has." },
502
- tools: { type: "array", items: { type: "string", enum: [..._chunk6QNDGMK7cjs.BRUSH_TOOLS] }, description: "Tools it has (kind tool)." },
503
- tipShapes: { type: "array", items: { type: "string", enum: [..._chunk6QNDGMK7cjs.BRUSH_TIP_SHAPES] }, description: "Tip shapes it renders." },
504
- marks: { type: "array", items: { type: "string", enum: [..._chunk6QNDGMK7cjs.BRUSH_MARKS_KINDS] }, description: "Marks kinds it renders." },
505
- surfaces: { type: "array", items: { type: "string", enum: [..._chunk6QNDGMK7cjs.BRUSH_SURFACES] }, description: "Surfaces it renders." },
511
+ kinds: { type: "array", items: { type: "string", enum: [..._chunkQ33V5PEFcjs.BRUSH_KINDS] }, description: "Brush kinds it has." },
512
+ tools: { type: "array", items: { type: "string", enum: [..._chunkQ33V5PEFcjs.BRUSH_TOOLS] }, description: "Tools it has (kind tool)." },
513
+ tipShapes: { type: "array", items: { type: "string", enum: [..._chunkQ33V5PEFcjs.BRUSH_TIP_SHAPES] }, description: "Tip shapes it renders." },
514
+ marks: { type: "array", items: { type: "string", enum: [..._chunkQ33V5PEFcjs.BRUSH_MARKS_KINDS] }, description: "Marks kinds it renders." },
515
+ surfaces: { type: "array", items: { type: "string", enum: [..._chunkQ33V5PEFcjs.BRUSH_SURFACES] }, description: "Surfaces it renders." },
506
516
  exchange: { ...SUPPORT, description: "Picking paint up and mixing it in." },
507
517
  reservoir: { ...SUPPORT, description: "The Mixer Brush reservoir." },
508
518
  grain: { ...SUPPORT, description: "Paper grain response." },
@@ -513,6 +523,7 @@ var BRUSH_PROFILE_JSON_SCHEMA = {
513
523
  tilt: { ...SUPPORT, description: "Tilt dynamics." },
514
524
  tipGrain: { ...SUPPORT, description: "Texture inside the tip (tip.grain); missing: none." },
515
525
  dabCount: { ...SUPPORT, description: "Several dabs per spacing step (dynamics.count); missing: none." },
526
+ livePatterns: { ...SUPPORT, description: "Gray-Scott reaction-diffusion patterns in wet paint (livePatterns); missing: none." },
516
527
  iridescence: { ...SUPPORT, description: "Thin-film iridescence (iridescence); missing: none." }
517
528
  },
518
529
  required: ["id", "name", "kinds", "tools", "tipShapes", "marks", "surfaces", "exchange", "reservoir", "grain", "light", "motion", "colours", "velocity", "tilt"],
@@ -527,16 +538,16 @@ var BRUSH_LIBRARY_FILE_JSON_SCHEMA = {
527
538
  brushes: { type: "array", items: { $ref: "brush.schema.json" }, description: "The brushes, each a facet brush (brush.schema.json)." },
528
539
  sources: {
529
540
  type: "array",
530
- maxItems: _chunk6QNDGMK7cjs.MAX_BRUSH_SOURCES,
541
+ maxItems: _chunkQ33V5PEFcjs.MAX_BRUSH_SOURCES,
531
542
  description: "The image sources the brushes name (source:<id> in tip.stamps or tip.grain.source), bytes included.",
532
543
  items: {
533
544
  type: "object",
534
545
  description: "An image source: a grey mask, 255 paints (or is a grain peak).",
535
546
  properties: {
536
- id: { type: "string", pattern: _chunk6QNDGMK7cjs.BRUSH_ID.source, description: "Source id." },
547
+ id: { type: "string", pattern: _chunkQ33V5PEFcjs.BRUSH_ID.source, description: "Source id." },
537
548
  name: { type: "string", maxLength: 80, description: "Display name." },
538
- width: { type: "integer", minimum: 1, maximum: _chunk6QNDGMK7cjs.MAX_BRUSH_SOURCE_SIZE, description: "Width in px." },
539
- height: { type: "integer", minimum: 1, maximum: _chunk6QNDGMK7cjs.MAX_BRUSH_SOURCE_SIZE, description: "Height in px." },
549
+ width: { type: "integer", minimum: 1, maximum: _chunkQ33V5PEFcjs.MAX_BRUSH_SOURCE_SIZE, description: "Width in px." },
550
+ height: { type: "integer", minimum: 1, maximum: _chunkQ33V5PEFcjs.MAX_BRUSH_SOURCE_SIZE, description: "Height in px." },
540
551
  image: { type: "string", pattern: "^data:image/png;base64,", description: "The mask as a greyscale PNG data: URL." },
541
552
  licence: { type: "string", maxLength: 2e3, description: "The licence or terms it came with." },
542
553
  origin: { type: "string", maxLength: 200, description: "Where it came from (a file or pack name)." }
@@ -550,7 +561,7 @@ var BRUSH_LIBRARY_FILE_JSON_SCHEMA = {
550
561
  additionalProperties: false
551
562
  };
552
563
  function migrationInputs() {
553
- const pick = (id) => _chunk6QNDGMK7cjs.brushToJSON.call(void 0, _chunk6QNDGMK7cjs.BUILTIN_BRUSHES.find((b) => b.id === id));
564
+ const pick = (id) => _chunkQ33V5PEFcjs.brushToJSON.call(void 0, _chunkQ33V5PEFcjs.BUILTIN_BRUSHES.find((b) => b.id === id));
554
565
  return [
555
566
  { id: "plain", name: "Plain" },
556
567
  pick("pencil-6b"),
@@ -572,34 +583,34 @@ function migrationInputs() {
572
583
  function editorSectionFixtures() {
573
584
  const ids = ["highlighter", "chalk", "watercolor-wash", "hair-strands", "mixer-brush", "blender", "spray-can"];
574
585
  return ids.flatMap(
575
- (id) => Object.keys(_chunk6QNDGMK7cjs.BRUSH_HOST_PROFILES).map((host) => {
576
- const brush = _chunk6QNDGMK7cjs.BRUSH_PRESETS.find((b) => b.id === id);
577
- return { brush: id, host, sections: brushEditorSections(brush, _chunk6QNDGMK7cjs.BRUSH_HOST_PROFILES[host]) };
586
+ (id) => Object.keys(_chunkQ33V5PEFcjs.BRUSH_HOST_PROFILES).map((host) => {
587
+ const brush = _chunkQ33V5PEFcjs.BRUSH_PRESETS.find((b) => b.id === id);
588
+ return { brush: id, host, sections: brushEditorSections(brush, _chunkQ33V5PEFcjs.BRUSH_HOST_PROFILES[host]) };
578
589
  })
579
590
  );
580
591
  }
581
592
  function toolDefinitions() {
582
- const library = _chunk6QNDGMK7cjs.createBrushLibrary.call(void 0, );
583
- const toolsFor = (host) => _chunkY354ETAEcjs.createBrushLibraryTools.call(void 0, library, { profile: _chunk6QNDGMK7cjs.BRUSH_HOST_PROFILES[host], renderPreview: () => ({ data: "", mimeType: "image/png" }) });
593
+ const library = _chunkQ33V5PEFcjs.createBrushLibrary.call(void 0, );
594
+ const toolsFor = (host) => _chunk5HR5CSLCcjs.createBrushLibraryTools.call(void 0, library, { profile: _chunkQ33V5PEFcjs.BRUSH_HOST_PROFILES[host], renderPreview: () => ({ data: "", mimeType: "image/png" }) });
584
595
  return {
585
596
  tools: toolsFor("paint").map((t) => ({ name: t.name, title: t.title, description: t.description, inputSchema: t.inputSchema, annotations: t.annotations })),
586
- hosts: Object.fromEntries(Object.keys(_chunk6QNDGMK7cjs.BRUSH_HOST_PROFILES).map((host) => [host, toolsFor(host).map((t) => t.name)]))
597
+ hosts: Object.fromEntries(Object.keys(_chunkQ33V5PEFcjs.BRUSH_HOST_PROFILES).map((host) => [host, toolsFor(host).map((t) => t.name)]))
587
598
  };
588
599
  }
589
600
  function brushContracts() {
590
- const presets = _chunk6QNDGMK7cjs.BRUSH_PRESETS.map((b) => JSON.parse(JSON.stringify(b)));
591
- const resolved = _chunk6QNDGMK7cjs.BRUSH_PRESETS.map((b) => ({ id: b.id, engine: _chunk6QNDGMK7cjs.resolveEngine.call(void 0, b), category: _chunk6QNDGMK7cjs.resolveCategory.call(void 0, b), brush: _chunk6QNDGMK7cjs.brushToJSON.call(void 0, _chunk6QNDGMK7cjs.resolveBrush.call(void 0, b)) }));
592
- const profiles = Object.values(_chunk6QNDGMK7cjs.BRUSH_HOST_PROFILES);
593
- const checks = _chunk6QNDGMK7cjs.BRUSH_PRESETS.map((b) => ({
601
+ const presets = _chunkQ33V5PEFcjs.BRUSH_PRESETS.map((b) => JSON.parse(JSON.stringify(b)));
602
+ const resolved = _chunkQ33V5PEFcjs.BRUSH_PRESETS.map((b) => ({ id: b.id, engine: _chunkQ33V5PEFcjs.resolveEngine.call(void 0, b), category: _chunkQ33V5PEFcjs.resolveCategory.call(void 0, b), brush: _chunkQ33V5PEFcjs.brushToJSON.call(void 0, _chunkQ33V5PEFcjs.resolveBrush.call(void 0, b)) }));
603
+ const profiles = Object.values(_chunkQ33V5PEFcjs.BRUSH_HOST_PROFILES);
604
+ const checks = _chunkQ33V5PEFcjs.BRUSH_PRESETS.map((b) => ({
594
605
  id: b.id,
595
606
  hosts: Object.fromEntries(profiles.map((p) => {
596
- const c = _chunk6QNDGMK7cjs.checkBrush.call(void 0, b, p);
607
+ const c = _chunkQ33V5PEFcjs.checkBrush.call(void 0, b, p);
597
608
  return [p.id, { ok: c.ok, issues: c.issues.map(({ facet, severity }) => ({ facet, severity })) }];
598
609
  }))
599
610
  }));
600
- const migrations = migrationInputs().map((input) => ({ input, output: _chunk6QNDGMK7cjs.migrateBrushV1.call(void 0, input), resolved: _chunk6QNDGMK7cjs.brushToJSON.call(void 0, _chunk6QNDGMK7cjs.resolveBrush.call(void 0, _chunk6QNDGMK7cjs.migrateBrushV1.call(void 0, input))), engine: _chunk6QNDGMK7cjs.normalizePaintBrush.call(void 0, input).engine }));
611
+ const migrations = migrationInputs().map((input) => ({ input, output: _chunkQ33V5PEFcjs.migrateBrushV1.call(void 0, input), resolved: _chunkQ33V5PEFcjs.brushToJSON.call(void 0, _chunkQ33V5PEFcjs.resolveBrush.call(void 0, _chunkQ33V5PEFcjs.migrateBrushV1.call(void 0, input))), engine: _chunkQ33V5PEFcjs.normalizePaintBrush.call(void 0, input).engine }));
601
612
  const files = {
602
- "brush.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "brush.schema.json", ..._chunk6QNDGMK7cjs.BRUSH_JSON_SCHEMA },
613
+ "brush.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "brush.schema.json", ..._chunkQ33V5PEFcjs.BRUSH_JSON_SCHEMA },
603
614
  "profile.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "profile.schema.json", ...BRUSH_PROFILE_JSON_SCHEMA },
604
615
  "library-file.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "library-file.schema.json", ...BRUSH_LIBRARY_FILE_JSON_SCHEMA },
605
616
  "presets.json": presets,
@@ -613,7 +624,7 @@ function brushContracts() {
613
624
  files["manifest.json"] = {
614
625
  domain: "brushes",
615
626
  formatVersion: BRUSH_CONTRACT_FORMAT_VERSION,
616
- limits: { maxPalette: _chunk6QNDGMK7cjs.MAX_BRUSH_PALETTE },
627
+ limits: { maxPalette: _chunkQ33V5PEFcjs.MAX_BRUSH_PALETTE },
617
628
  counts: { presets: presets.length, profiles: profiles.length, migrations: migrations.length, editorSections: files["editor-sections.json"].length },
618
629
  files: Object.keys(files).sort()
619
630
  };
@@ -714,4 +725,4 @@ function brushContracts() {
714
725
 
715
726
 
716
727
 
717
- exports.AbrError = _chunkY354ETAEcjs.AbrError; exports.BRUSH_CONTRACT_FORMAT_VERSION = BRUSH_CONTRACT_FORMAT_VERSION; exports.BRUSH_FACET_SCHEMAS = _chunk6QNDGMK7cjs.BRUSH_FACET_SCHEMAS; exports.BRUSH_FLIPS = _chunk6QNDGMK7cjs.BRUSH_FLIPS; exports.BRUSH_GRAIN_MODES = _chunk6QNDGMK7cjs.BRUSH_GRAIN_MODES; exports.BRUSH_GRAIN_SOURCE_PATTERN = _chunk6QNDGMK7cjs.BRUSH_GRAIN_SOURCE_PATTERN; exports.BRUSH_HOST_PROFILES = _chunk6QNDGMK7cjs.BRUSH_HOST_PROFILES; exports.BRUSH_JSON_SCHEMA = _chunk6QNDGMK7cjs.BRUSH_JSON_SCHEMA; exports.BRUSH_KINDS = _chunk6QNDGMK7cjs.BRUSH_KINDS; exports.BRUSH_LIBRARY_FILE_JSON_SCHEMA = BRUSH_LIBRARY_FILE_JSON_SCHEMA; exports.BRUSH_MARKS_KINDS = _chunk6QNDGMK7cjs.BRUSH_MARKS_KINDS; exports.BRUSH_MEDIA = _chunk6QNDGMK7cjs.BRUSH_MEDIA; exports.BRUSH_PACK_FORMATS = _chunkY354ETAEcjs.BRUSH_PACK_FORMATS; exports.BRUSH_PATCH_PROPERTIES = _chunkY354ETAEcjs.BRUSH_PATCH_PROPERTIES; exports.BRUSH_PRESETS = _chunk6QNDGMK7cjs.BRUSH_PRESETS; exports.BRUSH_PREVIEW_DARK_PAPER = _chunkJDXWLCXFcjs.BRUSH_PREVIEW_DARK_PAPER; exports.BRUSH_PREVIEW_FALLBACKS = _chunkJDXWLCXFcjs.BRUSH_PREVIEW_FALLBACKS; exports.BRUSH_PREVIEW_MIN_CONTRAST = _chunkJDXWLCXFcjs.BRUSH_PREVIEW_MIN_CONTRAST; exports.BRUSH_PROFILE_JSON_SCHEMA = BRUSH_PROFILE_JSON_SCHEMA; exports.BRUSH_SOURCE_CHANNELS = _chunk6QNDGMK7cjs.BRUSH_SOURCE_CHANNELS; exports.BRUSH_SOURCE_PROVIDER = _chunk6QNDGMK7cjs.BRUSH_SOURCE_PROVIDER; exports.BRUSH_SURFACES = _chunk6QNDGMK7cjs.BRUSH_SURFACES; exports.BRUSH_TIP_SHAPES = _chunk6QNDGMK7cjs.BRUSH_TIP_SHAPES; exports.BRUSH_TOOLS = _chunk6QNDGMK7cjs.BRUSH_TOOLS; exports.BRUSH_UNSET_PROPERTY = _chunkY354ETAEcjs.BRUSH_UNSET_PROPERTY; exports.BRUSH_WORD = _chunk6QNDGMK7cjs.BRUSH_WORD; exports.BrushLibraryError = _chunk6QNDGMK7cjs.BrushLibraryError; exports.BrushPackError = _chunkY354ETAEcjs.BrushPackError; exports.BrushSourceError = _chunk6QNDGMK7cjs.BrushSourceError; exports.CANVAS_PROFILE = _chunk6QNDGMK7cjs.CANVAS_PROFILE; exports.GHOST_TRIGGERS = _chunk6QNDGMK7cjs.GHOST_TRIGGERS2; exports.IMAGE_PROFILE = _chunk6QNDGMK7cjs.IMAGE_PROFILE; exports.MAX_BRUSH_PACK_BYTES = _chunkY354ETAEcjs.MAX_BRUSH_PACK_BYTES; exports.MAX_BRUSH_PALETTE = _chunk6QNDGMK7cjs.MAX_BRUSH_PALETTE; exports.MAX_BRUSH_SOURCES = _chunk6QNDGMK7cjs.MAX_BRUSH_SOURCES; exports.MAX_BRUSH_SOURCE_INPUT_PIXELS = _chunk6QNDGMK7cjs.MAX_BRUSH_SOURCE_INPUT_PIXELS; exports.MAX_BRUSH_SOURCE_SIZE = _chunk6QNDGMK7cjs.MAX_BRUSH_SOURCE_SIZE; exports.MAX_PACK_BRUSHES = _chunkY354ETAEcjs.MAX_PACK_BRUSHES; exports.PAINT_PROFILE = _chunk6QNDGMK7cjs.PAINT_PROFILE; exports.TIP_GRAIN_PATTERNS = _chunk6QNDGMK7cjs.TIP_GRAIN_PATTERNS; exports.UNSETTABLE_BRUSH_FACETS = _chunkY354ETAEcjs.UNSETTABLE_BRUSH_FACETS; exports.VECTOR_PROFILE = _chunk6QNDGMK7cjs.VECTOR_PROFILE; exports.WET_BRUSH_TOOLS = _chunk6QNDGMK7cjs.WET_BRUSH_TOOLS; exports.addBrushSourceFromFile = addBrushSourceFromFile; exports.brushColourHex = _chunkJBZRVLXVcjs.brushColourHex; exports.brushColourKind = _chunk6QNDGMK7cjs.brushColourKind; exports.brushContracts = brushContracts; exports.brushEditorScope = brushEditorScope; exports.brushEditorSections = brushEditorSections; exports.brushIdFromName = brushIdFromName; exports.brushLibraryGroups = brushLibraryGroups; exports.brushPackFormat = _chunkY354ETAEcjs.brushPackFormat; exports.brushPreviewColours = _chunkJDXWLCXFcjs.brushPreviewColours; exports.brushPreviewStrength = _chunkJDXWLCXFcjs.brushPreviewStrength; exports.brushPreviewsOnDark = _chunkJDXWLCXFcjs.brushPreviewsOnDark; exports.brushSourceFromMask = _chunk6QNDGMK7cjs.brushSourceFromMask; exports.brushSourceIdFromName = _chunk6QNDGMK7cjs.brushSourceIdFromName; exports.brushSourceIdOf = _chunk6QNDGMK7cjs.brushSourceIdOf; exports.brushSourceIds = _chunk6QNDGMK7cjs.brushSourceIds; exports.brushSourceRef = _chunk6QNDGMK7cjs.brushSourceRef; exports.changedBrushPaths = _chunkY354ETAEcjs.changedBrushPaths; exports.checkBrush = _chunk6QNDGMK7cjs.checkBrush; exports.checkBrushEdit = _chunk6QNDGMK7cjs.checkBrushEdit; exports.createBrushLibrary = _chunk6QNDGMK7cjs.createBrushLibrary; exports.createBrushLibraryToolProvider = _chunkY354ETAEcjs.createBrushLibraryToolProvider; exports.createBrushLibraryTools = _chunkY354ETAEcjs.createBrushLibraryTools; exports.decodeBrushSource = _chunk6QNDGMK7cjs.decodeBrushSource; exports.degrade = _chunk6QNDGMK7cjs.degrade; exports.imageFileToPngDataUrl = imageFileToPngDataUrl; exports.importBrushFile = _chunkY354ETAEcjs.importBrushFile; exports.importBrushPack = _chunkY354ETAEcjs.importBrushPack; exports.isBrushLibrary = _chunk6QNDGMK7cjs.isBrushLibrary; exports.isBrushSourceRef = _chunk6QNDGMK7cjs.isBrushSourceRef; exports.listPresets = _chunk6QNDGMK7cjs.listPresets; exports.makeBrushSource = _chunk6QNDGMK7cjs.makeBrushSource; exports.mergeBrushPatch = _chunkY354ETAEcjs.mergeBrushPatch; exports.migrateBrushV1 = _chunk6QNDGMK7cjs.migrateBrushV1; exports.newBrushFor = newBrushFor; exports.normalizeBrushSpec = _chunk6QNDGMK7cjs.normalizeBrushSpec; exports.parseBrushLibrary = _chunk6QNDGMK7cjs.parseBrushLibrary2; exports.parseBrushLibrarySources = _chunk6QNDGMK7cjs.parseBrushLibrarySources; exports.readAbr = _chunkY354ETAEcjs.readAbr; exports.readBrushPack = _chunkY354ETAEcjs.readBrushPack; exports.readBrushSource = _chunk6QNDGMK7cjs.readBrushSource; exports.resolveBrush = _chunk6QNDGMK7cjs.resolveBrush; exports.resolveCategory = _chunk6QNDGMK7cjs.resolveCategory; exports.resolveEngine = _chunk6QNDGMK7cjs.resolveEngine; exports.resolveWetMedium = _chunk6QNDGMK7cjs.resolveWetMedium; exports.saveBrushEdit = saveBrushEdit; exports.setBrushEditorValue = setBrushEditorValue; exports.snapshotBrushLibrary = _chunk6QNDGMK7cjs.snapshotBrushLibrary; exports.summarizeBrushSource = _chunk6QNDGMK7cjs.summarizeBrushSource; exports.withBrushSource = withBrushSource; exports.withKeptIssues = _chunk6QNDGMK7cjs.withKeptIssues;
728
+ exports.AbrError = _chunk5HR5CSLCcjs.AbrError; exports.BRUSH_CONTRACT_FORMAT_VERSION = BRUSH_CONTRACT_FORMAT_VERSION; exports.BRUSH_FACET_SCHEMAS = _chunkQ33V5PEFcjs.BRUSH_FACET_SCHEMAS; exports.BRUSH_FLIPS = _chunkQ33V5PEFcjs.BRUSH_FLIPS; exports.BRUSH_GRAIN_MODES = _chunkQ33V5PEFcjs.BRUSH_GRAIN_MODES; exports.BRUSH_GRAIN_SOURCE_PATTERN = _chunkQ33V5PEFcjs.BRUSH_GRAIN_SOURCE_PATTERN; exports.BRUSH_HOST_PROFILES = _chunkQ33V5PEFcjs.BRUSH_HOST_PROFILES; exports.BRUSH_JSON_SCHEMA = _chunkQ33V5PEFcjs.BRUSH_JSON_SCHEMA; exports.BRUSH_KINDS = _chunkQ33V5PEFcjs.BRUSH_KINDS; exports.BRUSH_LIBRARY_FILE_JSON_SCHEMA = BRUSH_LIBRARY_FILE_JSON_SCHEMA; exports.BRUSH_MARKS_KINDS = _chunkQ33V5PEFcjs.BRUSH_MARKS_KINDS; exports.BRUSH_MEDIA = _chunkQ33V5PEFcjs.BRUSH_MEDIA; exports.BRUSH_PACK_FORMATS = _chunk5HR5CSLCcjs.BRUSH_PACK_FORMATS; exports.BRUSH_PATCH_PROPERTIES = _chunk5HR5CSLCcjs.BRUSH_PATCH_PROPERTIES; exports.BRUSH_PRESETS = _chunkQ33V5PEFcjs.BRUSH_PRESETS; exports.BRUSH_PREVIEW_DARK_PAPER = _chunkQNNZA3V5cjs.BRUSH_PREVIEW_DARK_PAPER; exports.BRUSH_PREVIEW_FALLBACKS = _chunkQNNZA3V5cjs.BRUSH_PREVIEW_FALLBACKS; exports.BRUSH_PREVIEW_MIN_CONTRAST = _chunkQNNZA3V5cjs.BRUSH_PREVIEW_MIN_CONTRAST; exports.BRUSH_PROFILE_JSON_SCHEMA = BRUSH_PROFILE_JSON_SCHEMA; exports.BRUSH_SOURCE_CHANNELS = _chunkQ33V5PEFcjs.BRUSH_SOURCE_CHANNELS; exports.BRUSH_SOURCE_PROVIDER = _chunkQ33V5PEFcjs.BRUSH_SOURCE_PROVIDER; exports.BRUSH_SURFACES = _chunkQ33V5PEFcjs.BRUSH_SURFACES; exports.BRUSH_TIP_SHAPES = _chunkQ33V5PEFcjs.BRUSH_TIP_SHAPES; exports.BRUSH_TOOLS = _chunkQ33V5PEFcjs.BRUSH_TOOLS; exports.BRUSH_UNSET_PROPERTY = _chunk5HR5CSLCcjs.BRUSH_UNSET_PROPERTY; exports.BRUSH_WORD = _chunkQ33V5PEFcjs.BRUSH_WORD; exports.BrushLibraryError = _chunkQ33V5PEFcjs.BrushLibraryError; exports.BrushPackError = _chunk5HR5CSLCcjs.BrushPackError; exports.BrushSourceError = _chunkQ33V5PEFcjs.BrushSourceError; exports.CANVAS_PROFILE = _chunkQ33V5PEFcjs.CANVAS_PROFILE; exports.GHOST_TRIGGERS = _chunkQ33V5PEFcjs.GHOST_TRIGGERS2; exports.IMAGE_PROFILE = _chunkQ33V5PEFcjs.IMAGE_PROFILE; exports.MAX_BRUSH_PACK_BYTES = _chunk5HR5CSLCcjs.MAX_BRUSH_PACK_BYTES; exports.MAX_BRUSH_PALETTE = _chunkQ33V5PEFcjs.MAX_BRUSH_PALETTE; exports.MAX_BRUSH_SOURCES = _chunkQ33V5PEFcjs.MAX_BRUSH_SOURCES; exports.MAX_BRUSH_SOURCE_INPUT_PIXELS = _chunkQ33V5PEFcjs.MAX_BRUSH_SOURCE_INPUT_PIXELS; exports.MAX_BRUSH_SOURCE_SIZE = _chunkQ33V5PEFcjs.MAX_BRUSH_SOURCE_SIZE; exports.MAX_PACK_BRUSHES = _chunk5HR5CSLCcjs.MAX_PACK_BRUSHES; exports.PAINT_PROFILE = _chunkQ33V5PEFcjs.PAINT_PROFILE; exports.TIP_GRAIN_PATTERNS = _chunkQ33V5PEFcjs.TIP_GRAIN_PATTERNS; exports.UNSETTABLE_BRUSH_FACETS = _chunk5HR5CSLCcjs.UNSETTABLE_BRUSH_FACETS; exports.VECTOR_PROFILE = _chunkQ33V5PEFcjs.VECTOR_PROFILE; exports.WET_BRUSH_TOOLS = _chunkQ33V5PEFcjs.WET_BRUSH_TOOLS; exports.addBrushSourceFromFile = addBrushSourceFromFile; exports.brushColourHex = _chunkJBZRVLXVcjs.brushColourHex; exports.brushColourKind = _chunkQ33V5PEFcjs.brushColourKind; exports.brushContracts = brushContracts; exports.brushEditorScope = brushEditorScope; exports.brushEditorSections = brushEditorSections; exports.brushIdFromName = brushIdFromName; exports.brushLibraryGroups = brushLibraryGroups; exports.brushPackFormat = _chunk5HR5CSLCcjs.brushPackFormat; exports.brushPreviewColours = _chunkQNNZA3V5cjs.brushPreviewColours; exports.brushPreviewStrength = _chunkQNNZA3V5cjs.brushPreviewStrength; exports.brushPreviewsOnDark = _chunkQNNZA3V5cjs.brushPreviewsOnDark; exports.brushSourceFromMask = _chunkQ33V5PEFcjs.brushSourceFromMask; exports.brushSourceIdFromName = _chunkQ33V5PEFcjs.brushSourceIdFromName; exports.brushSourceIdOf = _chunkQ33V5PEFcjs.brushSourceIdOf; exports.brushSourceIds = _chunkQ33V5PEFcjs.brushSourceIds; exports.brushSourceRef = _chunkQ33V5PEFcjs.brushSourceRef; exports.changedBrushPaths = _chunk5HR5CSLCcjs.changedBrushPaths; exports.checkBrush = _chunkQ33V5PEFcjs.checkBrush; exports.checkBrushEdit = _chunkQ33V5PEFcjs.checkBrushEdit; exports.createBrushLibrary = _chunkQ33V5PEFcjs.createBrushLibrary; exports.createBrushLibraryToolProvider = _chunk5HR5CSLCcjs.createBrushLibraryToolProvider; exports.createBrushLibraryTools = _chunk5HR5CSLCcjs.createBrushLibraryTools; exports.decodeBrushSource = _chunkQ33V5PEFcjs.decodeBrushSource; exports.degrade = _chunkQ33V5PEFcjs.degrade; exports.imageFileToPngDataUrl = imageFileToPngDataUrl; exports.importBrushFile = _chunk5HR5CSLCcjs.importBrushFile; exports.importBrushPack = _chunk5HR5CSLCcjs.importBrushPack; exports.isBrushLibrary = _chunkQ33V5PEFcjs.isBrushLibrary; exports.isBrushSourceRef = _chunkQ33V5PEFcjs.isBrushSourceRef; exports.listPresets = _chunkQ33V5PEFcjs.listPresets; exports.makeBrushSource = _chunkQ33V5PEFcjs.makeBrushSource; exports.mergeBrushPatch = _chunk5HR5CSLCcjs.mergeBrushPatch; exports.migrateBrushV1 = _chunkQ33V5PEFcjs.migrateBrushV1; exports.newBrushFor = newBrushFor; exports.normalizeBrushSpec = _chunkQ33V5PEFcjs.normalizeBrushSpec; exports.parseBrushLibrary = _chunkQ33V5PEFcjs.parseBrushLibrary2; exports.parseBrushLibrarySources = _chunkQ33V5PEFcjs.parseBrushLibrarySources; exports.readAbr = _chunk5HR5CSLCcjs.readAbr; exports.readBrushPack = _chunk5HR5CSLCcjs.readBrushPack; exports.readBrushSource = _chunkQ33V5PEFcjs.readBrushSource; exports.resolveBrush = _chunkQ33V5PEFcjs.resolveBrush; exports.resolveCategory = _chunkQ33V5PEFcjs.resolveCategory; exports.resolveEngine = _chunkQ33V5PEFcjs.resolveEngine; exports.resolveWetMedium = _chunkQ33V5PEFcjs.resolveWetMedium; exports.saveBrushEdit = saveBrushEdit; exports.setBrushEditorValue = setBrushEditorValue; exports.snapshotBrushLibrary = _chunkQ33V5PEFcjs.snapshotBrushLibrary; exports.summarizeBrushSource = _chunkQ33V5PEFcjs.summarizeBrushSource; exports.withBrushSource = withBrushSource; exports.withKeptIssues = _chunkQ33V5PEFcjs.withKeptIssues;
@@ -18,7 +18,7 @@ import {
18
18
  mergeBrushPatch,
19
19
  readAbr,
20
20
  readBrushPack
21
- } from "../chunk-G5TCMKX5.js";
21
+ } from "../chunk-IJ6NSWKM.js";
22
22
  import "../chunk-VE6UKYQ2.js";
23
23
  import {
24
24
  BRUSH_PREVIEW_DARK_PAPER,
@@ -27,7 +27,7 @@ import {
27
27
  brushPreviewColours,
28
28
  brushPreviewStrength,
29
29
  brushPreviewsOnDark
30
- } from "../chunk-RJGW3RQS.js";
30
+ } from "../chunk-GHYJAMMS.js";
31
31
  import {
32
32
  BRUSH_FACET_SCHEMAS,
33
33
  BRUSH_FIELDS,
@@ -102,7 +102,7 @@ import {
102
102
  snapshotBrushLibrary,
103
103
  summarizeBrushSource,
104
104
  withKeptIssues
105
- } from "../chunk-ZM7IKQ4X.js";
105
+ } from "../chunk-XVPQNX7O.js";
106
106
  import {
107
107
  STAMP_KINDS
108
108
  } from "../chunk-JL6YH5WD.js";
@@ -112,8 +112,9 @@ import {
112
112
  import "../chunk-SZTI6EER.js";
113
113
  import "../chunk-V2ZCJRDM.js";
114
114
  import {
115
+ LIVE_PATTERN_KINDS,
115
116
  MAX_BRUSH_SIZE
116
- } from "../chunk-ACTHSQOI.js";
117
+ } from "../chunk-LDZOXESA.js";
117
118
  import "../chunk-4D6KP5BV.js";
118
119
  import {
119
120
  BLEND_MODES
@@ -162,7 +163,8 @@ var ARIA = {
162
163
  "behaviour.thick.applicator": "Impasto tool",
163
164
  "light.pressure": "Light follows pressure",
164
165
  "tip.grain.source": "Tip grain",
165
- "dynamics.angleJitter": "Brush rotation jitter"
166
+ "dynamics.angleJitter": "Brush rotation jitter",
167
+ "livePatterns.pattern": "Live pattern"
166
168
  };
167
169
  var aria = (path, label) => ARIA[path] ?? `Brush ${label.toLowerCase()}`;
168
170
  function brushEditorSections(brush, profile = PAINT_PROFILE, context = {}) {
@@ -273,7 +275,11 @@ function brushEditorSections(brush, profile = PAINT_PROFILE, context = {}) {
273
275
  ...engineSliders.map((c) => exchangeOk(facetOf(c.path)) ? slider(c) : null),
274
276
  engineOk && engineControls?.mixingPath && (engine === "wet" || exchangeFull) && fieldOf("behaviour.mixing") && choice("behaviour.mixing", "Mixing", options(["pigment", "rgb"], { pigment: "Pigment", rgb: "RGB" })),
275
277
  engineOk && engineControls?.dirty && exchangeFull && fieldOf("behaviour.exchange.dirty") && toggle("behaviour.exchange.dirty", "Dirty brush"),
276
- engine === "mixer" && engineOk && full(profile.reservoir) && toggle("behaviour.exchange.reservoir.refill", "Load each stroke")
278
+ engine === "mixer" && engineOk && full(profile.reservoir) && toggle("behaviour.exchange.reservoir.refill", "Load each stroke"),
279
+ engine === "wet" && engineOk && !isTool && full(profile.livePatterns ?? "none") && choice("livePatterns.pattern", "Live pattern", [{ value: "", label: "None" }, ...options(LIVE_PATTERN_KINDS)], ""),
280
+ engine === "wet" && engineOk && !isTool && full(profile.livePatterns ?? "none") && !!brush.livePatterns && withNote({ kind: "slider", path: "livePatterns.scale", label: "Pattern scale", ariaLabel: "Brush pattern scale", min: 0.5, max: 4, step: 0.1, value: brush.livePatterns.scale ?? 1 }),
281
+ engine === "wet" && engineOk && !isTool && full(profile.livePatterns ?? "none") && !!brush.livePatterns && withNote({ kind: "slider", path: "livePatterns.contrast", label: "Pattern contrast", ariaLabel: "Brush pattern contrast", min: 0, max: 2, step: 0.05, value: brush.livePatterns.contrast ?? 1 }),
282
+ engine === "wet" && engineOk && !isTool && full(profile.livePatterns ?? "none") && !!brush.livePatterns && withNote({ kind: "slider", path: "livePatterns.relief", label: "Pattern relief", ariaLabel: "Brush pattern relief", min: 0, max: 1, step: 0.05, value: brush.livePatterns.relief ?? 0 })
277
283
  ]);
278
284
  if (!isTool && full(profile.light) && engineOk && engineControls?.light) {
279
285
  add("light", "Light", [
@@ -338,6 +344,8 @@ function hiddenLabel(issue, b) {
338
344
  return "several dabs per step";
339
345
  case "light":
340
346
  return "light";
347
+ case "livePatterns":
348
+ return "live patterns";
341
349
  case "fluorescence":
342
350
  return "UV-fluorescent ink";
343
351
  case "thermochromic":
@@ -427,6 +435,8 @@ function setBrushEditorValue(brush, path, value) {
427
435
  else delete next.iridescence;
428
436
  } else if (path === "tip.grain.source" && (value === void 0 || value === "")) {
429
437
  delete next.tip.grain;
438
+ } else if (path === "livePatterns.pattern" && (value === void 0 || value === "")) {
439
+ delete next.livePatterns;
430
440
  } else if (path === "tip.shape" && value !== "stamp") {
431
441
  next.tip = { ...next.tip, shape: value, stamps: void 0 };
432
442
  if (value !== "bristle") delete next.tip.bristle;
@@ -513,6 +523,7 @@ var BRUSH_PROFILE_JSON_SCHEMA = {
513
523
  tilt: { ...SUPPORT, description: "Tilt dynamics." },
514
524
  tipGrain: { ...SUPPORT, description: "Texture inside the tip (tip.grain); missing: none." },
515
525
  dabCount: { ...SUPPORT, description: "Several dabs per spacing step (dynamics.count); missing: none." },
526
+ livePatterns: { ...SUPPORT, description: "Gray-Scott reaction-diffusion patterns in wet paint (livePatterns); missing: none." },
516
527
  iridescence: { ...SUPPORT, description: "Thin-film iridescence (iridescence); missing: none." }
517
528
  },
518
529
  required: ["id", "name", "kinds", "tools", "tipShapes", "marks", "surfaces", "exchange", "reservoir", "grain", "light", "motion", "colours", "velocity", "tilt"],
@@ -13,7 +13,7 @@ export type BrushPatch = {
13
13
  /** The schema of every facet except `id` and `name`, as tool arguments (a patch may leave out `tip.shape`). */
14
14
  export declare const BRUSH_PATCH_PROPERTIES: Readonly<Record<string, JsonSchema>>;
15
15
  /** The facets a patch can remove (`unset`), back to the engine's defaults. */
16
- export declare const UNSETTABLE_BRUSH_FACETS: readonly ["description", "tags", "medium", "variety", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "motion", "colour"];
16
+ export declare const UNSETTABLE_BRUSH_FACETS: readonly ["description", "tags", "medium", "variety", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "motion", "livePatterns", "colour"];
17
17
  export type UnsettableBrushFacet = (typeof UNSETTABLE_BRUSH_FACETS)[number];
18
18
  /** The `unset` argument of tools that take a patch. */
19
19
  export declare const BRUSH_UNSET_PROPERTY: JsonSchema;
@@ -13,7 +13,7 @@ export type BrushPatch = {
13
13
  /** The schema of every facet except `id` and `name`, as tool arguments (a patch may leave out `tip.shape`). */
14
14
  export declare const BRUSH_PATCH_PROPERTIES: Readonly<Record<string, JsonSchema>>;
15
15
  /** The facets a patch can remove (`unset`), back to the engine's defaults. */
16
- export declare const UNSETTABLE_BRUSH_FACETS: readonly ["description", "tags", "medium", "variety", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "motion", "colour"];
16
+ export declare const UNSETTABLE_BRUSH_FACETS: readonly ["description", "tags", "medium", "variety", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "motion", "livePatterns", "colour"];
17
17
  export type UnsettableBrushFacet = (typeof UNSETTABLE_BRUSH_FACETS)[number];
18
18
  /** The `unset` argument of tools that take a patch. */
19
19
  export declare const BRUSH_UNSET_PROPERTY: JsonSchema;
@@ -39,6 +39,8 @@ export interface BrushHostProfile {
39
39
  tipGrain?: BrushSupport;
40
40
  /** Several dabs per spacing step (`dynamics.count` above 1); missing: `none`. */
41
41
  dabCount?: BrushSupport;
42
+ /** Gray-Scott reaction-diffusion patterns in wet paint (`livePatterns`); missing: `none`. */
43
+ livePatterns?: BrushSupport;
42
44
  /** Structural colour (`iridescence`); missing: `none`. */
43
45
  iridescence?: BrushSupport;
44
46
  }
@@ -39,6 +39,8 @@ export interface BrushHostProfile {
39
39
  tipGrain?: BrushSupport;
40
40
  /** Several dabs per spacing step (`dynamics.count` above 1); missing: `none`. */
41
41
  dabCount?: BrushSupport;
42
+ /** Gray-Scott reaction-diffusion patterns in wet paint (`livePatterns`); missing: `none`. */
43
+ livePatterns?: BrushSupport;
42
44
  /** Structural colour (`iridescence`); missing: `none`. */
43
45
  iridescence?: BrushSupport;
44
46
  }
@@ -34,6 +34,7 @@ export declare const BRUSH_FACET_SCHEMAS: {
34
34
  readonly ghost: JsonSchema;
35
35
  readonly mutation: JsonSchema;
36
36
  readonly motion: JsonSchema;
37
+ readonly livePatterns: JsonSchema;
37
38
  readonly colour: JsonSchema;
38
39
  readonly paint: JsonSchema;
39
40
  };
@@ -34,6 +34,7 @@ export declare const BRUSH_FACET_SCHEMAS: {
34
34
  readonly ghost: JsonSchema;
35
35
  readonly mutation: JsonSchema;
36
36
  readonly motion: JsonSchema;
37
+ readonly livePatterns: JsonSchema;
37
38
  readonly colour: JsonSchema;
38
39
  readonly paint: JsonSchema;
39
40
  };