@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
@@ -19,7 +19,11 @@ var _chunkWOFXZ3XUcjs = require('./chunk-WOFXZ3XU.cjs');
19
19
 
20
20
 
21
21
 
22
- var _chunkNIBFKXMDcjs = require('./chunk-NIBFKXMD.cjs');
22
+
23
+
24
+
25
+
26
+ var _chunkIFWLZZCWcjs = require('./chunk-IFWLZZCW.cjs');
23
27
 
24
28
 
25
29
  var _chunk2VX55NFMcjs = require('./chunk-2VX55NFM.cjs');
@@ -278,6 +282,29 @@ var BRUSH_PRESETS = [
278
282
  behaviour: { surface: "wet", exchange: { pickup: 0.4, mix: 0.4 }, wet: { water: 0.8, pigment: 0.45, rewet: 0.2 } },
279
283
  colour: { default: { type: "gradient", stops: [{ offset: 0, color: "paint" }, { offset: 1, color: "secondary" }], space: "pigment", map: { by: "distance", length: 500, repeat: "mirror", unit: "px" } } }
280
284
  },
285
+ // Live patterns: Gray-Scott reaction-diffusion that grows inside the wet wash and freezes as it dries.
286
+ {
287
+ id: "turing-wash",
288
+ name: "Turing wash",
289
+ kind: "mark",
290
+ medium: "paint",
291
+ variety: "watercolor",
292
+ tip: { shape: "round", size: 36, hardness: 0.5, spacing: 0.12 },
293
+ dynamics: { smoothing: 0.25, pressureOpacity: true, minSize: 0.35 },
294
+ behaviour: { surface: "wet", exchange: { pickup: 0.45 }, wet: { water: 0.75, pigment: 0.55, rewet: 0.2 } },
295
+ livePatterns: { pattern: "spots", contrast: 1.2, secondColor: "#0ea5e9", relief: 0.25 }
296
+ },
297
+ {
298
+ id: "coral-ink",
299
+ name: "Coral ink",
300
+ kind: "mark",
301
+ medium: "ink",
302
+ variety: "watercolor",
303
+ tip: { shape: "round", size: 22, hardness: 0.65, spacing: 0.12 },
304
+ dynamics: { smoothing: 0.3, pressureOpacity: true, minSize: 0.3 },
305
+ behaviour: { surface: "wet", exchange: { pickup: 0.35 }, wet: { water: 0.7, pigment: 0.75, rewet: 0.15 } },
306
+ livePatterns: { pattern: "coral", scale: 1.2, contrast: 1.3 }
307
+ },
281
308
  {
282
309
  id: "watercolor-detail",
283
310
  name: "Watercolour rigger",
@@ -1152,6 +1179,12 @@ function countAndFlip(raw, base) {
1152
1179
  return { ...n > 1 ? { count: n } : {}, ...flip !== "none" ? { flip } : {} };
1153
1180
  }
1154
1181
  var withTipGrain = (g) => g ? { tipGrain: g } : {};
1182
+ var withLivePatterns = (lp) => lp ? { livePatterns: lp } : {};
1183
+ function readLivePatterns(raw, base) {
1184
+ if (raw === null) return null;
1185
+ if (!raw || typeof raw !== "object") return base;
1186
+ return _chunkIFWLZZCWcjs.normalizeLivePatterns.call(void 0, raw, _nullishCoalesce(base, () => ( _chunkIFWLZZCWcjs.DEFAULT_LIVE_PATTERNS)));
1187
+ }
1155
1188
  function readTipGrain(raw, base) {
1156
1189
  if (raw === null) return null;
1157
1190
  if (!raw || typeof raw !== "object") return base;
@@ -1250,12 +1283,12 @@ function normalizePaintBrush(raw = {}, base) {
1250
1283
  if (rb.dirty !== void 0) mixerIn.cleanEachStroke = !rb.dirty;
1251
1284
  if (rb.mixing !== void 0) mixerIn.mixing = rb.mixing;
1252
1285
  }
1253
- const mixer = _chunkNIBFKXMDcjs.normalizeMixer.call(void 0, mixerIn);
1286
+ const mixer = _chunkIFWLZZCWcjs.normalizeMixer.call(void 0, mixerIn);
1254
1287
  let behavior = normalizeBehavior(rb, b.behavior);
1255
1288
  if (engine === "mixer") behavior = { ...behavior, pickup: mixer.wet, mix: mixer.mix, dirty: !mixer.cleanEachStroke, mixing: mixer.mixing };
1256
1289
  let paint = _nullishCoalesce(b.paint, () => ( null));
1257
1290
  if (raw.paint !== void 0) {
1258
- const p = _chunkNIBFKXMDcjs.readPaintSource.call(void 0, raw.paint);
1291
+ const p = _chunkIFWLZZCWcjs.readPaintSource.call(void 0, raw.paint);
1259
1292
  paint = p && p.type === "gradient" ? p : null;
1260
1293
  } else if (rb.multicolor !== void 0) {
1261
1294
  const mc = normalizeMultiColor(rb.multicolor, _nullishCoalesce(paintToMultiColor(paint), () => ( DEFAULT_MULTI_COLOR)));
@@ -1275,7 +1308,7 @@ function normalizePaintBrush(raw = {}, base) {
1275
1308
  name: typeof raw.name === "string" && raw.name.trim() ? raw.name.trim().slice(0, 80) : b.name,
1276
1309
  category: PAINT_BRUSH_CATEGORIES.includes(raw.category) ? raw.category : b.category,
1277
1310
  engine,
1278
- tip: _chunkNIBFKXMDcjs.normalizeBrush.call(void 0, { ...b.tip, ..._nullishCoalesce(raw.tip, () => ( {})) }),
1311
+ tip: _chunkIFWLZZCWcjs.normalizeBrush.call(void 0, { ...b.tip, ..._nullishCoalesce(raw.tip, () => ( {})) }),
1279
1312
  grain: unit2(raw.grain, b.grain),
1280
1313
  pressureSmoothing: range(raw.pressureSmoothing, 0, 0.95, b.pressureSmoothing),
1281
1314
  rotation: BRUSH_ROTATIONS.includes(raw.rotation) ? raw.rotation : b.rotation,
@@ -1283,8 +1316,8 @@ function normalizePaintBrush(raw = {}, base) {
1283
1316
  mixer,
1284
1317
  loadsColor: typeof raw.loadsColor === "boolean" ? raw.loadsColor : b.loadsColor,
1285
1318
  behavior,
1286
- blender: _chunkNIBFKXMDcjs.normalizeBlender.call(void 0, _nullishCoalesce(raw.blender, () => ( {})), b.blender),
1287
- smudge: _chunkNIBFKXMDcjs.normalizeSmudge.call(void 0, _nullishCoalesce(raw.smudge, () => ( {})), b.smudge),
1319
+ blender: _chunkIFWLZZCWcjs.normalizeBlender.call(void 0, _nullishCoalesce(raw.blender, () => ( {})), b.blender),
1320
+ smudge: _chunkIFWLZZCWcjs.normalizeSmudge.call(void 0, _nullishCoalesce(raw.smudge, () => ( {})), b.smudge),
1288
1321
  wet,
1289
1322
  impasto: normalizeImpasto(_nullishCoalesce(raw.impasto, () => ( {})), _nullishCoalesce(b.impasto, () => ( DEFAULT_IMPASTO))),
1290
1323
  particle: normalizeParticle(_nullishCoalesce(raw.particle, () => ( {})), _nullishCoalesce(b.particle, () => ( DEFAULT_PARTICLE))),
@@ -1299,6 +1332,7 @@ function normalizePaintBrush(raw = {}, base) {
1299
1332
  stamp: readStamp(raw.stamp, _nullishCoalesce(b.stamp, () => ( null))),
1300
1333
  spray: readSpray(raw.spray, _nullishCoalesce(b.spray, () => ( null))),
1301
1334
  ...withTipGrain(readTipGrain(raw.tipGrain, _nullishCoalesce(b.tipGrain, () => ( null)))),
1335
+ ...withLivePatterns(readLivePatterns(raw.livePatterns, _nullishCoalesce(b.livePatterns, () => ( null)))),
1302
1336
  paint,
1303
1337
  ...raw.builtin ? { builtin: true } : {}
1304
1338
  };
@@ -1308,16 +1342,16 @@ var BASE_BRUSH = {
1308
1342
  name: "Round",
1309
1343
  category: "custom",
1310
1344
  engine: "dab",
1311
- tip: { ..._chunkNIBFKXMDcjs.DEFAULT_BRUSH },
1345
+ tip: { ..._chunkIFWLZZCWcjs.DEFAULT_BRUSH },
1312
1346
  grain: 0,
1313
1347
  pressureSmoothing: 0,
1314
1348
  rotation: "fixed",
1315
1349
  dynamics: { ...DEFAULT_DYNAMICS },
1316
- mixer: { ..._chunkNIBFKXMDcjs.DEFAULT_MIXER },
1350
+ mixer: { ..._chunkIFWLZZCWcjs.DEFAULT_MIXER },
1317
1351
  loadsColor: true,
1318
1352
  behavior: { ...DEFAULT_BEHAVIOR, multicolor: { ...DEFAULT_MULTI_COLOR, colors: [] } },
1319
- blender: { ..._chunkNIBFKXMDcjs.DEFAULT_BLENDER },
1320
- smudge: { ..._chunkNIBFKXMDcjs.DEFAULT_SMUDGE },
1353
+ blender: { ..._chunkIFWLZZCWcjs.DEFAULT_BLENDER },
1354
+ smudge: { ..._chunkIFWLZZCWcjs.DEFAULT_SMUDGE },
1321
1355
  wet: { ...DEFAULT_WET_BRUSH },
1322
1356
  impasto: { ...DEFAULT_IMPASTO },
1323
1357
  particle: { ...DEFAULT_PARTICLE },
@@ -1352,6 +1386,23 @@ var ONE_STAMP = {
1352
1386
  toEngine: (v) => _nullishCoalesce(v[0], () => ( null)),
1353
1387
  toFacet: (v) => v ? [v] : void 0
1354
1388
  };
1389
+ var LIVE_PATTERNS_MAP = {
1390
+ toEngine: (v) => v !== null && typeof v === "object" ? JSON.parse(JSON.stringify(v)) : v,
1391
+ toFacet: (v) => {
1392
+ if (!v || typeof v !== "object") return void 0;
1393
+ const lp = v;
1394
+ const preset = _nullishCoalesce(_chunkIFWLZZCWcjs.LIVE_PATTERN_PRESETS[lp.pattern], () => ( _chunkIFWLZZCWcjs.LIVE_PATTERN_PRESETS.spots));
1395
+ return {
1396
+ pattern: lp.pattern,
1397
+ ...lp.feed !== preset.feed ? { feed: lp.feed } : {},
1398
+ ...lp.kill !== preset.kill ? { kill: lp.kill } : {},
1399
+ ...lp.scale !== _chunkIFWLZZCWcjs.DEFAULT_LIVE_PATTERNS.scale ? { scale: lp.scale } : {},
1400
+ ...lp.contrast !== _chunkIFWLZZCWcjs.DEFAULT_LIVE_PATTERNS.contrast ? { contrast: lp.contrast } : {},
1401
+ ...lp.secondColor !== null ? { secondColor: lp.secondColor } : {},
1402
+ ...lp.relief !== _chunkIFWLZZCWcjs.DEFAULT_LIVE_PATTERNS.relief ? { relief: lp.relief } : {}
1403
+ };
1404
+ }
1405
+ };
1355
1406
  var f = (facet, engine, engines = ALL, extra = {}) => ({ facet, engine, engines, ...extra });
1356
1407
  var each = (facetPrefix, enginePrefix, keys, engines) => keys.map((k) => f(`${facetPrefix}.${k}`, `${enginePrefix}.${k}`, engines));
1357
1408
  var BRUSH_FIELDS = [
@@ -1390,6 +1441,7 @@ var BRUSH_FIELDS = [
1390
1441
  f("behaviour.strength", "wet.strength", ["wet"]),
1391
1442
  // Surfaces.
1392
1443
  ...each("behaviour.wet", "wet", ["medium", "water", "pigment", "body", "rewet", "load", "thinner"], ["wet"]),
1444
+ f("livePatterns", "livePatterns", ["wet"], { map: LIVE_PATTERNS_MAP }),
1393
1445
  ...each("ghost", "ghost", ["linger", "smear", "trigger"], ["wet"]),
1394
1446
  ...each("mutation", "mutation", ["settle", "patience", "fade"], ["dab", "wet", "impasto", "particle", "bristle"]),
1395
1447
  f("behaviour.thick.applicator", "impasto.tool", ["impasto"]),
@@ -1486,6 +1538,7 @@ function resolveBrush(brush, options = {}) {
1486
1538
  if (brush.iridescence && FILM.includes(engine)) input.iridescence = { ..._nullishCoalesce(input.iridescence, () => ( {})) };
1487
1539
  if (engine === "wet") {
1488
1540
  setPath(input, "wet.tool", brush.kind === "tool" ? WET_TOOL_OF[brush.tool] : "paint");
1541
+ if (brush.kind === "tool") delete input.livePatterns;
1489
1542
  const medium = resolveWetMedium(brush);
1490
1543
  if (medium) setPath(input, "wet.medium", medium);
1491
1544
  if (brush.kind !== "tool" && brush.ghost) input.ghost = { ..._nullishCoalesce(input.ghost, () => ( {})) };
@@ -1574,7 +1627,7 @@ function skeleton(b) {
1574
1627
  }
1575
1628
  }
1576
1629
  }
1577
- var ORDER = ["id", "name", "description", "tags", "kind", "medium", "variety", "tool", "tip", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "motion", "colour"];
1630
+ var ORDER = ["id", "name", "description", "tags", "kind", "medium", "variety", "tool", "tip", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "motion", "livePatterns", "colour"];
1578
1631
  function migrateBrushV1(raw) {
1579
1632
  const b = normalizePaintBrush(raw);
1580
1633
  const brush = skeleton(b);
@@ -1621,7 +1674,7 @@ var STAMP = {
1621
1674
  pattern: BRUSH_STAMP_PATTERN,
1622
1675
  description: `A sticker ref provider:id (find_stickers lists them; the editor's own are ${_chunkRDFHWAHLcjs.STAMP_KINDS.map((k) => `editor:${k}`).join(", ")}), or an image source source:<id> (list_brush_sources lists them; add_brush_source makes one from an image). A bare editor stamp (star) means editor:star.`
1623
1676
  };
1624
- var TIP_GRAIN_PATTERNS = _chunkNIBFKXMDcjs.GRAIN_PATTERNS.filter((p) => p !== "smooth");
1677
+ var TIP_GRAIN_PATTERNS = _chunkIFWLZZCWcjs.GRAIN_PATTERNS.filter((p) => p !== "smooth");
1625
1678
  var BRUSH_GRAIN_SOURCE_PATTERN = `^(?:source:[a-z0-9][a-z0-9-]{0,63}|${TIP_GRAIN_PATTERNS.join("|")})$`;
1626
1679
  var TIP_GRAIN = object(
1627
1680
  "Texture inside the tip (a grain source), separate from the paper's grain: each dab is multiplied by it. Continuous and scatter marks of round and stamp tips.",
@@ -1647,7 +1700,7 @@ var TIP = object(
1647
1700
  "The tip: its shape and the dab it lays.",
1648
1701
  {
1649
1702
  shape: oneOf2(BRUSH_TIP_SHAPES, "round, flat, chisel: an ellipse set by roundness and angle (the name is for people); bristle: a tuft of bristles (see bristle); stamp: dabs shaped like a stamp, a sticker or an image source (see stamps), also laid as sprays, objects or particles."),
1650
- size: num(1, _chunkNIBFKXMDcjs.MAX_BRUSH_SIZE, "Diameter in px (default 20)."),
1703
+ size: num(1, _chunkIFWLZZCWcjs.MAX_BRUSH_SIZE, "Diameter in px (default 20)."),
1651
1704
  hardness: unit3("Edge hardness, 0 soft .. 1 hard (default 1). Image-source stamps fade towards their edges below 1."),
1652
1705
  spacing: num(0.01, 10, "Dab spacing as a fraction of the diameter: 0.05 smooth, 1 dotted (default 0.1)."),
1653
1706
  angle: num(-360, 360, "Tip angle in degrees, counter-clockwise; added to the stroke direction or barrel with dynamics.rotation (default 0)."),
@@ -1818,6 +1871,19 @@ var MOTION = object("Animation after painting.", {
1818
1871
  fps: int(1, 60, "Suggested playback rate in frames per second."),
1819
1872
  waveSpeed: unit3("Particle waves: how fast they run by stroke time; 0 they stand still (default 0), 1 about four a second.")
1820
1873
  });
1874
+ var LIVE_PATTERNS = object("Gray-Scott reaction-diffusion patterns (spots, stripes, coral, maze, worms) that grow inside wet paint (behaviour.surface: wet) and freeze where the paint dries.", {
1875
+ pattern: oneOf2(_chunkIFWLZZCWcjs.LIVE_PATTERN_KINDS, "Named Pearson feed/kill preset: spots (default, dividing mitosis spots), stripes (labyrinthine stripes), coral (branching dendrites), maze (winding channels) or worms (vermicular tracks)."),
1876
+ feed: num(0.01, 0.1, "Gray-Scott feed rate F (0.01..0.1; default from pattern)."),
1877
+ kill: num(0.03, 0.08, "Gray-Scott kill rate k (0.03..0.08; default from pattern)."),
1878
+ scale: num(0.5, 4, "Spatial scale of the pattern (diffusion multiplier, 0.5..4, default 1)."),
1879
+ contrast: num(0, 2, "Visual contrast of the V concentration field (0..2, default 1)."),
1880
+ secondColor: {
1881
+ type: ["string", "null"],
1882
+ pattern: "^#[0-9a-fA-F]{6}$",
1883
+ description: "Optional second colour (#rrggbb) that V blends the wet paint towards; null or omitted modulates pigment density/contrast."
1884
+ },
1885
+ relief: unit3("Impasto relief height written to the layer thick map as patterns grow (0..1, default 0).")
1886
+ });
1821
1887
  var SOLID = {
1822
1888
  type: "string",
1823
1889
  pattern: "^(#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})|rgba?\\(\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*(,\\s*(0|1|0?\\.\\d+)\\s*)?\\))$",
@@ -1847,7 +1913,7 @@ var EFFECT = object(
1847
1913
  );
1848
1914
  var PAINT = {
1849
1915
  description: "A colour a brush can carry: a solid colour, a light / dark pair, a gradient or effect paint.",
1850
- anyOf: [SOLID, DUAL, _chunkNIBFKXMDcjs.PAINT_GRADIENT_JSON_SCHEMA, EFFECT]
1916
+ anyOf: [SOLID, DUAL, _chunkIFWLZZCWcjs.PAINT_GRADIENT_JSON_SCHEMA, EFFECT]
1851
1917
  };
1852
1918
  var COLOUR = object("The brush's colours. The user's colour still wins unless own is set.", {
1853
1919
  default: {
@@ -1882,6 +1948,7 @@ var BRUSH_JSON_SCHEMA = object(
1882
1948
  ghost: GHOST,
1883
1949
  mutation: MUTATION,
1884
1950
  motion: MOTION,
1951
+ livePatterns: LIVE_PATTERNS,
1885
1952
  colour: COLOUR
1886
1953
  },
1887
1954
  ["id", "name", "kind", "tip"]
@@ -1899,6 +1966,7 @@ var BRUSH_FACET_SCHEMAS = {
1899
1966
  ghost: GHOST,
1900
1967
  mutation: MUTATION,
1901
1968
  motion: MOTION,
1969
+ livePatterns: LIVE_PATTERNS,
1902
1970
  colour: COLOUR,
1903
1971
  paint: PAINT
1904
1972
  };
@@ -2047,17 +2115,19 @@ function read(value, schema, path, problems) {
2047
2115
  problems.push(`${path} must be ${JSON.stringify(schema.const)}; dropped.`);
2048
2116
  return void 0;
2049
2117
  }
2050
- switch (schema.type) {
2118
+ if (value === null && Array.isArray(schema.type) && schema.type.includes("null")) return null;
2119
+ const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type;
2120
+ switch (schemaType) {
2051
2121
  case "number":
2052
2122
  case "integer": {
2053
2123
  if (typeof value !== "number" || !Number.isFinite(value)) {
2054
2124
  problems.push(`${path} must be a number; dropped.`);
2055
2125
  return void 0;
2056
2126
  }
2057
- let n = schema.type === "integer" ? Math.round(value) : value;
2127
+ let n = schemaType === "integer" ? Math.round(value) : value;
2058
2128
  if (schema.minimum !== void 0 && n < schema.minimum) n = schema.minimum;
2059
2129
  if (schema.maximum !== void 0 && n > schema.maximum) n = schema.maximum;
2060
- if (n !== value) problems.push(`${path} ${value} is out of range (${schema.minimum}..${schema.maximum}${schema.type === "integer" ? ", whole" : ""}); set to ${n}.`);
2130
+ if (n !== value) problems.push(`${path} ${value} is out of range (${schema.minimum}..${schema.maximum}${schemaType === "integer" ? ", whole" : ""}); set to ${n}.`);
2061
2131
  return n;
2062
2132
  }
2063
2133
  case "boolean":
@@ -2070,7 +2140,8 @@ function read(value, schema, path, problems) {
2070
2140
  problems.push(`${path} ${JSON.stringify(value)} is not valid${schema.enum ? `; use one of ${schema.enum.join(", ")}` : ""}; dropped.`);
2071
2141
  return void 0;
2072
2142
  }
2073
- return schema.maxLength !== void 0 ? value.slice(0, schema.maxLength) : value;
2143
+ const s = schema.pattern === "^#[0-9a-fA-F]{6}$" ? value.toLowerCase() : value;
2144
+ return schema.maxLength !== void 0 ? s.slice(0, schema.maxLength) : s;
2074
2145
  }
2075
2146
  case "array": {
2076
2147
  if (!Array.isArray(value)) {
@@ -2117,7 +2188,7 @@ function readPaint(value, path, problems) {
2117
2188
  if (typeof value === "string") {
2118
2189
  if (SOLID2.test(value.trim())) return value.trim().toLowerCase();
2119
2190
  } else if (isObject(value) && value.type === "gradient") {
2120
- const g = _chunkNIBFKXMDcjs.readPaintSource.call(void 0, value);
2191
+ const g = _chunkIFWLZZCWcjs.readPaintSource.call(void 0, value);
2121
2192
  if (g && g.type === "gradient") return g;
2122
2193
  } else if (isObject(value) && value.type === "effect") {
2123
2194
  if (!_chunkWOFXZ3XUcjs.validateJsonSchema.call(void 0, value, BRUSH_FACET_SCHEMAS.paint.anyOf[3]).length) return clone(value);
@@ -2145,8 +2216,8 @@ function normalizeBrushSpec(raw, problems = []) {
2145
2216
  if (_optionalChain([out, 'access', _29 => _29.thermochromic, 'optionalAccess', _30 => _30.hotColor])) out.thermochromic.hotColor = out.thermochromic.hotColor.toLowerCase();
2146
2217
  const brush = { id: raw.id, name, ...out, kind, ...tool ? { tool } : {}, tip: _nullishCoalesce(out.tip, () => ( { shape: "round" })) };
2147
2218
  const ordered = () => {
2148
- const { id, name: n, description, tags, kind: k, medium, variety, tool: t, tip, marks, dynamics, behaviour, light, fluorescence, thermochromic, iridescence, ghost, mutation, motion, colour } = brush;
2149
- const all = { id, name: n, description, tags, kind: k, medium, variety, tool: t, tip, marks, dynamics, behaviour, light, fluorescence, thermochromic, iridescence, ghost, mutation, motion, colour };
2219
+ const { id, name: n, description, tags, kind: k, medium, variety, tool: t, tip, marks, dynamics, behaviour, light, fluorescence, thermochromic, iridescence, ghost, mutation, motion, livePatterns, colour } = brush;
2220
+ const all = { id, name: n, description, tags, kind: k, medium, variety, tool: t, tip, marks, dynamics, behaviour, light, fluorescence, thermochromic, iridescence, ghost, mutation, motion, livePatterns, colour };
2150
2221
  return Object.fromEntries(Object.entries(all).filter(([, v]) => v !== void 0));
2151
2222
  };
2152
2223
  if (!out.tip) problems.push("tip is missing; a round tip is used.");
@@ -2279,8 +2350,12 @@ function normalizeBrushSpec(raw, problems = []) {
2279
2350
  delete b.exchange;
2280
2351
  }
2281
2352
  }
2353
+ if (brush.livePatterns && (kind !== "mark" || _optionalChain([brush, 'access', _52 => _52.behaviour, 'optionalAccess', _53 => _53.surface]) !== "wet" || _optionalChain([brush, 'access', _54 => _54.marks, 'optionalAccess', _55 => _55.kind]) === "particles" || _optionalChain([brush, 'access', _56 => _56.marks, 'optionalAccess', _57 => _57.kind]) === "objects" || brush.tip.shape === "bristle")) {
2354
+ drop("livePatterns", "only wet paint brushes grow live patterns (set behaviour.surface to wet)");
2355
+ delete brush.livePatterns;
2356
+ }
2282
2357
  if (brush.ghost) {
2283
- if (kind === "tool" || _optionalChain([brush, 'access', _52 => _52.behaviour, 'optionalAccess', _53 => _53.surface]) !== "wet") {
2358
+ if (kind === "tool" || _optionalChain([brush, 'access', _58 => _58.behaviour, 'optionalAccess', _59 => _59.surface]) !== "wet") {
2284
2359
  drop("ghost", "it needs a mark brush with behaviour.surface wet");
2285
2360
  delete brush.ghost;
2286
2361
  } else {
@@ -2288,7 +2363,7 @@ function normalizeBrushSpec(raw, problems = []) {
2288
2363
  drop("light", "ghost paint uses the wet light channel as its tracer");
2289
2364
  delete brush.light;
2290
2365
  }
2291
- if (_optionalChain([brush, 'access', _54 => _54.behaviour, 'optionalAccess', _55 => _55.exchange])) {
2366
+ if (_optionalChain([brush, 'access', _60 => _60.behaviour, 'optionalAccess', _61 => _61.exchange])) {
2292
2367
  drop("behaviour.exchange", "ghost paint does not pick paint up");
2293
2368
  delete brush.behaviour.exchange;
2294
2369
  }
@@ -2318,6 +2393,7 @@ var PAINT_PROFILE = {
2318
2393
  tilt: "full",
2319
2394
  tipGrain: "full",
2320
2395
  dabCount: "full",
2396
+ livePatterns: "full",
2321
2397
  iridescence: "full"
2322
2398
  };
2323
2399
  var IMAGE_PROFILE = {
@@ -2386,27 +2462,28 @@ function checkBrush(brush, profile) {
2386
2462
  const image = brush.tip.shape === "stamp" && (_nullishCoalesce(brush.tip.stamps, () => ( []))).some(isBrushSourceRef);
2387
2463
  add("tip.shape", "error", `${host} can't lay a ${image ? "stamp tip (image tips are stamps)" : `${brush.tip.shape} tip`}.`, `Set tip.shape to one of: ${list(profile.tipShapes)}.`);
2388
2464
  }
2389
- const marks = _nullishCoalesce(_optionalChain([brush, 'access', _56 => _56.marks, 'optionalAccess', _57 => _57.kind]), () => ( "continuous"));
2465
+ const marks = _nullishCoalesce(_optionalChain([brush, 'access', _62 => _62.marks, 'optionalAccess', _63 => _63.kind]), () => ( "continuous"));
2390
2466
  if (!profile.marks.includes(marks)) add("marks", "error", `${host} can't lay ${marks} marks.`, `Use marks ${list(profile.marks)} (remove marks for continuous dabs).`);
2391
- const surface = _nullishCoalesce(_optionalChain([brush, 'access', _58 => _58.behaviour, 'optionalAccess', _59 => _59.surface]), () => ( "dry"));
2467
+ const surface = _nullishCoalesce(_optionalChain([brush, 'access', _64 => _64.behaviour, 'optionalAccess', _65 => _65.surface]), () => ( "dry"));
2392
2468
  if (brush.kind === "mark" && !profile.surfaces.includes(surface)) add("behaviour.surface", "error", `${host} has no ${surface} surface.`, `Set behaviour.surface to ${list(profile.surfaces)}.`);
2393
- const ex = _optionalChain([brush, 'access', _60 => _60.behaviour, 'optionalAccess', _61 => _61.exchange]);
2394
- feature(profile.reservoir, !!_optionalChain([ex, 'optionalAccess', _62 => _62.reservoir]), "behaviour.exchange.reservoir", "a Mixer Brush reservoir", "Remove behaviour.exchange.reservoir.");
2395
- feature(profile.exchange, !_optionalChain([ex, 'optionalAccess', _63 => _63.reservoir]) && (nonZero(_optionalChain([ex, 'optionalAccess', _64 => _64.pickup])) || !!_optionalChain([ex, 'optionalAccess', _65 => _65.dirty])), "behaviour.exchange", "picking paint up", "Remove behaviour.exchange.");
2396
- feature(profile.grain, nonZero(_optionalChain([brush, 'access', _66 => _66.behaviour, 'optionalAccess', _67 => _67.grain])), "behaviour.grain", "paper grain", "Set behaviour.grain to 0.");
2397
- feature(profile.light, nonZero(_optionalChain([brush, 'access', _68 => _68.light, 'optionalAccess', _69 => _69.amount])), "light", "light paint (glow and spill)", "Remove light (or set light.amount to 0).");
2469
+ const ex = _optionalChain([brush, 'access', _66 => _66.behaviour, 'optionalAccess', _67 => _67.exchange]);
2470
+ feature(profile.reservoir, !!_optionalChain([ex, 'optionalAccess', _68 => _68.reservoir]), "behaviour.exchange.reservoir", "a Mixer Brush reservoir", "Remove behaviour.exchange.reservoir.");
2471
+ feature(profile.exchange, !_optionalChain([ex, 'optionalAccess', _69 => _69.reservoir]) && (nonZero(_optionalChain([ex, 'optionalAccess', _70 => _70.pickup])) || !!_optionalChain([ex, 'optionalAccess', _71 => _71.dirty])), "behaviour.exchange", "picking paint up", "Remove behaviour.exchange.");
2472
+ feature(profile.grain, nonZero(_optionalChain([brush, 'access', _72 => _72.behaviour, 'optionalAccess', _73 => _73.grain])), "behaviour.grain", "paper grain", "Set behaviour.grain to 0.");
2473
+ feature(profile.light, nonZero(_optionalChain([brush, 'access', _74 => _74.light, 'optionalAccess', _75 => _75.amount])), "light", "light paint (glow and spill)", "Remove light (or set light.amount to 0).");
2474
+ feature(_nullishCoalesce(profile.livePatterns, () => ( "none")), Boolean(brush.livePatterns), "livePatterns", "live reaction-diffusion patterns", "Remove livePatterns.");
2398
2475
  const smartInks = profile.id === "paint" ? "full" : "none";
2399
- feature(smartInks, nonZero(_optionalChain([brush, 'access', _70 => _70.fluorescence, 'optionalAccess', _71 => _71.amount])), "fluorescence", "UV-fluorescent ink", "Remove fluorescence (or set fluorescence.amount to 0).");
2400
- feature(smartInks, Boolean(_optionalChain([brush, 'access', _72 => _72.thermochromic, 'optionalAccess', _73 => _73.hotColor])), "thermochromic", "thermochromic ink", "Remove thermochromic.");
2476
+ feature(smartInks, nonZero(_optionalChain([brush, 'access', _76 => _76.fluorescence, 'optionalAccess', _77 => _77.amount])), "fluorescence", "UV-fluorescent ink", "Remove fluorescence (or set fluorescence.amount to 0).");
2477
+ feature(smartInks, Boolean(_optionalChain([brush, 'access', _78 => _78.thermochromic, 'optionalAccess', _79 => _79.hotColor])), "thermochromic", "thermochromic ink", "Remove thermochromic.");
2401
2478
  feature(_nullishCoalesce(profile.iridescence, () => ( "none")), !!brush.iridescence, "iridescence", "thin-film iridescence", "Remove iridescence.");
2402
2479
  feature(profile.surfaces.includes("wet") && profile.light === "full" ? "full" : "none", !!brush.ghost, "ghost", "ghost paint", "Remove ghost.");
2403
2480
  if (brush.mutation) {
2404
2481
  if (profile.id !== "paint") feature("none", true, "mutation", "mutation paint", "Remove mutation.");
2405
2482
  else if (brush.kind === "tool") add("mutation", "error", "Tools work on paint already down and cannot be mutation brushes.", "Use a mark brush, or remove mutation.");
2406
- else if (_optionalChain([brush, 'access', _74 => _74.marks, 'optionalAccess', _75 => _75.kind]) === "objects") add("mutation", "error", "Sticker brushes place objects rather than paint strokes and cannot be mutation brushes.", "Remove marks (or use continuous marks), or remove mutation.");
2407
- else if (_optionalChain([ex, 'optionalAccess', _76 => _76.reservoir])) add("mutation", "error", "A Mixer Brush reads paint from the layer beneath and cannot be a mutation brush.", "Remove behaviour.exchange.reservoir, or remove mutation.");
2408
- else if (nonZero(_optionalChain([ex, 'optionalAccess', _77 => _77.pickup])) || !!_optionalChain([ex, 'optionalAccess', _78 => _78.dirty])) add("mutation", "error", "Mutation brushes must lay their own colour without picking up paint from beneath.", "Remove behaviour.exchange (or set pickup to 0 and dirty to false), or remove mutation.");
2409
- else if (surface === "wet" && nonZero(_optionalChain([brush, 'access', _79 => _79.light, 'optionalAccess', _80 => _80.amount]))) add("mutation", "error", "Wet mutation brushes use the wet light channel as their fade tracer.", "Remove light (or set light.amount to 0), or remove mutation.");
2483
+ else if (_optionalChain([brush, 'access', _80 => _80.marks, 'optionalAccess', _81 => _81.kind]) === "objects") add("mutation", "error", "Sticker brushes place objects rather than paint strokes and cannot be mutation brushes.", "Remove marks (or use continuous marks), or remove mutation.");
2484
+ else if (_optionalChain([ex, 'optionalAccess', _82 => _82.reservoir])) add("mutation", "error", "A Mixer Brush reads paint from the layer beneath and cannot be a mutation brush.", "Remove behaviour.exchange.reservoir, or remove mutation.");
2485
+ else if (nonZero(_optionalChain([ex, 'optionalAccess', _83 => _83.pickup])) || !!_optionalChain([ex, 'optionalAccess', _84 => _84.dirty])) add("mutation", "error", "Mutation brushes must lay their own colour without picking up paint from beneath.", "Remove behaviour.exchange (or set pickup to 0 and dirty to false), or remove mutation.");
2486
+ else if (surface === "wet" && nonZero(_optionalChain([brush, 'access', _85 => _85.light, 'optionalAccess', _86 => _86.amount]))) add("mutation", "error", "Wet mutation brushes use the wet light channel as their fade tracer.", "Remove light (or set light.amount to 0), or remove mutation.");
2410
2487
  }
2411
2488
  const m = brush.motion;
2412
2489
  feature(profile.motion, !!m && (!!m.live || nonZero(m.waveSpeed) || m.fps !== void 0), "motion", "animation", "Remove motion.");
@@ -2416,16 +2493,16 @@ function checkBrush(brush, profile) {
2416
2493
  feature(profile.velocity, nonZero(d.velocitySize) || nonZero(d.velocityOpacity), "dynamics.velocity", "velocity dynamics (its strokes carry no timing)", "Set dynamics.velocitySize and velocityOpacity to 0.");
2417
2494
  feature(profile.tilt, nonZero(d.tiltSize) || nonZero(d.tiltRoundness) || nonZero(d.tiltOpacity), "dynamics.tilt", "tilt dynamics", "Set dynamics.tiltSize, tiltRoundness and tiltOpacity to 0.");
2418
2495
  const colour = brush.colour;
2419
- if (_optionalChain([colour, 'optionalAccess', _81 => _81.default]) !== void 0) {
2496
+ if (_optionalChain([colour, 'optionalAccess', _87 => _87.default]) !== void 0) {
2420
2497
  const kind = brushColourKind(colour.default);
2421
2498
  if (!profile.colours.includes(kind)) add("colour.default", "error", `${host} can't paint ${kind} colours.`, `Use a ${list(profile.colours)} colour, or remove colour.default.`);
2422
2499
  }
2423
- const foreign = [...new Set((_nullishCoalesce(_optionalChain([colour, 'optionalAccess', _82 => _82.palette]), () => ( []))).map(brushColourKind).filter((k) => !profile.colours.includes(k)))];
2500
+ const foreign = [...new Set((_nullishCoalesce(_optionalChain([colour, 'optionalAccess', _88 => _88.palette]), () => ( []))).map(brushColourKind).filter((k) => !profile.colours.includes(k)))];
2424
2501
  if (foreign.length) add("colour.palette", "warning", `${host} skips the palette's ${list(foreign)} colours.`, `Nothing to do; or keep to ${list(profile.colours)} colours.`);
2425
2502
  return { ok: !issues.some((i) => i.severity === "error"), issues };
2426
2503
  }
2427
2504
  function withKeptIssues(check, before) {
2428
- const had = new Set((_nullishCoalesce(_optionalChain([before, 'optionalAccess', _83 => _83.issues]), () => ( []))).filter((i) => i.severity === "error").map((i) => i.facet));
2505
+ const had = new Set((_nullishCoalesce(_optionalChain([before, 'optionalAccess', _89 => _89.issues]), () => ( []))).filter((i) => i.severity === "error").map((i) => i.facet));
2429
2506
  const kept = check.issues.filter((i) => i.severity === "error" && had.has(i.facet));
2430
2507
  const issues = check.issues.filter((i) => !kept.includes(i));
2431
2508
  return { ok: !issues.some((i) => i.severity === "error"), issues, kept };
@@ -2458,8 +2535,12 @@ function degrade(brush, profile, options = {}) {
2458
2535
  delete b.behaviour.surface;
2459
2536
  delete b.behaviour.wet;
2460
2537
  delete b.behaviour.thick;
2538
+ delete b.livePatterns;
2461
2539
  delete b.ghost;
2462
2540
  break;
2541
+ case "livePatterns":
2542
+ delete b.livePatterns;
2543
+ break;
2463
2544
  case "ghost":
2464
2545
  delete b.ghost;
2465
2546
  break;
@@ -2517,7 +2598,7 @@ function degrade(brush, profile, options = {}) {
2517
2598
  }
2518
2599
  dropped.push(issue);
2519
2600
  }
2520
- if (_optionalChain([b, 'access', _84 => _84.behaviour, 'optionalAccess', _85 => _85.exchange]) && !Object.keys(b.behaviour.exchange).length) delete b.behaviour.exchange;
2601
+ if (_optionalChain([b, 'access', _90 => _90.behaviour, 'optionalAccess', _91 => _91.exchange]) && !Object.keys(b.behaviour.exchange).length) delete b.behaviour.exchange;
2521
2602
  if (b.colour && !Object.keys(b.colour).length) delete b.colour;
2522
2603
  const after = checkBrush(b, profile).issues.filter((i) => i.severity === "error");
2523
2604
  return { brush: b, dropped, issues: after };
@@ -2561,7 +2642,7 @@ function parseBrushLibrary2(raw, problems = []) {
2561
2642
  if (file && file.version !== void 0 && file.version !== 1 && file.version !== 2) {
2562
2643
  throw new BrushLibraryError("invalid", `Brush library version ${JSON.stringify(file.version)} is not supported; this library reads versions 1 and 2.`);
2563
2644
  }
2564
- const v1File = _optionalChain([file, 'optionalAccess', _86 => _86.version]) === 1;
2645
+ const v1File = _optionalChain([file, 'optionalAccess', _92 => _92.version]) === 1;
2565
2646
  const out = [];
2566
2647
  items.forEach((item, i) => {
2567
2648
  const at = `brushes[${i}]`;
@@ -2822,7 +2903,7 @@ function snapshotBrushLibrary(library, onChange) {
2822
2903
  (list2) => {
2823
2904
  entries = list2;
2824
2905
  user = list2.filter((e) => !e.builtin).map((e) => e.brush);
2825
- _optionalChain([onChange, 'optionalCall', _87 => _87()]);
2906
+ _optionalChain([onChange, 'optionalCall', _93 => _93()]);
2826
2907
  },
2827
2908
  () => void 0
2828
2909
  );
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunk6QNDGMK7cjs = require('./chunk-6QNDGMK7.cjs');
3
+ var _chunkQ33V5PEFcjs = require('./chunk-Q33V5PEF.cjs');
4
4
 
5
5
 
6
6
  var _chunkJBZRVLXVcjs = require('./chunk-JBZRVLXV.cjs');
@@ -44,7 +44,7 @@ function brushPreviewStrength(brush) {
44
44
  }
45
45
  var over = (fg, bg, alpha) => ({ r: fg.r * alpha + bg.r * (1 - alpha), g: fg.g * alpha + bg.g * (1 - alpha), b: fg.b * alpha + bg.b * (1 - alpha), a: 1 });
46
46
  function brushPreviewColours(brush, options = {}) {
47
- const profile = _nullishCoalesce(options.profile, () => ( _chunk6QNDGMK7cjs.PAINT_PROFILE));
47
+ const profile = _nullishCoalesce(options.profile, () => ( _chunkQ33V5PEFcjs.PAINT_PROFILE));
48
48
  let background = profile.light === "full" && brushPreviewsOnDark(brush) ? BRUSH_PREVIEW_DARK_PAPER : hexOf(_nullishCoalesce(options.paper, () => ( "#ffffff")));
49
49
  if (!options.keepPaper && brush.tip.blend === "multiply" && _chunk5UEFBM2Mcjs.relativeLuminance.call(void 0, _chunk5UEFBM2Mcjs.parseColor.call(void 0, background)) < 0.18) background = "#ffffff";
50
50
  const bg = _chunk5UEFBM2Mcjs.parseColor.call(void 0, background);