@pixerate/editor 0.275.1 → 0.277.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 (81) 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 +84 -71
  4. package/dist/brushes/index.js +20 -7
  5. package/dist/brushes/patch.d.cts +4 -4
  6. package/dist/brushes/patch.d.ts +4 -4
  7. package/dist/brushes/profiles.d.cts +4 -0
  8. package/dist/brushes/profiles.d.ts +4 -0
  9. package/dist/brushes/schema.d.cts +2 -0
  10. package/dist/brushes/schema.d.ts +2 -0
  11. package/dist/brushes/types.d.cts +32 -1
  12. package/dist/brushes/types.d.ts +32 -1
  13. package/dist/{brushstrokeRender-2RQL46WB.cjs → brushstrokeRender-7HOMKZNO.cjs} +21 -21
  14. package/dist/{brushstrokeRender-WGY4DZHF.js → brushstrokeRender-EIK3625T.js} +5 -5
  15. package/dist/canvas/index.cjs +13 -13
  16. package/dist/canvas/index.js +1 -1
  17. package/dist/{chunk-T6KNJVBP.cjs → chunk-4NZTK7LM.cjs} +35 -35
  18. package/dist/{chunk-LMG57L5A.js → chunk-CTHWXW5M.js} +121 -8
  19. package/dist/{chunk-43XJC3PP.js → chunk-DWPFGWW5.js} +1 -1
  20. package/dist/{chunk-MSWLJI4G.js → chunk-FNIWHSLI.js} +2 -2
  21. package/dist/{chunk-JDXWLCXF.cjs → chunk-H3EME3S5.cjs} +2 -2
  22. package/dist/{chunk-Y2ISSVPH.js → chunk-HHX7ST6Z.js} +167 -9
  23. package/dist/{chunk-6QNDGMK7.cjs → chunk-HRUK5TFW.cjs} +187 -45
  24. package/dist/{chunk-NIBFKXMD.cjs → chunk-IFWLZZCW.cjs} +619 -2
  25. package/dist/{chunk-W6JDUXP3.js → chunk-J23JOHPR.js} +1 -1
  26. package/dist/{chunk-HVO6XCI4.js → chunk-JPB65KQ6.js} +1 -1
  27. package/dist/{chunk-Y354ETAE.cjs → chunk-JVFGK3DK.cjs} +69 -58
  28. package/dist/{chunk-ACTHSQOI.js → chunk-LDZOXESA.js} +618 -1
  29. package/dist/{chunk-IEEY5RJV.js → chunk-LZE2QZCT.js} +1 -1
  30. package/dist/{chunk-DHC4E2DU.cjs → chunk-MITQGHN3.cjs} +121 -8
  31. package/dist/{chunk-75SYKUOT.js → chunk-N4MCWQZ7.js} +1 -1
  32. package/dist/{chunk-G5TCMKX5.js → chunk-NJLPW4MM.js} +17 -6
  33. package/dist/{chunk-STLTOISW.cjs → chunk-NPPMKMCU.cjs} +17 -17
  34. package/dist/{chunk-OLZXXAER.cjs → chunk-PEQWARCP.cjs} +408 -250
  35. package/dist/{chunk-ZM7IKQ4X.js → chunk-PPDUCZZV.js} +152 -10
  36. package/dist/{chunk-RAPNRK4R.cjs → chunk-QBZEC2JJ.cjs} +88 -88
  37. package/dist/{chunk-RJGW3RQS.js → chunk-ROURHJYT.js} +1 -1
  38. package/dist/{chunk-62MFUHSI.cjs → chunk-UQXULAMQ.cjs} +16 -16
  39. package/dist/{chunk-YYZC2EQO.cjs → chunk-VNF7A2JA.cjs} +23 -23
  40. package/dist/{chunk-C6VB7CPY.cjs → chunk-WFSYZO5Z.cjs} +1 -1
  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 +21 -1
  52. package/dist/paint-editor/brush-model.d.ts +21 -1
  53. package/dist/paint-editor/controller.d.cts +102 -1
  54. package/dist/paint-editor/controller.d.ts +102 -1
  55. package/dist/paint-editor/document.d.cts +8 -1
  56. package/dist/paint-editor/document.d.ts +8 -1
  57. package/dist/paint-editor/ferro-tools.d.cts +10 -0
  58. package/dist/paint-editor/ferro-tools.d.ts +10 -0
  59. package/dist/paint-editor/ferro.d.cts +134 -0
  60. package/dist/paint-editor/ferro.d.ts +134 -0
  61. package/dist/paint-editor/index.cjs +2291 -904
  62. package/dist/paint-editor/index.d.cts +2 -0
  63. package/dist/paint-editor/index.d.ts +2 -0
  64. package/dist/paint-editor/index.js +1533 -146
  65. package/dist/paint-editor/stroke.d.cts +14 -0
  66. package/dist/paint-editor/stroke.d.ts +14 -0
  67. package/dist/paint-editor/thick.d.cts +15 -2
  68. package/dist/paint-editor/thick.d.ts +15 -2
  69. package/dist/paint-editor/ui.d.cts +7 -1
  70. package/dist/paint-editor/ui.d.ts +7 -1
  71. package/dist/paint-editor/wet-tools.d.cts +42 -0
  72. package/dist/paint-editor/wet-tools.d.ts +42 -0
  73. package/dist/paint-editor/wet.d.cts +22 -1
  74. package/dist/paint-editor/wet.d.ts +22 -1
  75. package/dist/vector/index.cjs +1002 -1002
  76. package/dist/vector/index.js +7 -7
  77. package/package.json +1 -1
  78. package/skills/pixerate-brushes/SKILL.md +1 -0
  79. package/skills/pixerate-paint-editor/SKILL.md +11 -29
  80. package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
  81. package/skills/pixerate-paint-editor/references/ferrofluid.md +36 -0
