@pixerate/editor 0.280.0 → 0.282.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/animation-editor/index.cjs +59 -59
  2. package/dist/animation-editor/index.js +3 -3
  3. package/dist/brushes/index.cjs +76 -71
  4. package/dist/brushes/index.js +13 -8
  5. package/dist/brushes/schema.d.cts +1 -0
  6. package/dist/brushes/schema.d.ts +1 -0
  7. package/dist/brushes/types.d.cts +29 -3
  8. package/dist/brushes/types.d.ts +29 -3
  9. package/dist/{brushstrokeRender-EIK3625T.js → brushstrokeRender-4MA5CBW3.js} +3 -3
  10. package/dist/{brushstrokeRender-7HOMKZNO.cjs → brushstrokeRender-JVKVKWDA.cjs} +15 -15
  11. package/dist/canvas/edge-animation-tools.d.cts +115 -0
  12. package/dist/canvas/edge-animation-tools.d.ts +115 -0
  13. package/dist/canvas/edgeAnimation.d.cts +164 -0
  14. package/dist/canvas/edgeAnimation.d.ts +164 -0
  15. package/dist/canvas/edgePaths.d.cts +63 -0
  16. package/dist/canvas/edgePaths.d.ts +63 -0
  17. package/dist/canvas/index.cjs +691 -21
  18. package/dist/canvas/index.d.cts +3 -0
  19. package/dist/canvas/index.d.ts +3 -0
  20. package/dist/canvas/index.js +679 -9
  21. package/dist/{chunk-NJDEKU5D.js → chunk-2YGYYVSG.js} +1 -1
  22. package/dist/{chunk-JVFGK3DK.cjs → chunk-3JUKN3RM.cjs} +76 -53
  23. package/dist/{chunk-HN57I7KT.js → chunk-CG3FBNXE.js} +1 -1
  24. package/dist/{chunk-WFSYZO5Z.cjs → chunk-DFZ4I7EZ.cjs} +1 -1
  25. package/dist/{chunk-LZE2QZCT.js → chunk-IHMMHZGP.js} +1 -1
  26. package/dist/{chunk-K4ANWBF7.cjs → chunk-IJQ2YH2M.cjs} +35 -35
  27. package/dist/{chunk-HRUK5TFW.cjs → chunk-IUKPQCYQ.cjs} +164 -71
  28. package/dist/{chunk-WCC4NXWY.cjs → chunk-JD63B33V.cjs} +86 -86
  29. package/dist/{chunk-NJLPW4MM.js → chunk-KV7QHPKL.js} +27 -4
  30. package/dist/{chunk-H3EME3S5.cjs → chunk-MRIAR5DY.cjs} +2 -2
  31. package/dist/{chunk-ROURHJYT.js → chunk-NEDS6DSC.js} +1 -1
  32. package/dist/{chunk-FKO3CY2C.js → chunk-ULPXJ3NB.js} +1 -1
  33. package/dist/{chunk-WZYRYYXN.cjs → chunk-UOM5S4W5.cjs} +12 -12
  34. package/dist/{chunk-YOM76H2V.js → chunk-VEX6YVNG.js} +1 -1
  35. package/dist/{chunk-MNHP3AQT.cjs → chunk-WEXWDBUF.cjs} +23 -23
  36. package/dist/{chunk-PPDUCZZV.js → chunk-YRGTQLDN.js} +122 -29
  37. package/dist/image-editor/index.cjs +788 -788
  38. package/dist/image-editor/index.js +4 -4
  39. package/dist/paint-editor/antimatter.d.cts +154 -0
  40. package/dist/paint-editor/antimatter.d.ts +154 -0
  41. package/dist/paint-editor/brush-controls.d.cts +1 -0
  42. package/dist/paint-editor/brush-controls.d.ts +1 -0
  43. package/dist/paint-editor/brush-model.d.cts +9 -5
  44. package/dist/paint-editor/brush-model.d.ts +9 -5
  45. package/dist/paint-editor/controller.d.cts +23 -0
  46. package/dist/paint-editor/controller.d.ts +23 -0
  47. package/dist/paint-editor/index.cjs +1931 -891
  48. package/dist/paint-editor/index.d.cts +1 -0
  49. package/dist/paint-editor/index.d.ts +1 -0
  50. package/dist/paint-editor/index.js +1098 -58
  51. package/dist/paint-editor/mark-settings.d.cts +30 -0
  52. package/dist/paint-editor/mark-settings.d.ts +30 -0
  53. package/dist/paint-editor/stroke.d.cts +16 -0
  54. package/dist/paint-editor/stroke.d.ts +16 -0
  55. package/dist/paint-editor/strokes.d.cts +2 -0
  56. package/dist/paint-editor/strokes.d.ts +2 -0
  57. package/dist/paint-editor/ui.d.cts +4 -2
  58. package/dist/paint-editor/ui.d.ts +4 -2
  59. package/dist/vector/index.cjs +999 -999
  60. package/dist/vector/index.js +4 -4
  61. package/package.json +1 -1
  62. package/skills/pixerate-brushes/SKILL.md +1 -1
  63. package/skills/pixerate-canvas/SKILL.md +25 -2
  64. package/skills/pixerate-paint-editor/SKILL.md +8 -2
  65. package/skills/pixerate-paint-editor/references/antimatter.md +49 -0
  66. package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
