axstack 0.36.1 → 0.38.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 (137) hide show
  1. package/README.md +5 -4
  2. package/bin/axstack.js +4 -18
  3. package/docs/getting-started.md +6 -6
  4. package/docs/host-operations.md +9 -5
  5. package/docs/installation.md +20 -41
  6. package/docs/workflows.md +12 -9
  7. package/package.json +1 -1
  8. package/profiles/presets/claude-only.json +3 -3
  9. package/profiles/presets/codex-only.json +3 -3
  10. package/profiles/presets/mixed.json +5 -4
  11. package/skills/axstack/references/automations.md +7 -2
  12. package/skills/axstack/references/lifecycle.md +2 -1
  13. package/skills/axstack/references/review-manager-prompt.md +44 -3
  14. package/skills/axstack/references/role-roster.md +13 -6
  15. package/skills/axstack/references/routing.md +2 -1
  16. package/skills/axstack/references/run-record.md +2 -0
  17. package/skills/axstack/references/ui-verification.md +6 -10
  18. package/skills/axstack-anatomy/ANATOMY.md +172 -0
  19. package/skills/axstack-anatomy/CHANGELOG.md +70 -0
  20. package/skills/axstack-anatomy/LICENSE +21 -0
  21. package/skills/axstack-anatomy/PROVENANCE.md +21 -0
  22. package/skills/axstack-anatomy/README.md +316 -0
  23. package/skills/axstack-anatomy/SKILL.md +74 -0
  24. package/skills/axstack-anatomy/SOURCE-PROOF.json +107 -0
  25. package/skills/axstack-anatomy/examples/arcade-cabinet/arcade-cabinet.css +44 -0
  26. package/skills/axstack-anatomy/examples/arcade-cabinet/build.mjs +714 -0
  27. package/skills/axstack-anatomy/examples/arcade-cabinet/live.js +597 -0
  28. package/skills/axstack-anatomy/examples/arcade-cabinet/parts.mjs +89 -0
  29. package/skills/axstack-anatomy/examples/arcade-cabinet/screen.mjs +114 -0
  30. package/skills/axstack-anatomy/examples/desk-computer/build.mjs +435 -0
  31. package/skills/axstack-anatomy/examples/dial-indicator/build.mjs +275 -0
  32. package/skills/axstack-anatomy/examples/raptor-engine/build.mjs +938 -0
  33. package/skills/axstack-anatomy/examples/raptor-engine/live.js +907 -0
  34. package/skills/axstack-anatomy/examples/raptor-engine/raptor.css +359 -0
  35. package/skills/axstack-anatomy/examples/ripple-tank/build.mjs +558 -0
  36. package/skills/axstack-anatomy/examples/ripple-tank/live.js +579 -0
  37. package/skills/axstack-anatomy/examples/ripple-tank/model.mjs +131 -0
  38. package/skills/axstack-anatomy/examples/ripple-tank/ripple.css +103 -0
  39. package/skills/axstack-anatomy/examples/spot-plate/art.tsx +110 -0
  40. package/skills/axstack-anatomy/examples/spot-plate/drops.ts +541 -0
  41. package/skills/axstack-anatomy/examples/spot-plate/fan.ts +84 -0
  42. package/skills/axstack-anatomy/examples/spot-plate/figure.tsx +25 -0
  43. package/skills/axstack-anatomy/examples/spot-plate/geometry.ts +280 -0
  44. package/skills/axstack-anatomy/examples/spot-plate/live.tsx +339 -0
  45. package/skills/axstack-anatomy/examples/spot-plate/rig.ts +230 -0
  46. package/skills/axstack-anatomy/examples/spot-plate/spot-plate.css +92 -0
  47. package/skills/axstack-anatomy/examples/test-rig/art.tsx +196 -0
  48. package/skills/axstack-anatomy/examples/test-rig/figure.tsx +23 -0
  49. package/skills/axstack-anatomy/examples/test-rig/geometry.ts +305 -0
  50. package/skills/axstack-anatomy/examples/test-rig/live.tsx +511 -0
  51. package/skills/axstack-anatomy/examples/test-rig/pose.ts +107 -0
  52. package/skills/axstack-anatomy/examples/test-rig/rig.ts +170 -0
  53. package/skills/axstack-anatomy/examples/test-rig/stretch.css +80 -0
  54. package/skills/axstack-anatomy/examples/test-rig/view.ts +99 -0
  55. package/skills/axstack-anatomy/examples/turning-dial/build.mjs +479 -0
  56. package/skills/axstack-anatomy/examples/turning-dial/dial.css +10 -0
  57. package/skills/axstack-anatomy/examples/turning-dial/live.js +342 -0
  58. package/skills/axstack-anatomy/examples/turning-dial/stress.mjs +269 -0
  59. package/skills/axstack-anatomy/kit/audit.mjs +1913 -0
  60. package/skills/axstack-anatomy/kit/canvas-painter.mjs +722 -0
  61. package/skills/axstack-anatomy/kit/gl-shared.mjs +184 -0
  62. package/skills/axstack-anatomy/kit/gl.mjs +184 -0
  63. package/skills/axstack-anatomy/kit/iso-kit.mjs +674 -0
  64. package/skills/axstack-anatomy/kit/iso-kit.ts +842 -0
  65. package/skills/axstack-anatomy/kit/iso.css +106 -0
  66. package/skills/axstack-anatomy/kit/lathe.mjs +1184 -0
  67. package/skills/axstack-anatomy/kit/react/draw.tsx +205 -0
  68. package/skills/axstack-anatomy/kit/react/live.tsx +140 -0
  69. package/skills/axstack-anatomy/kit/tube.mjs +306 -0
  70. package/skills/axstack-anatomy/kit/turn-audit.mjs +1096 -0
  71. package/skills/axstack-anatomy/kit/turn-build.mjs +1355 -0
  72. package/skills/axstack-anatomy/kit/turn-fixed.mjs +3800 -0
  73. package/skills/axstack-anatomy/kit/turn.mjs +3610 -0
  74. package/skills/axstack-anatomy/references/3d.md +352 -0
  75. package/skills/axstack-anatomy/references/craft.md +325 -0
  76. package/skills/axstack-anatomy/references/kit.md +419 -0
  77. package/skills/axstack-anatomy/references/motion.md +165 -0
  78. package/skills/axstack-anatomy/references/react.md +42 -0
  79. package/skills/axstack-anatomy/references/verify.md +351 -0
  80. package/skills/axstack-anatomy/references/walkthrough.md +75 -0
  81. package/skills/axstack-anatomy/references/webgl.md +473 -0
  82. package/skills/axstack-anatomy/scripts/audit-affine-bounds.test.mjs +54 -0
  83. package/skills/axstack-anatomy/scripts/audit-cache.test.mjs +37 -0
  84. package/skills/axstack-anatomy/scripts/audit-clearance-envelope.test.mjs +39 -0
  85. package/skills/axstack-anatomy/scripts/audit-empty-clipping.test.mjs +38 -0
  86. package/skills/axstack-anatomy/scripts/audit-order-convergence.test.mjs +55 -0
  87. package/skills/axstack-anatomy/scripts/audit-path-origin.test.mjs +26 -0
  88. package/skills/axstack-anatomy/scripts/audit-profile.test.mjs +41 -0
  89. package/skills/axstack-anatomy/scripts/audit-projection.test.mjs +36 -0
  90. package/skills/axstack-anatomy/scripts/audit-slide-clearance.test.mjs +58 -0
  91. package/skills/axstack-anatomy/scripts/audit-svg-attributes.test.mjs +46 -0
  92. package/skills/axstack-anatomy/scripts/audit-taper.test.mjs +29 -0
  93. package/skills/axstack-anatomy/scripts/capture-selector.test.mjs +131 -0
  94. package/skills/axstack-anatomy/scripts/capture.mjs +228 -0
  95. package/skills/axstack-anatomy/scripts/capture.test.mjs +42 -0
  96. package/skills/axstack-anatomy/scripts/chrome-port.mjs +36 -0
  97. package/skills/axstack-anatomy/scripts/chrome-startup.test.mjs +38 -0
  98. package/skills/axstack-anatomy/scripts/drive-actions.test.mjs +46 -0
  99. package/skills/axstack-anatomy/scripts/drive.mjs +302 -0
  100. package/skills/axstack-anatomy/scripts/drive.test.mjs +72 -0
  101. package/skills/axstack-anatomy/scripts/enter-key.test.mjs +38 -0
  102. package/skills/axstack-anatomy/scripts/fidelity-cut.test.mjs +54 -0
  103. package/skills/axstack-anatomy/scripts/fidelity-tube-coverage.test.mjs +40 -0
  104. package/skills/axstack-anatomy/scripts/inline-kit.mjs +26 -0
  105. package/skills/axstack-anatomy/scripts/line-ends.mjs +306 -0
  106. package/skills/axstack-anatomy/scripts/lines-arcs.test.mjs +50 -0
  107. package/skills/axstack-anatomy/scripts/lines-bezier.test.mjs +28 -0
  108. package/skills/axstack-anatomy/scripts/lines-round.test.mjs +56 -0
  109. package/skills/axstack-anatomy/scripts/lines-rounded-rect.test.mjs +32 -0
  110. package/skills/axstack-anatomy/scripts/lines.mjs +31 -0
  111. package/skills/axstack-anatomy/scripts/lines.test.mjs +38 -0
  112. package/skills/axstack-anatomy/scripts/navigation.test.mjs +55 -0
  113. package/skills/axstack-anatomy/scripts/orbit-sampling.test.mjs +50 -0
  114. package/skills/axstack-anatomy/scripts/react-dots-opacity.test.mjs +51 -0
  115. package/skills/axstack-anatomy/scripts/react-readout-first-write.test.mjs +37 -0
  116. package/skills/axstack-anatomy/scripts/react-solid-seamless.test.mjs +47 -0
  117. package/skills/axstack-anatomy/scripts/react-visibility-target.test.mjs +38 -0
  118. package/skills/axstack-anatomy/scripts/turn-bench.mjs +74 -0
  119. package/skills/axstack-anatomy/scripts/turn-check.mjs +641 -0
  120. package/skills/axstack-anatomy/scripts/turn-fidelity.mjs +413 -0
  121. package/skills/axstack-anatomy/scripts/turn-group-reference.test.mjs +40 -0
  122. package/skills/axstack-anatomy/scripts/turn-split-owner.test.mjs +62 -0
  123. package/skills/axstack-anatomy/theme.js +56 -0
  124. package/skills/axstack-explain/SKILL.md +28 -15
  125. package/skills/axstack-explain/references/delivery.md +22 -0
  126. package/skills/{axstack-diagram → axstack-explain}/references/fidelity.md +1 -7
  127. package/skills/axstack-explain/references/inline-pages.md +8 -8
  128. package/skills/axstack-explain/references/visual-qa.md +3 -5
  129. package/skills/axstack-explain/references/whiteboard-defense.md +59 -0
  130. package/skills/axstack-research/SKILL.md +18 -6
  131. package/src/archify-legacy.js +70 -0
  132. package/src/capabilities.js +1 -29
  133. package/src/installer.js +11 -31
  134. package/skills/axstack-diagram/SKILL.md +0 -28
  135. package/skills/axstack-diagram/references/archify.md +0 -76
  136. package/src/archify-pin.js +0 -5
  137. package/src/archify.js +0 -151