@@ -18,7 +18,7 @@
18
18
 
19
19
 
20
20
 
21
- var _chunkY354ETAEcjs = require('../chunk-Y354ETAE.cjs');
21
+ var _chunkJVFGK3DKcjs = require('../chunk-JVFGK3DK.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 _chunkH3EME3S5cjs = require('../chunk-H3EME3S5.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 _chunkHRUK5TFWcjs = require('../chunk-HRUK5TFW.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 = _chunkHRUK5TFWcjs.PAINT_PROFILE, context = {}) {
171
+ const resolved = _chunkHRUK5TFWcjs.resolveBrush.call(void 0, brush);
172
+ const engine = _chunkHRUK5TFWcjs.resolveEngine.call(void 0, brush);
173
+ const fieldOf = (facet) => _chunkHRUK5TFWcjs.BRUSH_FIELDS.find((f) => f.facet === facet && f.engines.includes(engine));
174
+ const facetOf = (enginePath) => _optionalChain([(_nullishCoalesce(_chunkHRUK5TFWcjs.BRUSH_FIELDS.find((f) => f.engine === enginePath && f.engines.includes(engine)), () => ( _chunkHRUK5TFWcjs.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 = _chunkHRUK5TFWcjs.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 = _chunkHRUK5TFWcjs.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 = _chunkHRUK5TFWcjs.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: _chunkHRUK5TFWcjs.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(_chunkHRUK5TFWcjs.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 = _chunkJVFGK3DKcjs.brushEngineControls.call(void 0, resolved);
218
+ const tipSliders = _chunkJVFGK3DKcjs.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" ? _chunkHRUK5TFWcjs.MAX_DOT_TEXTURES : _chunkHRUK5TFWcjs.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(_chunkHRUK5TFWcjs.TIP_GRAIN_PATTERNS), ...sourceOptions], ""),
231
+ !!grain && choice("tip.grain.mode", "Grain moves", options(_chunkHRUK5TFWcjs.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(_chunkHRUK5TFWcjs.PARTICLE_KINDS, PHYSICS_LABELS)),
245
+ marks === "particles" && choice("marks.render", "Drawn as", options(_chunkHRUK5TFWcjs.PARTICLE_RENDERS, RENDER_LABELS)),
246
+ marks === "particles" && render === "ribbon" && choice("marks.ribbonPairs", "Ribbon joins", options(_chunkHRUK5TFWcjs.RIBBON_PAIRS, { adjacent: "Neighbours", "first-last": "Outermost" })),
247
+ marks === "particles" && shape === "stamp" && choice("marks.stampOrder", "Texture order", options(_chunkHRUK5TFWcjs.DOT_TEXTURE_ORDERS, { sequence: "In turn", random: "Random" })),
248
+ marks === "particles" && shape === "stamp" && choice("marks.stampRotation", "Texture turn", options(_chunkHRUK5TFWcjs.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 = _chunkJVFGK3DKcjs.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(_chunkHRUK5TFWcjs.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(_chunkHRUK5TFWcjs.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(_chunkHRUK5TFWcjs.WET_MEDIA, WET_MEDIUM_LABELS), "watercolor"),
274
+ engine === "impasto" && engineOk && choice("behaviour.thick.applicator", "Tool", options(_chunkHRUK5TFWcjs.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: _chunkHRUK5TFWcjs.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 ${_chunkHRUK5TFWcjs.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 = _chunkHRUK5TFWcjs.PAINT_PROFILE) {
365
373
  const hidden = [];
366
- for (const issue of _chunk6QNDGMK7cjs.checkBrush.call(void 0, brush, profile).issues) {
374
+ for (const issue of _chunkHRUK5TFWcjs.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 = _chunkHRUK5TFWcjs.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 = _chunkHRUK5TFWcjs.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, _chunkHRUK5TFWcjs.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;
@@ -413,6 +421,7 @@ function setBrushEditorValue(brush, path, value) {
413
421
  delete next.iridescence;
414
422
  delete next.ghost;
415
423
  delete next.mutation;
424
+ delete next.ferro;
416
425
  delete next.colour;
417
426
  next.tool = _nullishCoalesce(next.tool, () => ( "eraser"));
418
427
  if (next.tip.shape === "stamp") next.tip = { shape: "round", ...next.tip.size !== void 0 ? { size: next.tip.size } : {} };
@@ -427,18 +436,20 @@ function setBrushEditorValue(brush, path, value) {
427
436
  else delete next.iridescence;
428
437
  } else if (path === "tip.grain.source" && (value === void 0 || value === "")) {
429
438
  delete next.tip.grain;
439
+ } else if (path === "livePatterns.pattern" && (value === void 0 || value === "")) {
440
+ delete next.livePatterns;
430
441
  } else if (path === "tip.shape" && value !== "stamp") {
431
442
  next.tip = { ...next.tip, shape: value, stamps: void 0 };
432
443
  if (value !== "bristle") delete next.tip.bristle;
433
444
  } else if (value === void 0 || value === "") {
434
445
  const keys = path.split(".");
435
- const parent = _chunk6QNDGMK7cjs.getPath.call(void 0, next, keys.slice(0, -1).join("."));
446
+ const parent = _chunkHRUK5TFWcjs.getPath.call(void 0, next, keys.slice(0, -1).join("."));
436
447
  if (keys.length === 1) delete next[keys[0]];
437
448
  else if (parent) delete parent[keys[keys.length - 1]];
438
- } else _chunk6QNDGMK7cjs.setPath.call(void 0, next, path, value);
449
+ } else _chunkHRUK5TFWcjs.setPath.call(void 0, next, path, value);
439
450
  const problems = [];
440
451
  try {
441
- return { brush: _chunk6QNDGMK7cjs.normalizeBrushSpec.call(void 0, JSON.parse(JSON.stringify(next)), problems), problems };
452
+ return { brush: _chunkHRUK5TFWcjs.normalizeBrushSpec.call(void 0, JSON.parse(JSON.stringify(next)), problems), problems };
442
453
  } catch (e) {
443
454
  return { brush, problems: [e.message] };
444
455
  }
@@ -454,7 +465,7 @@ function brushLibraryGroups(entries, filter = {}) {
454
465
  const text = words([b.id, b.name, _nullishCoalesce(b.variety, () => ( "")), _nullishCoalesce(b.medium, () => ( "")), _nullishCoalesce(b.tool, () => ( "")), ..._nullishCoalesce(b.tags, () => ( []))].join(" "));
455
466
  if (!wanted.every((w) => text.some((t) => t.startsWith(w)))) continue;
456
467
  }
457
- if (filter.profile && !_chunk6QNDGMK7cjs.checkBrush.call(void 0, b, filter.profile).ok) continue;
468
+ if (filter.profile && !_chunkHRUK5TFWcjs.checkBrush.call(void 0, b, filter.profile).ok) continue;
458
469
  const id = b.kind === "tool" ? "tools" : _nullishCoalesce(b.medium, () => ( "other"));
459
470
  let g = groups.get(id);
460
471
  if (!g) groups.set(id, g = { id, title: id === "tools" ? "Tools" : id === "other" ? "Other" : MEDIUM_LABELS[id], varieties: [] });
@@ -470,7 +481,7 @@ async function imageFileToPngDataUrl(file) {
470
481
  const bytes = new Uint8Array(await file.arrayBuffer());
471
482
  if (_chunkW5M3J7PVcjs.isPng.call(void 0, bytes)) return `data:image/png;base64,${_chunkW5M3J7PVcjs.bytesToBase64.call(void 0, bytes)}`;
472
483
  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.");
484
+ if (!g.createImageBitmap || !g.OffscreenCanvas) throw new (0, _chunkHRUK5TFWcjs.BrushSourceError)("Only PNG images can be read here; convert the image to PNG.");
474
485
  const bitmap = await g.createImageBitmap(file);
475
486
  const canvas = new g.OffscreenCanvas(bitmap.width, bitmap.height);
476
487
  canvas.getContext("2d").drawImage(bitmap, 0, 0);
@@ -480,7 +491,7 @@ async function imageFileToPngDataUrl(file) {
480
491
  async function addBrushSourceFromFile(library, file, options2 = {}) {
481
492
  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
493
  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) };
494
+ return { ...result, ref: _chunkHRUK5TFWcjs.brushSourceRef.call(void 0, result.source.id) };
484
495
  }
485
496
  function withBrushSource(brush, ref, as) {
486
497
  if (as === "grain") return setBrushEditorValue(brush, "tip.grain.source", ref);
@@ -498,11 +509,11 @@ var BRUSH_PROFILE_JSON_SCHEMA = {
498
509
  properties: {
499
510
  id: { type: "string", description: "paint, image, canvas, vector\u2026" },
500
511
  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." },
512
+ kinds: { type: "array", items: { type: "string", enum: [..._chunkHRUK5TFWcjs.BRUSH_KINDS] }, description: "Brush kinds it has." },
513
+ tools: { type: "array", items: { type: "string", enum: [..._chunkHRUK5TFWcjs.BRUSH_TOOLS] }, description: "Tools it has (kind tool)." },
514
+ tipShapes: { type: "array", items: { type: "string", enum: [..._chunkHRUK5TFWcjs.BRUSH_TIP_SHAPES] }, description: "Tip shapes it renders." },
515
+ marks: { type: "array", items: { type: "string", enum: [..._chunkHRUK5TFWcjs.BRUSH_MARKS_KINDS] }, description: "Marks kinds it renders." },
516
+ surfaces: { type: "array", items: { type: "string", enum: [..._chunkHRUK5TFWcjs.BRUSH_SURFACES] }, description: "Surfaces it renders." },
506
517
  exchange: { ...SUPPORT, description: "Picking paint up and mixing it in." },
507
518
  reservoir: { ...SUPPORT, description: "The Mixer Brush reservoir." },
508
519
  grain: { ...SUPPORT, description: "Paper grain response." },
@@ -513,7 +524,9 @@ var BRUSH_PROFILE_JSON_SCHEMA = {
513
524
  tilt: { ...SUPPORT, description: "Tilt dynamics." },
514
525
  tipGrain: { ...SUPPORT, description: "Texture inside the tip (tip.grain); missing: none." },
515
526
  dabCount: { ...SUPPORT, description: "Several dabs per spacing step (dynamics.count); missing: none." },
516
- iridescence: { ...SUPPORT, description: "Thin-film iridescence (iridescence); missing: none." }
527
+ livePatterns: { ...SUPPORT, description: "Gray-Scott reaction-diffusion patterns in wet paint (livePatterns); missing: none." },
528
+ iridescence: { ...SUPPORT, description: "Thin-film iridescence (iridescence); missing: none." },
529
+ ferro: { ...SUPPORT, description: "Liquid ferrofluid (ferro); missing: none." }
517
530
  },
518
531
  required: ["id", "name", "kinds", "tools", "tipShapes", "marks", "surfaces", "exchange", "reservoir", "grain", "light", "motion", "colours", "velocity", "tilt"],
519
532
  additionalProperties: false
@@ -527,16 +540,16 @@ var BRUSH_LIBRARY_FILE_JSON_SCHEMA = {
527
540
  brushes: { type: "array", items: { $ref: "brush.schema.json" }, description: "The brushes, each a facet brush (brush.schema.json)." },
528
541
  sources: {
529
542
  type: "array",
530
- maxItems: _chunk6QNDGMK7cjs.MAX_BRUSH_SOURCES,
543
+ maxItems: _chunkHRUK5TFWcjs.MAX_BRUSH_SOURCES,
531
544
  description: "The image sources the brushes name (source:<id> in tip.stamps or tip.grain.source), bytes included.",
532
545
  items: {
533
546
  type: "object",
534
547
  description: "An image source: a grey mask, 255 paints (or is a grain peak).",
535
548
  properties: {
536
- id: { type: "string", pattern: _chunk6QNDGMK7cjs.BRUSH_ID.source, description: "Source id." },
549
+ id: { type: "string", pattern: _chunkHRUK5TFWcjs.BRUSH_ID.source, description: "Source id." },
537
550
  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." },
551
+ width: { type: "integer", minimum: 1, maximum: _chunkHRUK5TFWcjs.MAX_BRUSH_SOURCE_SIZE, description: "Width in px." },
552
+ height: { type: "integer", minimum: 1, maximum: _chunkHRUK5TFWcjs.MAX_BRUSH_SOURCE_SIZE, description: "Height in px." },
540
553
  image: { type: "string", pattern: "^data:image/png;base64,", description: "The mask as a greyscale PNG data: URL." },
541
554
  licence: { type: "string", maxLength: 2e3, description: "The licence or terms it came with." },
542
555
  origin: { type: "string", maxLength: 200, description: "Where it came from (a file or pack name)." }
@@ -550,7 +563,7 @@ var BRUSH_LIBRARY_FILE_JSON_SCHEMA = {
550
563
  additionalProperties: false
551
564
  };
552
565
  function migrationInputs() {
553
- const pick = (id) => _chunk6QNDGMK7cjs.brushToJSON.call(void 0, _chunk6QNDGMK7cjs.BUILTIN_BRUSHES.find((b) => b.id === id));
566
+ const pick = (id) => _chunkHRUK5TFWcjs.brushToJSON.call(void 0, _chunkHRUK5TFWcjs.BUILTIN_BRUSHES.find((b) => b.id === id));
554
567
  return [
555
568
  { id: "plain", name: "Plain" },
556
569
  pick("pencil-6b"),
@@ -572,34 +585,34 @@ function migrationInputs() {
572
585
  function editorSectionFixtures() {
573
586
  const ids = ["highlighter", "chalk", "watercolor-wash", "hair-strands", "mixer-brush", "blender", "spray-can"];
574
587
  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]) };
588
+ (id) => Object.keys(_chunkHRUK5TFWcjs.BRUSH_HOST_PROFILES).map((host) => {
589
+ const brush = _chunkHRUK5TFWcjs.BRUSH_PRESETS.find((b) => b.id === id);
590
+ return { brush: id, host, sections: brushEditorSections(brush, _chunkHRUK5TFWcjs.BRUSH_HOST_PROFILES[host]) };
578
591
  })
579
592
  );
580
593
  }
581
594
  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" }) });
595
+ const library = _chunkHRUK5TFWcjs.createBrushLibrary.call(void 0, );
596
+ const toolsFor = (host) => _chunkJVFGK3DKcjs.createBrushLibraryTools.call(void 0, library, { profile: _chunkHRUK5TFWcjs.BRUSH_HOST_PROFILES[host], renderPreview: () => ({ data: "", mimeType: "image/png" }) });
584
597
  return {
585
598
  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)]))
599
+ hosts: Object.fromEntries(Object.keys(_chunkHRUK5TFWcjs.BRUSH_HOST_PROFILES).map((host) => [host, toolsFor(host).map((t) => t.name)]))
587
600
  };
588
601
  }
589
602
  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) => ({
603
+ const presets = _chunkHRUK5TFWcjs.BRUSH_PRESETS.map((b) => JSON.parse(JSON.stringify(b)));
604
+ const resolved = _chunkHRUK5TFWcjs.BRUSH_PRESETS.map((b) => ({ id: b.id, engine: _chunkHRUK5TFWcjs.resolveEngine.call(void 0, b), category: _chunkHRUK5TFWcjs.resolveCategory.call(void 0, b), brush: _chunkHRUK5TFWcjs.brushToJSON.call(void 0, _chunkHRUK5TFWcjs.resolveBrush.call(void 0, b)) }));
605
+ const profiles = Object.values(_chunkHRUK5TFWcjs.BRUSH_HOST_PROFILES);
606
+ const checks = _chunkHRUK5TFWcjs.BRUSH_PRESETS.map((b) => ({
594
607
  id: b.id,
595
608
  hosts: Object.fromEntries(profiles.map((p) => {
596
- const c = _chunk6QNDGMK7cjs.checkBrush.call(void 0, b, p);
609
+ const c = _chunkHRUK5TFWcjs.checkBrush.call(void 0, b, p);
597
610
  return [p.id, { ok: c.ok, issues: c.issues.map(({ facet, severity }) => ({ facet, severity })) }];
598
611
  }))
599
612
  }));
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 }));
613
+ const migrations = migrationInputs().map((input) => ({ input, output: _chunkHRUK5TFWcjs.migrateBrushV1.call(void 0, input), resolved: _chunkHRUK5TFWcjs.brushToJSON.call(void 0, _chunkHRUK5TFWcjs.resolveBrush.call(void 0, _chunkHRUK5TFWcjs.migrateBrushV1.call(void 0, input))), engine: _chunkHRUK5TFWcjs.normalizePaintBrush.call(void 0, input).engine }));
601
614
  const files = {
602
- "brush.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "brush.schema.json", ..._chunk6QNDGMK7cjs.BRUSH_JSON_SCHEMA },
615
+ "brush.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "brush.schema.json", ..._chunkHRUK5TFWcjs.BRUSH_JSON_SCHEMA },
603
616
  "profile.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "profile.schema.json", ...BRUSH_PROFILE_JSON_SCHEMA },
604
617
  "library-file.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "library-file.schema.json", ...BRUSH_LIBRARY_FILE_JSON_SCHEMA },
605
618
  "presets.json": presets,
@@ -613,7 +626,7 @@ function brushContracts() {
613
626
  files["manifest.json"] = {
614
627
  domain: "brushes",
615
628
  formatVersion: BRUSH_CONTRACT_FORMAT_VERSION,
616
- limits: { maxPalette: _chunk6QNDGMK7cjs.MAX_BRUSH_PALETTE },
629
+ limits: { maxPalette: _chunkHRUK5TFWcjs.MAX_BRUSH_PALETTE },
617
630
  counts: { presets: presets.length, profiles: profiles.length, migrations: migrations.length, editorSections: files["editor-sections.json"].length },
618
631
  files: Object.keys(files).sort()
619
632
  };
@@ -714,4 +727,4 @@ function brushContracts() {
714
727
 
715
728
 
716
729
 
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;
730
+ exports.AbrError = _chunkJVFGK3DKcjs.AbrError; exports.BRUSH_CONTRACT_FORMAT_VERSION = BRUSH_CONTRACT_FORMAT_VERSION; exports.BRUSH_FACET_SCHEMAS = _chunkHRUK5TFWcjs.BRUSH_FACET_SCHEMAS; exports.BRUSH_FLIPS = _chunkHRUK5TFWcjs.BRUSH_FLIPS; exports.BRUSH_GRAIN_MODES = _chunkHRUK5TFWcjs.BRUSH_GRAIN_MODES; exports.BRUSH_GRAIN_SOURCE_PATTERN = _chunkHRUK5TFWcjs.BRUSH_GRAIN_SOURCE_PATTERN; exports.BRUSH_HOST_PROFILES = _chunkHRUK5TFWcjs.BRUSH_HOST_PROFILES; exports.BRUSH_JSON_SCHEMA = _chunkHRUK5TFWcjs.BRUSH_JSON_SCHEMA; exports.BRUSH_KINDS = _chunkHRUK5TFWcjs.BRUSH_KINDS; exports.BRUSH_LIBRARY_FILE_JSON_SCHEMA = BRUSH_LIBRARY_FILE_JSON_SCHEMA; exports.BRUSH_MARKS_KINDS = _chunkHRUK5TFWcjs.BRUSH_MARKS_KINDS; exports.BRUSH_MEDIA = _chunkHRUK5TFWcjs.BRUSH_MEDIA; exports.BRUSH_PACK_FORMATS = _chunkJVFGK3DKcjs.BRUSH_PACK_FORMATS; exports.BRUSH_PATCH_PROPERTIES = _chunkJVFGK3DKcjs.BRUSH_PATCH_PROPERTIES; exports.BRUSH_PRESETS = _chunkHRUK5TFWcjs.BRUSH_PRESETS; exports.BRUSH_PREVIEW_DARK_PAPER = _chunkH3EME3S5cjs.BRUSH_PREVIEW_DARK_PAPER; exports.BRUSH_PREVIEW_FALLBACKS = _chunkH3EME3S5cjs.BRUSH_PREVIEW_FALLBACKS; exports.BRUSH_PREVIEW_MIN_CONTRAST = _chunkH3EME3S5cjs.BRUSH_PREVIEW_MIN_CONTRAST; exports.BRUSH_PROFILE_JSON_SCHEMA = BRUSH_PROFILE_JSON_SCHEMA; exports.BRUSH_SOURCE_CHANNELS = _chunkHRUK5TFWcjs.BRUSH_SOURCE_CHANNELS; exports.BRUSH_SOURCE_PROVIDER = _chunkHRUK5TFWcjs.BRUSH_SOURCE_PROVIDER; exports.BRUSH_SURFACES = _chunkHRUK5TFWcjs.BRUSH_SURFACES; exports.BRUSH_TIP_SHAPES = _chunkHRUK5TFWcjs.BRUSH_TIP_SHAPES; exports.BRUSH_TOOLS = _chunkHRUK5TFWcjs.BRUSH_TOOLS; exports.BRUSH_UNSET_PROPERTY = _chunkJVFGK3DKcjs.BRUSH_UNSET_PROPERTY; exports.BRUSH_WORD = _chunkHRUK5TFWcjs.BRUSH_WORD; exports.BrushLibraryError = _chunkHRUK5TFWcjs.BrushLibraryError; exports.BrushPackError = _chunkJVFGK3DKcjs.BrushPackError; exports.BrushSourceError = _chunkHRUK5TFWcjs.BrushSourceError; exports.CANVAS_PROFILE = _chunkHRUK5TFWcjs.CANVAS_PROFILE; exports.GHOST_TRIGGERS = _chunkHRUK5TFWcjs.GHOST_TRIGGERS2; exports.IMAGE_PROFILE = _chunkHRUK5TFWcjs.IMAGE_PROFILE; exports.MAX_BRUSH_PACK_BYTES = _chunkJVFGK3DKcjs.MAX_BRUSH_PACK_BYTES; exports.MAX_BRUSH_PALETTE = _chunkHRUK5TFWcjs.MAX_BRUSH_PALETTE; exports.MAX_BRUSH_SOURCES = _chunkHRUK5TFWcjs.MAX_BRUSH_SOURCES; exports.MAX_BRUSH_SOURCE_INPUT_PIXELS = _chunkHRUK5TFWcjs.MAX_BRUSH_SOURCE_INPUT_PIXELS; exports.MAX_BRUSH_SOURCE_SIZE = _chunkHRUK5TFWcjs.MAX_BRUSH_SOURCE_SIZE; exports.MAX_PACK_BRUSHES = _chunkJVFGK3DKcjs.MAX_PACK_BRUSHES; exports.PAINT_PROFILE = _chunkHRUK5TFWcjs.PAINT_PROFILE; exports.TIP_GRAIN_PATTERNS = _chunkHRUK5TFWcjs.TIP_GRAIN_PATTERNS; exports.UNSETTABLE_BRUSH_FACETS = _chunkJVFGK3DKcjs.UNSETTABLE_BRUSH_FACETS; exports.VECTOR_PROFILE = _chunkHRUK5TFWcjs.VECTOR_PROFILE; exports.WET_BRUSH_TOOLS = _chunkHRUK5TFWcjs.WET_BRUSH_TOOLS; exports.addBrushSourceFromFile = addBrushSourceFromFile; exports.brushColourHex = _chunkJBZRVLXVcjs.brushColourHex; exports.brushColourKind = _chunkHRUK5TFWcjs.brushColourKind; exports.brushContracts = brushContracts; exports.brushEditorScope = brushEditorScope; exports.brushEditorSections = brushEditorSections; exports.brushIdFromName = brushIdFromName; exports.brushLibraryGroups = brushLibraryGroups; exports.brushPackFormat = _chunkJVFGK3DKcjs.brushPackFormat; exports.brushPreviewColours = _chunkH3EME3S5cjs.brushPreviewColours; exports.brushPreviewStrength = _chunkH3EME3S5cjs.brushPreviewStrength; exports.brushPreviewsOnDark = _chunkH3EME3S5cjs.brushPreviewsOnDark; exports.brushSourceFromMask = _chunkHRUK5TFWcjs.brushSourceFromMask; exports.brushSourceIdFromName = _chunkHRUK5TFWcjs.brushSourceIdFromName; exports.brushSourceIdOf = _chunkHRUK5TFWcjs.brushSourceIdOf; exports.brushSourceIds = _chunkHRUK5TFWcjs.brushSourceIds; exports.brushSourceRef = _chunkHRUK5TFWcjs.brushSourceRef; exports.changedBrushPaths = _chunkJVFGK3DKcjs.changedBrushPaths; exports.checkBrush = _chunkHRUK5TFWcjs.checkBrush; exports.checkBrushEdit = _chunkHRUK5TFWcjs.checkBrushEdit; exports.createBrushLibrary = _chunkHRUK5TFWcjs.createBrushLibrary; exports.createBrushLibraryToolProvider = _chunkJVFGK3DKcjs.createBrushLibraryToolProvider; exports.createBrushLibraryTools = _chunkJVFGK3DKcjs.createBrushLibraryTools; exports.decodeBrushSource = _chunkHRUK5TFWcjs.decodeBrushSource; exports.degrade = _chunkHRUK5TFWcjs.degrade; exports.imageFileToPngDataUrl = imageFileToPngDataUrl; exports.importBrushFile = _chunkJVFGK3DKcjs.importBrushFile; exports.importBrushPack = _chunkJVFGK3DKcjs.importBrushPack; exports.isBrushLibrary = _chunkHRUK5TFWcjs.isBrushLibrary; exports.isBrushSourceRef = _chunkHRUK5TFWcjs.isBrushSourceRef; exports.listPresets = _chunkHRUK5TFWcjs.listPresets; exports.makeBrushSource = _chunkHRUK5TFWcjs.makeBrushSource; exports.mergeBrushPatch = _chunkJVFGK3DKcjs.mergeBrushPatch; exports.migrateBrushV1 = _chunkHRUK5TFWcjs.migrateBrushV1; exports.newBrushFor = newBrushFor; exports.normalizeBrushSpec = _chunkHRUK5TFWcjs.normalizeBrushSpec; exports.parseBrushLibrary = _chunkHRUK5TFWcjs.parseBrushLibrary2; exports.parseBrushLibrarySources = _chunkHRUK5TFWcjs.parseBrushLibrarySources; exports.readAbr = _chunkJVFGK3DKcjs.readAbr; exports.readBrushPack = _chunkJVFGK3DKcjs.readBrushPack; exports.readBrushSource = _chunkHRUK5TFWcjs.readBrushSource; exports.resolveBrush = _chunkHRUK5TFWcjs.resolveBrush; exports.resolveCategory = _chunkHRUK5TFWcjs.resolveCategory; exports.resolveEngine = _chunkHRUK5TFWcjs.resolveEngine; exports.resolveWetMedium = _chunkHRUK5TFWcjs.resolveWetMedium; exports.saveBrushEdit = saveBrushEdit; exports.setBrushEditorValue = setBrushEditorValue; exports.snapshotBrushLibrary = _chunkHRUK5TFWcjs.snapshotBrushLibrary; exports.summarizeBrushSource = _chunkHRUK5TFWcjs.summarizeBrushSource; exports.withBrushSource = withBrushSource; exports.withKeptIssues = _chunkHRUK5TFWcjs.withKeptIssues;
@@ -18,7 +18,7 @@ import {
18
18
  mergeBrushPatch,
19
19
  readAbr,
20
20
  readBrushPack
21
- } from "../chunk-G5TCMKX5.js";
21
+ } from "../chunk-NJLPW4MM.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-ROURHJYT.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-PPDUCZZV.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":
@@ -413,6 +421,7 @@ function setBrushEditorValue(brush, path, value) {
413
421
  delete next.iridescence;
414
422
  delete next.ghost;
415
423
  delete next.mutation;
424
+ delete next.ferro;
416
425
  delete next.colour;
417
426
  next.tool = next.tool ?? "eraser";
418
427
  if (next.tip.shape === "stamp") next.tip = { shape: "round", ...next.tip.size !== void 0 ? { size: next.tip.size } : {} };
@@ -427,6 +436,8 @@ function setBrushEditorValue(brush, path, value) {
427
436
  else delete next.iridescence;
428
437
  } else if (path === "tip.grain.source" && (value === void 0 || value === "")) {
429
438
  delete next.tip.grain;
439
+ } else if (path === "livePatterns.pattern" && (value === void 0 || value === "")) {
440
+ delete next.livePatterns;
430
441
  } else if (path === "tip.shape" && value !== "stamp") {
431
442
  next.tip = { ...next.tip, shape: value, stamps: void 0 };
432
443
  if (value !== "bristle") delete next.tip.bristle;
@@ -513,7 +524,9 @@ var BRUSH_PROFILE_JSON_SCHEMA = {
513
524
  tilt: { ...SUPPORT, description: "Tilt dynamics." },
514
525
  tipGrain: { ...SUPPORT, description: "Texture inside the tip (tip.grain); missing: none." },
515
526
  dabCount: { ...SUPPORT, description: "Several dabs per spacing step (dynamics.count); missing: none." },
516
- iridescence: { ...SUPPORT, description: "Thin-film iridescence (iridescence); missing: none." }
527
+ livePatterns: { ...SUPPORT, description: "Gray-Scott reaction-diffusion patterns in wet paint (livePatterns); missing: none." },
528
+ iridescence: { ...SUPPORT, description: "Thin-film iridescence (iridescence); missing: none." },
529
+ ferro: { ...SUPPORT, description: "Liquid ferrofluid (ferro); missing: none." }
517
530
  },
518
531
  required: ["id", "name", "kinds", "tools", "tipShapes", "marks", "surfaces", "exchange", "reservoir", "grain", "light", "motion", "colours", "velocity", "tilt"],
519
532
  additionalProperties: false
@@ -5,15 +5,15 @@
5
5
  * means the same everywhere.
6
6
  */
7
7
  import type { JsonSchema } from '../tools/schema.cjs';
8
- import type { Brush } from './types.cjs';
9
- /** A brush's facets, any of them, as a patch takes them (the tip's `shape` may be left out). */
8
+ import type { Brush, BrushFerro } from './types.cjs';
9
+ /** A brush's facets, any of them, as a patch takes them (the tip's `shape` may be left out, and `ferro` accepts a `0..1` amount shorthand). */
10
10
  export type BrushPatch = {
11
- [K in Exclude<keyof Brush, 'id'>]?: K extends 'tip' ? Partial<Brush['tip']> : Brush[K];
11
+ [K in Exclude<keyof Brush, 'id'>]?: K extends 'tip' ? Partial<Brush['tip']> : K extends 'ferro' ? number | BrushFerro : Brush[K];
12
12
  };
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", "ferro", "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;
@@ -5,15 +5,15 @@
5
5
  * means the same everywhere.
6
6
  */
7
7
  import type { JsonSchema } from '../tools/schema.js';
8
- import type { Brush } from './types.js';
9
- /** A brush's facets, any of them, as a patch takes them (the tip's `shape` may be left out). */
8
+ import type { Brush, BrushFerro } from './types.js';
9
+ /** A brush's facets, any of them, as a patch takes them (the tip's `shape` may be left out, and `ferro` accepts a `0..1` amount shorthand). */
10
10
  export type BrushPatch = {
11
- [K in Exclude<keyof Brush, 'id'>]?: K extends 'tip' ? Partial<Brush['tip']> : Brush[K];
11
+ [K in Exclude<keyof Brush, 'id'>]?: K extends 'tip' ? Partial<Brush['tip']> : K extends 'ferro' ? number | BrushFerro : Brush[K];
12
12
  };
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", "ferro", "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,8 +39,12 @@ 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;
46
+ /** Liquid ferrofluid (`ferro`); missing: `none`. */
47
+ ferro?: BrushSupport;
44
48
  }
45
49
  export interface BrushIssue {
46
50
  /** The facet path the issue is about, e.g. `tip.shape`, `light`, `behaviour.exchange.reservoir`. */