@@ -955,11 +955,47 @@ var BRUSH_PRESETS = [
955
955
  dynamics: { smoothing: 0.45, minSize: 0.25, velocitySize: -0.25 },
956
956
  ferro: { amount: 0.7, wavelength: 12, threshold: 0.15 },
957
957
  colour: { default: "#0f172a" }
958
+ },
959
+ // --- Antimatter paint: blasts that invert, scorch and destroy paint across visible layers ---
960
+ {
961
+ id: "antimatter",
962
+ name: "Antimatter",
963
+ kind: "mark",
964
+ medium: "paint",
965
+ variety: "antimatter",
966
+ tip: { shape: "round", size: 24, hardness: 0.75, spacing: 0.12 },
967
+ marks: { kind: "antimatter" },
968
+ dynamics: { smoothing: 0.25, minSize: 0.4 },
969
+ colour: { default: "#38bdf8", palette: ["#38bdf8", "#f472b6", "#a855f7", "#fb923c", "#4ade80"] }
970
+ },
971
+ {
972
+ id: "antimatter-flare",
973
+ name: "Antimatter flare",
974
+ kind: "mark",
975
+ medium: "paint",
976
+ variety: "antimatter",
977
+ tip: { shape: "round", size: 18, hardness: 0.7, spacing: 0.08 },
978
+ marks: { kind: "antimatter", blastRadius: 2, blastSpacing: 1.4, invert: 0.75, invertMode: "oklab", scorch: 0.35, annihilate: 0.15, sparks: 0.95, sparkReach: 5, crater: 0.3 },
979
+ dynamics: { smoothing: 0.2, minSize: 0.4 },
980
+ light: { amount: 0.9 },
981
+ colour: { default: "#f472b6", palette: ["#f472b6", "#38bdf8", "#facc15", "#a855f7", "#4ade80"] }
982
+ },
983
+ {
984
+ id: "antimatter-charge",
985
+ name: "Antimatter charge",
986
+ kind: "mark",
987
+ medium: "paint",
988
+ variety: "antimatter",
989
+ tip: { shape: "round", size: 32, hardness: 0.85, spacing: 0.15 },
990
+ marks: { kind: "antimatter", blastRadius: 4.5, blastSpacing: 4.5, threshold: 0.2, invertMode: "oklab", scorch: 0.8, annihilate: 0.6, sparks: 0.65, sparkReach: 4, crater: 0.85, duration: 1 },
991
+ dynamics: { smoothing: 0.3, minSize: 0.5 },
992
+ colour: { default: "#fb923c", palette: ["#fb923c", "#ef4444", "#38bdf8", "#a855f7"] }
958
993
  }
959
994
  ];
960
995
 
961
996
  // src/paint-editor/mark-settings.ts
962
997
  var unit = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
998
+ var range = (v, lo, hi, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : d;
963
999
  var oneOf = (v, all, d) => all.includes(v) ? v : d;
964
1000
  var MAX_STICKER_STAMPS = 16;
965
1001
  var DEFAULT_STICKER = { stamps: ["editor:star"], density: 0.4, sizeJitter: 0.4, rotationJitter: 1, scatter: 0.5, colorJitter: 0.2 };
@@ -1051,9 +1087,38 @@ function normalizeBristle(raw = {}, base = DEFAULT_BRISTLE) {
1051
1087
  jitter: unit(raw.jitter, base.jitter)
1052
1088
  };
1053
1089
  }
