@pixerate/editor 0.276.0 → 0.278.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 +59 -59
  2. package/dist/animation-editor/index.js +3 -3
  3. package/dist/brushes/index.cjs +69 -67
  4. package/dist/brushes/index.js +6 -4
  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 +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 +10 -0
  12. package/dist/brushes/types.d.ts +10 -0
  13. package/dist/{brushstrokeRender-I6MYFCOU.cjs → brushstrokeRender-7HOMKZNO.cjs} +15 -15
  14. package/dist/{brushstrokeRender-FK3ASUUJ.js → brushstrokeRender-EIK3625T.js} +3 -3
  15. package/dist/canvas/index.cjs +13 -13
  16. package/dist/canvas/index.js +1 -1
  17. package/dist/{chunk-U26LUXCJ.cjs → chunk-4NZTK7LM.cjs} +35 -35
  18. package/dist/{chunk-CVBTJVRN.js → chunk-DWPFGWW5.js} +1 -1
  19. package/dist/{chunk-XMSH66VU.js → chunk-FNIWHSLI.js} +1 -1
  20. package/dist/{chunk-QNNZA3V5.cjs → chunk-H3EME3S5.cjs} +2 -2
  21. package/dist/{chunk-4EM5NGCL.js → chunk-HHX7ST6Z.js} +1 -1
  22. package/dist/{chunk-Q33V5PEF.cjs → chunk-HRUK5TFW.cjs} +73 -12
  23. package/dist/{chunk-5HR5CSLC.cjs → chunk-JVFGK3DK.cjs} +69 -58
  24. package/dist/{chunk-JNMNM3SL.js → chunk-LZE2QZCT.js} +1 -1
  25. package/dist/{chunk-OS6OG7DN.js → chunk-N4MCWQZ7.js} +1 -1
  26. package/dist/{chunk-IJ6NSWKM.js → chunk-NJLPW4MM.js} +17 -6
  27. package/dist/{chunk-OY24XRYF.cjs → chunk-PEQWARCP.cjs} +12 -12
  28. package/dist/{chunk-XVPQNX7O.js → chunk-PPDUCZZV.js} +66 -5
  29. package/dist/{chunk-X7XBEX3O.cjs → chunk-QBZEC2JJ.cjs} +86 -86
  30. package/dist/{chunk-GHYJAMMS.js → chunk-ROURHJYT.js} +1 -1
  31. package/dist/{chunk-ZIE6EJGR.cjs → chunk-VNF7A2JA.cjs} +23 -23
  32. package/dist/{chunk-ISCAJBPQ.cjs → chunk-WFSYZO5Z.cjs} +1 -1
  33. package/dist/image-editor/index.cjs +788 -788
  34. package/dist/image-editor/index.js +4 -4
  35. package/dist/paint-editor/brush-model.d.cts +15 -1
  36. package/dist/paint-editor/brush-model.d.ts +15 -1
  37. package/dist/paint-editor/controller.d.cts +90 -9
  38. package/dist/paint-editor/controller.d.ts +90 -9
  39. package/dist/paint-editor/document.d.cts +8 -1
  40. package/dist/paint-editor/document.d.ts +8 -1
  41. package/dist/paint-editor/ferro-tools.d.cts +10 -0
  42. package/dist/paint-editor/ferro-tools.d.ts +10 -0
  43. package/dist/paint-editor/ferro.d.cts +134 -0
  44. package/dist/paint-editor/ferro.d.ts +134 -0
  45. package/dist/paint-editor/index.cjs +2138 -872
  46. package/dist/paint-editor/index.d.cts +2 -0
  47. package/dist/paint-editor/index.d.ts +2 -0
  48. package/dist/paint-editor/index.js +1446 -180
  49. package/dist/paint-editor/serialize.d.cts +3 -1
  50. package/dist/paint-editor/serialize.d.ts +3 -1
  51. package/dist/paint-editor/stroke.d.cts +17 -0
  52. package/dist/paint-editor/stroke.d.ts +17 -0
  53. package/dist/paint-editor/strokes.d.cts +11 -1
  54. package/dist/paint-editor/strokes.d.ts +11 -1
  55. package/dist/paint-editor/thick.d.cts +15 -2
  56. package/dist/paint-editor/thick.d.ts +15 -2
  57. package/dist/paint-editor/ui.d.cts +2 -0
  58. package/dist/paint-editor/ui.d.ts +2 -0
  59. package/dist/vector/index.cjs +998 -998
  60. package/dist/vector/index.js +4 -4
  61. package/package.json +1 -1
  62. package/skills/pixerate-brushes/SKILL.md +1 -0
  63. package/skills/pixerate-paint-editor/SKILL.md +12 -30
  64. package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
  65. package/skills/pixerate-paint-editor/references/ferrofluid.md +36 -0
@@ -932,6 +932,29 @@ var BRUSH_PRESETS = [
932
932
  tip: { shape: "round", size: 6, hardness: 0.9, spacing: 0.06 },
933
933
  dynamics: { smoothing: 0.35, pressureSmoothing: 0.25, minSize: 0.25 },
934
934
  mutation: {}
935
+ },
936
+ // --- Ferrofluid paint: liquid magnetic paint that rises into lit Rosensweig spikes near magnets ---
937
+ {
938
+ id: "ferrofluid",
939
+ name: "Ferrofluid",
940
+ kind: "mark",
941
+ medium: "paint",
942
+ variety: "ferrofluid",
943
+ tip: { shape: "round", size: 28, hardness: 0.75, flow: 0.9 },
944
+ dynamics: { smoothing: 0.35 },
945
+ ferro: { amount: 0.85 },
946
+ colour: { default: "#111827", palette: ["#111827", "#1e293b", "#312e81", "#0f3d3e", "#3b0764", "#451a03"] }
947
+ },
948
+ {
949
+ id: "ferro-ink",
950
+ name: "Ferro ink",
951
+ kind: "mark",
952
+ medium: "ink",
953
+ variety: "ferrofluid",
954
+ tip: { shape: "round", size: 12, spacing: 0.08 },
955
+ dynamics: { smoothing: 0.45, minSize: 0.25, velocitySize: -0.25 },
956
+ ferro: { amount: 0.7, wavelength: 12, threshold: 0.15 },
957
+ colour: { default: "#0f172a" }
935
958
  }
936
959
  ];
937
960
 
@@ -1101,6 +1124,18 @@ function normalizeMutation(raw = {}, base = DEFAULT_MUTATION) {
1101
1124
  fade: range(raw.fade, 0.5, 10, base.fade)
1102
1125
  };
1103
1126
  }