@@ -0,0 +1,722 @@
1
+ const CP_Q = 64;
2
+ const CP_SVG = "http://www.w3.org/2000/svg";
3
+
4
+ function cpScratch() {
5
+ const c = document.createElement("canvas");
6
+ c.width = c.height = 1;
7
+ return c.getContext("2d");
8
+ }
9
+
10
+ function cpRgba(css, probe) {
11
+ if (!css || css === "none" || css.startsWith("url(")) return null;
12
+ probe.fillStyle = "#000";
13
+ probe.fillStyle = css;
14
+ const out = probe.fillStyle;
15
+ if (out[0] === "#") return [parseInt(out.slice(1, 3), 16), parseInt(out.slice(3, 5), 16), parseInt(out.slice(5, 7), 16), 1];
16
+ const nums = out.match(/-?[\d.]+(e-?\d+)?/g);
17
+ if (!nums) return null;
18
+ if (out.startsWith("color(")) {
19
+ const v = nums.map(Number);
20
+ return [v[0] * 255, v[1] * 255, v[2] * 255, v.length > 3 ? v[3] : 1];
21
+ }
22
+ const v = nums.map(Number);
23
+ return [v[0], v[1], v[2], v.length > 3 ? v[3] : 1];
24
+ }
25
+
26
+ function cpCss(rgba, alpha = 1) {
27
+ const a = rgba[3] * alpha;
28
+ const r = Math.round(rgba[0]);
29
+ const g = Math.round(rgba[1]);
30
+ const b = Math.round(rgba[2]);
31
+ return a >= 1 ? `rgb(${r},${g},${b})` : `rgba(${r},${g},${b},${Math.round(a * 1000) / 1000})`;
32
+ }
33
+
34
+ function cpMatrixOf(text) {
35
+ if (!text) return null;
36
+ const nums = text.match(/-?[\d.]+(e-?\d+)?/g);
37
+ if (!nums) return null;
38
+ const v = nums.map(Number);
39
+ if (text.startsWith("translate")) return [1, 0, 0, 1, v[0], v[1] ?? 0];
40
+ if (text.startsWith("matrix")) return v;
41
+ const m = new DOMMatrix(text);
42
+ return [m.a, m.b, m.c, m.d, m.e, m.f];
43
+ }
44
+
45
+ function cpMul(A, B) {
46
+ if (!A) return B;
47
+ if (!B) return A;
48
+ return [A[0] * B[0] + A[2] * B[1], A[1] * B[0] + A[3] * B[1], A[0] * B[2] + A[2] * B[3], A[1] * B[2] + A[3] * B[3], A[0] * B[4] + A[2] * B[5] + A[4], A[1] * B[4] + A[3] * B[5] + A[5]];
49
+ }
50
+
51
+ function cpStaticMatrix(el) {
52
+ const list = el.transform && el.transform.baseVal;
53
+ if (!list || !list.numberOfItems) return null;
54
+ const m = list.consolidate().matrix;
55
+ return [m.a, m.b, m.c, m.d, m.e, m.f];
56
+ }
57
+
58
+ function cpGeometry(el) {
59
+ const tag = el.localName;
60
+ const num = (name) => parseFloat(el.getAttribute(name) ?? "0") || 0;
61
+ if (tag === "path") return el.getAttribute("d") ? new Path2D(el.getAttribute("d")) : null;
62
+ const p = new Path2D();
63
+ if (tag === "circle") p.arc(num("cx"), num("cy"), num("r"), 0, Math.PI * 2);
64
+ else if (tag === "ellipse") p.ellipse(num("cx"), num("cy"), num("rx"), num("ry"), 0, 0, Math.PI * 2);
65
+ else if (tag === "rect") {
66
+ const rx = num("rx") || num("ry");
67
+ if (rx && p.roundRect) p.roundRect(num("x"), num("y"), num("width"), num("height"), rx);
68
+ else p.rect(num("x"), num("y"), num("width"), num("height"));
69
+ } else if (tag === "line") {
70
+ p.moveTo(num("x1"), num("y1"));
71
+ p.lineTo(num("x2"), num("y2"));
72
+ } else if (tag === "polyline" || tag === "polygon") {
73
+ const v = (el.getAttribute("points") ?? "").trim().split(/[\s,]+/).map(Number);
74
+ for (let i = 0; i + 1 < v.length; i += 2) i ? p.lineTo(v[i], v[i + 1]) : p.moveTo(v[i], v[i + 1]);
75
+ if (tag === "polygon") p.closePath();
76
+ } else return null;
77
+ return p;
78
+ }
79
+
80
+ function cpStyle(el, probe) {
81
+ const cs = getComputedStyle(el);
82
+ if (cs.display === "none" || cs.visibility === "hidden") return null;
83
+ const fill = cpRgba(cs.fill, probe);
84
+ const stroke = cpRgba(cs.stroke, probe);
85
+ const fillOpacity = parseFloat(cs.fillOpacity);
86
+ const strokeOpacity = parseFloat(cs.strokeOpacity);
87
+ const dash = cs.strokeDasharray && cs.strokeDasharray !== "none" ? cs.strokeDasharray.split(/[\s,]+/).map(parseFloat).filter((v) => !Number.isNaN(v)) : null;
88
+ return {
89
+ cs,
90
+ fill: fill ? cpCss(fill, isNaN(fillOpacity) ? 1 : fillOpacity) : null,
91
+ fillRaw: fill ? cpCss(fill) : null,
92
+ stroke: stroke ? cpCss(stroke, isNaN(strokeOpacity) ? 1 : strokeOpacity) : null,
93
+ width: parseFloat(cs.strokeWidth) || 0,
94
+ cap: cs.strokeLinecap || "butt",
95
+ join: cs.strokeLinejoin || "miter",
96
+ miter: parseFloat(cs.strokeMiterlimit) || 4,
97
+ dash,
98
+ dashOffset: parseFloat(cs.strokeDashoffset) || 0,
99
+ rule: cs.fillRule === "evenodd" ? "evenodd" : "nonzero",
100
+ opacity: parseFloat(cs.opacity),
101
+ nonScaling: cs.vectorEffect === "non-scaling-stroke",
102
+ };
103
+ }
104
+
105
+ function cpCompilePart(node, run, probe, unsupported, vary) {
106
+ const ops = [];
107
+ const roleOf = (el) => {
108
+ const name = el === node ? "move" : el.getAttribute("data-r");
109
+ if (name === null || run.role[name] === undefined) return -1;
110
+ return run.role[name];
111
+ };
112
+ const varyOf = (el) => {
113
+ if (!vary || !el.classList) return null;
114
+ for (const cls of el.classList) if (vary[cls]) return vary[cls];
115
+ return null;
116
+ };
117
+ const walk = (el, chain, alphaRoles, alpha, varies = []) => {
118
+ const varied = varyOf(el);
119
+ const ownVaries = varied ? varies.concat([varied]) : varies;
120
+ const r = roleOf(el);
121
+ const type = r >= 0 ? run.roles[r].type : null;
122
+ const tag = el.localName;
123
+ let nextChain = chain;
124
+ if (type === "self" || type === "plane" || type === "xform") nextChain = chain.concat([{ r }]);
125
+ else {
126
+ const m = el === node ? null : cpStaticMatrix(el);
127
+ if (m) nextChain = chain.concat([{ m }]);
128
+ }
129
+ if (tag === "g" || tag === "svg") {
130
+ let groupAlpha = alpha;
131
+ let groupRoles = alphaRoles;
132
+ if (type === "plane") groupRoles = alphaRoles.concat([r]);
133
+ else if (varied) {
134
+ } else if (el !== node) {
135
+ const op = parseFloat(getComputedStyle(el).opacity);
136
+ if (!Number.isNaN(op)) groupAlpha *= op;
137
+ }
138
+ for (const child of el.children) walk(child, nextChain, groupRoles, groupAlpha, ownVaries);
139
+ return;
140
+ }
141
+ if (tag === "title" || tag === "desc") return;
142
+ const style = cpStyle(el, probe);
143
+ if (!style) return;
144
+ let ownAlpha = alpha;
145
+ let ownRoles = alphaRoles;
146
+ if (type === "slot") ownRoles = alphaRoles.concat([r]);
147
+ else if (!varied && !Number.isNaN(style.opacity)) ownAlpha *= style.opacity;
148
+ const op = { tag, r, type, chain: nextChain, alpha: ownAlpha, alphaRoles: ownRoles, varies: ownVaries, style, path: null, size: 0, last: null };
149
+ if (type === "face") {
150
+ const cs = style.cs;
151
+ op.ramp = [0, 1, 2, 3, 4].map((i) => cpRgba(cs.getPropertyValue(`--tn-${i}`).trim(), probe) ?? [0, 0, 0, 1]);
152
+ op.rampKey = op.ramp.map((c) => c.join(",")).join("|");
153
+ op.baseWidth = style.width;
154
+ } else if (type === "dot") {
155
+ op.size = parseFloat(el.getAttribute("r")) || 0.5;
156
+ } else if (type === "path" || type === "slot") {
157
+ } else if (tag === "text") {
158
+ const cs = style.cs;
159
+ op.text = el.textContent;
160
+ op.x = parseFloat(el.getAttribute("x") ?? "0") || 0;
161
+ op.y = parseFloat(el.getAttribute("y") ?? "0") || 0;
162
+ op.font = `${cs.fontStyle} ${cs.fontWeight} ${cs.fontSize} ${cs.fontFamily}`;
163
+ op.align = cs.textAnchor === "middle" ? "center" : cs.textAnchor === "end" ? "right" : "left";
164
+ op.spacing = cs.letterSpacing === "normal" ? "0px" : cs.letterSpacing;
165
+ op.baseline = cs.dominantBaseline === "central" || cs.dominantBaseline === "middle" ? "middle" : cs.dominantBaseline === "hanging" ? "hanging" : "alphabetic";
166
+ } else {
167
+ op.path = cpGeometry(el);
168
+ if (!op.path) {
169
+ unsupported.add(tag);
170
+ return;
171
+ }
172
+ }
173
+ ops.push(op);
174
+ };
175
+ walk(node, [], [], 1);
176
+ return ops;
177
+ }
178
+
179
+ function cpTrace(ctx, d, arena) {
180
+ ctx.beginPath();
181
+ for (let i = 0; i + 4 < d.length; i += 5) {
182
+ const at = 2 * (d.charCodeAt(i + 1) | (d.charCodeAt(i + 2) << 16));
183
+ const count = d.charCodeAt(i + 3);
184
+ ctx.moveTo(arena[at], arena[at + 1]);
185
+ for (let k = 1; k < count; k++) ctx.lineTo(arena[at + 2 * k], arena[at + 2 * k + 1]);
186
+ if (d.charCodeAt(i + 4)) ctx.closePath();
187
+ }
188
+ }
189
+
190
+ function cpMount(stage, controller, { keepSvg = false, vary = null, only = null, effects = null, below = null, mask = null } = {}) {
191
+ const probe = cpScratch();
192
+ const scene = () => controller.scene();
193
+ const unsupported = new Set();
194
+ const layers = [];
195
+ const programs = [];
196
+ const wanted = only ?? controller.canvasLayers ?? null;
197
+ const svgs = [...stage.querySelectorAll("svg[data-live]")].filter((svg) => !wanted || wanted.includes(svg.dataset.live));
198
+ const first = svgs[0];
199
+ const vb = first.viewBox.baseVal;
200
+ const W = vb.width;
201
+ const H = vb.height;
202
+ const runs = scene().runs;
203
+ const t0 = performance.now();
204
+ for (const svg of svgs) {
205
+ const name = svg.dataset.live;
206
+ const canvas = document.createElement("canvas");
207
+ canvas.className = "iso-layer iso-canvas";
208
+ canvas.dataset.canvasLive = name;
209
+ canvas.setAttribute("aria-hidden", "true");
210
+ svg.before(canvas);
211
+ const ctx = canvas.getContext("2d", { alpha: true, desynchronized: false });
212
+ const container = svg.querySelector(".iso-parts") ?? svg;
213
+ for (const node of container.querySelectorAll(":scope > [data-p]")) {
214
+ const index = Number(node.dataset.p);
215
+ const effect = effects && effects[runs[index].name];
216
+ programs[index] = effect === false ? [] : effect ? [{ effect }] : cpCompilePart(node, runs[index], probe, unsupported, vary);
217
+ }
218
+ layers.push({ name, canvas, ctx, svg });
219
+ }
220
+ const compileMs = performance.now() - t0;
221
+ const effectRuns = effects ? runs.filter((run) => typeof effects[run.name] === "function" && effects[run.name].always !== false && programs[run.index]).map((run) => run.index) : [];
222
+ const view = { zoom: 1, cx: W / 2, cy: H / 2 };
223
+ let cssPerUnit = 1;
224
+ let dpr = window.devicePixelRatio || 1;
225
+ let base = [1, 0, 0, 1, 0, 0];
226
+ const resize = () => {
227
+ forceFull = true;
228
+ const box = stage.getBoundingClientRect();
229
+ dpr = window.devicePixelRatio || 1;
230
+ const cssW = box.width;
231
+ const cssH = box.height;
232
+ const snapX = box.left - Math.round(box.left);
233
+ const snapY = box.top - Math.round(box.top);
234
+ const boxW = Math.ceil(cssW + snapX - 1e-6);
235
+ const boxH = Math.ceil(cssH + snapY - 1e-6);
236
+ const w = Math.max(1, Math.round(boxW * dpr));
237
+ const h = Math.max(1, Math.round(boxH * dpr));
238
+ for (const layer of layers) {
239
+ if (layer.canvas.width !== w) layer.canvas.width = w;
240
+ if (layer.canvas.height !== h) layer.canvas.height = h;
241
+ layer.canvas.style.width = `${boxW}px`;
242
+ layer.canvas.style.height = `${boxH}px`;
243
+ layer.canvas.style.left = `${-snapX}px`;
244
+ layer.canvas.style.top = `${-snapY}px`;
245
+ }
246
+ const fit = Math.min(cssW / W, cssH / H);
247
+ const offX = (cssW - W * fit) / 2;
248
+ const offY = (cssH - H * fit) / 2;
249
+ const s = fit * view.zoom;
250
+ cssPerUnit = s;
251
+ const tx = offX + (W / 2) * fit - view.cx * s;
252
+ const ty = offY + (H / 2) * fit - view.cy * s;
253
+ base = [s * dpr, 0, 0, s * dpr, (tx + snapX) * dpr, (ty + snapY) * dpr];
254
+ const unit = Math.round(Math.pow(2, Math.round(Math.log2(s) * 4) / 4) * 100) / 100;
255
+ if (controller.setUnit) controller.setUnit(unit);
256
+ };
257
+ const faceCache = new Map();
258
+ const faceColour = (op, q) => {
259
+ let table = faceCache.get(op.rampKey);
260
+ if (!table) {
261
+ table = new Array(4 * CP_Q + 1);
262
+ faceCache.set(op.rampKey, table);
263
+ }
264
+ let css = table[q];
265
+ if (css === undefined) {
266
+ const band = Math.min(3, Math.floor(q / CP_Q));
267
+ const f = (q - CP_Q * band) / CP_Q;
268
+ const a = op.ramp[band];
269
+ const b = op.ramp[band + 1];
270
+ css = cpCss([a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f, a[2] + (b[2] - a[2]) * f, a[3] + (b[3] - a[3]) * f]);
271
+ table[q] = css;
272
+ }
273
+ return css;
274
+ };
275
+ const stats = { ms: 0, ops: 0, paths: 0, parsed: 0, touched: 0, rects: 0, full: 0 };
276
+ const lastBox = [];
277
+ let painted = false;
278
+ const PAD = 3;
279
+ const rectsOf = (frames, emitted) => {
280
+ const list = [];
281
+ const pad = PAD / cssPerUnit;
282
+ for (const index of emitted) {
283
+ const b = frames[index].box;
284
+ const old = lastBox[index];
285
+ let x0 = b[0];
286
+ let y0 = b[1];
287
+ let x1 = b[2];
288
+ let y1 = b[3];
289
+ if (old) {
290
+ x0 = Math.min(x0, old[0]);
291
+ y0 = Math.min(y0, old[1]);
292
+ x1 = Math.max(x1, old[2]);
293
+ y1 = Math.max(y1, old[3]);
294
+ }
295
+ if (!(x1 >= x0)) continue;
296
+ list.push([x0 - pad, y0 - pad, x1 + pad, y1 + pad]);
297
+ }
298
+ let merged = true;
299
+ while (merged) {
300
+ merged = false;
301
+ for (let i = 0; i < list.length && !merged; i++)
302
+ for (let j = i + 1; j < list.length; j++) {
303
+ const a = list[i];
304
+ const c = list[j];
305
+ if (a[0] <= c[2] && c[0] <= a[2] && a[1] <= c[3] && c[1] <= a[3]) {
306
+ list[i] = [Math.min(a[0], c[0]), Math.min(a[1], c[1]), Math.max(a[2], c[2]), Math.max(a[3], c[3])];
307
+ list.splice(j, 1);
308
+ merged = true;
309
+ break;
310
+ }
311
+ }
312
+ }
313
+ return list.map(([x0, y0, x1, y1]) => {
314
+ const dx0 = Math.floor(base[0] * x0 + base[4]);
315
+ const dy0 = Math.floor(base[3] * y0 + base[5]);
316
+ const dx1 = Math.ceil(base[0] * x1 + base[4]);
317
+ const dy1 = Math.ceil(base[3] * y1 + base[5]);
318
+ return { dx0, dy0, dx1, dy1, x0: (dx0 - base[4]) / base[0] - pad, y0: (dy0 - base[5]) / base[3] - pad, x1: (dx1 - base[4]) / base[0] + pad, y1: (dy1 - base[5]) / base[3] + pad };
319
+ });
320
+ };
321
+ const keepBoxes = (frames, list) => {
322
+ for (const index of list) {
323
+ const b = frames[index].box;
324
+ const slot = lastBox[index] ?? (lastBox[index] = new Float64Array(4));
325
+ slot[0] = b[0];
326
+ slot[1] = b[1];
327
+ slot[2] = b[2];
328
+ slot[3] = b[3];
329
+ }
330
+ };
331
+ const pathOf = (op, d) => {
332
+ if (op.last === d) return op.path;
333
+ op.last = d;
334
+ op.path = d ? new Path2D(d) : null;
335
+ stats.parsed++;
336
+ return op.path;
337
+ };
338
+ const linkMatrix = (link, f) => {
339
+ if (link.m) return link.m;
340
+ const t = f.t[link.r];
341
+ if (t === "\u0002") return f.tm[link.r];
342
+ return cpMatrixOf(t);
343
+ };
344
+ const chainMatrix = (chain, f) => {
345
+ if (chain.length === 1) return linkMatrix(chain[0], f);
346
+ let M = null;
347
+ for (let i = 0; i < chain.length; i++) {
348
+ const m = linkMatrix(chain[i], f);
349
+ if (m) M = cpMul(M, m);
350
+ }
351
+ return M;
352
+ };
353
+ const tableOf = (op) => {
354
+ let table = faceCache.get(op.rampKey);
355
+ if (!table) {
356
+ table = new Array(4 * CP_Q + 1);
357
+ for (let q = 0; q <= 4 * CP_Q; q++) {
358
+ const band = Math.min(3, Math.floor(q / CP_Q));
359
+ const f = (q - CP_Q * band) / CP_Q;
360
+ const a = op.ramp[band];
361
+ const b = op.ramp[band + 1];
362
+ table[q] = cpCss([a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f, a[2] + (b[2] - a[2]) * f, a[3] + (b[3] - a[3]) * f]);
363
+ }
364
+ faceCache.set(op.rampKey, table);
365
+ }
366
+ return table;
367
+ };
368
+ const flat = (op) => ({
369
+ kind: op.effect ? 4 : op.tag === "text" ? 3 : op.type === "dot" ? 2 : op.type === "path" || op.type === "slot" || op.type === "face" ? 1 : 0,
370
+ face: op.type === "face",
371
+ r: op.r ?? -1,
372
+ chain: op.chain && op.chain.length ? op.chain : null,
373
+ alpha: op.alpha ?? 1,
374
+ roles: op.alphaRoles && op.alphaRoles.length ? Int32Array.from(op.alphaRoles) : null,
375
+ varies: op.varies && op.varies.length ? op.varies : null,
376
+ fill: op.style ? (op.type === "dot" ? op.style.fillRaw : op.style.fill) : null,
377
+ stroke: op.style ? op.style.stroke : null,
378
+ width: op.style ? op.style.width : 0,
379
+ cap: op.style ? op.style.cap : "butt",
380
+ join: op.style ? op.style.join : "miter",
381
+ miter: op.style ? op.style.miter : 4,
382
+ dash: op.style && op.style.dash ? op.style.dash : null,
383
+ dashOffset: op.style ? op.style.dashOffset : 0,
384
+ evenodd: Boolean(op.style && op.style.rule === "evenodd"),
385
+ nonScaling: Boolean(op.style && op.style.nonScaling),
386
+ table: op.type === "face" ? tableOf(op) : null,
387
+ size: op.size ?? 0,
388
+ path: op.path ?? null,
389
+ last: null,
390
+ text: op.text ?? "",
391
+ x: op.x ?? 0,
392
+ y: op.y ?? 0,
393
+ font: op.font ?? "",
394
+ align: op.align ?? "left",
395
+ spacing: op.spacing ?? "0px",
396
+ baseline: op.baseline ?? "alphabetic",
397
+ effect: op.effect ?? null,
398
+ });
399
+ for (let i = 0; i < programs.length; i++) if (programs[i]) programs[i] = programs[i].map(flat);
400
+ const paintLayer = (layer, frames, rect = null) => {
401
+ const ctx = layer.ctx;
402
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
403
+ if (rect) {
404
+ ctx.save();
405
+ ctx.beginPath();
406
+ ctx.rect(rect.dx0, rect.dy0, rect.dx1 - rect.dx0, rect.dy1 - rect.dy0);
407
+ ctx.clip();
408
+ ctx.clearRect(rect.dx0, rect.dy0, rect.dx1 - rect.dx0, rect.dy1 - rect.dy0);
409
+ } else ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
410
+ const b0 = base[0];
411
+ const b3 = base[3];
412
+ const b4 = base[4];
413
+ const b5 = base[5];
414
+ ctx.setTransform(b0, 0, 0, b3, b4, b5);
415
+ const order = controller.orderIndex(layer.name);
416
+ if (!order) {
417
+ if (rect) ctx.restore();
418
+ return;
419
+ }
420
+ const unitLine = 1 / cssPerUnit;
421
+ const seen = { x0: -b4 / b0 - 4, y0: -b5 / b3 - 4, x1: (layer.canvas.width - b4) / b0 + 4, y1: (layer.canvas.height - b5) / b3 + 4 };
422
+ let curFill = null;
423
+ let curStroke = null;
424
+ let curWidth = -1;
425
+ let curCap = null;
426
+ let curJoin = null;
427
+ let curMiter = -1;
428
+ let curAlpha = 1;
429
+ let curTransformed = false;
430
+ let curBx = b4;
431
+ let curBy = b5;
432
+ let dashed = false;
433
+ let opCount = 0;
434
+ ctx.globalAlpha = 1;
435
+ const drawRun = (index) => {
436
+ const ops = programs[index];
437
+ if (ops === undefined) return;
438
+ const f = frames[index];
439
+ const fb = f.box;
440
+ if (fb[0] > seen.x1 || fb[2] < seen.x0 || fb[1] > seen.y1 || fb[3] < seen.y0) return;
441
+ if (rect !== null) {
442
+ if (fb[0] > rect.x1 || fb[2] < rect.x0 || fb[1] > rect.y1 || fb[3] < rect.y0) return;
443
+ stats.touched++;
444
+ }
445
+ const bx = b4 + b0 * (f.sx || 0);
446
+ const by = b5 + b3 * (f.sy || 0);
447
+ if (bx !== curBx || by !== curBy) {
448
+ curBx = bx;
449
+ curBy = by;
450
+ curTransformed = true;
451
+ }
452
+ for (let o = 0; o < ops.length; o++) {
453
+ const op = ops[o];
454
+ const kind = op.kind;
455
+ if (kind === 4) {
456
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
457
+ ctx.globalAlpha = 1;
458
+ curAlpha = 1;
459
+ op.effect(ctx, layer.canvas.width, layer.canvas.height, base, rect);
460
+ ctx.setTransform(b0, 0, 0, b3, bx, by);
461
+ curTransformed = false;
462
+ curFill = null;
463
+ curStroke = null;
464
+ curWidth = -1;
465
+ curCap = null;
466
+ curJoin = null;
467
+ curMiter = -1;
468
+ continue;
469
+ }
470
+ let alpha = op.alpha;
471
+ const roles = op.roles;
472
+ if (roles !== null) for (let k = 0; k < roles.length; k++) alpha *= f.o[roles[k]];
473
+ const varies = op.varies;
474
+ if (varies !== null) for (let k = 0; k < varies.length; k++) alpha *= varies[k]();
475
+ if (alpha <= 0.001) continue;
476
+ let path = null;
477
+ let token = null;
478
+ let fill = op.fill;
479
+ let stroke = op.stroke;
480
+ let width = op.width;
481
+ if (kind === 1) {
482
+ const d = f.d[op.r];
483
+ if (d === "" || d === undefined) continue;
484
+ if (d.charCodeAt(0) === 1) token = d;
485
+ else {
486
+ path = pathOf(op, d);
487
+ if (path === null) continue;
488
+ }
489
+ if (op.face) {
490
+ fill = stroke = op.table[f.q[op.r]];
491
+ const w = f.w[op.r];
492
+ if (w >= 0) width = w;
493
+ }
494
+ } else if (kind === 2) {
495
+ alpha *= f.o[op.r];
496
+ if (alpha <= 0.001) continue;
497
+ } else path = op.path;
498
+ opCount++;
499
+ if (alpha !== curAlpha) {
500
+ ctx.globalAlpha = alpha;
501
+ curAlpha = alpha;
502
+ }
503
+ const M = op.chain !== null ? chainMatrix(op.chain, f) : null;
504
+ if (kind === 3) {
505
+ const T = cpMul(base, M);
506
+ ctx.setTransform(T[0], T[1], T[2], T[3], T[4], T[5]);
507
+ curTransformed = true;
508
+ ctx.font = op.font;
509
+ ctx.textAlign = op.align;
510
+ ctx.textBaseline = op.baseline;
511
+ if ("letterSpacing" in ctx) ctx.letterSpacing = op.spacing;
512
+ if (fill) {
513
+ ctx.fillStyle = fill;
514
+ curFill = fill;
515
+ ctx.fillText(op.text, op.x, op.y);
516
+ }
517
+ continue;
518
+ }
519
+ let drawPath = path;
520
+ const plain = M === null || (M[0] === 1 && M[1] === 0 && M[2] === 0 && M[3] === 1);
521
+ if (kind === 2) {
522
+ if (M === null) {
523
+ if (curTransformed) {
524
+ ctx.setTransform(b0, 0, 0, b3, bx, by);
525
+ curTransformed = false;
526
+ }
527
+ } else {
528
+ ctx.setTransform(b0 * M[0], b3 * M[1], b0 * M[2], b3 * M[3], b0 * M[4] + bx, b3 * M[5] + by);
529
+ curTransformed = true;
530
+ }
531
+ ctx.beginPath();
532
+ ctx.arc(f.cx[op.r], f.cy[op.r], op.size, 0, 6.283185307179586);
533
+ drawPath = null;
534
+ } else if (M === null) {
535
+ if (curTransformed) {
536
+ ctx.setTransform(b0, 0, 0, b3, bx, by);
537
+ curTransformed = false;
538
+ }
539
+ if (token !== null) cpTrace(ctx, token, f.arena);
540
+ } else if (plain || !(stroke && op.nonScaling)) {
541
+ ctx.setTransform(b0 * M[0], b3 * M[1], b0 * M[2], b3 * M[3], b0 * M[4] + bx, b3 * M[5] + by);
542
+ curTransformed = true;
543
+ if (token !== null) cpTrace(ctx, token, f.arena);
544
+ } else {
545
+ if (curTransformed) {
546
+ ctx.setTransform(b0, 0, 0, b3, bx, by);
547
+ curTransformed = false;
548
+ }
549
+ drawPath = new Path2D();
550
+ drawPath.addPath(path, new DOMMatrix(M));
551
+ }
552
+ if (fill !== null) {
553
+ if (fill !== curFill) {
554
+ ctx.fillStyle = fill;
555
+ curFill = fill;
556
+ }
557
+ if (drawPath !== null) op.evenodd ? ctx.fill(drawPath, "evenodd") : ctx.fill(drawPath);
558
+ else op.evenodd ? ctx.fill("evenodd") : ctx.fill();
559
+ }
560
+ if (stroke !== null && width > 0) {
561
+ if (stroke !== curStroke) {
562
+ ctx.strokeStyle = stroke;
563
+ curStroke = stroke;
564
+ }
565
+ const scaleOf = M === null || plain ? 1 : Math.sqrt(Math.abs(M[0] * M[3] - M[1] * M[2]));
566
+ const lw = op.nonScaling ? (width * unitLine) / (drawPath === path ? scaleOf : 1) : width;
567
+ if (lw !== curWidth) {
568
+ ctx.lineWidth = lw;
569
+ curWidth = lw;
570
+ }
571
+ if (op.cap !== curCap) {
572
+ ctx.lineCap = op.cap;
573
+ curCap = op.cap;
574
+ }
575
+ if (op.join !== curJoin) {
576
+ ctx.lineJoin = op.join;
577
+ curJoin = op.join;
578
+ }
579
+ if (op.miter !== curMiter) {
580
+ ctx.miterLimit = op.miter;
581
+ curMiter = op.miter;
582
+ }
583
+ if (op.dash !== null || dashed) {
584
+ ctx.setLineDash(op.dash !== null ? op.dash.map((v) => v * (op.nonScaling ? unitLine : 1)) : []);
585
+ ctx.lineDashOffset = op.dashOffset;
586
+ dashed = op.dash !== null;
587
+ }
588
+ if (drawPath !== null) ctx.stroke(drawPath);
589
+ else ctx.stroke();
590
+ }
591
+ }
592
+ };
593
+ if (below) {
594
+ for (let at = 0; at < order.length; at++) if (below(order[at])) drawRun(order[at]);
595
+ if (mask) {
596
+ mask(ctx, rect);
597
+ ctx.setTransform(b0, 0, 0, b3, b4, b5);
598
+ curTransformed = false;
599
+ curBx = b4;
600
+ curBy = b5;
601
+ curFill = null;
602
+ curStroke = null;
603
+ curWidth = -1;
604
+ curAlpha = -1;
605
+ ctx.globalAlpha = 1;
606
+ }
607
+ for (let at = 0; at < order.length; at++) if (!below(order[at])) drawRun(order[at]);
608
+ } else for (let at = 0; at < order.length; at++) drawRun(order[at]);
609
+ stats.ops += opCount;
610
+ stats.paths += opCount;
611
+ if (dashed) ctx.setLineDash([]);
612
+ if (rect) ctx.restore();
613
+ };
614
+ let numeric = Boolean(controller.numeric);
615
+ let incremental = false;
616
+ let forceFull = true;
617
+ const paint = () => {
618
+ const t = performance.now();
619
+ stats.ops = 0;
620
+ stats.paths = 0;
621
+ stats.parsed = 0;
622
+ stats.touched = 0;
623
+ stats.rects = 0;
624
+ stats.full = 0;
625
+ const frames = controller.frames();
626
+ const emitted = controller.emitted ? controller.emitted() : null;
627
+ const all = runs.length;
628
+ const full = !incremental || forceFull || !painted || !emitted || emitted.length > all * 0.25;
629
+ if (full) {
630
+ for (const layer of layers) paintLayer(layer, frames);
631
+ stats.full = 1;
632
+ keepBoxes(frames, runs.map((run) => run.index));
633
+ } else if (emitted.length || effectRuns.length) {
634
+ const dirty = effectRuns.length ? emitted.concat(effectRuns) : emitted;
635
+ const rects = rectsOf(frames, dirty);
636
+ stats.rects = rects.length;
637
+ for (const rect of rects) for (const layer of layers) paintLayer(layer, frames, rect);
638
+ keepBoxes(frames, dirty);
639
+ }
640
+ painted = true;
641
+ forceFull = false;
642
+ stats.ms = performance.now() - t;
643
+ return stats;
644
+ };
645
+ resize();
646
+ for (const layer of layers) {
647
+ if (keepSvg) layer.svg.style.visibility = "hidden";
648
+ else layer.svg.remove();
649
+ }
650
+ let watching = null;
651
+ if (typeof ResizeObserver === "function") {
652
+ watching = new ResizeObserver(() => {
653
+ resize();
654
+ paint();
655
+ });
656
+ watching.observe(stage);
657
+ }
658
+ const dprQuery = () => {
659
+ const mq = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
660
+ mq.addEventListener("change", () => {
661
+ resize();
662
+ paint();
663
+ dprQuery();
664
+ }, { once: true });
665
+ };
666
+ dprQuery();
667
+ stage.setAttribute("data-painter", "canvas");
668
+ return {
669
+ paint,
670
+ base: () => base.slice(),
671
+ stats: () => ({ ...stats }),
672
+ compileMs,
673
+ exportSvg({ gl = true, background = true } = {}) {
674
+ controller.snapshot();
675
+ const out = [];
676
+ for (const child of stage.children) {
677
+ const tag = child.localName;
678
+ if (tag === "svg") {
679
+ if (child.style.visibility === "hidden") continue;
680
+ out.push(`<g class="${child.getAttribute("class") || ""}">${child.innerHTML}</g>`);
681
+ } else if (tag === "canvas") {
682
+ const layer = layers.find((l) => l.canvas === child);
683
+ if (layer) out.push(`<g class="${layer.svg.getAttribute("class") || ""}">${layer.svg.innerHTML}</g>`);
684
+ else if (gl && child.width > 1 && getComputedStyle(child).display !== "none") out.push(`<image href="${child.toDataURL("image/png")}" x="0" y="0" width="${W}" height="${H}" preserveAspectRatio="none"/>`);
685
+ }
686
+ }
687
+ const css = [...document.querySelectorAll("style")].map((s) => s.textContent).join("\n");
688
+ const holder = stage.closest("[data-theme]");
689
+ const theme = holder ? holder.getAttribute("data-theme") : "dark";
690
+ const card = getComputedStyle(stage.closest(".iso-plate") || stage).getPropertyValue("--anatomy-card").trim() || "#fff";
691
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}"><style>${css.replace(/<\/style/gi, "")}</style><g class="iso" data-theme="${theme}"><g class="iso-plate" style="padding:0;box-shadow:none">${background ? `<rect width="${W}" height="${H}" fill="${card}"/>` : ""}<g class="${stage.getAttribute("class") || ""}">${out.join("")}</g></g></g></svg>`;
692
+ },
693
+ set incremental(on) {
694
+ incremental = Boolean(on);
695
+ forceFull = true;
696
+ },
697
+ get incremental() {
698
+ return incremental;
699
+ },
700
+ flush() {
701
+ for (const layer of layers) layer.ctx.getImageData(0, 0, 1, 1);
702
+ },
703
+ get numeric() {
704
+ return numeric;
705
+ },
706
+ unsupported: [...unsupported],
707
+ layers: layers.map((l) => l.name),
708
+ ops: programs.reduce((sum, ops) => sum + (ops ? ops.length : 0), 0),
709
+ zoom(z, cx = W / 2, cy = H / 2) {
710
+ view.zoom = z;
711
+ view.cx = cx;
712
+ view.cy = cy;
713
+ resize();
714
+ return paint();
715
+ },
716
+ destroy() {
717
+ if (watching) watching.disconnect();
718
+ },
719
+ };
720
+ }
721
+
722
+ export const CANVAS2D = { mount: cpMount };