1090
+ var INVERT_MODES = ["rgb", "oklab"];
1091
+ var DEFAULT_ANTIMATTER = {
1092
+ blastRadius: 2.5,
1093
+ blastSpacing: 2,
1094
+ threshold: 0.1,
1095
+ invert: 1,
1096
+ invertMode: "rgb",
1097
+ scorch: 0.6,
1098
+ annihilate: 0.25,
1099
+ sparks: 0.5,
1100
+ sparkReach: 3,
1101
+ crater: 0.5,
1102
+ duration: 0.7
1103
+ };
1104
+ function normalizeAntimatter(raw = {}, base = DEFAULT_ANTIMATTER) {
1105
+ return {
1106
+ blastRadius: range(raw.blastRadius, 1, 6, base.blastRadius),
1107
+ blastSpacing: range(raw.blastSpacing, 0.5, 8, base.blastSpacing),
1108
+ threshold: unit(raw.threshold, base.threshold),
1109
+ invert: unit(raw.invert, base.invert),
1110
+ invertMode: oneOf(raw.invertMode, INVERT_MODES, base.invertMode),
1111
+ scorch: unit(raw.scorch, base.scorch),
1112
+ annihilate: unit(raw.annihilate, base.annihilate),
1113
+ sparks: unit(raw.sparks, base.sparks),
1114
+ sparkReach: range(raw.sparkReach, 0.5, 8, base.sparkReach),
1115
+ crater: unit(raw.crater, base.crater),
1116
+ duration: range(raw.duration, 0, 2, base.duration)
1117
+ };
1118
+ }
1054
1119
 
1055
1120
  // src/paint-editor/brush-model.ts
1056
- var PAINT_BRUSH_ENGINES = ["dab", "eraser", "mixer", "blender", "smudge", "wet", "impasto", "particle", "bristle", "sticker"];
1121
+ var PAINT_BRUSH_ENGINES = ["dab", "eraser", "mixer", "blender", "smudge", "wet", "impasto", "particle", "bristle", "sticker", "antimatter"];
1057
1122
  var PAINT_BRUSH_CATEGORIES = ["pencil", "pastel", "charcoal", "airbrush", "marker", "ink", "paint", "eraser", "blender", "smudge", "mixer", "watercolor", "wet-tool", "oil", "impasto", "procedural", "bristle", "sticker", "light", "custom"];
1058
1123
  var WET_TOOLS = ["paint", "water", "dry", "blow", "blend", "smudge", "blot"];
1059
1124
  var WET_MEDIA = ["watercolor", "gouache", "oil"];
@@ -1090,7 +1155,7 @@ function normalizeThermochromic(raw = {}, base = DEFAULT_THERMOCHROMIC) {
1090
1155
  const hotColor = raw.hotColor === null ? null : typeof raw.hotColor === "string" && HEX.test(raw.hotColor.trim()) ? raw.hotColor.trim().toLowerCase() : base.hotColor;
1091
1156
  return {
1092
1157
  hotColor,
1093
- threshold: range(raw.threshold, 0.05, 0.95, base.threshold)
1158
+ threshold: range2(raw.threshold, 0.05, 0.95, base.threshold)
1094
1159
  };
1095
1160
  }
1096
1161
  var DEFAULT_IRIDESCENCE = { thickness: 400, pressureToThickness: 0, ior: 1.5 };
@@ -1101,9 +1166,9 @@ function readIridescence(raw, base) {
1101
1166
  const r = raw;
1102
1167
  const b = base ?? DEFAULT_IRIDESCENCE;
1103
1168
  return {
1104
- thickness: range(r.thickness, 100, 1e3, b.thickness),
1169
+ thickness: range2(r.thickness, 100, 1e3, b.thickness),
1105
1170
  pressureToThickness: unit2(r.pressureToThickness, b.pressureToThickness),
1106
- ior: range(r.ior, 1.2, 2, b.ior)
1171
+ ior: range2(r.ior, 1.2, 2, b.ior)
1107
1172
  };
1108
1173
  }
1109
1174
  var GHOST_TRIGGERS = ["immediate", "agent"];