1127
+ var DEFAULT_FERRO = { amount: 0, wavelength: 16, threshold: 0.12 };
1128
+ function normalizeFerro(raw = {}, base = DEFAULT_FERRO) {
1129
+ if (typeof raw === "number") {
1130
+ return { amount: unit2(raw, base.amount), wavelength: base.wavelength, threshold: base.threshold };
1131
+ }
1132
+ const r = _nullishCoalesce(raw, () => ( {}));
1133
+ return {
1134
+ 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)
1137
+ };
1138
+ }
1104
1139
  function normalizeImpasto(raw = {}, base = DEFAULT_IMPASTO) {
1105
1140
  return {
1106
1141
  tool: IMPASTO_TOOLS.includes(raw.tool) ? raw.tool : base.tool,
@@ -1329,6 +1364,7 @@ function normalizePaintBrush(raw = {}, base) {
1329
1364
  ...withIridescence(readIridescence(raw.iridescence, _nullishCoalesce(b.iridescence, () => ( null)))),
1330
1365
  ...ghost ? { ghost } : {},
1331
1366
  ...mutation ? { mutation } : {},
1367
+ ferro: normalizeFerro(raw.ferro, _nullishCoalesce(b.ferro, () => ( DEFAULT_FERRO))),
1332
1368
  stamp: readStamp(raw.stamp, _nullishCoalesce(b.stamp, () => ( null))),
1333
1369
  spray: readSpray(raw.spray, _nullishCoalesce(b.spray, () => ( null))),
1334
1370
  ...withTipGrain(readTipGrain(raw.tipGrain, _nullishCoalesce(b.tipGrain, () => ( null)))),
@@ -1360,6 +1396,7 @@ var BASE_BRUSH = {
1360
1396
  light: { ...DEFAULT_LIGHT },
1361
1397
  fluorescence: { ...DEFAULT_FLUORESCENCE },
1362
1398
  thermochromic: { ...DEFAULT_THERMOCHROMIC },
1399
+ ferro: { ...DEFAULT_FERRO },
1363
1400
  stamp: null,
1364
1401
  spray: null,
1365
1402
  paint: null
@@ -1419,6 +1456,7 @@ var BRUSH_FIELDS = [
1419
1456
  ...each("fluorescence", "fluorescence", ["amount", "invisible"]),
1420
1457
  ...each("thermochromic", "thermochromic", ["hotColor", "threshold"]),
1421
1458
  ...each("iridescence", "iridescence", ["thickness", "pressureToThickness", "ior"], FILM),
1459
+ ...each("ferro", "ferro", ["amount", "wavelength", "threshold"]),
1422
1460
  // Paint exchange: the dab, wet and impasto engines' behaviour, or the Mixer Brush's settings.
1423
1461
  f("behaviour.exchange.pickup", "behavior.pickup", EXCHANGE),
1424
1462
  f("behaviour.exchange.mix", "behavior.mix", EXCHANGE),
@@ -1627,7 +1665,7 @@ function skeleton(b) {
1627
1665
  }
1628
1666
  }
1629
1667
  }
1630
- var ORDER = ["id", "name", "description", "tags", "kind", "medium", "variety", "tool", "tip", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "motion", "livePatterns", "colour"];
1668
+ var ORDER = ["id", "name", "description", "tags", "kind", "medium", "variety", "tool", "tip", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "ferro", "motion", "livePatterns", "colour"];
1631
1669
  function migrateBrushV1(raw) {
1632
1670
  const b = normalizePaintBrush(raw);
1633
1671
  const brush = skeleton(b);
@@ -1866,6 +1904,11 @@ var MUTATION = object("Mutation paint: you paint something and pause; once an ag
1866
1904
  patience: num(5, 300, "Seconds to wait for an agent before giving up and keeping the paint as ordinary paint (default 45)."),
1867
1905
  fade: num(0.5, 10, "Seconds the paint takes to sink away once claimed by read_mutation (default 2).")
1868
1906
  });
1907
+ var FERRO = object("Liquid ferrofluid paint: rises into glossy Rosensweig spikes near magnets until cured.", {
1908
+ amount: unit3("How much liquid ferrofluid the brush lays per dab (default 0: none)."),
1909
+ wavelength: num(4, 64, "Capillary wavelength (spike-to-spike spacing) in pixels (default 16)."),
1910
+ threshold: num(0.01, 0.95, "Critical magnetic field |B| threshold where spikes begin to rise (default 0.12).")
1911
+ });
1869
1912
  var MOTION = object("Animation after painting.", {
1870
1913
  live: bool("Effect paint keeps playing on stroke layers (default false)."),
1871
1914
  fps: int(1, 60, "Suggested playback rate in frames per second."),
@@ -1947,6 +1990,7 @@ var BRUSH_JSON_SCHEMA = object(
1947
1990
  iridescence: IRIDESCENCE,
1948
1991
  ghost: GHOST,
1949
1992
  mutation: MUTATION,
1993
+ ferro: FERRO,
1950
1994
  motion: MOTION,
1951
1995
  livePatterns: LIVE_PATTERNS,
1952
1996
  colour: COLOUR
@@ -1965,6 +2009,7 @@ var BRUSH_FACET_SCHEMAS = {
1965
2009
  iridescence: IRIDESCENCE,
1966
2010
  ghost: GHOST,
1967
2011
  mutation: MUTATION,
2012
+ ferro: FERRO,
1968
2013
  motion: MOTION,
1969
2014
  livePatterns: LIVE_PATTERNS,
1970
2015
  colour: COLOUR,
@@ -2210,14 +2255,14 @@ function normalizeBrushSpec(raw, problems = []) {
2210
2255
  else if (raw.kind === void 0) kind = tool ? "tool" : "mark";
2211
2256
  else throw new Error(`Brush "${raw.id}" kind ${JSON.stringify(raw.kind)} is not valid: use mark (lays a material) or tool (works on paint already down).`);
2212
2257
  if (kind === "tool" && !tool) throw new Error(`Brush "${raw.id}" is a tool and needs tool: one of ${BRUSH_TOOLS.join(", ")}.`);
2213
- const { colour: rawColour, marks: rawMarks, kind: _k, id: _i, name: _n, tool: _t, ...rest } = raw;
2258
+ const { colour: rawColour, marks: rawMarks, ferro: rawFerro, kind: _k, id: _i, name: _n, tool: _t, ...rest } = raw;
2214
2259
  if (typeof rest.fluorescence === "number" && Number.isFinite(rest.fluorescence)) rest.fluorescence = { amount: rest.fluorescence };
2215
2260
  const out = readObject(rest, BRUSH_JSON_SCHEMA, "brush", problems);
2216
2261
  if (_optionalChain([out, 'access', _29 => _29.thermochromic, 'optionalAccess', _30 => _30.hotColor])) out.thermochromic.hotColor = out.thermochromic.hotColor.toLowerCase();
2217
2262
  const brush = { id: raw.id, name, ...out, kind, ...tool ? { tool } : {}, tip: _nullishCoalesce(out.tip, () => ( { shape: "round" })) };
2218
2263
  const ordered = () => {
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 };
2264
+ const { id, name: n, description, tags, kind: k, medium, variety, tool: t, tip, marks, dynamics, behaviour, light, fluorescence, thermochromic, iridescence, ghost, mutation, ferro, motion, livePatterns, colour } = brush;
2265
+ const all = { id, name: n, description, tags, kind: k, medium, variety, tool: t, tip, marks, dynamics, behaviour, light, fluorescence, thermochromic, iridescence, ghost, mutation, ferro, motion, livePatterns, colour };
2221
2266
  return Object.fromEntries(Object.entries(all).filter(([, v]) => v !== void 0));
2222
2267
  };
2223
2268
  if (!out.tip) problems.push("tip is missing; a round tip is used.");
@@ -2231,6 +2276,11 @@ function normalizeBrushSpec(raw, problems = []) {
2231
2276
  if (!schema) problems.push(`marks.kind ${JSON.stringify(k)} is not valid; use continuous, scatter, particles or objects. marks dropped.`);
2232
2277
  else brush.marks = readObject(rawMarks, schema, "brush.marks", problems);
2233
2278
  }
2279
+ if (rawFerro !== void 0) {
2280
+ const ferroObj = typeof rawFerro === "number" ? { amount: rawFerro } : rawFerro;
2281
+ const f2 = readObject(ferroObj, BRUSH_FACET_SCHEMAS.ferro, "brush.ferro", problems);
2282
+ if (f2 && Object.keys(f2).length) brush.ferro = f2;
2283
+ }
2234
2284
  if (rawColour !== void 0) {
2235
2285
  const c = readObject(rawColour, { ...BRUSH_FACET_SCHEMAS.colour, properties: { own: BRUSH_FACET_SCHEMAS.colour.properties.own, default: {}, palette: {} } }, "brush.colour", problems);
2236
2286
  if (c) {
@@ -2265,6 +2315,10 @@ function normalizeBrushSpec(raw, problems = []) {
2265
2315
  drop("marks", "tools lay no marks");
2266
2316
  delete brush.marks;
2267
2317
  }
2318
+ if (brush.ferro) {
2319
+ drop("ferro", "tools lay no ferrofluid");
2320
+ delete brush.ferro;
2321
+ }
2268
2322
  if (brush.iridescence) {
2269
2323
  drop("iridescence", "tools lay no film");
2270
2324
  delete brush.iridescence;
@@ -2394,7 +2448,8 @@ var PAINT_PROFILE = {
2394
2448
  tipGrain: "full",
2395
2449
  dabCount: "full",
2396
2450
  livePatterns: "full",
2397
- iridescence: "full"
2451
+ iridescence: "full",
2452
+ ferro: "full"
2398
2453
  };
2399
2454
  var IMAGE_PROFILE = {
2400
2455
  id: "image",
@@ -2485,6 +2540,7 @@ function checkBrush(brush, profile) {
2485
2540
  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
2541
  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.");
2487
2542
  }
2543
+ feature(_nullishCoalesce(profile.ferro, () => ( "none")), nonZero(_optionalChain([brush, 'access', _87 => _87.ferro, 'optionalAccess', _88 => _88.amount])), "ferro", "ferrofluid spikes and magnets", "Remove ferro (or set ferro.amount to 0).");
2488
2544
  const m = brush.motion;
2489
2545
  feature(profile.motion, !!m && (!!m.live || nonZero(m.waveSpeed) || m.fps !== void 0), "motion", "animation", "Remove motion.");
2490
2546
  const d = _nullishCoalesce(brush.dynamics, () => ( {}));
@@ -2493,16 +2549,16 @@ function checkBrush(brush, profile) {
2493
2549
  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.");
2494
2550
  feature(profile.tilt, nonZero(d.tiltSize) || nonZero(d.tiltRoundness) || nonZero(d.tiltOpacity), "dynamics.tilt", "tilt dynamics", "Set dynamics.tiltSize, tiltRoundness and tiltOpacity to 0.");
2495
2551
  const colour = brush.colour;
2496
- if (_optionalChain([colour, 'optionalAccess', _87 => _87.default]) !== void 0) {
2552
+ if (_optionalChain([colour, 'optionalAccess', _89 => _89.default]) !== void 0) {
2497
2553
  const kind = brushColourKind(colour.default);
2498
2554
  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.`);
2499
2555
  }
2500
- const foreign = [...new Set((_nullishCoalesce(_optionalChain([colour, 'optionalAccess', _88 => _88.palette]), () => ( []))).map(brushColourKind).filter((k) => !profile.colours.includes(k)))];
2556
+ const foreign = [...new Set((_nullishCoalesce(_optionalChain([colour, 'optionalAccess', _90 => _90.palette]), () => ( []))).map(brushColourKind).filter((k) => !profile.colours.includes(k)))];
2501
2557
  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.`);
2502
2558
  return { ok: !issues.some((i) => i.severity === "error"), issues };
2503
2559
  }
2504
2560
  function withKeptIssues(check, before) {
2505
- const had = new Set((_nullishCoalesce(_optionalChain([before, 'optionalAccess', _89 => _89.issues]), () => ( []))).filter((i) => i.severity === "error").map((i) => i.facet));
2561
+ const had = new Set((_nullishCoalesce(_optionalChain([before, 'optionalAccess', _91 => _91.issues]), () => ( []))).filter((i) => i.severity === "error").map((i) => i.facet));
2506
2562
  const kept = check.issues.filter((i) => i.severity === "error" && had.has(i.facet));
2507
2563
  const issues = check.issues.filter((i) => !kept.includes(i));
2508
2564
  return { ok: !issues.some((i) => i.severity === "error"), issues, kept };
@@ -2574,6 +2630,9 @@ function degrade(brush, profile, options = {}) {
2574
2630
  case "iridescence":
2575
2631
  delete b.iridescence;
2576
2632
  break;
2633
+ case "ferro":
2634
+ delete b.ferro;
2635
+ break;
2577
2636
  case "motion":
2578
2637
  delete b.motion;
2579
2638
  break;
@@ -2598,7 +2657,7 @@ function degrade(brush, profile, options = {}) {
2598
2657
  }
2599
2658
  dropped.push(issue);
2600
2659
  }
2601
- if (_optionalChain([b, 'access', _90 => _90.behaviour, 'optionalAccess', _91 => _91.exchange]) && !Object.keys(b.behaviour.exchange).length) delete b.behaviour.exchange;
2660
+ if (_optionalChain([b, 'access', _92 => _92.behaviour, 'optionalAccess', _93 => _93.exchange]) && !Object.keys(b.behaviour.exchange).length) delete b.behaviour.exchange;
2602
2661
  if (b.colour && !Object.keys(b.colour).length) delete b.colour;
2603
2662
  const after = checkBrush(b, profile).issues.filter((i) => i.severity === "error");
2604
2663
  return { brush: b, dropped, issues: after };
@@ -2642,7 +2701,7 @@ function parseBrushLibrary2(raw, problems = []) {
2642
2701
  if (file && file.version !== void 0 && file.version !== 1 && file.version !== 2) {
2643
2702
  throw new BrushLibraryError("invalid", `Brush library version ${JSON.stringify(file.version)} is not supported; this library reads versions 1 and 2.`);
2644
2703
  }
2645
- const v1File = _optionalChain([file, 'optionalAccess', _92 => _92.version]) === 1;
2704
+ const v1File = _optionalChain([file, 'optionalAccess', _94 => _94.version]) === 1;
2646
2705
  const out = [];
2647
2706
  items.forEach((item, i) => {
2648
2707
  const at = `brushes[${i}]`;
@@ -2903,7 +2962,7 @@ function snapshotBrushLibrary(library, onChange) {
2903
2962
  (list2) => {
2904
2963
  entries = list2;
2905
2964
  user = list2.filter((e) => !e.builtin).map((e) => e.brush);
2906
- _optionalChain([onChange, 'optionalCall', _93 => _93()]);
2965
+ _optionalChain([onChange, 'optionalCall', _95 => _95()]);
2907
2966
  },
2908
2967
  () => void 0
2909
2968
  );
@@ -3045,4 +3104,6 @@ function snapshotBrushLibrary(library, onChange) {
3045
3104
 
3046
3105
 
3047
3106
 
3048
- exports.BRUSH_PRESETS = BRUSH_PRESETS; exports.MAX_STICKER_STAMPS = MAX_STICKER_STAMPS; exports.DEFAULT_STICKER = DEFAULT_STICKER; exports.normalizeStickerSettings = normalizeStickerSettings; exports.PARTICLE_KINDS = PARTICLE_KINDS; exports.PARTICLE_RENDERS = PARTICLE_RENDERS; exports.RIBBON_PAIRS = RIBBON_PAIRS; exports.DOT_TEXTURE_ORDERS = DOT_TEXTURE_ORDERS; exports.DOT_ROTATIONS = DOT_ROTATIONS; exports.MAX_DOT_TEXTURES = MAX_DOT_TEXTURES; exports.DEFAULT_PARTICLE = DEFAULT_PARTICLE; exports.normalizeParticle = normalizeParticle; exports.DEFAULT_BRISTLE = DEFAULT_BRISTLE; exports.normalizeBristle = normalizeBristle; exports.PAINT_BRUSH_ENGINES = PAINT_BRUSH_ENGINES; exports.PAINT_BRUSH_CATEGORIES = PAINT_BRUSH_CATEGORIES; exports.WET_TOOLS = WET_TOOLS; exports.WET_MEDIA = WET_MEDIA; exports.DEFAULT_WET_BRUSH = DEFAULT_WET_BRUSH; exports.normalizeWetBrush = normalizeWetBrush; exports.IMPASTO_TOOLS = IMPASTO_TOOLS; exports.DEFAULT_IMPASTO = DEFAULT_IMPASTO; exports.DEFAULT_LIGHT = DEFAULT_LIGHT; exports.normalizeLight = normalizeLight; exports.DEFAULT_FLUORESCENCE = DEFAULT_FLUORESCENCE; exports.normalizeFluorescence = normalizeFluorescence; exports.DEFAULT_THERMOCHROMIC = DEFAULT_THERMOCHROMIC; exports.normalizeThermochromic = normalizeThermochromic; exports.DEFAULT_IRIDESCENCE = DEFAULT_IRIDESCENCE; exports.GHOST_TRIGGERS = GHOST_TRIGGERS; exports.DEFAULT_GHOST = DEFAULT_GHOST; exports.normalizeGhost = normalizeGhost; exports.DEFAULT_MUTATION = DEFAULT_MUTATION; exports.normalizeMutation = normalizeMutation; exports.normalizeImpasto = normalizeImpasto; exports.MULTI_COLOR_MODES = MULTI_COLOR_MODES; exports.DEFAULT_MULTI_COLOR = DEFAULT_MULTI_COLOR; exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR; exports.BRUSH_ROTATIONS = BRUSH_ROTATIONS; exports.DAB_FLIPS = DAB_FLIPS; exports.TIP_GRAIN_MODES = TIP_GRAIN_MODES; exports.DEFAULT_TIP_GRAIN = DEFAULT_TIP_GRAIN; exports.DEFAULT_DYNAMICS = DEFAULT_DYNAMICS; exports.normalizeDynamics = normalizeDynamics; exports.BRUSH_ID = BRUSH_ID; exports.normalizeMultiColor = normalizeMultiColor; exports.normalizeBehavior = normalizeBehavior; exports.DEFAULT_SPRAY = DEFAULT_SPRAY; exports.isWetCapable = isWetCapable; exports.isWetBrush = isWetBrush; exports.usesPaintExchange = usesPaintExchange; exports.brushTakesPaint = brushTakesPaint; exports.paintToMultiColor = paintToMultiColor; exports.multiColorToPaint = multiColorToPaint; exports.normalizePaintBrush = normalizePaintBrush; exports.BRUSH_FIELDS = BRUSH_FIELDS; exports.getPath = getPath; exports.setPath = setPath; exports.resolveEngine = resolveEngine; exports.resolveWetMedium = resolveWetMedium; exports.resolveCategory = resolveCategory; exports.resolveBrush = resolveBrush; exports.BUILTIN_BRUSHES = BUILTIN_BRUSHES; exports.DEFAULT_BRUSH_ID = DEFAULT_BRUSH_ID; exports.brushToJSON = brushToJSON; exports.parseBrushLibrary = parseBrushLibrary; exports.migrateBrushV1 = migrateBrushV1; exports.BRUSH_KINDS = BRUSH_KINDS; exports.BRUSH_MEDIA = BRUSH_MEDIA; exports.BRUSH_TOOLS = BRUSH_TOOLS; exports.BRUSH_TIP_SHAPES = BRUSH_TIP_SHAPES; exports.BRUSH_MARKS_KINDS = BRUSH_MARKS_KINDS; exports.BRUSH_SURFACES = BRUSH_SURFACES; exports.BRUSH_GRAIN_MODES = BRUSH_GRAIN_MODES; exports.BRUSH_FLIPS = BRUSH_FLIPS; exports.GHOST_TRIGGERS2 = GHOST_TRIGGERS2; exports.MAX_BRUSH_PALETTE = MAX_BRUSH_PALETTE; exports.BRUSH_WORD = BRUSH_WORD; exports.TIP_GRAIN_PATTERNS = TIP_GRAIN_PATTERNS; exports.BRUSH_GRAIN_SOURCE_PATTERN = BRUSH_GRAIN_SOURCE_PATTERN; exports.BRUSH_JSON_SCHEMA = BRUSH_JSON_SCHEMA; exports.BRUSH_FACET_SCHEMAS = BRUSH_FACET_SCHEMAS; exports.BRUSH_SOURCE_PROVIDER = BRUSH_SOURCE_PROVIDER; exports.MAX_BRUSH_SOURCE_SIZE = MAX_BRUSH_SOURCE_SIZE; exports.MAX_BRUSH_SOURCE_INPUT_PIXELS = MAX_BRUSH_SOURCE_INPUT_PIXELS; exports.MAX_BRUSH_SOURCES = MAX_BRUSH_SOURCES; exports.BRUSH_SOURCE_CHANNELS = BRUSH_SOURCE_CHANNELS; exports.BrushSourceError = BrushSourceError; exports.brushSourceRef = brushSourceRef; exports.isBrushSourceRef = isBrushSourceRef; exports.brushSourceIdOf = brushSourceIdOf; exports.brushSourceIds = brushSourceIds; exports.summarizeBrushSource = summarizeBrushSource; exports.brushSourceIdFromName = brushSourceIdFromName; exports.brushSourceFromMask = brushSourceFromMask; exports.makeBrushSource = makeBrushSource; exports.readBrushSource = readBrushSource; exports.decodeBrushSource = decodeBrushSource; exports.WET_BRUSH_TOOLS = WET_BRUSH_TOOLS; exports.normalizeBrushSpec = normalizeBrushSpec; exports.PAINT_PROFILE = PAINT_PROFILE; exports.IMAGE_PROFILE = IMAGE_PROFILE; exports.CANVAS_PROFILE = CANVAS_PROFILE; exports.VECTOR_PROFILE = VECTOR_PROFILE; exports.BRUSH_HOST_PROFILES = BRUSH_HOST_PROFILES; exports.brushColourKind = brushColourKind; exports.checkBrush = checkBrush; exports.withKeptIssues = withKeptIssues; exports.checkBrushEdit = checkBrushEdit; exports.listPresets = listPresets; exports.degrade = degrade; exports.BrushLibraryError = BrushLibraryError; exports.isBrushLibrary = isBrushLibrary; exports.parseBrushLibrarySources = parseBrushLibrarySources; exports.parseBrushLibrary2 = parseBrushLibrary2; exports.createBrushLibrary = createBrushLibrary; exports.closest = closest; exports.snapshotBrushLibrary = snapshotBrushLibrary;
3107
+
3108
+
3109
+ exports.BRUSH_PRESETS = BRUSH_PRESETS; exports.MAX_STICKER_STAMPS = MAX_STICKER_STAMPS; exports.DEFAULT_STICKER = DEFAULT_STICKER; exports.normalizeStickerSettings = normalizeStickerSettings; exports.PARTICLE_KINDS = PARTICLE_KINDS; exports.PARTICLE_RENDERS = PARTICLE_RENDERS; exports.RIBBON_PAIRS = RIBBON_PAIRS; exports.DOT_TEXTURE_ORDERS = DOT_TEXTURE_ORDERS; exports.DOT_ROTATIONS = DOT_ROTATIONS; exports.MAX_DOT_TEXTURES = MAX_DOT_TEXTURES; exports.DEFAULT_PARTICLE = DEFAULT_PARTICLE; exports.normalizeParticle = normalizeParticle; exports.DEFAULT_BRISTLE = DEFAULT_BRISTLE; exports.normalizeBristle = normalizeBristle; exports.PAINT_BRUSH_ENGINES = PAINT_BRUSH_ENGINES; exports.PAINT_BRUSH_CATEGORIES = PAINT_BRUSH_CATEGORIES; exports.WET_TOOLS = WET_TOOLS; exports.WET_MEDIA = WET_MEDIA; exports.DEFAULT_WET_BRUSH = DEFAULT_WET_BRUSH; exports.normalizeWetBrush = normalizeWetBrush; exports.IMPASTO_TOOLS = IMPASTO_TOOLS; exports.DEFAULT_IMPASTO = DEFAULT_IMPASTO; exports.DEFAULT_LIGHT = DEFAULT_LIGHT; exports.normalizeLight = normalizeLight; exports.DEFAULT_FLUORESCENCE = DEFAULT_FLUORESCENCE; exports.normalizeFluorescence = normalizeFluorescence; exports.DEFAULT_THERMOCHROMIC = DEFAULT_THERMOCHROMIC; exports.normalizeThermochromic = normalizeThermochromic; exports.DEFAULT_IRIDESCENCE = DEFAULT_IRIDESCENCE; exports.GHOST_TRIGGERS = GHOST_TRIGGERS; exports.DEFAULT_GHOST = DEFAULT_GHOST; exports.normalizeGhost = normalizeGhost; exports.DEFAULT_MUTATION = DEFAULT_MUTATION; exports.normalizeMutation = normalizeMutation; exports.DEFAULT_FERRO = DEFAULT_FERRO; exports.normalizeFerro = normalizeFerro; exports.normalizeImpasto = normalizeImpasto; exports.MULTI_COLOR_MODES = MULTI_COLOR_MODES; exports.DEFAULT_MULTI_COLOR = DEFAULT_MULTI_COLOR; exports.DEFAULT_BEHAVIOR = DEFAULT_BEHAVIOR; exports.BRUSH_ROTATIONS = BRUSH_ROTATIONS; exports.DAB_FLIPS = DAB_FLIPS; exports.TIP_GRAIN_MODES = TIP_GRAIN_MODES; exports.DEFAULT_TIP_GRAIN = DEFAULT_TIP_GRAIN; exports.DEFAULT_DYNAMICS = DEFAULT_DYNAMICS; exports.normalizeDynamics = normalizeDynamics; exports.BRUSH_ID = BRUSH_ID; exports.normalizeMultiColor = normalizeMultiColor; exports.normalizeBehavior = normalizeBehavior; exports.DEFAULT_SPRAY = DEFAULT_SPRAY; exports.isWetCapable = isWetCapable; exports.isWetBrush = isWetBrush; exports.usesPaintExchange = usesPaintExchange; exports.brushTakesPaint = brushTakesPaint; exports.paintToMultiColor = paintToMultiColor; exports.multiColorToPaint = multiColorToPaint; exports.normalizePaintBrush = normalizePaintBrush; exports.BRUSH_FIELDS = BRUSH_FIELDS; exports.getPath = getPath; exports.setPath = setPath; exports.resolveEngine = resolveEngine; exports.resolveWetMedium = resolveWetMedium; exports.resolveCategory = resolveCategory; exports.resolveBrush = resolveBrush; exports.BUILTIN_BRUSHES = BUILTIN_BRUSHES; exports.DEFAULT_BRUSH_ID = DEFAULT_BRUSH_ID; exports.brushToJSON = brushToJSON; exports.parseBrushLibrary = parseBrushLibrary; exports.migrateBrushV1 = migrateBrushV1; exports.BRUSH_KINDS = BRUSH_KINDS; exports.BRUSH_MEDIA = BRUSH_MEDIA; exports.BRUSH_TOOLS = BRUSH_TOOLS; exports.BRUSH_TIP_SHAPES = BRUSH_TIP_SHAPES; exports.BRUSH_MARKS_KINDS = BRUSH_MARKS_KINDS; exports.BRUSH_SURFACES = BRUSH_SURFACES; exports.BRUSH_GRAIN_MODES = BRUSH_GRAIN_MODES; exports.BRUSH_FLIPS = BRUSH_FLIPS; exports.GHOST_TRIGGERS2 = GHOST_TRIGGERS2; exports.MAX_BRUSH_PALETTE = MAX_BRUSH_PALETTE; exports.BRUSH_WORD = BRUSH_WORD; exports.TIP_GRAIN_PATTERNS = TIP_GRAIN_PATTERNS; exports.BRUSH_GRAIN_SOURCE_PATTERN = BRUSH_GRAIN_SOURCE_PATTERN; exports.BRUSH_JSON_SCHEMA = BRUSH_JSON_SCHEMA; exports.BRUSH_FACET_SCHEMAS = BRUSH_FACET_SCHEMAS; exports.BRUSH_SOURCE_PROVIDER = BRUSH_SOURCE_PROVIDER; exports.MAX_BRUSH_SOURCE_SIZE = MAX_BRUSH_SOURCE_SIZE; exports.MAX_BRUSH_SOURCE_INPUT_PIXELS = MAX_BRUSH_SOURCE_INPUT_PIXELS; exports.MAX_BRUSH_SOURCES = MAX_BRUSH_SOURCES; exports.BRUSH_SOURCE_CHANNELS = BRUSH_SOURCE_CHANNELS; exports.BrushSourceError = BrushSourceError; exports.brushSourceRef = brushSourceRef; exports.isBrushSourceRef = isBrushSourceRef; exports.brushSourceIdOf = brushSourceIdOf; exports.brushSourceIds = brushSourceIds; exports.summarizeBrushSource = summarizeBrushSource; exports.brushSourceIdFromName = brushSourceIdFromName; exports.brushSourceFromMask = brushSourceFromMask; exports.makeBrushSource = makeBrushSource; exports.readBrushSource = readBrushSource; exports.decodeBrushSource = decodeBrushSource; exports.WET_BRUSH_TOOLS = WET_BRUSH_TOOLS; exports.normalizeBrushSpec = normalizeBrushSpec; exports.PAINT_PROFILE = PAINT_PROFILE; exports.IMAGE_PROFILE = IMAGE_PROFILE; exports.CANVAS_PROFILE = CANVAS_PROFILE; exports.VECTOR_PROFILE = VECTOR_PROFILE; exports.BRUSH_HOST_PROFILES = BRUSH_HOST_PROFILES; exports.brushColourKind = brushColourKind; exports.checkBrush = checkBrush; exports.withKeptIssues = withKeptIssues; exports.checkBrushEdit = checkBrushEdit; exports.listPresets = listPresets; exports.degrade = degrade; exports.BrushLibraryError = BrushLibraryError; exports.isBrushLibrary = isBrushLibrary; exports.parseBrushLibrarySources = parseBrushLibrarySources; exports.parseBrushLibrary2 = parseBrushLibrary2; exports.createBrushLibrary = createBrushLibrary; exports.closest = closest; exports.snapshotBrushLibrary = snapshotBrushLibrary;
@@ -27,7 +27,8 @@ var _chunkPFHJS4HEcjs = require('./chunk-PFHJS4HE.cjs');
27
27
 
28
28
 
29
29
 
30
- var _chunkQ33V5PEFcjs = require('./chunk-Q33V5PEF.cjs');
30
+
31
+ var _chunkHRUK5TFWcjs = require('./chunk-HRUK5TFW.cjs');
31
32
 
32
33
 
33
34
  var _chunkW5M3J7PVcjs = require('./chunk-W5M3J7PV.cjs');
@@ -38,13 +39,20 @@ var _chunkXFM3OCHUcjs = require('./chunk-XFM3OCHU.cjs');
38
39
  // src/brushes/patch.ts
39
40
  var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
40
41
  var copy = (v) => JSON.parse(JSON.stringify(v));
41
- var FACETS = _chunkQ33V5PEFcjs.BRUSH_JSON_SCHEMA.properties;
42
+ var FACETS = _chunkHRUK5TFWcjs.BRUSH_JSON_SCHEMA.properties;
42
43
  var BRUSH_PATCH_PROPERTIES = (() => {
43
44
  const { id: _id, name: _name, ...rest } = FACETS;
44
45
  const { required: _r, ...tip } = rest.tip;
45
- return { ...rest, tip: { ...tip, description: `${tip.description} In a patch, shape may be left out to keep it.` } };
46
+ const ferro = {
47
+ description: "Liquid ferrofluid paint: 0..1 (amount), or { amount, wavelength, threshold }. Rises into glossy Rosensweig spikes near magnets until cured.",
48
+ anyOf: [
49
+ { type: "number", minimum: 0, maximum: 1, description: "How much liquid ferrofluid the brush lays per dab (0..1)." },
50
+ _chunkHRUK5TFWcjs.BRUSH_FACET_SCHEMAS.ferro
51
+ ]
52
+ };
53
+ return { ...rest, tip: { ...tip, description: `${tip.description} In a patch, shape may be left out to keep it.` }, ferro };
46
54
  })();
47
- var UNSETTABLE_BRUSH_FACETS = ["description", "tags", "medium", "variety", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "motion", "livePatterns", "colour"];
55
+ var UNSETTABLE_BRUSH_FACETS = ["description", "tags", "medium", "variety", "marks", "dynamics", "behaviour", "light", "fluorescence", "thermochromic", "iridescence", "ghost", "mutation", "ferro", "motion", "livePatterns", "colour"];
48
56
  var BRUSH_UNSET_PROPERTY = {
49
57
  type: "array",
50
58
  minItems: 1,
@@ -54,9 +62,11 @@ var BRUSH_UNSET_PROPERTY = {
54
62
  };
55
63
  function mergeBrushPatch(base, patch, path = "") {
56
64
  const out = { ...base };
57
- for (const [k, v] of Object.entries(patch)) {
58
- if (v === void 0) continue;
59
- const old = out[k];
65
+ for (const [k, rawV] of Object.entries(patch)) {
66
+ if (rawV === void 0) continue;
67
+ const v = path === "" && k === "ferro" && typeof rawV === "number" ? { amount: rawV } : rawV;
68
+ const rawOld = out[k];
69
+ const old = path === "" && k === "ferro" && typeof rawOld === "number" ? { amount: rawOld } : rawOld;
60
70
  const replaceMarks = path === "" && k === "marks" && isObject(old) && isObject(v) && old.kind !== v.kind;
61
71
  out[k] = isObject(v) && isObject(old) && !replaceMarks ? mergeBrushPatch(old, v, `${path}${k}.`) : copy(v);
62
72
  }
@@ -557,7 +567,7 @@ function idMaker(prefix) {
557
567
  return (name) => {
558
568
  const base = `${prefix}-${slug(name, 20) || "brush"}`.slice(0, 58).replace(/-+$/, "");
559
569
  let id = base;
560
- for (let n = 2; used.has(id) || !_chunkQ33V5PEFcjs.BRUSH_ID.test(id); n++) id = `${base}-${n}`;
570
+ for (let n = 2; used.has(id) || !_chunkHRUK5TFWcjs.BRUSH_ID.test(id); n++) id = `${base}-${n}`;
561
571
  used.add(id);
562
572
  return id;
563
573
  };
@@ -582,9 +592,9 @@ function readAbrPack(bytes, name, prefix, licence, pack) {
582
592
  return;
583
593
  }
584
594
  try {
585
- const source = _chunkQ33V5PEFcjs.brushSourceFromMask.call(void 0, { id: `${brushId}-shape`, name: label, licence, origin: `${name}.abr` }, tip.width, tip.height, tip.mask);
595
+ const source = _chunkHRUK5TFWcjs.brushSourceFromMask.call(void 0, { id: `${brushId}-shape`, name: label, licence, origin: `${name}.abr` }, tip.width, tip.height, tip.mask);
586
596
  pack.sources.push(source);
587
- pack.brushes.push({ ...base, tip: { shape: "stamp", size: clamp2(Math.max(tip.width, tip.height), 1, 2500), spacing: _nullishCoalesce(tip.spacing, () => ( 0.25)), stamps: [_chunkQ33V5PEFcjs.brushSourceRef.call(void 0, source.id)] } });
597
+ pack.brushes.push({ ...base, tip: { shape: "stamp", size: clamp2(Math.max(tip.width, tip.height), 1, 2500), spacing: _nullishCoalesce(tip.spacing, () => ( 0.25)), stamps: [_chunkHRUK5TFWcjs.brushSourceRef.call(void 0, source.id)] } });
588
598
  } catch (e) {
589
599
  pack.unsupported.push(`"${label}": ${e.message}`);
590
600
  }
@@ -626,7 +636,7 @@ function readProcreatePack(bytes, name, prefix, licence, pack) {
626
636
  const e = byName.get(`${dir}${file}`);
627
637
  if (!e) return null;
628
638
  try {
629
- const s = _chunkQ33V5PEFcjs.makeBrushSource.call(void 0, { id: `${brushId}-${kind}`, name: `${label} ${kind}`, image: e.read(), channel: "lightness", licence: [licence, terms].filter(Boolean).join("\n\n") || void 0, origin: `${where}/${file}` });
639
+ const s = _chunkHRUK5TFWcjs.makeBrushSource.call(void 0, { id: `${brushId}-${kind}`, name: `${label} ${kind}`, image: e.read(), channel: "lightness", licence: [licence, terms].filter(Boolean).join("\n\n") || void 0, origin: `${where}/${file}` });
630
640
  pack.sources.push(s);
631
641
  return s;
632
642
  } catch (err) {
@@ -647,8 +657,8 @@ function readProcreatePack(bytes, name, prefix, licence, pack) {
647
657
  shape: shape ? "stamp" : "round",
648
658
  size: 40,
649
659
  ...plot !== void 0 ? { spacing: plot } : {},
650
- ...shape ? { stamps: [_chunkQ33V5PEFcjs.brushSourceRef.call(void 0, shape.id)] } : {},
651
- ...grain ? { grain: { source: _chunkQ33V5PEFcjs.brushSourceRef.call(void 0, grain.id), mode: "moving" } } : {}
660
+ ...shape ? { stamps: [_chunkHRUK5TFWcjs.brushSourceRef.call(void 0, shape.id)] } : {},
661
+ ...grain ? { grain: { source: _chunkHRUK5TFWcjs.brushSourceRef.call(void 0, grain.id), mode: "moving" } } : {}
652
662
  }
653
663
  });
654
664
  if (!settings) pack.unsupported.push(`"${label}": its Brush.archive could not be read; the brush takes its name from the file and default settings.`);
@@ -702,17 +712,17 @@ async function importBrushFile(library, file, options = {}) {
702
712
  // src/brushes/tools.ts
703
713
  var READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
704
714
  var WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
705
- var FACETS2 = _chunkQ33V5PEFcjs.BRUSH_JSON_SCHEMA.properties;
715
+ var FACETS2 = _chunkHRUK5TFWcjs.BRUSH_JSON_SCHEMA.properties;
706
716
  var ID = { type: "string", pattern: String(FACETS2.id.pattern), description: "Brush id from list_brush_presets (lowercase letters, digits and dashes)." };
707
717
  var FACET_PROPS = BRUSH_PATCH_PROPERTIES;
708
- var HOSTS = Object.keys(_chunkQ33V5PEFcjs.BRUSH_HOST_PROFILES);
718
+ var HOSTS = Object.keys(_chunkHRUK5TFWcjs.BRUSH_HOST_PROFILES);
709
719
  var copy2 = (v) => JSON.parse(JSON.stringify(v));
710
720
  var merge = mergeBrushPatch;
711
721
  var changedPaths = changedBrushPaths;
712
722
  function summary(entry, profile, withCheck) {
713
723
  const b = entry.brush;
714
- const engine = _chunkQ33V5PEFcjs.resolveEngine.call(void 0, b);
715
- const check = withCheck ? _chunkQ33V5PEFcjs.checkBrush.call(void 0, b, profile) : null;
724
+ const engine = _chunkHRUK5TFWcjs.resolveEngine.call(void 0, b);
725
+ const check = withCheck ? _chunkHRUK5TFWcjs.checkBrush.call(void 0, b, profile) : null;
716
726
  return {
717
727
  id: b.id,
718
728
  name: b.name,
@@ -721,47 +731,48 @@ function summary(entry, profile, withCheck) {
721
731
  ...b.medium ? { medium: b.medium } : {},
722
732
  ...b.variety ? { variety: b.variety } : {},
723
733
  ...b.tool ? { tool: b.tool } : {},
724
- category: _chunkQ33V5PEFcjs.resolveCategory.call(void 0, b, engine),
734
+ category: _chunkHRUK5TFWcjs.resolveCategory.call(void 0, b, engine),
725
735
  engine,
726
736
  tip: b.tip.shape,
727
737
  ...b.tip.size !== void 0 ? { size: b.tip.size } : {},
728
738
  marks: _nullishCoalesce(_optionalChain([b, 'access', _16 => _16.marks, 'optionalAccess', _17 => _17.kind]), () => ( "continuous")),
729
739
  surface: _nullishCoalesce(_optionalChain([b, 'access', _18 => _18.behaviour, 'optionalAccess', _19 => _19.surface]), () => ( "dry")),
730
740
  ..._optionalChain([b, 'access', _20 => _20.light, 'optionalAccess', _21 => _21.amount]) ? { light: b.light.amount } : {},
731
- ..._optionalChain([b, 'access', _22 => _22.tags, 'optionalAccess', _23 => _23.length]) ? { tags: b.tags } : {},
741
+ ..._optionalChain([b, 'access', _22 => _22.ferro, 'optionalAccess', _23 => _23.amount]) ? { ferro: b.ferro.amount } : {},
742
+ ..._optionalChain([b, 'access', _24 => _24.tags, 'optionalAccess', _25 => _25.length]) ? { tags: b.tags } : {},
732
743
  ...check ? { renderable: check.ok } : {}
733
744
  };
734
745
  }
735
746
  var refusal = (check, host) => `${host.name} can't use this brush: ${check.issues.filter((i) => i.severity === "error").map((i) => `${i.facet}: ${i.reason} ${i.fix}`).join(" ")} (check_brush lists every issue; nothing was saved.)`;
736
747
  var warnings = (check) => check.issues.filter((i) => i.severity === "warning");
737
748
  function createBrushLibraryTools(library, options = {}) {
738
- const profile = _nullishCoalesce(options.profile, () => ( _chunkQ33V5PEFcjs.PAINT_PROFILE));
749
+ const profile = _nullishCoalesce(options.profile, () => ( _chunkHRUK5TFWcjs.PAINT_PROFILE));
739
750
  const hostOf = (id) => {
740
751
  if (id === void 0) return profile;
741
- const p = _chunkQ33V5PEFcjs.BRUSH_HOST_PROFILES[id];
742
- if (!p) throw new (0, _chunkQ33V5PEFcjs.BrushLibraryError)("invalid", `No host "${id}"; use one of: ${HOSTS.join(", ")}.`);
752
+ const p = _chunkHRUK5TFWcjs.BRUSH_HOST_PROFILES[id];
753
+ if (!p) throw new (0, _chunkHRUK5TFWcjs.BrushLibraryError)("invalid", `No host "${id}"; use one of: ${HOSTS.join(", ")}.`);
743
754
  return p;
744
755
  };
745
756
  const entry = async (id) => {
746
757
  const found = await library.get(id);
747
758
  if (found) return found;
748
759
  const ids = (await library.list()).map((e) => e.brush.id);
749
- throw new (0, _chunkQ33V5PEFcjs.BrushLibraryError)("unknown_brush", `No brush "${id}". ${_chunkQ33V5PEFcjs.closest.call(void 0, id, ids)}list_brush_presets lists them.`);
760
+ throw new (0, _chunkHRUK5TFWcjs.BrushLibraryError)("unknown_brush", `No brush "${id}". ${_chunkHRUK5TFWcjs.closest.call(void 0, id, ids)}list_brush_presets lists them.`);
750
761
  };
751
762
  const checkFor = async (brush, p) => {
752
- const check2 = _chunkQ33V5PEFcjs.checkBrush.call(void 0, brush, p);
763
+ const check2 = _chunkHRUK5TFWcjs.checkBrush.call(void 0, brush, p);
753
764
  const issues = [...check2.issues];
754
765
  const refs = [...(_nullishCoalesce(brush.tip.stamps, () => ( []))).map((r) => ["tip.stamps", r]), ...brush.tip.grain ? [["tip.grain.source", brush.tip.grain.source]] : []];
755
766
  for (const [facet, ref] of refs) {
756
- const id = _chunkQ33V5PEFcjs.brushSourceIdOf.call(void 0, ref);
767
+ const id = _chunkHRUK5TFWcjs.brushSourceIdOf.call(void 0, ref);
757
768
  if (id === null || await library.getSource(id)) continue;
758
769
  const ids = (await library.listSources()).map((x) => x.id);
759
- issues.push({ facet, severity: "error", reason: `There is no image source "${id}" in the brush library. ${_chunkQ33V5PEFcjs.closest.call(void 0, id, ids)}`.trim(), fix: "Add it with add_brush_source, or use a ref list_brush_sources lists." });
770
+ issues.push({ facet, severity: "error", reason: `There is no image source "${id}" in the brush library. ${_chunkHRUK5TFWcjs.closest.call(void 0, id, ids)}`.trim(), fix: "Add it with add_brush_source, or use a ref list_brush_sources lists." });
760
771
  }
761
- const provider = _optionalChain([options, 'access', _24 => _24.stickers, 'optionalCall', _25 => _25()]);
772
+ const provider = _optionalChain([options, 'access', _26 => _26.stickers, 'optionalCall', _27 => _27()]);
762
773
  if (!provider || brush.tip.shape !== "stamp") return { ok: !issues.some((i) => i.severity === "error"), issues };
763
774
  for (const ref of _nullishCoalesce(brush.tip.stamps, () => ( []))) {
764
- if (ref.startsWith("editor:") || _chunkQ33V5PEFcjs.isBrushSourceRef.call(void 0, ref)) continue;
775
+ if (ref.startsWith("editor:") || _chunkHRUK5TFWcjs.isBrushSourceRef.call(void 0, ref)) continue;
765
776
  try {
766
777
  await _chunkPFHJS4HEcjs.describeStickerRef.call(void 0, provider, ref);
767
778
  } catch (e) {
@@ -774,12 +785,12 @@ function createBrushLibraryTools(library, options = {}) {
774
785
  const problems = [];
775
786
  let brush;
776
787
  try {
777
- brush = _chunkQ33V5PEFcjs.normalizeBrushSpec.call(void 0, raw, problems);
788
+ brush = _chunkHRUK5TFWcjs.normalizeBrushSpec.call(void 0, raw, problems);
778
789
  } catch (e) {
779
- throw new (0, _chunkQ33V5PEFcjs.BrushLibraryError)("invalid", e.message);
790
+ throw new (0, _chunkHRUK5TFWcjs.BrushLibraryError)("invalid", e.message);
780
791
  }
781
- const check2 = _chunkQ33V5PEFcjs.withKeptIssues.call(void 0, await checkFor(brush, profile), original ? await checkFor(original, profile) : null);
782
- if (!check2.ok) throw new (0, _chunkQ33V5PEFcjs.BrushLibraryError)("invalid", refusal(check2, profile));
792
+ const check2 = _chunkHRUK5TFWcjs.withKeptIssues.call(void 0, await checkFor(brush, profile), original ? await checkFor(original, profile) : null);
793
+ if (!check2.ok) throw new (0, _chunkHRUK5TFWcjs.BrushLibraryError)("invalid", refusal(check2, profile));
783
794
  const saved = await library.save(brush, { overwrite });
784
795
  return { ...saved, problems, check: check2 };
785
796
  };
@@ -790,10 +801,10 @@ function createBrushLibraryTools(library, options = {}) {
790
801
  inputSchema: {
791
802
  type: "object",
792
803
  properties: {
793
- kind: { type: "string", enum: [..._chunkQ33V5PEFcjs.BRUSH_KINDS], description: "mark (lays a material) or tool (works on paint already down)." },
794
- medium: { type: "string", enum: [..._chunkQ33V5PEFcjs.BRUSH_MEDIA], description: "Marks laying this medium." },
804
+ kind: { type: "string", enum: [..._chunkHRUK5TFWcjs.BRUSH_KINDS], description: "mark (lays a material) or tool (works on paint already down)." },
805
+ medium: { type: "string", enum: [..._chunkHRUK5TFWcjs.BRUSH_MEDIA], description: "Marks laying this medium." },
795
806
  variety: { type: "string", maxLength: 40, description: "This variety (oil, watercolor, pencil, highlighter\u2026)." },
796
- tool: { type: "string", enum: [..._chunkQ33V5PEFcjs.BRUSH_TOOLS], description: "Tools that do this." },
807
+ tool: { type: "string", enum: [..._chunkHRUK5TFWcjs.BRUSH_TOOLS], description: "Tools that do this." },
797
808
  tag: { type: "string", maxLength: 40, description: "Brushes with this tag." },
798
809
  text: { type: "string", maxLength: 200, description: "Words in the id, name, description, variety or tags (each word must match the start of one)." },
799
810
  source: { type: "string", enum: ["all", "builtin", "user"], description: "all (default), builtin presets only, or user (saved) brushes only." },
@@ -820,8 +831,8 @@ function createBrushLibraryTools(library, options = {}) {
820
831
  annotations: { title: "Describe a brush", ...READ },
821
832
  handler: async ({ id }) => {
822
833
  const e = await entry(id);
823
- const engine = _chunkQ33V5PEFcjs.resolveEngine.call(void 0, e.brush);
824
- return { brush: e.brush, builtin: e.builtin, engine, category: _chunkQ33V5PEFcjs.resolveCategory.call(void 0, e.brush, engine), check: await checkFor(e.brush, profile) };
834
+ const engine = _chunkHRUK5TFWcjs.resolveEngine.call(void 0, e.brush);
835
+ return { brush: e.brush, builtin: e.builtin, engine, category: _chunkHRUK5TFWcjs.resolveCategory.call(void 0, e.brush, engine), check: await checkFor(e.brush, profile) };
825
836
  }
826
837
  };
827
838
  const check = {
@@ -839,20 +850,20 @@ function createBrushLibraryTools(library, options = {}) {
839
850
  },
840
851
  annotations: { title: "Check a brush for a host", ...READ },
841
852
  handler: async ({ id, brush, host }) => {
842
- if (id === void 0 === (brush === void 0)) throw new (0, _chunkQ33V5PEFcjs.BrushLibraryError)("invalid", "Give either id (a library brush) or brush (a spec), not both or neither.");
853
+ if (id === void 0 === (brush === void 0)) throw new (0, _chunkHRUK5TFWcjs.BrushLibraryError)("invalid", "Give either id (a library brush) or brush (a spec), not both or neither.");
843
854
  const p = hostOf(host);
844
855
  let b;
845
856
  const problems = [];
846
857
  if (id !== void 0) b = (await entry(id)).brush;
847
858
  else {
848
859
  try {
849
- b = _chunkQ33V5PEFcjs.normalizeBrushSpec.call(void 0, brush, problems);
860
+ b = _chunkHRUK5TFWcjs.normalizeBrushSpec.call(void 0, brush, problems);
850
861
  } catch (e) {
851
- throw new (0, _chunkQ33V5PEFcjs.BrushLibraryError)("invalid", e.message);
862
+ throw new (0, _chunkHRUK5TFWcjs.BrushLibraryError)("invalid", e.message);
852
863
  }
853
864
  }
854
865
  const result = await checkFor(b, p);
855
- return { host: p.id, ...result, ...problems.length ? { problems } : {}, ...!result.ok ? { degraded: _chunkQ33V5PEFcjs.degrade.call(void 0, b, p) } : {} };
866
+ return { host: p.id, ...result, ...problems.length ? { problems } : {}, ...!result.ok ? { degraded: _chunkHRUK5TFWcjs.degrade.call(void 0, b, p) } : {} };
856
867
  }
857
868
  };
858
869
  const exportTool = {
@@ -887,7 +898,7 @@ function createBrushLibraryTools(library, options = {}) {
887
898
  });
888
899
  const start = _nullishCoalesce(offset, () => ( 0));
889
900
  const page = found.slice(start, start + (_nullishCoalesce(limit, () => ( 100))));
890
- return { total: found.length, sources: page.map((x) => ({ ..._chunkQ33V5PEFcjs.summarizeBrushSource.call(void 0, x), usedBy: brushes.filter((b) => _chunkQ33V5PEFcjs.brushSourceIds.call(void 0, b).includes(x.id)).map((b) => b.id) })) };
901
+ return { total: found.length, sources: page.map((x) => ({ ..._chunkHRUK5TFWcjs.summarizeBrushSource.call(void 0, x), usedBy: brushes.filter((b) => _chunkHRUK5TFWcjs.brushSourceIds.call(void 0, b).includes(x.id)).map((b) => b.id) })) };
891
902
  }
892
903
  };
893
904
  const tools = [list, describe, check, exportTool, listSources];
@@ -911,7 +922,7 @@ function createBrushLibraryTools(library, options = {}) {
911
922
  annotations: { title: "Preview a brush", ...READ },
912
923
  handler: async ({ id, width, height, color }) => {
913
924
  const { brush } = await entry(id);
914
- const own = typeof _optionalChain([brush, 'access', _26 => _26.colour, 'optionalAccess', _27 => _27.default]) === "string" && /^#[0-9a-f]{6}/i.test(brush.colour.default) ? brush.colour.default.slice(0, 7) : void 0;
925
+ const own = typeof _optionalChain([brush, 'access', _28 => _28.colour, 'optionalAccess', _29 => _29.default]) === "string" && /^#[0-9a-f]{6}/i.test(brush.colour.default) ? brush.colour.default.slice(0, 7) : void 0;
915
926
  const w = _nullishCoalesce(width, () => ( 256));
916
927
  const h = _nullishCoalesce(height, () => ( 96));
917
928
  const image = await render(brush, { width: w, height: h, color: _nullishCoalesce(_nullishCoalesce(color, () => ( own)), () => ( "#1f2937")) });
@@ -938,12 +949,12 @@ function createBrushLibraryTools(library, options = {}) {
938
949
  },
939
950
  annotations: { title: "Save a new brush", ...WRITE },
940
951
  handler: async ({ basedOn, ...spec }) => {
941
- if (library.isBuiltin(spec.id)) throw new (0, _chunkQ33V5PEFcjs.BrushLibraryError)("builtin", `"${spec.id}" is a built-in brush id; pick another id (or basedOn "${spec.id}" with a new id).`);
942
- if (await library.get(spec.id)) throw new (0, _chunkQ33V5PEFcjs.BrushLibraryError)("exists", `A brush "${spec.id}" exists already; pick another id, or change it with update_brush.`);
952
+ if (library.isBuiltin(spec.id)) throw new (0, _chunkHRUK5TFWcjs.BrushLibraryError)("builtin", `"${spec.id}" is a built-in brush id; pick another id (or basedOn "${spec.id}" with a new id).`);
953
+ if (await library.get(spec.id)) throw new (0, _chunkHRUK5TFWcjs.BrushLibraryError)("exists", `A brush "${spec.id}" exists already; pick another id, or change it with update_brush.`);
943
954
  const base = basedOn !== void 0 ? (await entry(basedOn)).brush : void 0;
944
955
  const raw = base ? merge(base, spec) : spec;
945
956
  const saved = await store(raw, false);
946
- return { id: saved.brush.id, brush: saved.brush, engine: _chunkQ33V5PEFcjs.resolveEngine.call(void 0, saved.brush), problems: saved.problems, warnings: warnings(saved.check) };
957
+ return { id: saved.brush.id, brush: saved.brush, engine: _chunkHRUK5TFWcjs.resolveEngine.call(void 0, saved.brush), problems: saved.problems, warnings: warnings(saved.check) };
947
958
  }
948
959
  },
949
960
  {
@@ -964,7 +975,7 @@ function createBrushLibraryTools(library, options = {}) {
964
975
  annotations: { title: "Update a brush", ...WRITE, idempotentHint: true },
965
976
  handler: async ({ id, unset, ...patch }) => {
966
977
  const e = await entry(id);
967
- if (e.builtin) throw new (0, _chunkQ33V5PEFcjs.BrushLibraryError)("builtin", `"${id}" is a built-in brush and can't be changed; duplicate_brush makes a copy you can change.`);
978
+ if (e.builtin) throw new (0, _chunkHRUK5TFWcjs.BrushLibraryError)("builtin", `"${id}" is a built-in brush and can't be changed; duplicate_brush makes a copy you can change.`);
968
979
  const base = copy2(e.brush);
969
980
  for (const k of _nullishCoalesce(unset, () => ( []))) delete base[k];
970
981
  const saved = await store(merge(base, { ...patch, id }), true, e.brush);
@@ -974,14 +985,14 @@ function createBrushLibraryTools(library, options = {}) {
974
985
  {
975
986
  name: "add_brush_source",
976
987
  title: "Add a brush image source",
977
- description: `Adds an image to the brush library as a source for image tips and image grain (as Procreate's shape and grain sources). The image is a PNG data: URL; the marks are read from its transparency, its dark parts (a scan of marks on paper) or its light parts (white on black), as channel says, and kept as a grey mask at most ${_chunkQ33V5PEFcjs.MAX_BRUSH_SOURCE_SIZE} px on a side. Returns id, ref (source:<id>), the stored source's width and height, problems (the channel auto picked, scaling) and replaced. Then make the brush: create_brush or update_brush with tip { shape: "stamp", stamps: [ref] } for an image tip, or tip.grain { source: ref } for grain. A taken id is refused unless overwrite.`,
988
+ description: `Adds an image to the brush library as a source for image tips and image grain (as Procreate's shape and grain sources). The image is a PNG data: URL; the marks are read from its transparency, its dark parts (a scan of marks on paper) or its light parts (white on black), as channel says, and kept as a grey mask at most ${_chunkHRUK5TFWcjs.MAX_BRUSH_SOURCE_SIZE} px on a side. Returns id, ref (source:<id>), the stored source's width and height, problems (the channel auto picked, scaling) and replaced. Then make the brush: create_brush or update_brush with tip { shape: "stamp", stamps: [ref] } for an image tip, or tip.grain { source: ref } for grain. A taken id is refused unless overwrite.`,
978
989
  inputSchema: {
979
990
  type: "object",
980
991
  properties: {
981
992
  name: { type: "string", minLength: 1, maxLength: 80, description: 'Display name, e.g. "Charcoal scan".' },
982
- image: { type: "string", pattern: "^data:image/png;base64,", maxLength: 4e7, description: `The image: a PNG data: URL, at most ${_chunkQ33V5PEFcjs.MAX_BRUSH_SOURCE_INPUT_PIXELS} pixels; larger than ${_chunkQ33V5PEFcjs.MAX_BRUSH_SOURCE_SIZE} px it is scaled down.` },
993
+ image: { type: "string", pattern: "^data:image/png;base64,", maxLength: 4e7, description: `The image: a PNG data: URL, at most ${_chunkHRUK5TFWcjs.MAX_BRUSH_SOURCE_INPUT_PIXELS} pixels; larger than ${_chunkHRUK5TFWcjs.MAX_BRUSH_SOURCE_SIZE} px it is scaled down.` },
983
994
  id: { type: "string", pattern: String(FACETS2.id.pattern), description: "The source id (default: from the name, made unique): lowercase letters, digits and dashes." },
984
- channel: { type: "string", enum: [..._chunkQ33V5PEFcjs.BRUSH_SOURCE_CHANNELS], description: "Where the marks are: auto (default: the transparency when the image has any, else the dark parts), alpha, darkness (dark marks on light paper) or lightness (light marks on black)." },
995
+ channel: { type: "string", enum: [..._chunkHRUK5TFWcjs.BRUSH_SOURCE_CHANNELS], description: "Where the marks are: auto (default: the transparency when the image has any, else the dark parts), alpha, darkness (dark marks on light paper) or lightness (light marks on black)." },
985
996
  invert: { type: "boolean", description: "Swap painted and empty parts (default false)." },
986
997
  licence: { type: "string", maxLength: 2e3, description: "The licence or terms the image came with, kept with the source." },
987
998
  origin: { type: "string", maxLength: 200, description: "Where it came from: a file name, a pack or a URL." },
@@ -993,7 +1004,7 @@ function createBrushLibraryTools(library, options = {}) {
993
1004
  annotations: { title: "Add a brush image source", ...WRITE },
994
1005
  handler: async ({ overwrite, ...input }) => {
995
1006
  const r = await library.addSource(input, { overwrite: !!overwrite });
996
- return { id: r.source.id, ref: _chunkQ33V5PEFcjs.brushSourceRef.call(void 0, r.source.id), width: r.source.width, height: r.source.height, problems: r.problems, replaced: r.replaced };
1007
+ return { id: r.source.id, ref: _chunkHRUK5TFWcjs.brushSourceRef.call(void 0, r.source.id), width: r.source.width, height: r.source.height, problems: r.problems, replaced: r.replaced };
997
1008
  }
998
1009
  },
999
1010
  {
@@ -1067,24 +1078,24 @@ function createBrushLibraryTools(library, options = {}) {
1067
1078
  try {
1068
1079
  bytes = _chunkW5M3J7PVcjs.base64ToBytes.call(void 0, file.replace(/^data:[^,]*,/, ""));
1069
1080
  } catch (e6) {
1070
- throw new (0, _chunkQ33V5PEFcjs.BrushLibraryError)("invalid", "file is not base64: pass the pack's bytes as base64 (a data: URL works too).");
1081
+ throw new (0, _chunkHRUK5TFWcjs.BrushLibraryError)("invalid", "file is not base64: pass the pack's bytes as base64 (a data: URL works too).");
1071
1082
  }
1072
1083
  let r;
1073
1084
  try {
1074
1085
  r = await importBrushPack(library, bytes, { fileName, ...format ? { format } : {}, ...prefix ? { prefix } : {}, ...licence ? { licence } : {}, overwrite: !!overwrite });
1075
1086
  } catch (e) {
1076
- if (e instanceof _chunkQ33V5PEFcjs.BrushLibraryError) throw e;
1077
- throw new (0, _chunkQ33V5PEFcjs.BrushLibraryError)("invalid", `${e.message} Nothing was imported.`);
1087
+ if (e instanceof _chunkHRUK5TFWcjs.BrushLibraryError) throw e;
1088
+ throw new (0, _chunkHRUK5TFWcjs.BrushLibraryError)("invalid", `${e.message} Nothing was imported.`);
1078
1089
  }
1079
1090
  const entries = await Promise.all(r.brushes.map((b) => library.get(b.id)));
1080
- return { ...r, brushes: r.brushes.map((b, i) => ({ ...b, renderable: entries[i] ? _chunkQ33V5PEFcjs.checkBrush.call(void 0, entries[i].brush, profile).ok : false })) };
1091
+ return { ...r, brushes: r.brushes.map((b, i) => ({ ...b, renderable: entries[i] ? _chunkHRUK5TFWcjs.checkBrush.call(void 0, entries[i].brush, profile).ok : false })) };
1081
1092
  }
1082
1093
  }
1083
1094
  );
1084
1095
  return tools;
1085
1096
  }
1086
1097
  function createBrushLibraryToolProvider(library, options = {}) {
1087
- const profile = _nullishCoalesce(options.profile, () => ( _chunkQ33V5PEFcjs.PAINT_PROFILE));
1098
+ const profile = _nullishCoalesce(options.profile, () => ( _chunkHRUK5TFWcjs.PAINT_PROFILE));
1088
1099
  const tools = createBrushLibraryTools(library, options);
1089
1100
  return {
1090
1101
  id: _nullishCoalesce(options.id, () => ( "brushes")),
@@ -1135,7 +1146,7 @@ function brushEngineControls(brush) {
1135
1146
  const c = engineControls(brush);
1136
1147
  if (!c) return null;
1137
1148
  const mut = brush.mutation ? MUTATION_SLIDERS : [];
1138
- const wetPaint = brush.engine === "wet" && _optionalChain([brush, 'access', _28 => _28.wet, 'optionalAccess', _29 => _29.tool]) === "paint" && !brush.ghost && !brush.mutation;
1149
+ const wetPaint = brush.engine === "wet" && _optionalChain([brush, 'access', _30 => _30.wet, 'optionalAccess', _31 => _31.tool]) === "paint" && !brush.ghost && !brush.mutation;
1139
1150
  if (!(wetPaint || ["dab", "mixer", "particle", "bristle", "impasto"].includes(brush.engine))) {
1140
1151
  return mut.length ? { ...c, sliders: [...c.sliders, ...mut] } : c;
1141
1152
  }
@@ -1221,7 +1232,7 @@ function engineControls(brush) {
1221
1232
  impastoTool: true
1222
1233
  };
1223
1234
  case "wet": {
1224
- const tool = _nullishCoalesce(_optionalChain([brush, 'access', _30 => _30.wet, 'optionalAccess', _31 => _31.tool]), () => ( "paint"));
1235
+ const tool = _nullishCoalesce(_optionalChain([brush, 'access', _32 => _32.wet, 'optionalAccess', _33 => _33.tool]), () => ( "paint"));
1225
1236
  const ghost = tool === "paint" && (Boolean(brush.ghost) || Boolean(brush.mutation));
1226
1237
  const sliders = tool === "paint" ? [
1227
1238
  { path: "wet.water", label: "Water", min: 0, max: 1, step: 0.01 },
@@ -74,7 +74,7 @@ function profileAlongPolyline(points, values, count = 32) {
74
74
  var round3 = (v) => Math.round(v * 1e3) / 1e3;
75
75
  var loading = null;
76
76
  function loadCanvasBrushstrokeRenderer() {
77
- loading ??= import("./brushstrokeRender-FK3ASUUJ.js").catch((error) => {
77
+ loading ??= import("./brushstrokeRender-EIK3625T.js").catch((error) => {
78
78
  loading = null;
79
79
  throw error;
80
80
  });