@@ -1111,7 +1176,7 @@ var DEFAULT_GHOST = { linger: 4, smear: 0.3 };
1111
1176
  function normalizeGhost(raw = {}, base = DEFAULT_GHOST) {
1112
1177
  const trigger = raw.trigger === "agent" || raw.trigger === "immediate" ? raw.trigger : base.trigger;
1113
1178
  return {
1114
- linger: range(raw.linger, 0.5, 60, base.linger),
1179
+ linger: range2(raw.linger, 0.5, 60, base.linger),
1115
1180
  smear: unit2(raw.smear, base.smear),
1116
1181
  ...trigger === "agent" ? { trigger: "agent" } : {}
1117
1182
  };
@@ -1119,9 +1184,9 @@ function normalizeGhost(raw = {}, base = DEFAULT_GHOST) {
1119
1184
  var DEFAULT_MUTATION = { settle: 1.5, patience: 45, fade: 2 };
1120
1185
  function normalizeMutation(raw = {}, base = DEFAULT_MUTATION) {
1121
1186
  return {
1122
- settle: range(raw.settle, 0.5, 10, base.settle),
1123
- patience: range(raw.patience, 5, 300, base.patience),
1124
- fade: range(raw.fade, 0.5, 10, base.fade)
1187
+ settle: range2(raw.settle, 0.5, 10, base.settle),
1188
+ patience: range2(raw.patience, 5, 300, base.patience),
1189
+ fade: range2(raw.fade, 0.5, 10, base.fade)
1125
1190
  };
1126
1191
  }
1127
1192
  var DEFAULT_FERRO = { amount: 0, wavelength: 16, threshold: 0.12 };
@@ -1132,8 +1197,8 @@ function normalizeFerro(raw = {}, base = DEFAULT_FERRO) {
1132
1197
  const r = raw ?? {};
1133
1198
  return {
1134
1199
  amount: unit2(r.amount, base.amount),
1135
- wavelength: range(r.wavelength, 4, 64, base.wavelength),
1136
- threshold: range(r.threshold, 0.01, 0.95, base.threshold)
1200
+ wavelength: range2(r.wavelength, 4, 64, base.wavelength),
1201
+ threshold: range2(r.threshold, 0.01, 0.95, base.threshold)
1137
1202
  };
1138
1203
  }
1139
1204
  function normalizeImpasto(raw = {}, base = DEFAULT_IMPASTO) {
@@ -1170,7 +1235,7 @@ var DEFAULT_DYNAMICS = {
1170
1235
  scatter: 0
1171
1236
  };
1172
1237
  var unit2 = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
1173
- var range = (v, lo, hi, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : d;
1238
+ var range2 = (v, lo, hi, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : d;
1174
1239
  function normalizeDynamics(raw = {}, base = DEFAULT_DYNAMICS) {
1175
1240
  return {
1176
1241
  minSize: unit2(raw.minSize, base.minSize),
@@ -1178,12 +1243,12 @@ function normalizeDynamics(raw = {}, base = DEFAULT_DYNAMICS) {
1178
1243
  tiltSize: unit2(raw.tiltSize, base.tiltSize),
1179
1244
  tiltRoundness: unit2(raw.tiltRoundness, base.tiltRoundness),
1180
1245
  tiltOpacity: unit2(raw.tiltOpacity, base.tiltOpacity),
1181
- velocitySize: range(raw.velocitySize, -1, 1, base.velocitySize),
1182
- velocityOpacity: range(raw.velocityOpacity, -1, 1, base.velocityOpacity),
1246
+ velocitySize: range2(raw.velocitySize, -1, 1, base.velocitySize),
1247
+ velocityOpacity: range2(raw.velocityOpacity, -1, 1, base.velocityOpacity),
1183
1248
  sizeJitter: unit2(raw.sizeJitter, base.sizeJitter),
1184
1249
  angleJitter: unit2(raw.angleJitter, base.angleJitter),
1185
1250
  opacityJitter: unit2(raw.opacityJitter, base.opacityJitter),
1186
- scatter: range(raw.scatter, 0, 4, base.scatter),
1251
+ scatter: range2(raw.scatter, 0, 4, base.scatter),
1187
1252
  ...countAndFlip(raw, base)
1188
1253
  };
1189
1254
  }
@@ -1230,9 +1295,9 @@ function readTipGrain(raw, base) {
1230
1295
  return {
1231
1296
  source,
1232
1297
  mode: TIP_GRAIN_MODES.includes(r.mode) ? r.mode : b.mode,
1233
- scale: range(r.scale, 0.05, 20, b.scale),
1298
+ scale: range2(r.scale, 0.05, 20, b.scale),
1234
1299
  depth: unit2(r.depth, b.depth),
1235
- contrast: range(r.contrast, 0, 3, b.contrast),
1300
+ contrast: range2(r.contrast, 0, 3, b.contrast),
1236
1301
  invert: typeof r.invert === "boolean" ? r.invert : b.invert
1237
1302
  };
1238
1303
  }
@@ -1248,7 +1313,7 @@ function readSpray(raw, base) {
1248
1313
  if (!raw || typeof raw !== "object") return base;
1249
1314
  const r = raw;
1250
1315
  const b = base ?? DEFAULT_SPRAY;
1251
- return { density: unit2(r.density, b.density), size: range(r.size, 0.02, 1, b.size) };
1316
+ return { density: unit2(r.density, b.density), size: range2(r.size, 0.02, 1, b.size) };
1252
1317
  }
1253
1318
  function isWetCapable(brush) {
1254
1319
  return brush.engine === "dab" || brush.engine === "mixer" || brush.engine === "wet" || brush.engine === "impasto";
@@ -1345,7 +1410,7 @@ function normalizePaintBrush(raw = {}, base) {
1345
1410
  engine,
1346
1411
  tip: normalizeBrush({ ...b.tip, ...raw.tip ?? {} }),
1347
1412
  grain: unit2(raw.grain, b.grain),
1348
- pressureSmoothing: range(raw.pressureSmoothing, 0, 0.95, b.pressureSmoothing),
1413
+ pressureSmoothing: range2(raw.pressureSmoothing, 0, 0.95, b.pressureSmoothing),
1349
1414
  rotation: BRUSH_ROTATIONS.includes(raw.rotation) ? raw.rotation : b.rotation,
1350
1415
  dynamics: normalizeDynamics(raw.dynamics ?? {}, b.dynamics),
1351
1416
  mixer,
@@ -1358,6 +1423,7 @@ function normalizePaintBrush(raw = {}, base) {
1358
1423
  particle: normalizeParticle(raw.particle ?? {}, b.particle ?? DEFAULT_PARTICLE),
1359
1424
  bristle: normalizeBristle(raw.bristle ?? {}, b.bristle ?? DEFAULT_BRISTLE),
1360
1425
  sticker: normalizeStickerSettings(raw.sticker ?? {}, b.sticker ?? DEFAULT_STICKER),
1426
+ antimatter: normalizeAntimatter(raw.antimatter ?? {}, b.antimatter ?? DEFAULT_ANTIMATTER),
1361
1427
  light: ghost && !wantsLight ? { ...DEFAULT_LIGHT } : normalizeLight(raw.light ?? {}, b.light ?? DEFAULT_LIGHT),
1362
1428
  fluorescence: normalizeFluorescence(raw.fluorescence ?? {}, b.fluorescence ?? DEFAULT_FLUORESCENCE),
1363
1429
  thermochromic: normalizeThermochromic(raw.thermochromic ?? {}, b.thermochromic ?? DEFAULT_THERMOCHROMIC),
@@ -1393,6 +1459,7 @@ var BASE_BRUSH = {
1393
1459
  particle: { ...DEFAULT_PARTICLE },
1394
1460
  bristle: { ...DEFAULT_BRISTLE },
1395
1461
  sticker: { ...DEFAULT_STICKER, stamps: [...DEFAULT_STICKER.stamps] },
1462
+ antimatter: { ...DEFAULT_ANTIMATTER },
1396
1463
  light: { ...DEFAULT_LIGHT },
1397
1464
  fluorescence: { ...DEFAULT_FLUORESCENCE },
1398
1465
  thermochromic: { ...DEFAULT_THERMOCHROMIC },
@@ -1403,7 +1470,7 @@ var BASE_BRUSH = {
1403
1470
  };
1404
1471
 
1405
1472
  // src/brushes/resolve.ts
1406
- var ALL = ["dab", "eraser", "mixer", "blender", "smudge", "wet", "impasto", "particle", "bristle", "sticker"];
1473
+ var ALL = ["dab", "eraser", "mixer", "blender", "smudge", "wet", "impasto", "particle", "bristle", "sticker", "antimatter"];
1407
1474
  var EXCHANGE = ["dab", "wet", "impasto"];
1408
1475
  var DABS = ["dab", "eraser", "mixer", "blender", "smudge"];
1409
1476
  var FILM = ["dab", "mixer", "wet", "impasto", "particle", "bristle"];
@@ -1497,6 +1564,7 @@ var BRUSH_FIELDS = [
1497
1564
  f("marks.size", "spray.size", ["dab"], { marks: ["scatter"] }),
1498
1565
  ...each("marks", "sticker", ["density", "sizeJitter", "rotationJitter", "scatter"], ["sticker"]),
1499
1566
  f("marks.colourJitter", "sticker.colorJitter", ["sticker"]),
1567
+ ...each("marks", "antimatter", ["blastRadius", "blastSpacing", "threshold", "invert", "invertMode", "scorch", "annihilate", "sparks", "sparkReach", "crater", "duration"], ["antimatter"]),
1500
1568
  ...each("tip.bristle", "bristle", ["count", "stiffness", "splay", "thickness", "load", "jitter"], ["bristle"])
1501
1569
  ];
1502
1570
  var WET_TOOL_OF = { water: "water", dryer: "dry", blow: "blow", blot: "blot", "wet-blend": "blend", "wet-drag": "smudge" };
@@ -1520,6 +1588,7 @@ function setPath(o, path, value) {
1520
1588
  var copy = (v) => v !== null && typeof v === "object" ? JSON.parse(JSON.stringify(v)) : v;
1521
1589
  function resolveEngine(brush) {
1522
1590
  if (brush.kind === "tool") return brush.tool === "eraser" || brush.tool === "blender" || brush.tool === "smudge" ? brush.tool : "wet";
1591
+ if (brush.marks?.kind === "antimatter") return "antimatter";
1523
1592
  if (brush.marks?.kind === "particles") return "particle";
1524
1593
  if (brush.marks?.kind === "objects") return "sticker";
1525
1594
  if (brush.tip.shape === "bristle") return "bristle";
@@ -1657,6 +1726,8 @@ function skeleton(b) {
1657
1726
  return { ...brush, medium: "paint", ...mut };
1658
1727
  case "particle":
1659
1728
  return { ...brush, marks: { kind: "particles" }, ...mut };
1729
+ case "antimatter":
1730
+ return { ...brush, medium: "paint", marks: { kind: "antimatter" }, ...mut };
1660
1731
  case "sticker":
1661
1732
  return { id: b.id, name: b.name, kind: "mark", tip: { shape: tipShape(b) }, marks: { kind: "objects" }, ...mut };
1662
1733
  default: {
@@ -1684,7 +1755,7 @@ var BRUSH_KINDS = ["mark", "tool"];
1684
1755
  var BRUSH_MEDIA = ["paint", "ink", "dry", "spray"];
1685
1756
  var BRUSH_TOOLS = ["eraser", "blender", "smudge", "water", "dryer", "blow", "blot", "wet-blend", "wet-drag"];
1686
1757
  var BRUSH_TIP_SHAPES = ["round", "flat", "chisel", "bristle", "stamp"];
1687
- var BRUSH_MARKS_KINDS = ["continuous", "scatter", "particles", "objects"];
1758
+ var BRUSH_MARKS_KINDS = ["continuous", "scatter", "particles", "objects", "antimatter"];
1688
1759
  var BRUSH_SURFACES = ["dry", "wet", "thick"];
1689
1760
  var BRUSH_GRAIN_MODES = ["moving", "texturised"];
1690
1761
  var BRUSH_FLIPS = ["none", "x", "y", "xy"];
@@ -1811,6 +1882,24 @@ var OBJECTS = object(
1811
1882
  },
1812
1883
  ["kind"]
1813
1884
  );
1885
+ var ANTIMATTER = object(
1886
+ "Antimatter paint: where the tip touches paint across any visible layer, it detonates \u2014 inverting colours in the core, removing alpha, burning a soot ring, denting thick relief, clearing core light, and throwing sparks on the active layer. Missing fields take their defaults.",
1887
+ {
1888
+ kind: { type: "string", const: "antimatter", description: 'Always "antimatter".' },
1889
+ blastRadius: num(1, 6, "Blast size, in tip radii (default 2.5)."),
1890
+ blastSpacing: num(0.5, 8, "Stroke travel between blasts, in tip diameters (default 2)."),
1891
+ threshold: unit3("Paint needed under the tip to fire (default 0.1)."),
1892
+ invert: unit3("Strength of the colour inversion in the blast core (default 1)."),
1893
+ invertMode: oneOf2(INVERT_MODES, "Colour inversion mode: rgb (default, channel complement) or oklab (flips lightness and hue so mid-grey changes too)."),
1894
+ scorch: unit3("Strength of the soot ring around the blast (default 0.6)."),
1895
+ annihilate: unit3("Paint destroyed at the centre \u2014 alpha removed, unless the layer has lockTransparency (default 0.25)."),
1896
+ sparks: unit3("How many sparks fly out from each blast on the active layer (default 0.5; 0: none)."),
1897
+ sparkReach: num(0.5, 8, "How far sparks fly, in tip diameters (default 3)."),
1898
+ crater: unit3("Depth of the crater dent and raised rim on layers with thick relief (default 0.5)."),
1899
+ duration: num(0, 2, "Seconds a blast plays on screen (default 0.7; 0 shows the result at once).")
1900
+ },
1901
+ ["kind"]
1902
+ );
1814
1903
  var DYNAMICS = object("Pen feel: pressure, tilt, velocity, jitter, rotation and smoothing. Every amount 0 (off) .. 1 unless noted; missing fields take their defaults.", {
1815
1904
  pressureSize: bool("Pressure scales the size, down to minSize (default true)."),
1816
1905
  pressureOpacity: bool("Pressure scales the opacity (default false)."),
@@ -1981,7 +2070,7 @@ var BRUSH_JSON_SCHEMA = object(
1981
2070
  "Tools: eraser removes paint; blender mixes the colours under the tip; smudge drags paint along; water lays clear water (lifts dried paint with behaviour.wet.rewet); dryer dries wet paint; blow pushes water along; blot takes wet paint up; wet-blend evens out wet pigment; wet-drag drags wet paint."
1982
2071
  ),
1983
2072
  tip: TIP,
1984
- marks: { description: "How the tip is laid along the path (default continuous): continuous dabs, a scatter of spray droplets, particles, or objects (editable stickers).", oneOf: [CONTINUOUS, SCATTER, PARTICLES, OBJECTS] },
2073
+ marks: { description: "How the tip is laid along the path (default continuous): continuous dabs, a scatter of spray droplets, particles, objects (editable stickers), or antimatter (detonations that invert, scorch and destroy paint across every visible layer).", oneOf: [CONTINUOUS, SCATTER, PARTICLES, OBJECTS, ANTIMATTER] },
1985
2074
  dynamics: DYNAMICS,
1986
2075
  behaviour: BEHAVIOUR,
1987
2076
  light: LIGHT,
@@ -2000,7 +2089,7 @@ var BRUSH_JSON_SCHEMA = object(
2000
2089
  var BRUSH_FACET_SCHEMAS = {
2001
2090
  tip: TIP,
2002
2091
  tipGrain: TIP_GRAIN,
2003
- marks: { continuous: CONTINUOUS, scatter: SCATTER, particles: PARTICLES, objects: OBJECTS },
2092
+ marks: { continuous: CONTINUOUS, scatter: SCATTER, particles: PARTICLES, objects: OBJECTS, antimatter: ANTIMATTER },
2004
2093
  dynamics: DYNAMICS,
2005
2094
  behaviour: BEHAVIOUR,
2006
2095
  light: LIGHT,
@@ -2273,7 +2362,7 @@ function normalizeBrushSpec(raw, problems = []) {
2273
2362
  if (rawMarks !== void 0) {
2274
2363
  const k = isObject(rawMarks) ? rawMarks.kind : void 0;
2275
2364
  const schema = BRUSH_FACET_SCHEMAS.marks[k];
2276
- if (!schema) problems.push(`marks.kind ${JSON.stringify(k)} is not valid; use continuous, scatter, particles or objects. marks dropped.`);
2365
+ if (!schema) problems.push(`marks.kind ${JSON.stringify(k)} is not valid; use continuous, scatter, particles, objects or antimatter. marks dropped.`);
2277
2366
  else brush.marks = readObject(rawMarks, schema, "brush.marks", problems);
2278
2367
  }
2279
2368
  if (rawFerro !== void 0) {
@@ -2324,8 +2413,8 @@ function normalizeBrushSpec(raw, problems = []) {
2324
2413
  delete brush.iridescence;
2325
2414
  }
2326
2415
  }
2327
- if (brush.iridescence && brush.marks?.kind === "objects") {
2328
- drop("iridescence", "object marks are stickers, not film");
2416
+ if (brush.iridescence && (brush.marks?.kind === "objects" || brush.marks?.kind === "antimatter")) {
2417
+ drop("iridescence", brush.marks.kind === "antimatter" ? "antimatter marks detonate paint, not film" : "object marks are stickers, not film");
2329
2418
  delete brush.iridescence;
2330
2419
  }
2331
2420
  if (brush.marks?.kind === "scatter") {
@@ -2363,7 +2452,7 @@ function normalizeBrushSpec(raw, problems = []) {
2363
2452
  if (brush.tip.grain) {
2364
2453
  const surface = brush.behaviour?.surface;
2365
2454
  const marks = brush.marks?.kind;
2366
- const why = !brush.tip.grain.source ? "it needs a source (a grain pattern or source:<id>)" : brush.tip.shape === "bristle" ? "bristle tips lay their own marks" : marks === "particles" || marks === "objects" ? `${marks} marks are not dabs` : surface === "wet" || surface === "thick" ? `${surface} paint lays its own texture` : kind === "tool" && WET_BRUSH_TOOLS.includes(brush.tool) ? "the wet tools work on the wet layer" : null;
2455
+ const why = !brush.tip.grain.source ? "it needs a source (a grain pattern or source:<id>)" : brush.tip.shape === "bristle" ? "bristle tips lay their own marks" : marks === "particles" || marks === "objects" || marks === "antimatter" ? `${marks} marks are not dabs` : surface === "wet" || surface === "thick" ? `${surface} paint lays its own texture` : kind === "tool" && WET_BRUSH_TOOLS.includes(brush.tool) ? "the wet tools work on the wet layer" : null;
2367
2456
  if (why) {
2368
2457
  drop("tip.grain", why);
2369
2458
  delete brush.tip.grain;
@@ -2399,12 +2488,12 @@ function normalizeBrushSpec(raw, problems = []) {
2399
2488
  if (b.exchange && kind === "tool" && !wetTool) {
2400
2489
  drop("behaviour.exchange", `the ${brush.tool} does not pick paint up`);
2401
2490
  delete b.exchange;
2402
- } else if (b.exchange && (brush.marks?.kind === "particles" || brush.marks?.kind === "objects" || brush.tip.shape === "bristle")) {
2403
- drop("behaviour.exchange", "particle, object and bristle marks do not pick paint up");
2491
+ } else if (b.exchange && (brush.marks?.kind === "particles" || brush.marks?.kind === "objects" || brush.marks?.kind === "antimatter" || brush.tip.shape === "bristle")) {
2492
+ drop("behaviour.exchange", "particle, object, antimatter and bristle marks do not pick paint up");
2404
2493
  delete b.exchange;
2405
2494
  }
2406
2495
  }
2407
- if (brush.livePatterns && (kind !== "mark" || brush.behaviour?.surface !== "wet" || brush.marks?.kind === "particles" || brush.marks?.kind === "objects" || brush.tip.shape === "bristle")) {
2496
+ if (brush.livePatterns && (kind !== "mark" || brush.behaviour?.surface !== "wet" || brush.marks?.kind === "particles" || brush.marks?.kind === "objects" || brush.marks?.kind === "antimatter" || brush.tip.shape === "bristle")) {
2408
2497
  drop("livePatterns", "only wet paint brushes grow live patterns (set behaviour.surface to wet)");
2409
2498
  delete brush.livePatterns;
2410
2499
  }
@@ -2536,6 +2625,7 @@ function checkBrush(brush, profile) {
2536
2625
  if (profile.id !== "paint") feature("none", true, "mutation", "mutation paint", "Remove mutation.");
2537
2626
  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.");
2538
2627
  else if (brush.marks?.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.");
2628
+ else if (brush.marks?.kind === "antimatter") add("mutation", "error", "Antimatter brushes detonate paint already down and cannot be mutation brushes.", "Remove marks (or use continuous marks), or remove mutation.");
2539
2629
  else if (ex?.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.");
2540
2630
  else if (nonZero(ex?.pickup) || !!ex?.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.");
2541
2631
  else if (surface === "wet" && nonZero(brush.light?.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.");
@@ -3000,6 +3090,9 @@ export {
3000
3090
  normalizeParticle,
3001
3091
  DEFAULT_BRISTLE,
3002
3092
  normalizeBristle,
3093
+ INVERT_MODES,
3094
+ DEFAULT_ANTIMATTER,
3095
+ normalizeAntimatter,
3003
3096
  PAINT_BRUSH_ENGINES,
3004
3097
  PAINT_BRUSH_CATEGORIES,
3005
3098
  WET_TOOLS,