@codefrydev/svg-engine 0.1.0 → 0.1.2

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.
@@ -0,0 +1,3439 @@
1
+ // src/core/geometry.ts
2
+ var GRID_SIZE = 20;
3
+ var snap = (val, gridSize = GRID_SIZE) => Math.round(val / gridSize) * gridSize;
4
+ var distance = (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1);
5
+ var angleDeg = (x1, y1, x2, y2) => Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;
6
+ var getBounds = (x1, y1, x2, y2) => {
7
+ const minX = Math.min(x1, x2);
8
+ const minY = Math.min(y1, y2);
9
+ const maxX = Math.max(x1, x2);
10
+ const maxY = Math.max(y1, y2);
11
+ return {
12
+ minX,
13
+ minY,
14
+ maxX,
15
+ maxY,
16
+ width: Math.max(1, maxX - minX),
17
+ height: Math.max(1, maxY - minY)
18
+ };
19
+ };
20
+ var OPTICS_CHORD_EPS = 1;
21
+ function opticsEffectiveChord(x1, y1, x2, y2) {
22
+ const d = distance(x1, y1, x2, y2);
23
+ if (d >= OPTICS_CHORD_EPS) return d;
24
+ const b = getBounds(x1, y1, x2, y2);
25
+ return Math.max(12, b.width, b.height);
26
+ }
27
+ var getMouseCoords = (svg, e) => {
28
+ const ctm = svg.getScreenCTM();
29
+ if (!ctm) return { x: 0, y: 0 };
30
+ return {
31
+ x: (e.clientX - ctm.e) / ctm.a,
32
+ y: (e.clientY - ctm.f) / ctm.d
33
+ };
34
+ };
35
+ function quadraticChordWorldBounds(x1, y1, x2, y2, curveHeight, pad = 18) {
36
+ const d = distance(x1, y1, x2, y2);
37
+ const angleRad = Math.atan2(y2 - y1, x2 - x1);
38
+ const lx = d / 2;
39
+ const ly = -curveHeight * 2;
40
+ const apexX = x1 + lx * Math.cos(angleRad) - ly * Math.sin(angleRad);
41
+ const apexY = y1 + lx * Math.sin(angleRad) + ly * Math.cos(angleRad);
42
+ const xs = [x1, x2, apexX];
43
+ const ys = [y1, y2, apexY];
44
+ const minX = Math.min(...xs);
45
+ const minY = Math.min(...ys);
46
+ const maxX = Math.max(...xs);
47
+ const maxY = Math.max(...ys);
48
+ return paddedBounds(minX, minY, maxX, maxY, pad);
49
+ }
50
+ function paddedBounds(minX, minY, maxX, maxY, pad) {
51
+ return {
52
+ x: minX - pad,
53
+ y: minY - pad,
54
+ width: maxX - minX + 2 * pad,
55
+ height: maxY - minY + 2 * pad
56
+ };
57
+ }
58
+
59
+ // src/core/renderers/registry.ts
60
+ var rendererMap = /* @__PURE__ */ new Map();
61
+ var registerRenderer = (type, renderer) => {
62
+ rendererMap.set(type, renderer);
63
+ };
64
+ var getRenderer = (type) => rendererMap.get(type);
65
+ var hasRenderer = (type) => rendererMap.has(type);
66
+
67
+ // src/core/Renderer.tsx
68
+ import { useMemo } from "react";
69
+
70
+ // src/core/renderers/selectionChrome.tsx
71
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
72
+ var SELECTION_STROKE = "#2563eb";
73
+ var HANDLE_END = "#ef4444";
74
+ var BEZIER_ANCHOR = "#10b981";
75
+ var BEZIER_CP = "#3b82f6";
76
+ function selectionDashRect(x, y, width2, height) {
77
+ return /* @__PURE__ */ jsx(
78
+ "rect",
79
+ {
80
+ x,
81
+ y,
82
+ width: width2,
83
+ height,
84
+ fill: "none",
85
+ stroke: SELECTION_STROKE,
86
+ strokeWidth: 1,
87
+ strokeDasharray: "4 4",
88
+ pointerEvents: "none"
89
+ }
90
+ );
91
+ }
92
+ function chordBoundsDashRect(el, padX = 15, padY = 20) {
93
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
94
+ return selectionDashRect(b.minX - padX, b.minY - padY, b.width + 2 * padX, b.height + 2 * padY);
95
+ }
96
+ function worldChordHitLine(el, strokeWidth = 24) {
97
+ return /* @__PURE__ */ jsx(
98
+ "line",
99
+ {
100
+ x1: el.x1,
101
+ y1: el.y1,
102
+ x2: el.x2,
103
+ y2: el.y2,
104
+ stroke: "transparent",
105
+ strokeWidth,
106
+ strokeLinecap: "round"
107
+ }
108
+ );
109
+ }
110
+ function endpointHandles(el, ctx) {
111
+ if (!ctx.isSelected || ctx.viewMode || ctx.isGhost || !ctx.onHandlePointerDown) return null;
112
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
113
+ /* @__PURE__ */ jsx(
114
+ "circle",
115
+ {
116
+ cx: el.x1,
117
+ cy: el.y1,
118
+ r: 5,
119
+ fill: HANDLE_END,
120
+ cursor: "grab",
121
+ onMouseDown: (e) => ctx.onHandlePointerDown(e, "start")
122
+ }
123
+ ),
124
+ /* @__PURE__ */ jsx(
125
+ "circle",
126
+ {
127
+ cx: el.x2,
128
+ cy: el.y2,
129
+ r: 5,
130
+ fill: HANDLE_END,
131
+ cursor: "grab",
132
+ onMouseDown: (e) => ctx.onHandlePointerDown(e, "end")
133
+ }
134
+ )
135
+ ] });
136
+ }
137
+ function ChordInteractionOverlay({
138
+ el,
139
+ ctx,
140
+ padX = 15,
141
+ padY = 20,
142
+ hitWidth = 24
143
+ }) {
144
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
145
+ worldChordHitLine(el, hitWidth),
146
+ ctx.isSelected && !ctx.viewMode && !ctx.isGhost ? chordBoundsDashRect(el, padX, padY) : null,
147
+ endpointHandles(el, ctx)
148
+ ] });
149
+ }
150
+ function LocalFatPathHit({
151
+ dPath,
152
+ strokeWidth = 24,
153
+ strokeLinecap = "round",
154
+ strokeLinejoin
155
+ }) {
156
+ return /* @__PURE__ */ jsx(
157
+ "path",
158
+ {
159
+ d: dPath,
160
+ fill: "none",
161
+ stroke: "transparent",
162
+ strokeWidth,
163
+ strokeLinecap,
164
+ strokeLinejoin
165
+ }
166
+ );
167
+ }
168
+ function OriginHitPlate({
169
+ ctx,
170
+ hitHalfSize,
171
+ selectionHalfSize,
172
+ children
173
+ }) {
174
+ const selHalf = selectionHalfSize ?? hitHalfSize + 10;
175
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
176
+ ctx.isSelected && !ctx.viewMode && !ctx.isGhost ? /* @__PURE__ */ jsx(
177
+ "rect",
178
+ {
179
+ x: -selHalf,
180
+ y: -selHalf,
181
+ width: 2 * selHalf,
182
+ height: 2 * selHalf,
183
+ fill: "none",
184
+ stroke: SELECTION_STROKE,
185
+ strokeWidth: 1,
186
+ strokeDasharray: "4 4",
187
+ pointerEvents: "none"
188
+ }
189
+ ) : null,
190
+ /* @__PURE__ */ jsx("rect", { x: -hitHalfSize, y: -hitHalfSize, width: 2 * hitHalfSize, height: 2 * hitHalfSize, fill: "transparent" }),
191
+ children
192
+ ] });
193
+ }
194
+ function bboxInsetSelectionChrome(minX, minY, width2, height, ctx, inset = 10) {
195
+ if (!ctx.isSelected || ctx.viewMode || ctx.isGhost) return null;
196
+ return selectionDashRect(minX - inset, minY - inset, width2 + 2 * inset, height + 2 * inset);
197
+ }
198
+ function BBoxInteractionOverlay({
199
+ minX,
200
+ minY,
201
+ width: width2,
202
+ height,
203
+ ctx,
204
+ inset = 10,
205
+ includeHitFill = true
206
+ }) {
207
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
208
+ includeHitFill ? /* @__PURE__ */ jsx("rect", { x: minX, y: minY, width: width2, height, fill: "transparent" }) : null,
209
+ bboxInsetSelectionChrome(minX, minY, width2, height, ctx, inset)
210
+ ] });
211
+ }
212
+ function BezierEditChrome({
213
+ x1,
214
+ y1,
215
+ x2,
216
+ y2,
217
+ cp0,
218
+ ctx,
219
+ pts,
220
+ pad = 10
221
+ }) {
222
+ const show = ctx.isSelected && !ctx.viewMode && !ctx.isGhost && ctx.onHandlePointerDown;
223
+ if (!show) return null;
224
+ const bMinX = Math.min(...pts.map((p) => p.x));
225
+ const bMaxX = Math.max(...pts.map((p) => p.x));
226
+ const bMinY = Math.min(...pts.map((p) => p.y));
227
+ const bMaxY = Math.max(...pts.map((p) => p.y));
228
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
229
+ selectionDashRect(bMinX - pad, bMinY - pad, bMaxX - bMinX + 2 * pad, bMaxY - bMinY + 2 * pad),
230
+ /* @__PURE__ */ jsx(
231
+ "polyline",
232
+ {
233
+ points: pts.map((p) => `${p.x},${p.y}`).join(" "),
234
+ fill: "none",
235
+ stroke: "#94a3b8",
236
+ strokeDasharray: "4 4",
237
+ strokeWidth: 1,
238
+ pointerEvents: "none"
239
+ }
240
+ ),
241
+ /* @__PURE__ */ jsx(
242
+ "circle",
243
+ {
244
+ cx: x1,
245
+ cy: y1,
246
+ r: 6,
247
+ fill: BEZIER_ANCHOR,
248
+ cursor: "grab",
249
+ onMouseDown: (e) => ctx.onHandlePointerDown(e, "start")
250
+ }
251
+ ),
252
+ /* @__PURE__ */ jsx(
253
+ "circle",
254
+ {
255
+ cx: x2,
256
+ cy: y2,
257
+ r: 6,
258
+ fill: BEZIER_ANCHOR,
259
+ cursor: "grab",
260
+ onMouseDown: (e) => ctx.onHandlePointerDown(e, "end")
261
+ }
262
+ ),
263
+ /* @__PURE__ */ jsx(
264
+ "circle",
265
+ {
266
+ cx: cp0.x,
267
+ cy: cp0.y,
268
+ r: 6,
269
+ fill: BEZIER_CP,
270
+ cursor: "grab",
271
+ onMouseDown: (e) => ctx.onHandlePointerDown(e, "cp", 0)
272
+ }
273
+ )
274
+ ] });
275
+ }
276
+
277
+ // src/core/renderers/primitives.tsx
278
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
279
+ var colorOf = (el) => el.color || "#0f172a";
280
+ var strokeOf = (el) => el.strokeWidth || 2;
281
+ var fontOf = (el) => el.fontSize || 16;
282
+ registerRenderer("point", (el, ctx) => /* @__PURE__ */ jsx2("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs2(OriginHitPlate, { ctx, hitHalfSize: 28, selectionHalfSize: 32, children: [
283
+ /* @__PURE__ */ jsx2("circle", { cx: "0", cy: "0", r: 3, fill: colorOf(el) }),
284
+ el.label ? /* @__PURE__ */ jsx2("text", { x: "8", y: "-8", fontSize: fontOf(el), fill: colorOf(el), children: String(el.label) }) : null
285
+ ] }) }));
286
+ registerRenderer("text", (el, ctx) => /* @__PURE__ */ jsx2("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsx2(OriginHitPlate, { ctx, hitHalfSize: Math.max(40, (el.label || el.value || "").length * 4), selectionHalfSize: void 0, children: /* @__PURE__ */ jsx2(
287
+ "text",
288
+ {
289
+ x: 0,
290
+ y: 0,
291
+ dominantBaseline: "central",
292
+ textAnchor: "middle",
293
+ fill: colorOf(el),
294
+ fontSize: fontOf(el),
295
+ fontFamily: el.fontFamily || "serif",
296
+ fontWeight: el.fontWeight || "normal",
297
+ children: String(el.label || el.value || "")
298
+ }
299
+ ) }) }));
300
+ var lineLike = (el, ctx, arrow = false) => {
301
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
302
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
303
+ const dashed = el.lineStyle === "dashed" || el.type === "dashed" || el.type === "dashed_line" ? "6 6" : el.lineStyle === "dotted" ? "2 4" : void 0;
304
+ return /* @__PURE__ */ jsxs2("g", { children: [
305
+ /* @__PURE__ */ jsxs2("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
306
+ /* @__PURE__ */ jsx2("line", { x1: 0, y1: 0, x2: d, y2: 0, stroke: colorOf(el), strokeWidth: strokeOf(el), strokeDasharray: dashed, strokeLinecap: "round" }),
307
+ arrow ? /* @__PURE__ */ jsx2("polygon", { points: `${d},0 ${d - 12},-6 ${d - 12},6`, fill: colorOf(el) }) : null,
308
+ el.label ? /* @__PURE__ */ jsx2("text", { x: d / 2, y: -10, fontSize: fontOf(el), fill: colorOf(el), textAnchor: "middle", children: String(el.label) }) : null
309
+ ] }),
310
+ /* @__PURE__ */ jsx2(ChordInteractionOverlay, { el, ctx })
311
+ ] });
312
+ };
313
+ registerRenderer("line", (el, ctx) => lineLike(el, ctx));
314
+ registerRenderer("dashed", (el, ctx) => lineLike({ ...el, lineStyle: "dashed" }, ctx));
315
+ registerRenderer("dashed_line", (el, ctx) => lineLike({ ...el, lineStyle: "dashed" }, ctx));
316
+ registerRenderer("vector", (el, ctx) => lineLike(el, ctx, true));
317
+ registerRenderer("ray", (el, ctx) => lineLike(el, ctx, true));
318
+ registerRenderer("wire", (el, ctx) => lineLike(el, ctx));
319
+ registerRenderer("table_edge", (el, ctx) => {
320
+ const minX = Math.min(el.x1, el.x2);
321
+ const maxX = Math.max(el.x1, el.x2);
322
+ const minY = Math.min(el.y1, el.y2);
323
+ const maxY = Math.max(el.y1, el.y2);
324
+ const w = Math.max(1, maxX - minX);
325
+ const h = Math.max(1, maxY - minY);
326
+ const hashes = [];
327
+ const numH = Math.max(2, Math.floor(w / 12));
328
+ const numV = Math.max(2, Math.floor(h / 12));
329
+ for (let j = 0; j <= numH; j++) {
330
+ const x = minX + j / numH * w;
331
+ hashes.push(/* @__PURE__ */ jsx2("line", { x1: x, y1: minY, x2: x - 8, y2: minY + 10, stroke: colorOf(el), strokeWidth: 1 }, `h-${j}`));
332
+ }
333
+ for (let j = 0; j <= numV; j++) {
334
+ const y = minY + j / numV * h;
335
+ hashes.push(/* @__PURE__ */ jsx2("line", { x1: maxX, y1: y, x2: maxX - 10, y2: y + 8, stroke: colorOf(el), strokeWidth: 1 }, `v-${j}`));
336
+ }
337
+ return /* @__PURE__ */ jsxs2("g", { children: [
338
+ /* @__PURE__ */ jsx2("polyline", { points: `${minX},${minY} ${maxX},${minY} ${maxX},${maxY}`, fill: "none", stroke: colorOf(el), strokeWidth: strokeOf(el) }),
339
+ hashes,
340
+ el.label ? /* @__PURE__ */ jsx2("text", { x: minX + w / 2, y: minY - 10, fill: colorOf(el), textAnchor: "middle", fontSize: fontOf(el), children: String(el.label) }) : null,
341
+ /* @__PURE__ */ jsx2(BBoxInteractionOverlay, { minX, minY, width: w, height: h, ctx, inset: 12 }),
342
+ endpointHandles(el, ctx)
343
+ ] });
344
+ });
345
+
346
+ // src/core/renderers/mechanics.tsx
347
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
348
+ var c = (el) => el.color || "#0f172a";
349
+ var sw = (el) => el.strokeWidth || 2;
350
+ var fo = (el) => el.fillOpacity ?? 0.2;
351
+ var font = (el) => el.fontSize || 18;
352
+ var rotOf = (el) => el.rotation || 0;
353
+ registerRenderer("slab", (el) => {
354
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
355
+ const depth = el.curveHeight ?? 20;
356
+ return /* @__PURE__ */ jsxs3("g", { children: [
357
+ /* @__PURE__ */ jsx3("path", { d: `M ${b.minX} ${b.minY} L ${b.maxX} ${b.minY} L ${b.maxX} ${b.maxY} L ${b.minX} ${b.maxY} Z`, fill: c(el), fillOpacity: fo(el), stroke: c(el), strokeWidth: sw(el) }),
358
+ /* @__PURE__ */ jsx3("path", { d: `M ${b.minX} ${b.minY} L ${b.minX + depth} ${b.minY - depth} L ${b.maxX + depth} ${b.minY - depth} L ${b.maxX} ${b.minY} Z`, fill: c(el), fillOpacity: Math.max(0, fo(el) - 0.1), stroke: c(el), strokeWidth: sw(el) }),
359
+ /* @__PURE__ */ jsx3("path", { d: `M ${b.maxX} ${b.minY} L ${b.maxX + depth} ${b.minY - depth} L ${b.maxX + depth} ${b.maxY - depth} L ${b.maxX} ${b.maxY} Z`, fill: c(el), fillOpacity: Math.min(1, fo(el) + 0.1), stroke: c(el), strokeWidth: sw(el) })
360
+ ] });
361
+ });
362
+ registerRenderer("sphere", (el) => {
363
+ const r = Math.max(4, distance(el.x1, el.y1, el.x2, el.y2));
364
+ return /* @__PURE__ */ jsx3("circle", { cx: el.x1, cy: el.y1, r, fill: c(el), fillOpacity: fo(el), stroke: c(el), strokeWidth: sw(el) });
365
+ });
366
+ registerRenderer("shell", (el) => {
367
+ const r = Math.max(10, distance(el.x1, el.y1, el.x2, el.y2));
368
+ const t = el.curveHeight ?? 10;
369
+ const innerR = Math.max(1, r - t);
370
+ return /* @__PURE__ */ jsx3(
371
+ "path",
372
+ {
373
+ d: `M ${el.x1 - r} ${el.y1} A ${r} ${r} 0 1 0 ${el.x1 + r} ${el.y1} A ${r} ${r} 0 1 0 ${el.x1 - r} ${el.y1} Z M ${el.x1 - innerR} ${el.y1} A ${innerR} ${innerR} 0 1 1 ${el.x1 + innerR} ${el.y1} A ${innerR} ${innerR} 0 1 1 ${el.x1 - innerR} ${el.y1} Z`,
374
+ fill: c(el),
375
+ fillOpacity: fo(el),
376
+ fillRule: "evenodd",
377
+ stroke: c(el),
378
+ strokeWidth: sw(el)
379
+ }
380
+ );
381
+ });
382
+ registerRenderer("cylinder", (el) => {
383
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
384
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
385
+ const r = el.curveHeight ?? 20;
386
+ return /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
387
+ /* @__PURE__ */ jsx3("path", { d: `M 0 ${-r} L ${d} ${-r} A 10 ${r} 0 0 1 ${d} ${r} L 0 ${r} Z`, fill: c(el), fillOpacity: fo(el) }),
388
+ /* @__PURE__ */ jsx3("path", { d: `M 0 ${-r} L ${d} ${-r} M 0 ${r} L ${d} ${r}`, fill: "none", stroke: c(el), strokeWidth: sw(el) }),
389
+ /* @__PURE__ */ jsx3("ellipse", { cx: d, cy: 0, rx: 10, ry: r, fill: c(el), fillOpacity: fo(el), stroke: c(el), strokeWidth: sw(el) }),
390
+ /* @__PURE__ */ jsx3("ellipse", { cx: 0, cy: 0, rx: 10, ry: r, fill: "none", stroke: c(el), strokeWidth: sw(el), strokeDasharray: "2 4" })
391
+ ] });
392
+ });
393
+ registerRenderer("ladder", (el) => {
394
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
395
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
396
+ const hw = el.curveHeight ?? 10;
397
+ const rungs = [];
398
+ for (let i = 15; i < d - 10; i += 15) {
399
+ rungs.push(/* @__PURE__ */ jsx3("line", { x1: i, y1: -hw, x2: i, y2: hw, stroke: c(el), strokeWidth: Math.max(1, sw(el) - 1) }, i));
400
+ }
401
+ return /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
402
+ /* @__PURE__ */ jsx3("line", { x1: 0, y1: -hw, x2: d, y2: -hw, stroke: c(el), strokeWidth: sw(el) }),
403
+ /* @__PURE__ */ jsx3("line", { x1: 0, y1: hw, x2: d, y2: hw, stroke: c(el), strokeWidth: sw(el) }),
404
+ rungs
405
+ ] });
406
+ });
407
+ registerRenderer("spring", (el) => {
408
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
409
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
410
+ const zigCount = Math.max(0, Math.floor(d / 12));
411
+ let path = "M 0 0";
412
+ for (let i = 0; i < zigCount; i++) {
413
+ path += ` L ${i * 12 + 3} 8 L ${i * 12 + 9} -8`;
414
+ }
415
+ path += ` L ${d} 0`;
416
+ return /* @__PURE__ */ jsx3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: /* @__PURE__ */ jsx3("path", { d: path, fill: "none", stroke: c(el), strokeWidth: sw(el), strokeLinejoin: "round" }) });
417
+ });
418
+ registerRenderer("rod", (el) => {
419
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
420
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
421
+ return /* @__PURE__ */ jsx3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: /* @__PURE__ */ jsx3("line", { x1: 0, y1: 0, x2: d, y2: 0, stroke: c(el), strokeWidth: Math.max(4, sw(el)), strokeLinecap: "round" }) });
422
+ });
423
+ registerRenderer("arc_arrow", (el) => {
424
+ const r = Math.max(8, distance(el.x1, el.y1, el.x2, el.y2));
425
+ return /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
426
+ /* @__PURE__ */ jsx3("path", { d: `M ${r} 0 A ${r} ${r} 0 0 0 ${-r} 0`, fill: "none", stroke: c(el), strokeWidth: sw(el) }),
427
+ /* @__PURE__ */ jsx3("polygon", { points: `${-r},2 ${-r - 6},-10 ${-r + 6},-10`, fill: c(el) })
428
+ ] });
429
+ });
430
+ registerRenderer("lens_convex", (el, ctx) => {
431
+ const d = opticsEffectiveChord(el.x1, el.y1, el.x2, el.y2);
432
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
433
+ const arcH = el.curveHeight !== void 0 ? Number(el.curveHeight) : d * 0.15;
434
+ const fs = el.fontSize || 16;
435
+ const pathD = `M 0 0 Q ${d / 2} ${-arcH * 2} ${d} 0 Q ${d / 2} ${arcH * 2} 0 0 Z`;
436
+ const bulgePad = Math.ceil(2 * arcH) + Math.ceil(fs + 14);
437
+ return /* @__PURE__ */ jsxs3("g", { children: [
438
+ /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
439
+ /* @__PURE__ */ jsx3("path", { d: pathD, fill: c(el), fillOpacity: Math.max(0.15, fo(el)), stroke: c(el), strokeWidth: sw(el) }),
440
+ el.label ? /* @__PURE__ */ jsx3("text", { x: d / 2, y: -arcH - 10, fontSize: fs, fill: c(el), textAnchor: "middle", children: String(el.label) }) : null,
441
+ /* @__PURE__ */ jsx3(LocalFatPathHit, { dPath: pathD, strokeWidth: 28, strokeLinejoin: "round" })
442
+ ] }),
443
+ /* @__PURE__ */ jsx3(ChordInteractionOverlay, { el, ctx, padX: 15 + bulgePad, padY: 20 + bulgePad })
444
+ ] });
445
+ });
446
+ registerRenderer("lens_concave", (el, ctx) => {
447
+ const d = opticsEffectiveChord(el.x1, el.y1, el.x2, el.y2);
448
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
449
+ const bow = el.curveHeight !== void 0 ? Number(el.curveHeight) : Math.max(10, d * 0.04);
450
+ const hw = Math.max(15, d * 0.05);
451
+ const fs = el.fontSize || 16;
452
+ const pathD = `M 0 ${-hw} L 0 ${hw} Q ${d / 2} ${hw - bow * 2} ${d} ${hw} L ${d} ${-hw} Q ${d / 2} ${-hw + bow * 2} 0 ${-hw} Z`;
453
+ const bulgePad = Math.ceil(hw + 2 * bow) + Math.ceil(fs + 14);
454
+ return /* @__PURE__ */ jsxs3("g", { children: [
455
+ /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
456
+ /* @__PURE__ */ jsx3("path", { d: pathD, fill: c(el), fillOpacity: Math.max(0.15, fo(el)), stroke: c(el), strokeWidth: sw(el) }),
457
+ el.label ? /* @__PURE__ */ jsx3("text", { x: d / 2, y: -hw - 10, fontSize: fs, fill: c(el), textAnchor: "middle", children: String(el.label) }) : null,
458
+ /* @__PURE__ */ jsx3(LocalFatPathHit, { dPath: pathD, strokeWidth: 28, strokeLinejoin: "round" })
459
+ ] }),
460
+ /* @__PURE__ */ jsx3(ChordInteractionOverlay, { el, ctx, padX: 15 + bulgePad, padY: 20 + bulgePad })
461
+ ] });
462
+ });
463
+ registerRenderer("mirror_concave", (el, ctx) => {
464
+ const d = opticsEffectiveChord(el.x1, el.y1, el.x2, el.y2);
465
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
466
+ const arcH = el.curveHeight !== void 0 ? Number(el.curveHeight) : d * 0.2;
467
+ const fs = el.fontSize || 16;
468
+ const pathD = `M 0 0 Q ${d / 2} ${arcH * 2} ${d} 0`;
469
+ const bulgePad = Math.ceil(2 * arcH) + 24;
470
+ return /* @__PURE__ */ jsxs3("g", { children: [
471
+ /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
472
+ /* @__PURE__ */ jsx3("path", { d: pathD, fill: "none", stroke: c(el), strokeWidth: sw(el) }),
473
+ el.label ? /* @__PURE__ */ jsx3("text", { x: d / 2, y: -15, fontSize: fs, fill: c(el), textAnchor: "middle", children: String(el.label) }) : null,
474
+ /* @__PURE__ */ jsx3(LocalFatPathHit, { dPath: pathD, strokeWidth: 28, strokeLinecap: "round" })
475
+ ] }),
476
+ /* @__PURE__ */ jsx3(ChordInteractionOverlay, { el, ctx, padX: 15 + bulgePad, padY: 20 + bulgePad })
477
+ ] });
478
+ });
479
+ registerRenderer("mirror_convex", (el, ctx) => {
480
+ const d = opticsEffectiveChord(el.x1, el.y1, el.x2, el.y2);
481
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
482
+ const arcH = el.curveHeight !== void 0 ? Number(el.curveHeight) : d * 0.2;
483
+ const fs = el.fontSize || 16;
484
+ const pathD = `M 0 0 Q ${d / 2} ${-arcH * 2} ${d} 0`;
485
+ const bulgePad = Math.ceil(2 * arcH) + 24;
486
+ return /* @__PURE__ */ jsxs3("g", { children: [
487
+ /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
488
+ /* @__PURE__ */ jsx3("path", { d: pathD, fill: "none", stroke: c(el), strokeWidth: sw(el) }),
489
+ el.label ? /* @__PURE__ */ jsx3("text", { x: d / 2, y: -15, fontSize: fs, fill: c(el), textAnchor: "middle", children: String(el.label) }) : null,
490
+ /* @__PURE__ */ jsx3(LocalFatPathHit, { dPath: pathD, strokeWidth: 28, strokeLinecap: "round" })
491
+ ] }),
492
+ /* @__PURE__ */ jsx3(ChordInteractionOverlay, { el, ctx, padX: 15 + bulgePad, padY: 20 + bulgePad })
493
+ ] });
494
+ });
495
+ registerRenderer("prism", (el) => {
496
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
497
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
498
+ const py = -d * 0.866;
499
+ const fs = el.fontSize || 16;
500
+ return /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
501
+ /* @__PURE__ */ jsx3("polygon", { points: `0,0 ${d},0 ${d / 2},${py}`, fill: c(el), fillOpacity: Math.max(0.15, fo(el)), stroke: c(el), strokeWidth: sw(el), strokeLinejoin: "round" }),
502
+ el.label && /* @__PURE__ */ jsx3("text", { x: d / 2, y: py - 10, fontSize: fs, fill: c(el), textAnchor: "middle", children: String(el.label) })
503
+ ] });
504
+ });
505
+ registerRenderer("slit_single", (el) => {
506
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
507
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
508
+ const gap = 16;
509
+ const thick = Math.max(4, sw(el));
510
+ const fs = el.fontSize || 16;
511
+ return /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
512
+ /* @__PURE__ */ jsx3("line", { x1: 0, y1: 0, x2: d / 2 - gap / 2, y2: 0, stroke: c(el), strokeWidth: thick, strokeLinecap: "square" }),
513
+ /* @__PURE__ */ jsx3("line", { x1: d / 2 + gap / 2, y1: 0, x2: d, y2: 0, stroke: c(el), strokeWidth: thick, strokeLinecap: "square" }),
514
+ el.label && /* @__PURE__ */ jsx3("text", { x: d / 2, y: -14, fontSize: fs, fill: c(el), textAnchor: "middle", children: String(el.label) })
515
+ ] });
516
+ });
517
+ registerRenderer("slit_double", (el) => {
518
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
519
+ const cx = b.minX + b.width / 2;
520
+ return /* @__PURE__ */ jsxs3("g", { children: [
521
+ /* @__PURE__ */ jsx3("line", { x1: cx - 6, y1: b.minY, x2: cx - 6, y2: b.maxY, stroke: c(el), strokeWidth: sw(el) }),
522
+ /* @__PURE__ */ jsx3("line", { x1: cx + 6, y1: b.minY, x2: cx + 6, y2: b.maxY, stroke: c(el), strokeWidth: sw(el) })
523
+ ] });
524
+ });
525
+ registerRenderer("photon", (el) => {
526
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
527
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
528
+ const amp = 6;
529
+ const waves = Math.max(3, Math.floor(d / 24));
530
+ const step = d / (waves * 2);
531
+ let path = "M 0 0 ";
532
+ for (let i = 0; i < waves * 2; i++) {
533
+ const x = (i + 1) * step;
534
+ const y = i % 2 === 0 ? -amp : amp;
535
+ path += `L ${x} ${y} `;
536
+ }
537
+ path += `L ${d} 0`;
538
+ return /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
539
+ /* @__PURE__ */ jsx3("path", { d: path, fill: "none", stroke: c(el), strokeWidth: sw(el) }),
540
+ el.label && /* @__PURE__ */ jsx3("text", { x: d / 2, y: -12, textAnchor: "middle", fill: c(el), children: String(el.label) })
541
+ ] });
542
+ });
543
+ registerRenderer("glass_slab", (el) => {
544
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
545
+ return /* @__PURE__ */ jsx3("rect", { x: b.minX, y: b.minY, width: b.width, height: b.height, fill: c(el), fillOpacity: Math.max(0.1, fo(el)), stroke: c(el), strokeWidth: sw(el) });
546
+ });
547
+ registerRenderer("pipe", (el) => {
548
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
549
+ const neck = el.curveHeight ?? b.height * 0.35;
550
+ return /* @__PURE__ */ jsx3(
551
+ "path",
552
+ {
553
+ d: `M ${b.minX} ${b.minY} C ${b.minX + b.width * 0.3} ${b.minY}, ${b.minX + b.width * 0.3} ${b.minY + neck}, ${b.minX + b.width * 0.5} ${b.minY + neck} C ${b.minX + b.width * 0.7} ${b.minY + neck}, ${b.minX + b.width * 0.7} ${b.minY}, ${b.maxX} ${b.minY} L ${b.maxX} ${b.maxY} C ${b.minX + b.width * 0.7} ${b.maxY}, ${b.minX + b.width * 0.7} ${b.maxY - neck}, ${b.minX + b.width * 0.5} ${b.maxY - neck} C ${b.minX + b.width * 0.3} ${b.maxY - neck}, ${b.minX + b.width * 0.3} ${b.maxY}, ${b.minX} ${b.maxY} Z`,
554
+ fill: c(el),
555
+ fillOpacity: fo(el),
556
+ stroke: c(el),
557
+ strokeWidth: sw(el)
558
+ }
559
+ );
560
+ });
561
+ registerRenderer("liquid", (el) => {
562
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
563
+ return /* @__PURE__ */ jsxs3("g", { children: [
564
+ /* @__PURE__ */ jsx3("path", { d: `M ${b.minX} ${b.minY} Q ${b.minX + b.width / 2} ${b.minY + 10} ${b.maxX} ${b.minY} L ${b.maxX} ${b.maxY} L ${b.minX} ${b.maxY} Z`, fill: c(el), fillOpacity: Math.max(0.2, fo(el)) }),
565
+ /* @__PURE__ */ jsx3("path", { d: `M ${b.minX} ${b.minY} Q ${b.minX + b.width / 2} ${b.minY + 10} ${b.maxX} ${b.minY}`, fill: "none", stroke: c(el), strokeWidth: 2 })
566
+ ] });
567
+ });
568
+ registerRenderer("orbit", (el) => {
569
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
570
+ return /* @__PURE__ */ jsx3("ellipse", { cx: b.minX + b.width / 2, cy: b.minY + b.height / 2, rx: b.width / 2, ry: b.height / 2, fill: "none", stroke: c(el), strokeWidth: sw(el), strokeDasharray: "3 3" });
571
+ });
572
+ registerRenderer("pole_piece", (el, ctx) => {
573
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
574
+ const poleLabel = String(el.label || "N");
575
+ const isNorth = poleLabel.toUpperCase().includes("N");
576
+ const blockFill = isNorth ? "#ef4444" : "#3b82f6";
577
+ const labelFs = Math.min(b.width, b.height) * 0.5 + 10;
578
+ return /* @__PURE__ */ jsxs3("g", { children: [
579
+ /* @__PURE__ */ jsx3("rect", { x: b.minX, y: b.minY, width: b.width, height: b.height, fill: blockFill, fillOpacity: 0.2, stroke: c(el), strokeWidth: sw(el), rx: "4" }),
580
+ /* @__PURE__ */ jsx3("text", { x: b.minX + b.width / 2, y: b.minY + b.height / 2, textAnchor: "middle", dominantBaseline: "central", fill: c(el), fontWeight: "bold", fontSize: labelFs, children: poleLabel }),
581
+ bboxInsetSelectionChrome(b.minX, b.minY, b.width, b.height, ctx)
582
+ ] });
583
+ });
584
+ registerRenderer("b_field_line", (el, ctx) => {
585
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
586
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
587
+ const fs = el.fontSize || 18;
588
+ const mid = d / 2;
589
+ return /* @__PURE__ */ jsxs3("g", { children: [
590
+ /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
591
+ /* @__PURE__ */ jsx3("line", { x1: 0, y1: 0, x2: d, y2: 0, stroke: c(el), strokeWidth: sw(el), strokeLinecap: "round" }),
592
+ /* @__PURE__ */ jsx3("polygon", { points: `${mid + 6},0 ${mid - 6},-5 ${mid - 6},5`, fill: c(el) }),
593
+ el.label ? /* @__PURE__ */ jsx3("text", { x: mid, y: -10, textAnchor: "middle", fill: c(el), fontSize: fs, children: String(el.label) }) : null
594
+ ] }),
595
+ /* @__PURE__ */ jsx3(ChordInteractionOverlay, { el, ctx })
596
+ ] });
597
+ });
598
+ registerRenderer("b_field_curve", (el, ctx) => {
599
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
600
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
601
+ const curveHeight = el.curveHeight !== void 0 ? el.curveHeight : d * 0.4;
602
+ const fs = el.fontSize || 18;
603
+ const apexY = -curveHeight;
604
+ const bb = quadraticChordWorldBounds(el.x1, el.y1, el.x2, el.y2, curveHeight);
605
+ const curvePath = `M 0 0 Q ${d / 2} ${-curveHeight * 2} ${d} 0`;
606
+ return /* @__PURE__ */ jsxs3("g", { children: [
607
+ /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
608
+ /* @__PURE__ */ jsx3("path", { d: curvePath, fill: "none", stroke: c(el), strokeWidth: sw(el), strokeLinecap: "round" }),
609
+ /* @__PURE__ */ jsx3("polygon", { points: `${d / 2 + 6},${apexY} ${d / 2 - 6},${apexY - 5} ${d / 2 - 6},${apexY + 5}`, fill: c(el) }),
610
+ el.label ? /* @__PURE__ */ jsx3("text", { x: d / 2, y: apexY - 15, textAnchor: "middle", fill: c(el), fontSize: fs, children: String(el.label) }) : null,
611
+ /* @__PURE__ */ jsx3(LocalFatPathHit, { dPath: curvePath, strokeWidth: 24, strokeLinecap: "round" })
612
+ ] }),
613
+ ctx.isSelected && !ctx.viewMode && !ctx.isGhost ? selectionDashRect(bb.x, bb.y, bb.width, bb.height) : null,
614
+ endpointHandles(el, ctx)
615
+ ] });
616
+ });
617
+ registerRenderer("axes_3d", (el) => /* @__PURE__ */ jsxs3("g", { children: [
618
+ /* @__PURE__ */ jsx3("line", { x1: el.x1, y1: el.y1, x2: el.x1 + 100, y2: el.y1, stroke: c(el), strokeWidth: sw(el) }),
619
+ /* @__PURE__ */ jsx3("line", { x1: el.x1, y1: el.y1, x2: el.x1, y2: el.y1 - 100, stroke: c(el), strokeWidth: sw(el) }),
620
+ /* @__PURE__ */ jsx3("line", { x1: el.x1, y1: el.y1, x2: el.x1 + 70, y2: el.y1 + 50, stroke: c(el), strokeWidth: sw(el) })
621
+ ] }));
622
+ registerRenderer("string", (el, ctx) => {
623
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
624
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
625
+ return /* @__PURE__ */ jsxs3("g", { children: [
626
+ /* @__PURE__ */ jsx3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: /* @__PURE__ */ jsx3("line", { x1: 0, y1: 0, x2: d, y2: 0, stroke: c(el), strokeWidth: sw(el), strokeLinecap: "round" }) }),
627
+ /* @__PURE__ */ jsx3(ChordInteractionOverlay, { el, ctx })
628
+ ] });
629
+ });
630
+ registerRenderer("surface", (el, ctx) => {
631
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
632
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
633
+ const hatchCount = Math.max(0, Math.floor(d / 12));
634
+ const hatches = [];
635
+ for (let i = 0; i <= hatchCount; i++) {
636
+ hatches.push(/* @__PURE__ */ jsx3("line", { x1: i * 12, y1: 0, x2: i * 12 - 8, y2: 12, stroke: c(el), strokeWidth: 1.5 }, i));
637
+ }
638
+ return /* @__PURE__ */ jsxs3("g", { children: [
639
+ /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
640
+ /* @__PURE__ */ jsx3("line", { x1: 0, y1: 0, x2: d, y2: 0, stroke: c(el), strokeWidth: sw(el) + 0.5, strokeLinecap: "round" }),
641
+ hatches
642
+ ] }),
643
+ /* @__PURE__ */ jsx3(ChordInteractionOverlay, { el, ctx })
644
+ ] });
645
+ });
646
+ registerRenderer("block", (el, ctx) => {
647
+ const w = Math.abs(el.x2 - el.x1);
648
+ const h = Math.abs(el.y2 - el.y1);
649
+ const cx = (el.x1 + el.x2) / 2;
650
+ const cy = (el.y1 + el.y2) / 2;
651
+ const rot = rotOf(el);
652
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
653
+ return /* @__PURE__ */ jsxs3("g", { children: [
654
+ /* @__PURE__ */ jsxs3("g", { transform: `translate(${cx}, ${cy}) rotate(${rot})`, children: [
655
+ /* @__PURE__ */ jsx3("rect", { x: -w / 2, y: -h / 2, width: w, height: h, fill: "#f1f5f9", stroke: c(el), strokeWidth: sw(el) }),
656
+ el.label ? /* @__PURE__ */ jsx3(
657
+ "text",
658
+ {
659
+ x: 0,
660
+ y: el.value ? -6 : 0,
661
+ dominantBaseline: "central",
662
+ textAnchor: "middle",
663
+ fill: c(el),
664
+ fontSize: 18,
665
+ fontFamily: "serif",
666
+ fontWeight: "bold",
667
+ children: String(el.label)
668
+ }
669
+ ) : null,
670
+ el.value ? /* @__PURE__ */ jsx3(
671
+ "text",
672
+ {
673
+ x: 0,
674
+ y: el.label ? 10 : 0,
675
+ dominantBaseline: "central",
676
+ textAnchor: "middle",
677
+ fill: "#4b5563",
678
+ fontSize: 14,
679
+ fontFamily: "sans-serif",
680
+ children: String(el.value)
681
+ }
682
+ ) : null
683
+ ] }),
684
+ bboxInsetSelectionChrome(b.minX, b.minY, b.width, b.height, ctx),
685
+ /* @__PURE__ */ jsx3(ChordInteractionOverlay, { el, ctx })
686
+ ] });
687
+ });
688
+ registerRenderer("cart", (el, ctx) => {
689
+ const w = Math.abs(el.x2 - el.x1);
690
+ const h = Math.abs(el.y2 - el.y1);
691
+ const cx = (el.x1 + el.x2) / 2;
692
+ const cy = (el.y1 + el.y2) / 2;
693
+ const rot = rotOf(el);
694
+ const wheelR = Math.min(w * 0.15, h * 0.25, 12);
695
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
696
+ return /* @__PURE__ */ jsxs3("g", { children: [
697
+ /* @__PURE__ */ jsxs3("g", { transform: `translate(${cx}, ${cy}) rotate(${rot})`, children: [
698
+ /* @__PURE__ */ jsx3("rect", { x: -w / 2, y: -h / 2, width: w, height: h - wheelR, fill: "#f1f5f9", stroke: c(el), strokeWidth: sw(el) }),
699
+ /* @__PURE__ */ jsx3("circle", { cx: -w / 2 + wheelR * 1.5, cy: h / 2 - wheelR, r: wheelR, fill: "#cbd5e1", stroke: c(el), strokeWidth: sw(el) }),
700
+ /* @__PURE__ */ jsx3("circle", { cx: w / 2 - wheelR * 1.5, cy: h / 2 - wheelR, r: wheelR, fill: "#cbd5e1", stroke: c(el), strokeWidth: sw(el) }),
701
+ el.label ? /* @__PURE__ */ jsx3(
702
+ "text",
703
+ {
704
+ x: 0,
705
+ y: el.value ? -8 : -2,
706
+ dominantBaseline: "central",
707
+ textAnchor: "middle",
708
+ fill: c(el),
709
+ fontSize: 18,
710
+ fontFamily: "serif",
711
+ fontWeight: "bold",
712
+ children: String(el.label)
713
+ }
714
+ ) : null,
715
+ el.value ? /* @__PURE__ */ jsx3(
716
+ "text",
717
+ {
718
+ x: 0,
719
+ y: el.label ? 8 : -2,
720
+ dominantBaseline: "central",
721
+ textAnchor: "middle",
722
+ fill: "#4b5563",
723
+ fontSize: 14,
724
+ fontFamily: "sans-serif",
725
+ children: String(el.value)
726
+ }
727
+ ) : null
728
+ ] }),
729
+ bboxInsetSelectionChrome(b.minX, b.minY, b.width, b.height, ctx),
730
+ /* @__PURE__ */ jsx3(ChordInteractionOverlay, { el, ctx })
731
+ ] });
732
+ });
733
+ registerRenderer("particle", (el, ctx) => {
734
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
735
+ const r = Math.max(5, d);
736
+ return /* @__PURE__ */ jsxs3("g", { children: [
737
+ /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
738
+ /* @__PURE__ */ jsx3("circle", { cx: 0, cy: 0, r, fill: c(el) }),
739
+ el.label ? /* @__PURE__ */ jsx3("text", { x: 0, y: -r - 12, dominantBaseline: "central", textAnchor: "middle", fill: c(el), fontSize: font(el), fontFamily: "serif", fontWeight: "bold", children: String(el.label) }) : null,
740
+ el.value ? /* @__PURE__ */ jsx3("text", { x: 0, y: r + 12, dominantBaseline: "central", textAnchor: "middle", fill: "#4b5563", fontSize: 14, fontFamily: "sans-serif", children: String(el.value) }) : null
741
+ ] }),
742
+ /* @__PURE__ */ jsx3(ChordInteractionOverlay, { el, ctx })
743
+ ] });
744
+ });
745
+ registerRenderer("disk", (el, ctx) => {
746
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
747
+ const r = Math.max(10, d);
748
+ return /* @__PURE__ */ jsxs3("g", { children: [
749
+ /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
750
+ /* @__PURE__ */ jsx3("circle", { cx: 0, cy: 0, r, fill: "#e2e8f0", stroke: c(el), strokeWidth: sw(el) }),
751
+ /* @__PURE__ */ jsx3("line", { x1: 0, y1: 0, x2: r, y2: 0, stroke: c(el), strokeWidth: sw(el) }),
752
+ /* @__PURE__ */ jsx3("circle", { cx: 0, cy: 0, r: 3, fill: c(el) }),
753
+ el.label ? /* @__PURE__ */ jsx3("text", { x: 0, y: -r - 12, dominantBaseline: "central", textAnchor: "middle", fill: c(el), fontSize: font(el), fontFamily: "serif", fontWeight: "bold", children: String(el.label) }) : null,
754
+ el.value ? /* @__PURE__ */ jsx3("text", { x: 0, y: r + 12, dominantBaseline: "central", textAnchor: "middle", fill: "#4b5563", fontSize: 14, fontFamily: "sans-serif", children: String(el.value) }) : null
755
+ ] }),
756
+ /* @__PURE__ */ jsx3(ChordInteractionOverlay, { el, ctx })
757
+ ] });
758
+ });
759
+ registerRenderer("com", (el, ctx) => /* @__PURE__ */ jsx3("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs3(OriginHitPlate, { ctx, hitHalfSize: 22, selectionHalfSize: 28, children: [
760
+ /* @__PURE__ */ jsx3("circle", { cx: 0, cy: 0, r: 10, fill: "none", stroke: c(el), strokeWidth: sw(el) }),
761
+ /* @__PURE__ */ jsx3("path", { d: "M 0 -10 A 10 10 0 0 1 10 0 L 0 0 Z", fill: c(el) }),
762
+ /* @__PURE__ */ jsx3("path", { d: "M 0 10 A 10 10 0 0 1 -10 0 L 0 0 Z", fill: c(el) })
763
+ ] }) }));
764
+ registerRenderer("pivot", (el, ctx) => /* @__PURE__ */ jsx3("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs3(OriginHitPlate, { ctx, hitHalfSize: 24, selectionHalfSize: 32, children: [
765
+ /* @__PURE__ */ jsx3("polygon", { points: "0,0 -12,20 12,20", fill: "#f8fafc", stroke: c(el), strokeWidth: sw(el), strokeLinejoin: "round" }),
766
+ /* @__PURE__ */ jsx3("line", { x1: -20, y1: 20, x2: 20, y2: 20, stroke: c(el), strokeWidth: sw(el), strokeLinecap: "round" }),
767
+ /* @__PURE__ */ jsx3("circle", { cx: 0, cy: 0, r: 3, fill: c(el) })
768
+ ] }) }));
769
+ registerRenderer("pulley", (el, ctx) => {
770
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
771
+ const r = Math.max(10, d);
772
+ return /* @__PURE__ */ jsxs3("g", { children: [
773
+ /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
774
+ /* @__PURE__ */ jsx3("circle", { cx: 0, cy: 0, r, fill: "#f8fafc", stroke: c(el), strokeWidth: sw(el) }),
775
+ /* @__PURE__ */ jsx3("circle", { cx: 0, cy: 0, r: 4, fill: c(el) }),
776
+ el.label ? /* @__PURE__ */ jsx3("text", { x: 0, y: -r - 12, dominantBaseline: "central", textAnchor: "middle", fill: c(el), fontSize: font(el), fontFamily: "serif", fontWeight: "bold", children: String(el.label) }) : null
777
+ ] }),
778
+ /* @__PURE__ */ jsx3(ChordInteractionOverlay, { el, ctx })
779
+ ] });
780
+ });
781
+ registerRenderer("wedge", (el) => {
782
+ const dx = el.x2 - el.x1;
783
+ const dy = el.y2 - el.y1;
784
+ return /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
785
+ /* @__PURE__ */ jsx3("polygon", { points: `0,0 ${dx},0 ${dx},${dy}`, fill: "#f1f5f9", stroke: c(el), strokeWidth: sw(el), strokeLinejoin: "round" }),
786
+ el.label ? /* @__PURE__ */ jsx3(
787
+ "text",
788
+ {
789
+ x: dx * 0.6,
790
+ y: dy * 0.3,
791
+ dominantBaseline: "central",
792
+ textAnchor: "middle",
793
+ fill: c(el),
794
+ fontSize: 20,
795
+ fontFamily: "serif",
796
+ fontWeight: "bold",
797
+ children: String(el.label)
798
+ }
799
+ ) : null
800
+ ] });
801
+ });
802
+ registerRenderer("dimension", (el) => {
803
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
804
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
805
+ const w = Math.max(1, sw(el) - 0.5);
806
+ return /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
807
+ /* @__PURE__ */ jsx3("line", { x1: 0, y1: 0, x2: d, y2: 0, stroke: c(el), strokeWidth: w, strokeDasharray: "4 4" }),
808
+ /* @__PURE__ */ jsx3("polygon", { points: `0,0 10,-5 10,5`, fill: c(el) }),
809
+ /* @__PURE__ */ jsx3("polygon", { points: `${d},0 ${d - 10},-5 ${d - 10},5`, fill: c(el) }),
810
+ el.label ? /* @__PURE__ */ jsx3("text", { x: d / 2, y: -10, textAnchor: "middle", fill: c(el), fontSize: 18, fontFamily: "serif", fontStyle: "italic", fontWeight: "bold", children: String(el.label) }) : null
811
+ ] });
812
+ });
813
+ registerRenderer("arc", (el) => {
814
+ const dx = el.x2 - el.x1;
815
+ const dy = el.y2 - el.y1;
816
+ const d = Math.hypot(dx, dy);
817
+ const angleDegVal = Math.atan2(dy, dx) * 180 / Math.PI;
818
+ const R = Math.min(d, 40);
819
+ const sweepFlag = dy > 0 ? 1 : 0;
820
+ const startX = R;
821
+ const startY = 0;
822
+ const endX = R * Math.cos(angleDegVal * Math.PI / 180);
823
+ const endY = R * Math.sin(angleDegVal * Math.PI / 180);
824
+ const midAngle = angleDegVal / 2;
825
+ const textDist = R + 15;
826
+ const tx = textDist * Math.cos(midAngle * Math.PI / 180);
827
+ const ty = textDist * Math.sin(midAngle * Math.PI / 180);
828
+ const refLen = Math.max(d, 50);
829
+ return /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
830
+ /* @__PURE__ */ jsx3("line", { x1: 0, y1: 0, x2: refLen, y2: 0, stroke: c(el), strokeWidth: 1.5, strokeDasharray: "4 4" }),
831
+ /* @__PURE__ */ jsx3("path", { d: `M ${startX} ${startY} A ${R} ${R} 0 0 ${sweepFlag} ${endX} ${endY}`, fill: "none", stroke: c(el), strokeWidth: sw(el) }),
832
+ el.label ? /* @__PURE__ */ jsx3(
833
+ "text",
834
+ {
835
+ x: tx,
836
+ y: ty,
837
+ dominantBaseline: "central",
838
+ textAnchor: "middle",
839
+ fill: c(el),
840
+ fontSize: 16,
841
+ fontFamily: "serif",
842
+ fontStyle: "italic",
843
+ fontWeight: "bold",
844
+ children: String(el.label)
845
+ }
846
+ ) : null
847
+ ] });
848
+ });
849
+ registerRenderer("nucleus", (el) => {
850
+ const fs = el.fontSize || 18;
851
+ const stroke2 = c(el);
852
+ const w = sw(el);
853
+ return /* @__PURE__ */ jsxs3("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
854
+ /* @__PURE__ */ jsx3("ellipse", { cx: 0, cy: 0, rx: 24, ry: 8, fill: "none", stroke: stroke2, strokeWidth: w, transform: "rotate(30)" }),
855
+ /* @__PURE__ */ jsx3("ellipse", { cx: 0, cy: 0, rx: 24, ry: 8, fill: "none", stroke: stroke2, strokeWidth: w, transform: "rotate(-30)" }),
856
+ /* @__PURE__ */ jsx3("ellipse", { cx: 0, cy: 0, rx: 24, ry: 8, fill: "none", stroke: stroke2, strokeWidth: w, transform: "rotate(90)" }),
857
+ /* @__PURE__ */ jsx3("circle", { cx: 0, cy: 0, r: 6, fill: "#ef4444" }),
858
+ el.label && /* @__PURE__ */ jsx3("text", { x: 0, y: 36, fontSize: fs, fill: stroke2, textAnchor: "middle", children: String(el.label) })
859
+ ] });
860
+ });
861
+ ["cone", "curved_wedge", "incline", "container", "hinge"].forEach(
862
+ (t) => registerRenderer(t, (el) => {
863
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
864
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
865
+ return /* @__PURE__ */ jsx3("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: /* @__PURE__ */ jsx3("line", { x1: 0, y1: 0, x2: d, y2: 0, stroke: c(el), strokeWidth: sw(el) }) });
866
+ })
867
+ );
868
+ ["semicircle", "quarter_circle", "axes"].forEach(
869
+ (t) => registerRenderer(t, (el) => {
870
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
871
+ return /* @__PURE__ */ jsx3("rect", { x: b.minX, y: b.minY, width: b.width, height: b.height, fill: "none", stroke: c(el), strokeWidth: sw(el) });
872
+ })
873
+ );
874
+
875
+ // src/core/renderers/magnetism.tsx
876
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
877
+ var c2 = (el) => el.color || "#0f172a";
878
+ var sw2 = (el) => el.strokeWidth || 2;
879
+ var fontSizeOf = (el) => el.fontSize || 18;
880
+ var dashOf = (el) => el.lineStyle === "dashed" || el.type === "dashed_line" ? "6 6" : el.lineStyle === "dotted" ? "2 4" : void 0;
881
+ registerRenderer("b_region_in", (el, ctx) => {
882
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
883
+ const minX = b.minX;
884
+ const maxX = b.maxX;
885
+ const minY = b.minY;
886
+ const maxY = b.maxY;
887
+ const midX = (minX + maxX) / 2;
888
+ const spacing = 40;
889
+ const markR = 6;
890
+ const marks = [];
891
+ for (let px = minX + spacing / 2; px <= maxX; px += spacing) {
892
+ for (let py = minY + spacing / 2; py <= maxY; py += spacing) {
893
+ marks.push(
894
+ /* @__PURE__ */ jsxs4("g", { transform: `translate(${px}, ${py})`, children: [
895
+ /* @__PURE__ */ jsx4("circle", { cx: 0, cy: 0, r: markR, fill: "none", stroke: c2(el), strokeWidth: sw2(el) * 0.75 }),
896
+ /* @__PURE__ */ jsx4("line", { x1: -markR * 0.7, y1: -markR * 0.7, x2: markR * 0.7, y2: markR * 0.7, stroke: c2(el), strokeWidth: sw2(el) * 0.75 }),
897
+ /* @__PURE__ */ jsx4("line", { x1: markR * 0.7, y1: -markR * 0.7, x2: -markR * 0.7, y2: markR * 0.7, stroke: c2(el), strokeWidth: sw2(el) * 0.75 })
898
+ ] }, `${px}-${py}`)
899
+ );
900
+ }
901
+ }
902
+ return /* @__PURE__ */ jsxs4("g", { children: [
903
+ /* @__PURE__ */ jsx4("rect", { x: minX, y: minY, width: b.width, height: b.height, fill: c2(el), stroke: c2(el), strokeWidth: 1, strokeDasharray: "4 4", opacity: 0.1 }),
904
+ marks,
905
+ el.label ? /* @__PURE__ */ jsx4("text", { x: midX, y: minY - 10, fontSize: fontSizeOf(el), fill: c2(el), textAnchor: "middle", fontWeight: "bold", children: String(el.label) }) : null,
906
+ bboxInsetSelectionChrome(minX, minY, b.width, b.height, ctx)
907
+ ] });
908
+ });
909
+ registerRenderer("b_region_out", (el, ctx) => {
910
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
911
+ const minX = b.minX;
912
+ const maxX = b.maxX;
913
+ const minY = b.minY;
914
+ const maxY = b.maxY;
915
+ const midX = (minX + maxX) / 2;
916
+ const spacing = 40;
917
+ const markR = 6;
918
+ const marks = [];
919
+ for (let px = minX + spacing / 2; px <= maxX; px += spacing) {
920
+ for (let py = minY + spacing / 2; py <= maxY; py += spacing) {
921
+ marks.push(
922
+ /* @__PURE__ */ jsxs4("g", { transform: `translate(${px}, ${py})`, children: [
923
+ /* @__PURE__ */ jsx4("circle", { cx: 0, cy: 0, r: markR, fill: "none", stroke: c2(el), strokeWidth: sw2(el) * 0.75 }),
924
+ /* @__PURE__ */ jsx4("circle", { cx: 0, cy: 0, r: 2, fill: c2(el) })
925
+ ] }, `${px}-${py}`)
926
+ );
927
+ }
928
+ }
929
+ return /* @__PURE__ */ jsxs4("g", { children: [
930
+ /* @__PURE__ */ jsx4("rect", { x: minX, y: minY, width: b.width, height: b.height, fill: c2(el), stroke: c2(el), strokeWidth: 1, strokeDasharray: "4 4", opacity: 0.1 }),
931
+ marks,
932
+ el.label ? /* @__PURE__ */ jsx4("text", { x: midX, y: minY - 10, fontSize: fontSizeOf(el), fill: c2(el), textAnchor: "middle", fontWeight: "bold", children: String(el.label) }) : null,
933
+ bboxInsetSelectionChrome(minX, minY, b.width, b.height, ctx)
934
+ ] });
935
+ });
936
+ registerRenderer("current_wire", (el, ctx) => {
937
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
938
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
939
+ const mid = d / 2;
940
+ const flip = angle > 90 || angle <= -90;
941
+ return /* @__PURE__ */ jsxs4("g", { children: [
942
+ /* @__PURE__ */ jsxs4("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
943
+ /* @__PURE__ */ jsx4("line", { x1: 0, y1: 0, x2: d, y2: 0, stroke: c2(el), strokeWidth: sw2(el), strokeDasharray: dashOf(el), strokeLinecap: "round" }),
944
+ /* @__PURE__ */ jsx4("polygon", { points: `${mid + 8},0 ${mid - 6},-7 ${mid - 6},7`, fill: c2(el) }),
945
+ el.label ? /* @__PURE__ */ jsx4(
946
+ "text",
947
+ {
948
+ x: mid,
949
+ y: -15,
950
+ fontSize: fontSizeOf(el),
951
+ fill: c2(el),
952
+ textAnchor: "middle",
953
+ fontStyle: "italic",
954
+ transform: flip ? `rotate(180, ${mid}, -15)` : void 0,
955
+ children: String(el.label)
956
+ }
957
+ ) : null
958
+ ] }),
959
+ /* @__PURE__ */ jsx4(ChordInteractionOverlay, { el, ctx })
960
+ ] });
961
+ });
962
+ registerRenderer("bar_magnet", (el, ctx) => {
963
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
964
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
965
+ const magW = Math.max(20, sw2(el) * 10);
966
+ const flipLabel = angle > 90 || angle <= -90;
967
+ return /* @__PURE__ */ jsxs4("g", { children: [
968
+ /* @__PURE__ */ jsxs4("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
969
+ /* @__PURE__ */ jsx4("rect", { x: 0, y: -magW / 2, width: d / 2, height: magW, fill: "#ef4444", stroke: c2(el), strokeWidth: 1 }),
970
+ /* @__PURE__ */ jsx4("rect", { x: d / 2, y: -magW / 2, width: d / 2, height: magW, fill: "#3b82f6", stroke: c2(el), strokeWidth: 1 }),
971
+ /* @__PURE__ */ jsx4(
972
+ "text",
973
+ {
974
+ x: d / 4,
975
+ y: 0,
976
+ fill: "white",
977
+ fontSize: magW * 0.6,
978
+ fontWeight: "bold",
979
+ textAnchor: "middle",
980
+ dominantBaseline: "central",
981
+ transform: flipLabel ? `rotate(180, ${d / 4}, 0)` : void 0,
982
+ children: "N"
983
+ }
984
+ ),
985
+ /* @__PURE__ */ jsx4(
986
+ "text",
987
+ {
988
+ x: d * 0.75,
989
+ y: 0,
990
+ fill: "white",
991
+ fontSize: magW * 0.6,
992
+ fontWeight: "bold",
993
+ textAnchor: "middle",
994
+ dominantBaseline: "central",
995
+ transform: flipLabel ? `rotate(180, ${d * 0.75}, 0)` : void 0,
996
+ children: "S"
997
+ }
998
+ ),
999
+ el.label ? /* @__PURE__ */ jsx4(
1000
+ "text",
1001
+ {
1002
+ x: d / 2,
1003
+ y: -magW / 2 - 10,
1004
+ fill: c2(el),
1005
+ fontSize: fontSizeOf(el),
1006
+ textAnchor: "middle",
1007
+ transform: flipLabel ? `rotate(180, ${d / 2}, ${-magW / 2 - 10})` : void 0,
1008
+ children: String(el.label)
1009
+ }
1010
+ ) : null
1011
+ ] }),
1012
+ /* @__PURE__ */ jsx4(ChordInteractionOverlay, { el, ctx })
1013
+ ] });
1014
+ });
1015
+ registerRenderer("coil", (el, ctx) => {
1016
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
1017
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
1018
+ const coils = Math.max(Math.floor(d / 15), 1);
1019
+ let dPath = "M 0 0 ";
1020
+ for (let i = 1; i <= coils; i++) {
1021
+ dPath += `C ${i * (d / coils) - d / (coils * 2)} -15, ${i * (d / coils)} 20, ${i * (d / coils)} 0 `;
1022
+ }
1023
+ const flip = angle > 90 || angle <= -90;
1024
+ return /* @__PURE__ */ jsxs4("g", { children: [
1025
+ /* @__PURE__ */ jsxs4("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
1026
+ /* @__PURE__ */ jsx4("path", { d: dPath, fill: "none", stroke: c2(el), strokeWidth: sw2(el), strokeLinejoin: "round" }),
1027
+ el.label ? /* @__PURE__ */ jsx4(
1028
+ "text",
1029
+ {
1030
+ x: d / 2,
1031
+ y: -20,
1032
+ fontSize: fontSizeOf(el),
1033
+ fill: c2(el),
1034
+ textAnchor: "middle",
1035
+ transform: flip ? `rotate(180, ${d / 2}, -20)` : void 0,
1036
+ children: String(el.label)
1037
+ }
1038
+ ) : null,
1039
+ /* @__PURE__ */ jsx4(LocalFatPathHit, { dPath, strokeWidth: 24, strokeLinejoin: "round" })
1040
+ ] }),
1041
+ /* @__PURE__ */ jsx4(ChordInteractionOverlay, { el, ctx })
1042
+ ] });
1043
+ });
1044
+ registerRenderer("meter", (el, ctx) => {
1045
+ const r = Math.max(16, sw2(el) * 5);
1046
+ const label = String(el.label || "G");
1047
+ return /* @__PURE__ */ jsx4("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs4(OriginHitPlate, { ctx, hitHalfSize: 20, selectionHalfSize: 30, children: [
1048
+ /* @__PURE__ */ jsx4("circle", { cx: 0, cy: 0, r, fill: "white", stroke: c2(el), strokeWidth: sw2(el) }),
1049
+ /* @__PURE__ */ jsx4("text", { x: 0, y: 0, fontSize: fontSizeOf(el), fill: c2(el), fontWeight: "bold", textAnchor: "middle", dominantBaseline: "central", children: label })
1050
+ ] }) });
1051
+ });
1052
+ registerRenderer("ac_source", (el, ctx) => {
1053
+ const r = Math.max(16, sw2(el) * 5);
1054
+ return /* @__PURE__ */ jsx4("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs4(OriginHitPlate, { ctx, hitHalfSize: 22, selectionHalfSize: 30, children: [
1055
+ /* @__PURE__ */ jsx4("circle", { cx: 0, cy: 0, r, fill: "white", stroke: c2(el), strokeWidth: sw2(el) }),
1056
+ /* @__PURE__ */ jsx4(
1057
+ "path",
1058
+ {
1059
+ d: `M ${-r * 0.6} 0 Q ${-r * 0.3} ${-r * 0.6} 0 0 T ${r * 0.6} 0`,
1060
+ fill: "none",
1061
+ stroke: c2(el),
1062
+ strokeWidth: sw2(el) * 0.8
1063
+ }
1064
+ ),
1065
+ el.label ? /* @__PURE__ */ jsx4("text", { x: 0, y: r + 18, fontSize: fontSizeOf(el), fill: c2(el), textAnchor: "middle", children: String(el.label) }) : null
1066
+ ] }) });
1067
+ });
1068
+ registerRenderer("bezier", (el, ctx) => {
1069
+ const { x1, y1, x2, y2 } = el;
1070
+ const cps = el.cps?.length ? el.cps : [{ x: (x1 + x2) / 2, y: Math.min(y1, y2) - 50 }];
1071
+ const cp0 = cps[0];
1072
+ const dPath = `M ${x1} ${y1} Q ${cp0.x} ${cp0.y} ${x2} ${y2}`;
1073
+ const pts = [{ x: x1, y: y1 }, cp0, { x: x2, y: y2 }];
1074
+ const labelX = 0.25 * x1 + 0.5 * cp0.x + 0.25 * x2;
1075
+ const labelY = 0.25 * y1 + 0.5 * cp0.y + 0.25 * y2;
1076
+ const angleRad = Math.atan2(y2 - y1, x2 - x1);
1077
+ const showArrow = Boolean(el.showArrow);
1078
+ return /* @__PURE__ */ jsxs4("g", { children: [
1079
+ /* @__PURE__ */ jsx4("path", { d: dPath, fill: "none", stroke: "transparent", strokeWidth: 24, strokeLinecap: "round", strokeLinejoin: "round" }),
1080
+ /* @__PURE__ */ jsx4("path", { d: dPath, fill: "none", stroke: c2(el), strokeWidth: sw2(el), strokeDasharray: dashOf(el), strokeLinecap: "round", strokeLinejoin: "round" }),
1081
+ showArrow ? /* @__PURE__ */ jsx4("g", { transform: `translate(${labelX}, ${labelY}) rotate(${angleRad * 180 / Math.PI})`, children: /* @__PURE__ */ jsx4("polygon", { points: "6,0 -6,-5 -6,5", fill: c2(el) }) }) : null,
1082
+ el.label ? /* @__PURE__ */ jsx4("text", { x: labelX, y: labelY - 14, fontSize: fontSizeOf(el), fill: c2(el), textAnchor: "middle", children: String(el.label) }) : null,
1083
+ /* @__PURE__ */ jsx4(BezierEditChrome, { x1, y1, x2, y2, cp0, ctx, pts })
1084
+ ] });
1085
+ });
1086
+
1087
+ // src/core/renderers/graph.tsx
1088
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1089
+ var colorOf2 = (el) => el.color || "#0f172a";
1090
+ var strokeOf2 = (el) => el.strokeWidth || 2;
1091
+ var fontOf2 = (el) => el.fontSize || 16;
1092
+ var dashOf2 = (el) => {
1093
+ if (el.lineStyle === "dashed" || el.type === "dashed_line" || el.type === "dashed") return "6 6";
1094
+ if (el.lineStyle === "dotted") return "2 4";
1095
+ return void 0;
1096
+ };
1097
+ var op = (ctx) => ctx?.isGhost ? 0.5 : 1;
1098
+ registerRenderer("axes", (el, ctx) => {
1099
+ const minX = Math.min(el.x1, el.x2);
1100
+ const maxX = Math.max(el.x1, el.x2);
1101
+ const minY = Math.min(el.y1, el.y2);
1102
+ const maxY = Math.max(el.y1, el.y2);
1103
+ const midX = (minX + maxX) / 2;
1104
+ const midY = (minY + maxY) / 2;
1105
+ const w = Math.max(1, maxX - minX);
1106
+ const h = Math.max(1, maxY - minY);
1107
+ const c6 = colorOf2(el);
1108
+ const sw6 = strokeOf2(el);
1109
+ const dash = dashOf2(el);
1110
+ if (maxX - minX <= 1 && maxY - minY <= 1) {
1111
+ const cx = el.x1;
1112
+ const cy = el.y1;
1113
+ const arm = 40;
1114
+ const fs = fontOf2(el);
1115
+ return /* @__PURE__ */ jsx5("g", { opacity: op(ctx), transform: `translate(${cx}, ${cy})`, children: /* @__PURE__ */ jsxs5(OriginHitPlate, { ctx, hitHalfSize: 52, selectionHalfSize: 58, children: [
1116
+ /* @__PURE__ */ jsx5("line", { x1: 0, y1: 0, x2: arm, y2: 0, stroke: c6, strokeWidth: sw6 }),
1117
+ /* @__PURE__ */ jsx5("polygon", { points: `${arm},0 ${arm - 8},-5 ${arm - 8},5`, fill: c6 }),
1118
+ /* @__PURE__ */ jsx5(
1119
+ "text",
1120
+ {
1121
+ x: arm + 10,
1122
+ y: 0,
1123
+ dominantBaseline: "central",
1124
+ textAnchor: "middle",
1125
+ fill: c6,
1126
+ fontSize: fs,
1127
+ fontFamily: "serif",
1128
+ fontStyle: "italic",
1129
+ children: "x"
1130
+ }
1131
+ ),
1132
+ /* @__PURE__ */ jsx5("line", { x1: 0, y1: 0, x2: 0, y2: -arm, stroke: c6, strokeWidth: sw6 }),
1133
+ /* @__PURE__ */ jsx5("polygon", { points: `0,${-arm} -5,${-arm + 8} 5,${-arm + 8}`, fill: c6 }),
1134
+ /* @__PURE__ */ jsx5(
1135
+ "text",
1136
+ {
1137
+ x: 0,
1138
+ y: -arm - 12,
1139
+ dominantBaseline: "central",
1140
+ textAnchor: "middle",
1141
+ fill: c6,
1142
+ fontSize: fs,
1143
+ fontFamily: "serif",
1144
+ fontStyle: "italic",
1145
+ children: "y"
1146
+ }
1147
+ ),
1148
+ /* @__PURE__ */ jsx5("circle", { cx: 0, cy: 0, r: 2, fill: c6 })
1149
+ ] }) });
1150
+ }
1151
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1152
+ /* @__PURE__ */ jsx5("rect", { x: minX, y: minY, width: w, height: h, fill: "transparent" }),
1153
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
1154
+ /* @__PURE__ */ jsxs5("g", { pointerEvents: "none", children: [
1155
+ /* @__PURE__ */ jsx5("line", { x1: midX, y1: minY, x2: midX, y2: maxY, stroke: c6, strokeWidth: sw6, strokeDasharray: dash, opacity: 0.8 }),
1156
+ /* @__PURE__ */ jsx5("line", { x1: minX, y1: midY, x2: maxX, y2: midY, stroke: c6, strokeWidth: sw6, strokeDasharray: dash, opacity: 0.8 }),
1157
+ /* @__PURE__ */ jsx5("polygon", { points: `${maxX},${midY} ${maxX - 10},${midY - 5} ${maxX - 10},${midY + 5}`, fill: c6, opacity: 0.8 }),
1158
+ /* @__PURE__ */ jsx5("polygon", { points: `${midX},${minY} ${midX - 5},${minY + 10} ${midX + 5},${minY + 10}`, fill: c6, opacity: 0.8 })
1159
+ ] }),
1160
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1161
+ ] });
1162
+ });
1163
+ registerRenderer("circle", (el, ctx) => {
1164
+ const r = distance(el.x1, el.y1, el.x2, el.y2);
1165
+ const c6 = colorOf2(el);
1166
+ const sw6 = strokeOf2(el);
1167
+ const dash = dashOf2(el);
1168
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1169
+ /* @__PURE__ */ jsx5("circle", { cx: el.x1, cy: el.y1, r, fill: "none", stroke: c6, strokeWidth: sw6, strokeDasharray: dash, pointerEvents: "none" }),
1170
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1171
+ ] });
1172
+ });
1173
+ registerRenderer("ellipse", (el, ctx) => {
1174
+ const minX = Math.min(el.x1, el.x2);
1175
+ const maxX = Math.max(el.x1, el.x2);
1176
+ const minY = Math.min(el.y1, el.y2);
1177
+ const maxY = Math.max(el.y1, el.y2);
1178
+ const midX = (minX + maxX) / 2;
1179
+ const midY = (minY + maxY) / 2;
1180
+ const w = Math.max(1, maxX - minX);
1181
+ const h = Math.max(1, maxY - minY);
1182
+ const c6 = colorOf2(el);
1183
+ const sw6 = strokeOf2(el);
1184
+ const dash = dashOf2(el);
1185
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1186
+ /* @__PURE__ */ jsx5("rect", { x: minX, y: minY, width: w, height: h, fill: "transparent" }),
1187
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
1188
+ /* @__PURE__ */ jsx5("ellipse", { cx: midX, cy: midY, rx: w / 2, ry: h / 2, fill: "none", stroke: c6, strokeWidth: sw6, strokeDasharray: dash, pointerEvents: "none" }),
1189
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1190
+ ] });
1191
+ });
1192
+ registerRenderer("rectangle", (el, ctx) => {
1193
+ const b = getBounds(el.x1, el.y1, el.x2, el.y2);
1194
+ const c6 = colorOf2(el);
1195
+ const sw6 = strokeOf2(el);
1196
+ const dash = dashOf2(el);
1197
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1198
+ /* @__PURE__ */ jsx5("rect", { x: b.minX, y: b.minY, width: b.width, height: b.height, fill: "transparent" }),
1199
+ bboxInsetSelectionChrome(b.minX, b.minY, b.width, b.height, ctx),
1200
+ /* @__PURE__ */ jsx5(
1201
+ "rect",
1202
+ {
1203
+ x: b.minX,
1204
+ y: b.minY,
1205
+ width: b.width,
1206
+ height: b.height,
1207
+ fill: "none",
1208
+ stroke: c6,
1209
+ strokeWidth: sw6,
1210
+ strokeDasharray: dash,
1211
+ strokeLinejoin: "round",
1212
+ pointerEvents: "none"
1213
+ }
1214
+ ),
1215
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1216
+ ] });
1217
+ });
1218
+ registerRenderer("angle", (el, ctx) => {
1219
+ const { x1, y1, x2, y2 } = el;
1220
+ const dx = x2 - x1;
1221
+ const dy = y2 - y1;
1222
+ const dist = Math.hypot(dx, dy);
1223
+ const minX = Math.min(x1, x2);
1224
+ const maxX = Math.max(x1, x2);
1225
+ const minY = Math.min(y1, y2);
1226
+ const maxY = Math.max(y1, y2);
1227
+ const w = Math.max(1, maxX - minX);
1228
+ const h = Math.max(1, maxY - minY);
1229
+ const c6 = colorOf2(el);
1230
+ const sw6 = strokeOf2(el);
1231
+ const dash = dashOf2(el);
1232
+ const fs = fontOf2(el);
1233
+ const angleRad = Math.atan2(dy, dx);
1234
+ const r = dist;
1235
+ const arcR = Math.min(r * 0.4, 40);
1236
+ const hDir = dx >= 0 ? 1 : -1;
1237
+ const baseA = hDir === 1 ? 0 : Math.PI;
1238
+ let diff = angleRad - baseA;
1239
+ diff = Math.atan2(Math.sin(diff), Math.cos(diff));
1240
+ const midA = baseA + diff / 2;
1241
+ const sweepFlag = diff > 0 ? 1 : 0;
1242
+ const arcStartX = x1 + arcR * Math.cos(baseA);
1243
+ const arcStartY = y1 + arcR * Math.sin(baseA);
1244
+ const arcEndX = x1 + arcR * Math.cos(angleRad);
1245
+ const arcEndY = y1 + arcR * Math.sin(angleRad);
1246
+ const deg = angleRad * 180 / Math.PI;
1247
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1248
+ /* @__PURE__ */ jsx5("rect", { x: minX, y: minY, width: w, height: h, fill: "transparent" }),
1249
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
1250
+ /* @__PURE__ */ jsxs5("g", { pointerEvents: "none", children: [
1251
+ /* @__PURE__ */ jsx5("line", { x1, y1, x2: x1 + dx, y2: y1, stroke: c6, strokeWidth: sw6, strokeDasharray: "4 4", opacity: 0.6 }),
1252
+ /* @__PURE__ */ jsx5("line", { x1, y1, x2, y2, stroke: c6, strokeWidth: sw6, strokeLinecap: "round", strokeDasharray: dash }),
1253
+ /* @__PURE__ */ jsx5("g", { transform: `translate(${x2}, ${y2}) rotate(${deg})`, children: /* @__PURE__ */ jsx5("polygon", { points: "0,0 -12,-6 -12,6", fill: c6 }) }),
1254
+ dist > 20 && /* @__PURE__ */ jsx5(
1255
+ "path",
1256
+ {
1257
+ d: `M ${arcStartX} ${arcStartY} A ${arcR} ${arcR} 0 0 ${sweepFlag} ${arcEndX} ${arcEndY}`,
1258
+ fill: "none",
1259
+ stroke: c6,
1260
+ strokeWidth: sw6
1261
+ }
1262
+ ),
1263
+ el.label && /* @__PURE__ */ jsx5(
1264
+ "text",
1265
+ {
1266
+ x: x1 + (arcR + 16) * Math.cos(midA),
1267
+ y: y1 + (arcR + 16) * Math.sin(midA),
1268
+ fontSize: fs,
1269
+ fill: c6,
1270
+ fontFamily: "serif",
1271
+ fontStyle: "italic",
1272
+ dominantBaseline: "central",
1273
+ textAnchor: "middle",
1274
+ children: String(el.label)
1275
+ }
1276
+ )
1277
+ ] }),
1278
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1279
+ ] });
1280
+ });
1281
+ registerRenderer("parabola_v", (el, ctx) => {
1282
+ const minX = Math.min(el.x1, el.x2);
1283
+ const maxX = Math.max(el.x1, el.x2);
1284
+ const minY = Math.min(el.y1, el.y2);
1285
+ const maxY = Math.max(el.y1, el.y2);
1286
+ const midX = (minX + maxX) / 2;
1287
+ const w = Math.max(1, maxX - minX);
1288
+ const h = Math.max(1, maxY - minY);
1289
+ const cpY = 2 * el.y1 - el.y2;
1290
+ const c6 = colorOf2(el);
1291
+ const sw6 = strokeOf2(el);
1292
+ const dash = dashOf2(el);
1293
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1294
+ /* @__PURE__ */ jsx5("rect", { x: minX, y: minY, width: w, height: h, fill: "transparent" }),
1295
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
1296
+ /* @__PURE__ */ jsx5(
1297
+ "path",
1298
+ {
1299
+ d: `M ${minX} ${el.y2} Q ${midX} ${cpY} ${maxX} ${el.y2}`,
1300
+ fill: "none",
1301
+ stroke: c6,
1302
+ strokeWidth: sw6,
1303
+ strokeDasharray: dash,
1304
+ pointerEvents: "none"
1305
+ }
1306
+ ),
1307
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1308
+ ] });
1309
+ });
1310
+ registerRenderer("parabola_h", (el, ctx) => {
1311
+ const minX = Math.min(el.x1, el.x2);
1312
+ const maxX = Math.max(el.x1, el.x2);
1313
+ const minY = Math.min(el.y1, el.y2);
1314
+ const maxY = Math.max(el.y1, el.y2);
1315
+ const midY = (minY + maxY) / 2;
1316
+ const w = Math.max(1, maxX - minX);
1317
+ const h = Math.max(1, maxY - minY);
1318
+ const cpX = 2 * el.x1 - el.x2;
1319
+ const c6 = colorOf2(el);
1320
+ const sw6 = strokeOf2(el);
1321
+ const dash = dashOf2(el);
1322
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1323
+ /* @__PURE__ */ jsx5("rect", { x: minX, y: minY, width: w, height: h, fill: "transparent" }),
1324
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
1325
+ /* @__PURE__ */ jsx5(
1326
+ "path",
1327
+ {
1328
+ d: `M ${el.x2} ${minY} Q ${cpX} ${midY} ${el.x2} ${maxY}`,
1329
+ fill: "none",
1330
+ stroke: c6,
1331
+ strokeWidth: sw6,
1332
+ strokeDasharray: dash,
1333
+ pointerEvents: "none"
1334
+ }
1335
+ ),
1336
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1337
+ ] });
1338
+ });
1339
+ registerRenderer("hyperbola", (el, ctx) => {
1340
+ const minX = Math.min(el.x1, el.x2);
1341
+ const maxX = Math.max(el.x1, el.x2);
1342
+ const minY = Math.min(el.y1, el.y2);
1343
+ const maxY = Math.max(el.y1, el.y2);
1344
+ const midX = (minX + maxX) / 2;
1345
+ const midY = (minY + maxY) / 2;
1346
+ const w = Math.max(1, maxX - minX);
1347
+ const h = Math.max(1, maxY - minY);
1348
+ const c6 = colorOf2(el);
1349
+ const sw6 = strokeOf2(el);
1350
+ const dash = dashOf2(el);
1351
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1352
+ /* @__PURE__ */ jsx5("rect", { x: minX, y: minY, width: w, height: h, fill: "transparent" }),
1353
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
1354
+ /* @__PURE__ */ jsxs5("g", { pointerEvents: "none", children: [
1355
+ /* @__PURE__ */ jsx5("path", { d: `M ${minX} ${minY} Q ${midX} ${midY} ${minX} ${maxY}`, fill: "none", stroke: c6, strokeWidth: sw6, strokeDasharray: dash }),
1356
+ /* @__PURE__ */ jsx5("path", { d: `M ${maxX} ${minY} Q ${midX} ${midY} ${maxX} ${maxY}`, fill: "none", stroke: c6, strokeWidth: sw6, strokeDasharray: dash })
1357
+ ] }),
1358
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1359
+ ] });
1360
+ });
1361
+ registerRenderer("modulus", (el, ctx) => {
1362
+ const minX = Math.min(el.x1, el.x2);
1363
+ const maxX = Math.max(el.x1, el.x2);
1364
+ const minY = Math.min(el.y1, el.y2);
1365
+ const maxY = Math.max(el.y1, el.y2);
1366
+ const midX = (minX + maxX) / 2;
1367
+ const w = Math.max(1, maxX - minX);
1368
+ const h = Math.max(1, maxY - minY);
1369
+ const c6 = colorOf2(el);
1370
+ const sw6 = strokeOf2(el);
1371
+ const dash = dashOf2(el);
1372
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1373
+ /* @__PURE__ */ jsx5("rect", { x: minX, y: minY, width: w, height: h, fill: "transparent" }),
1374
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
1375
+ /* @__PURE__ */ jsx5(
1376
+ "polyline",
1377
+ {
1378
+ points: `${minX},${minY} ${midX},${maxY} ${maxX},${minY}`,
1379
+ fill: "none",
1380
+ stroke: c6,
1381
+ strokeWidth: sw6,
1382
+ strokeLinecap: "round",
1383
+ strokeLinejoin: "round",
1384
+ strokeDasharray: dash,
1385
+ pointerEvents: "none"
1386
+ }
1387
+ ),
1388
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1389
+ ] });
1390
+ });
1391
+ registerRenderer("exponential", (el, ctx) => {
1392
+ const minX = Math.min(el.x1, el.x2);
1393
+ const maxX = Math.max(el.x1, el.x2);
1394
+ const minY = Math.min(el.y1, el.y2);
1395
+ const maxY = Math.max(el.y1, el.y2);
1396
+ const midX = (minX + maxX) / 2;
1397
+ const w = Math.max(1, maxX - minX);
1398
+ const h = Math.max(1, maxY - minY);
1399
+ const c6 = colorOf2(el);
1400
+ const sw6 = strokeOf2(el);
1401
+ const dash = dashOf2(el);
1402
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1403
+ /* @__PURE__ */ jsx5("rect", { x: minX, y: minY, width: w, height: h, fill: "transparent" }),
1404
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
1405
+ /* @__PURE__ */ jsx5(
1406
+ "path",
1407
+ {
1408
+ d: `M ${minX} ${maxY} Q ${midX + w * 0.2} ${maxY} ${maxX} ${minY}`,
1409
+ fill: "none",
1410
+ stroke: c6,
1411
+ strokeWidth: sw6,
1412
+ strokeDasharray: dash,
1413
+ pointerEvents: "none"
1414
+ }
1415
+ ),
1416
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1417
+ ] });
1418
+ });
1419
+ registerRenderer("logarithmic", (el, ctx) => {
1420
+ const minX = Math.min(el.x1, el.x2);
1421
+ const maxX = Math.max(el.x1, el.x2);
1422
+ const minY = Math.min(el.y1, el.y2);
1423
+ const maxY = Math.max(el.y1, el.y2);
1424
+ const midY = (minY + maxY) / 2;
1425
+ const w = Math.max(1, maxX - minX);
1426
+ const h = Math.max(1, maxY - minY);
1427
+ const c6 = colorOf2(el);
1428
+ const sw6 = strokeOf2(el);
1429
+ const dash = dashOf2(el);
1430
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1431
+ /* @__PURE__ */ jsx5("rect", { x: minX, y: minY, width: w, height: h, fill: "transparent" }),
1432
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
1433
+ /* @__PURE__ */ jsx5(
1434
+ "path",
1435
+ {
1436
+ d: `M ${minX} ${maxY} Q ${minX} ${midY - h * 0.2} ${maxX} ${minY}`,
1437
+ fill: "none",
1438
+ stroke: c6,
1439
+ strokeWidth: sw6,
1440
+ strokeDasharray: dash,
1441
+ pointerEvents: "none"
1442
+ }
1443
+ ),
1444
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1445
+ ] });
1446
+ });
1447
+ registerRenderer("sine_wave", (el, ctx) => {
1448
+ const minX = Math.min(el.x1, el.x2);
1449
+ const maxX = Math.max(el.x1, el.x2);
1450
+ const minY = Math.min(el.y1, el.y2);
1451
+ const maxY = Math.max(el.y1, el.y2);
1452
+ const midY = (minY + maxY) / 2;
1453
+ const w = Math.max(1, maxX - minX);
1454
+ const h = Math.max(1, maxY - minY);
1455
+ const s = w / 4;
1456
+ const c6 = colorOf2(el);
1457
+ const sw6 = strokeOf2(el);
1458
+ const dash = dashOf2(el);
1459
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1460
+ /* @__PURE__ */ jsx5("rect", { x: minX, y: minY, width: w, height: h, fill: "transparent" }),
1461
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
1462
+ /* @__PURE__ */ jsx5(
1463
+ "path",
1464
+ {
1465
+ d: `M ${minX} ${midY} Q ${minX + s / 2} ${minY}, ${minX + s} ${midY} T ${minX + 2 * s} ${midY} T ${minX + 3 * s} ${midY} T ${maxX} ${midY}`,
1466
+ fill: "none",
1467
+ stroke: c6,
1468
+ strokeWidth: sw6,
1469
+ strokeLinecap: "round",
1470
+ strokeLinejoin: "round",
1471
+ strokeDasharray: dash,
1472
+ pointerEvents: "none"
1473
+ }
1474
+ ),
1475
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1476
+ ] });
1477
+ });
1478
+ registerRenderer("step_function", (el, ctx) => {
1479
+ const minX = Math.min(el.x1, el.x2);
1480
+ const maxX = Math.max(el.x1, el.x2);
1481
+ const minY = Math.min(el.y1, el.y2);
1482
+ const maxY = Math.max(el.y1, el.y2);
1483
+ const w = Math.max(1, maxX - minX);
1484
+ const h = Math.max(1, maxY - minY);
1485
+ const c6 = colorOf2(el);
1486
+ const sw6 = strokeOf2(el);
1487
+ const dash = dashOf2(el);
1488
+ const stepsCount = 4;
1489
+ const stepW = w / stepsCount;
1490
+ const stepH = h / stepsCount;
1491
+ const steps = [];
1492
+ for (let i = 0; i < stepsCount; i++) {
1493
+ const curX = minX + i * stepW;
1494
+ const curY = maxY - i * stepH;
1495
+ const nextX = curX + stepW;
1496
+ steps.push(
1497
+ /* @__PURE__ */ jsxs5("g", { children: [
1498
+ /* @__PURE__ */ jsx5("line", { x1: curX, y1: curY, x2: nextX, y2: curY, stroke: c6, strokeWidth: sw6, strokeDasharray: dash }),
1499
+ /* @__PURE__ */ jsx5("circle", { cx: curX, cy: curY, r: sw6 * 1.5, fill: c6 }),
1500
+ /* @__PURE__ */ jsx5("circle", { cx: nextX, cy: curY, r: sw6 * 1.5, fill: "white", stroke: c6, strokeWidth: sw6 * 0.75 })
1501
+ ] }, i)
1502
+ );
1503
+ }
1504
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1505
+ /* @__PURE__ */ jsx5("rect", { x: minX, y: minY, width: w, height: h, fill: "transparent" }),
1506
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
1507
+ /* @__PURE__ */ jsx5("g", { pointerEvents: "none", children: steps }),
1508
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1509
+ ] });
1510
+ });
1511
+ registerRenderer("shaded_area", (el, ctx) => {
1512
+ const minX = Math.min(el.x1, el.x2);
1513
+ const maxX = Math.max(el.x1, el.x2);
1514
+ const minY = Math.min(el.y1, el.y2);
1515
+ const maxY = Math.max(el.y1, el.y2);
1516
+ const midX = (minX + maxX) / 2;
1517
+ const w = Math.max(1, maxX - minX);
1518
+ const h = Math.max(1, maxY - minY);
1519
+ const c6 = colorOf2(el);
1520
+ const sw6 = strokeOf2(el);
1521
+ const dash = dashOf2(el);
1522
+ return /* @__PURE__ */ jsxs5("g", { opacity: op(ctx), children: [
1523
+ /* @__PURE__ */ jsx5("rect", { x: minX, y: minY, width: w, height: h, fill: "transparent" }),
1524
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
1525
+ /* @__PURE__ */ jsxs5("g", { pointerEvents: "none", children: [
1526
+ /* @__PURE__ */ jsx5(
1527
+ "path",
1528
+ {
1529
+ d: `M ${minX} ${maxY} L ${minX} ${minY + h * 0.3} Q ${midX} ${minY} ${maxX} ${minY + h * 0.3} L ${maxX} ${maxY} Z`,
1530
+ fill: c6,
1531
+ opacity: 0.2
1532
+ }
1533
+ ),
1534
+ /* @__PURE__ */ jsx5("path", { d: `M ${minX} ${minY + h * 0.3} Q ${midX} ${minY} ${maxX} ${minY + h * 0.3}`, fill: "none", stroke: c6, strokeWidth: sw6, strokeDasharray: dash })
1535
+ ] }),
1536
+ /* @__PURE__ */ jsx5(ChordInteractionOverlay, { el, ctx })
1537
+ ] });
1538
+ });
1539
+
1540
+ // src/core/renderers/waveOscillation.tsx
1541
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1542
+ var c3 = (el) => el.color || "#0f172a";
1543
+ var sw3 = (el) => el.strokeWidth || 2;
1544
+ var font2 = (el) => el.fontSize || 18;
1545
+ registerRenderer("mass_box", (el, ctx) => {
1546
+ const size = el.size || 30;
1547
+ const color = c3(el);
1548
+ const strokeWidth = sw3(el);
1549
+ const fontSize = font2(el);
1550
+ const plate = Math.max(36, size / 2 + 24);
1551
+ return /* @__PURE__ */ jsx6("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs6(OriginHitPlate, { ctx, hitHalfSize: plate, selectionHalfSize: plate + 8, children: [
1552
+ /* @__PURE__ */ jsx6(
1553
+ "rect",
1554
+ {
1555
+ x: -size / 2,
1556
+ y: -size / 2,
1557
+ width: size,
1558
+ height: size,
1559
+ fill: color,
1560
+ fillOpacity: 0.2,
1561
+ stroke: color,
1562
+ strokeWidth,
1563
+ rx: 4,
1564
+ pointerEvents: "none"
1565
+ }
1566
+ ),
1567
+ el.label ? /* @__PURE__ */ jsx6("text", { x: 0, y: 0, dominantBaseline: "central", textAnchor: "middle", fontSize, fill: color, fontWeight: "bold", pointerEvents: "none", children: String(el.label) }) : null
1568
+ ] }) });
1569
+ });
1570
+ registerRenderer("tuning_fork", (el, ctx) => {
1571
+ const color = c3(el);
1572
+ const strokeWidth = sw3(el);
1573
+ const fontSize = font2(el);
1574
+ const w = Math.max(3, strokeWidth);
1575
+ return /* @__PURE__ */ jsx6("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs6(OriginHitPlate, { ctx, hitHalfSize: 38, selectionHalfSize: 46, children: [
1576
+ /* @__PURE__ */ jsx6(
1577
+ "path",
1578
+ {
1579
+ d: "M -10 -20 L -10 0 Q -10 10 0 10 Q 10 10 10 0 L 10 -20",
1580
+ fill: "none",
1581
+ stroke: color,
1582
+ strokeWidth: w,
1583
+ strokeLinecap: "round",
1584
+ pointerEvents: "none"
1585
+ }
1586
+ ),
1587
+ /* @__PURE__ */ jsx6("line", { x1: "0", y1: "10", x2: "0", y2: "30", stroke: color, strokeWidth: Math.max(4, strokeWidth), strokeLinecap: "round", pointerEvents: "none" }),
1588
+ el.label ? /* @__PURE__ */ jsx6("text", { x: 0, y: 45, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
1589
+ ] }) });
1590
+ });
1591
+ registerRenderer("speaker", (el, ctx) => {
1592
+ const color = c3(el);
1593
+ const fontSize = font2(el);
1594
+ return /* @__PURE__ */ jsx6("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs6(OriginHitPlate, { ctx, hitHalfSize: 36, selectionHalfSize: 44, children: [
1595
+ /* @__PURE__ */ jsx6("polygon", { points: "-10,-15 5,-25 5,25 -10,15", fill: color, pointerEvents: "none" }),
1596
+ /* @__PURE__ */ jsx6("rect", { x: "-20", y: "-15", width: "10", height: "30", fill: color, pointerEvents: "none" }),
1597
+ /* @__PURE__ */ jsx6("path", { d: "M 12 -10 Q 18 0 12 10 M 18 -20 Q 28 0 18 20", fill: "none", stroke: color, strokeWidth: 2, strokeLinecap: "round", pointerEvents: "none" }),
1598
+ el.label ? /* @__PURE__ */ jsx6("text", { x: 0, y: 40, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
1599
+ ] }) });
1600
+ });
1601
+ registerRenderer("wavefronts", (el, ctx) => {
1602
+ const rings = el.rings || 5;
1603
+ const vRatio = el.velocityRatio ?? 0;
1604
+ const color = c3(el);
1605
+ const strokeWidth = sw3(el);
1606
+ const fontSize = font2(el);
1607
+ const circles = [];
1608
+ for (let i = 1; i <= rings; i++) {
1609
+ const r = i * 20;
1610
+ const cx = vRatio * r;
1611
+ circles.push(
1612
+ /* @__PURE__ */ jsx6(
1613
+ "circle",
1614
+ {
1615
+ cx,
1616
+ cy: 0,
1617
+ r,
1618
+ fill: "none",
1619
+ stroke: color,
1620
+ strokeWidth,
1621
+ opacity: 1 - i / rings * 0.5,
1622
+ pointerEvents: "none"
1623
+ },
1624
+ i
1625
+ )
1626
+ );
1627
+ }
1628
+ const extent = rings * 20 + Math.abs(vRatio) * rings * 20 + 30;
1629
+ return /* @__PURE__ */ jsx6("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs6(OriginHitPlate, { ctx, hitHalfSize: extent, selectionHalfSize: extent + 10, children: [
1630
+ /* @__PURE__ */ jsx6("circle", { cx: 0, cy: 0, r: 4, fill: color, pointerEvents: "none" }),
1631
+ circles,
1632
+ el.label ? /* @__PURE__ */ jsx6("text", { x: 0, y: rings * 20 + 20, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
1633
+ ] }) });
1634
+ });
1635
+ registerRenderer("pendulum", (el, ctx) => {
1636
+ const size = el.size || 15;
1637
+ const color = c3(el);
1638
+ const strokeWidth = sw3(el);
1639
+ const fontSize = font2(el);
1640
+ const theta = Math.atan2(el.x2 - el.x1, el.y2 - el.y1);
1641
+ const thetaDeg = theta * (180 / Math.PI);
1642
+ const l = Math.hypot(el.x2 - el.x1, el.y2 - el.y1);
1643
+ const arcR = Math.min(40, l * 0.5);
1644
+ const arcX = Math.sin(theta) * arcR;
1645
+ const arcY = Math.cos(theta) * arcR;
1646
+ const arcFlag = theta > 0 ? 0 : 1;
1647
+ const showF = el.showForces;
1648
+ return /* @__PURE__ */ jsxs6("g", { children: [
1649
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
1650
+ /* @__PURE__ */ jsx6("line", { x1: -20, y1: 0, x2: 20, y2: 0, stroke: color, strokeWidth: 2 }),
1651
+ /* @__PURE__ */ jsx6("line", { x1: -15, y1: 0, x2: -10, y2: -5, stroke: color }),
1652
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: 5, y2: -5, stroke: color }),
1653
+ /* @__PURE__ */ jsx6("line", { x1: 15, y1: 0, x2: 20, y2: -5, stroke: color }),
1654
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: 0, y2: l + 20, stroke: color, strokeWidth: 1, strokeDasharray: "4 4", opacity: 0.5 }),
1655
+ Math.abs(theta) > 0.05 ? /* @__PURE__ */ jsx6(
1656
+ "path",
1657
+ {
1658
+ d: `M 0 ${arcR} A ${arcR} ${arcR} 0 0 ${arcFlag} ${arcX} ${arcY}`,
1659
+ fill: "none",
1660
+ stroke: color,
1661
+ strokeWidth: 1.5
1662
+ }
1663
+ ) : null,
1664
+ /* @__PURE__ */ jsxs6("g", { transform: `rotate(${-thetaDeg})`, children: [
1665
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: 0, y2: l, stroke: color, strokeWidth }),
1666
+ /* @__PURE__ */ jsx6("circle", { cx: 0, cy: l, r: size, fill: color, stroke: "#fff", strokeWidth: 2 }),
1667
+ showF ? /* @__PURE__ */ jsxs6("g", { transform: `translate(0, ${l})`, children: [
1668
+ /* @__PURE__ */ jsxs6("g", { transform: `rotate(${thetaDeg})`, children: [
1669
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: 0, y2: 40, stroke: "#ef4444", strokeWidth: 2 }),
1670
+ /* @__PURE__ */ jsx6("polygon", { points: "0,45 -4,37 4,37", fill: "#ef4444" }),
1671
+ /* @__PURE__ */ jsx6("text", { x: 10, y: 40, fontSize: 12, fill: "#ef4444", children: "F_g" })
1672
+ ] }),
1673
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: 0, y2: -40, stroke: "#3b82f6", strokeWidth: 2 }),
1674
+ /* @__PURE__ */ jsx6("polygon", { points: "0,-45 -4,-37 4,-37", fill: "#3b82f6" }),
1675
+ /* @__PURE__ */ jsx6("text", { x: 10, y: -35, fontSize: 12, fill: "#3b82f6", children: "T" }),
1676
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: -35 * Math.sin(theta), y2: 0, stroke: "#10b981", strokeWidth: 2 }),
1677
+ /* @__PURE__ */ jsx6(
1678
+ "polygon",
1679
+ {
1680
+ points: `${-40 * Math.sin(theta)},0 ${-32 * Math.sin(theta)},-4 ${-32 * Math.sin(theta)},4`,
1681
+ fill: "#10b981"
1682
+ }
1683
+ ),
1684
+ /* @__PURE__ */ jsx6("text", { x: -45 * Math.sin(theta), y: -10, fontSize: 12, fill: "#10b981", children: "F_g sin(\u03B8)" })
1685
+ ] }) : null,
1686
+ el.label ? /* @__PURE__ */ jsx6("text", { x: 0, y: l + size + 15, fontSize, fill: color, textAnchor: "middle", children: String(el.label) }) : null
1687
+ ] })
1688
+ ] }),
1689
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padX: Math.max(80, l + size + 30), padY: Math.max(80, l + size + 30) })
1690
+ ] });
1691
+ });
1692
+ registerRenderer("phasor", (el, ctx) => {
1693
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
1694
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
1695
+ const dx = el.x2 - el.x1;
1696
+ const dy = el.y2 - el.y1;
1697
+ const color = c3(el);
1698
+ const strokeWidth = sw3(el);
1699
+ const fontSize = font2(el);
1700
+ const showProj = el.showProjection;
1701
+ return /* @__PURE__ */ jsxs6("g", { children: [
1702
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
1703
+ /* @__PURE__ */ jsx6("circle", { cx: 0, cy: 0, r: dist, fill: "none", stroke: color, strokeWidth: 1, strokeDasharray: "4 4", opacity: 0.5, pointerEvents: "none" }),
1704
+ /* @__PURE__ */ jsx6("line", { x1: -dist - 10, y1: 0, x2: dist + 10, y2: 0, stroke: color, strokeWidth: 1, opacity: 0.3, pointerEvents: "none" }),
1705
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: -dist - 10, x2: 0, y2: dist + 10, stroke: color, strokeWidth: 1, opacity: 0.3, pointerEvents: "none" }),
1706
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: dist, y2: 0, stroke: color, strokeWidth, pointerEvents: "none" }),
1707
+ /* @__PURE__ */ jsx6("polygon", { points: `${dist},0 ${dist - 12},-6 ${dist - 12},6`, fill: color, pointerEvents: "none" }),
1708
+ showProj ? /* @__PURE__ */ jsxs6("g", { transform: `rotate(${-angle})`, children: [
1709
+ /* @__PURE__ */ jsx6("line", { x1: dx, y1: dy, x2: dx, y2: 0, stroke: "#ef4444", strokeWidth: 1, strokeDasharray: "4 4" }),
1710
+ /* @__PURE__ */ jsx6("line", { x1: dx, y1: dy, x2: 0, y2: dy, stroke: "#3b82f6", strokeWidth: 1, strokeDasharray: "4 4" }),
1711
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: dx, y2: 0, stroke: "#ef4444", strokeWidth: 2 }),
1712
+ /* @__PURE__ */ jsx6("polygon", { points: `${dx},0 ${dx > 0 ? dx - 8 : dx + 8},-4 ${dx > 0 ? dx - 8 : dx + 8},4`, fill: "#ef4444" }),
1713
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: 0, y2: dy, stroke: "#3b82f6", strokeWidth: 2 }),
1714
+ /* @__PURE__ */ jsx6("polygon", { points: `0,${dy} -4,${dy > 0 ? dy - 8 : dy + 8} 4,${dy > 0 ? dy - 8 : dy + 8}`, fill: "#3b82f6" })
1715
+ ] }) : null,
1716
+ /* @__PURE__ */ jsx6(
1717
+ "path",
1718
+ {
1719
+ d: `M ${dist * 0.3} 0 A ${dist * 0.3} ${dist * 0.3} 0 0 0 ${dist * 0.3 * Math.cos(-angle * Math.PI / 180)} ${dist * 0.3 * Math.sin(-angle * Math.PI / 180)}`,
1720
+ fill: "none",
1721
+ stroke: color,
1722
+ strokeWidth: 1.5,
1723
+ transform: `rotate(${-angle})`
1724
+ }
1725
+ ),
1726
+ el.label ? /* @__PURE__ */ jsx6("text", { x: dist / 2, y: -15, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
1727
+ ] }),
1728
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padX: dist + 28, padY: dist + 28 })
1729
+ ] });
1730
+ });
1731
+ registerRenderer("sine_wave", (el, ctx) => {
1732
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
1733
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
1734
+ const amp = el.amplitude || 40;
1735
+ const loops = el.loops || 2;
1736
+ const wl = dist / (loops / 2);
1737
+ const color = c3(el);
1738
+ const strokeWidth = sw3(el);
1739
+ const fontSize = font2(el);
1740
+ let d = "M 0 0";
1741
+ const step = Math.max(1, dist / 100);
1742
+ for (let i = 0; i <= dist; i += step) {
1743
+ d += ` L ${i} ${-amp * Math.sin(i / wl * 2 * Math.PI)}`;
1744
+ }
1745
+ return /* @__PURE__ */ jsxs6("g", { children: [
1746
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
1747
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: dist, y2: 0, stroke: color, strokeWidth: 1, strokeDasharray: "4 4", opacity: 0.5, pointerEvents: "none" }),
1748
+ /* @__PURE__ */ jsx6(LocalFatPathHit, { dPath: d, strokeWidth: 22, strokeLinejoin: "round" }),
1749
+ /* @__PURE__ */ jsx6("path", { d, fill: "none", stroke: color, strokeWidth, strokeLinejoin: "round", pointerEvents: "none" }),
1750
+ el.label ? /* @__PURE__ */ jsx6("text", { x: dist / 2, y: -amp - 15, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
1751
+ ] }),
1752
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padY: amp + 28 })
1753
+ ] });
1754
+ });
1755
+ registerRenderer("standing_wave", (el, ctx) => {
1756
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
1757
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
1758
+ const amp = el.amplitude || 40;
1759
+ const loops = el.loops || 3;
1760
+ const wl = dist / (loops / 2);
1761
+ const color = c3(el);
1762
+ const strokeWidth = sw3(el);
1763
+ const fontSize = font2(el);
1764
+ let d1 = "M 0 0";
1765
+ let d2 = "M 0 0";
1766
+ const step = Math.max(1, dist / 100);
1767
+ for (let i = 0; i <= dist; i += step) {
1768
+ const y = amp * Math.sin(i / wl * 2 * Math.PI);
1769
+ d1 += ` L ${i} ${-y}`;
1770
+ d2 += ` L ${i} ${y}`;
1771
+ }
1772
+ return /* @__PURE__ */ jsxs6("g", { children: [
1773
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
1774
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: dist, y2: 0, stroke: color, strokeWidth: 1, opacity: 0.5, pointerEvents: "none" }),
1775
+ /* @__PURE__ */ jsx6(LocalFatPathHit, { dPath: d1, strokeWidth: 22, strokeLinejoin: "round" }),
1776
+ /* @__PURE__ */ jsx6(LocalFatPathHit, { dPath: d2, strokeWidth: 22, strokeLinejoin: "round" }),
1777
+ /* @__PURE__ */ jsx6("path", { d: d1, fill: color, fillOpacity: 0.1, stroke: color, strokeWidth, strokeLinejoin: "round", pointerEvents: "none" }),
1778
+ /* @__PURE__ */ jsx6("path", { d: d2, fill: "none", stroke: color, strokeWidth, strokeDasharray: "4 4", strokeLinejoin: "round", pointerEvents: "none" }),
1779
+ el.label ? /* @__PURE__ */ jsx6("text", { x: dist / 2, y: -amp - 15, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
1780
+ ] }),
1781
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padY: amp + 28 })
1782
+ ] });
1783
+ });
1784
+ registerRenderer("shm_graph", (el, ctx) => {
1785
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
1786
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
1787
+ const amp = el.amplitude || 40;
1788
+ const loops = el.loops || 1;
1789
+ const wl = dist / loops;
1790
+ const gType = el.graphType || "x";
1791
+ const color = c3(el);
1792
+ const strokeWidth = sw3(el);
1793
+ const fontSize = font2(el);
1794
+ let dX = `M 0 ${-amp}`;
1795
+ let dV = "M 0 0";
1796
+ let dA = `M 0 ${amp}`;
1797
+ const step = Math.max(1, dist / 100);
1798
+ for (let i = 0; i <= dist; i += step) {
1799
+ const phase = i / wl * 2 * Math.PI;
1800
+ dX += ` L ${i} ${-amp * Math.cos(phase)}`;
1801
+ dV += ` L ${i} ${-amp * 0.8 * -Math.sin(phase)}`;
1802
+ dA += ` L ${i} ${-amp * 0.6 * -Math.cos(phase)}`;
1803
+ }
1804
+ return /* @__PURE__ */ jsxs6("g", { children: [
1805
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
1806
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: dist, y2: 0, stroke: color, strokeWidth: 1, opacity: 0.5, pointerEvents: "none" }),
1807
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: -amp * 1.2, x2: 0, y2: amp * 1.2, stroke: color, strokeWidth: 1, opacity: 0.5, pointerEvents: "none" }),
1808
+ gType === "x" || gType === "all" ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
1809
+ /* @__PURE__ */ jsx6(LocalFatPathHit, { dPath: dX, strokeWidth: 20, strokeLinejoin: "round" }),
1810
+ /* @__PURE__ */ jsx6("path", { d: dX, fill: "none", stroke: "#3b82f6", strokeWidth, strokeLinejoin: "round", pointerEvents: "none" })
1811
+ ] }) : null,
1812
+ gType === "v" || gType === "all" ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
1813
+ /* @__PURE__ */ jsx6(LocalFatPathHit, { dPath: dV, strokeWidth: 20, strokeLinejoin: "round" }),
1814
+ /* @__PURE__ */ jsx6("path", { d: dV, fill: "none", stroke: "#ef4444", strokeWidth, strokeLinejoin: "round", pointerEvents: "none" })
1815
+ ] }) : null,
1816
+ gType === "a" || gType === "all" ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
1817
+ /* @__PURE__ */ jsx6(LocalFatPathHit, { dPath: dA, strokeWidth: 20, strokeLinejoin: "round" }),
1818
+ /* @__PURE__ */ jsx6("path", { d: dA, fill: "none", stroke: "#10b981", strokeWidth, strokeLinejoin: "round", pointerEvents: "none" })
1819
+ ] }) : null,
1820
+ el.label ? /* @__PURE__ */ jsx6("text", { x: dist / 2, y: -amp - 15, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
1821
+ ] }),
1822
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padY: amp * 1.2 + 28 })
1823
+ ] });
1824
+ });
1825
+ registerRenderer("energy_graph", (el, ctx) => {
1826
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
1827
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
1828
+ const amp = el.amplitude || 50;
1829
+ const loops = el.loops || 1;
1830
+ const wl = dist / loops;
1831
+ const dom = el.domain || "time";
1832
+ const color = c3(el);
1833
+ const strokeWidth = sw3(el);
1834
+ const fontSize = font2(el);
1835
+ let dU = "M 0 0";
1836
+ let dK = `M 0 ${-amp}`;
1837
+ const step = Math.max(1, dist / 100);
1838
+ if (dom === "time") {
1839
+ dU = `M 0 ${-amp}`;
1840
+ dK = "M 0 0";
1841
+ for (let i = 0; i <= dist; i += step) {
1842
+ const phase = i / wl * 2 * Math.PI;
1843
+ dU += ` L ${i} ${-amp * Math.pow(Math.cos(phase), 2)}`;
1844
+ dK += ` L ${i} ${-amp * Math.pow(Math.sin(phase), 2)}`;
1845
+ }
1846
+ } else {
1847
+ for (let i = 0; i <= dist; i += step) {
1848
+ const xNorm = 2 * (i / dist) - 1;
1849
+ const uVal = amp * xNorm * xNorm;
1850
+ dU += ` L ${i} ${-uVal}`;
1851
+ dK += ` L ${i} ${-(amp - uVal)}`;
1852
+ }
1853
+ }
1854
+ return /* @__PURE__ */ jsxs6("g", { children: [
1855
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
1856
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: dist, y2: 0, stroke: color, strokeWidth: 1, opacity: 0.5, pointerEvents: "none" }),
1857
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: -amp, x2: dist, y2: -amp, stroke: color, strokeWidth: 1, strokeDasharray: "4 4", opacity: 0.5, pointerEvents: "none" }),
1858
+ /* @__PURE__ */ jsx6(LocalFatPathHit, { dPath: dU, strokeWidth: 20, strokeLinejoin: "round" }),
1859
+ /* @__PURE__ */ jsx6(LocalFatPathHit, { dPath: dK, strokeWidth: 20, strokeLinejoin: "round" }),
1860
+ /* @__PURE__ */ jsx6("path", { d: dU, fill: "none", stroke: "#10b981", strokeWidth, strokeLinejoin: "round", pointerEvents: "none" }),
1861
+ /* @__PURE__ */ jsx6("path", { d: dK, fill: "none", stroke: "#ef4444", strokeWidth, strokeLinejoin: "round", pointerEvents: "none" }),
1862
+ el.label ? /* @__PURE__ */ jsx6("text", { x: dist / 2, y: -amp - 15, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
1863
+ ] }),
1864
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padY: amp + 28 })
1865
+ ] });
1866
+ });
1867
+ registerRenderer("strobe_shm", (el, ctx) => {
1868
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
1869
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
1870
+ const amp = el.amplitude || 40;
1871
+ const loops = el.loops || 1;
1872
+ const color = c3(el);
1873
+ const strokeWidth = sw3(el);
1874
+ const fontSize = font2(el);
1875
+ const rows = 10;
1876
+ const pts = [];
1877
+ const circles = [];
1878
+ const lines = [];
1879
+ for (let i = 0; i <= rows; i++) {
1880
+ const t = i / rows;
1881
+ const cx = amp * Math.cos(t * 2 * Math.PI * loops);
1882
+ const cy = t * dist;
1883
+ pts.push(`${cx},${cy}`);
1884
+ circles.push(/* @__PURE__ */ jsx6("circle", { cx, cy, r: strokeWidth * 2, fill: color, pointerEvents: "none" }, `c${i}`));
1885
+ lines.push(
1886
+ /* @__PURE__ */ jsx6("line", { x1: -amp - 20, y1: cy, x2: amp + 20, y2: cy, stroke: color, strokeWidth: 1, strokeDasharray: "4 4", opacity: 0.3 }, `l${i}`)
1887
+ );
1888
+ }
1889
+ return /* @__PURE__ */ jsxs6("g", { children: [
1890
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
1891
+ lines,
1892
+ /* @__PURE__ */ jsx6("polyline", { points: pts.join(" "), fill: "none", stroke: color, strokeWidth: 1, opacity: 0.4, pointerEvents: "none" }),
1893
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: 0, y2: dist, stroke: color, strokeWidth: 1, opacity: 0.5, pointerEvents: "none" }),
1894
+ circles,
1895
+ el.label ? /* @__PURE__ */ jsx6("text", { x: 0, y: -15, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
1896
+ ] }),
1897
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padX: amp + 35, padY: dist + 35 })
1898
+ ] });
1899
+ });
1900
+ registerRenderer("torsion_pendulum", (el, ctx) => {
1901
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
1902
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
1903
+ const color = c3(el);
1904
+ const strokeWidth = sw3(el);
1905
+ const fontSize = font2(el);
1906
+ const R = Math.max(18, Math.min(58, el.size || 26 + dist * 0.2));
1907
+ const halfT = Math.max(5, Math.min(11, R * 0.14));
1908
+ const twistDeg = Math.max(-45, Math.min(45, el.rotation ?? 16));
1909
+ const tw = twistDeg * Math.PI / 180;
1910
+ const rArc = Math.min(14, R * 0.35);
1911
+ const arcX2 = rArc * Math.sin(tw);
1912
+ const arcY2 = -rArc * Math.cos(tw);
1913
+ const fiberStart = 4;
1914
+ const fiberEnd = Math.max(fiberStart + 8, dist - halfT);
1915
+ const helixD = [];
1916
+ const helAmp = Math.min(2.8, halfT * 0.35);
1917
+ const helSteps = 24;
1918
+ for (let i = 0; i <= helSteps; i++) {
1919
+ const u = i / helSteps;
1920
+ const x = fiberStart + u * (fiberEnd - fiberStart);
1921
+ const y = helAmp * Math.sin(u * Math.PI * 5);
1922
+ helixD.push(i === 0 ? `M ${x} ${y}` : ` L ${x} ${y}`);
1923
+ }
1924
+ const pad = Math.max(R + halfT + 28, dist * 0.35 + 40);
1925
+ return /* @__PURE__ */ jsxs6("g", { children: [
1926
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
1927
+ /* @__PURE__ */ jsx6("rect", { x: -28, y: -14, width: 56, height: 10, rx: 2, fill: "#e2e8f0", stroke: color, strokeWidth: 1.25, pointerEvents: "none" }),
1928
+ /* @__PURE__ */ jsx6("rect", { x: -8, y: -4, width: 16, height: 8, rx: 1, fill: "#94a3b8", stroke: color, strokeWidth: 1, pointerEvents: "none" }),
1929
+ /* @__PURE__ */ jsx6("line", { x1: -22, y1: -9, x2: 22, y2: -9, stroke: "#64748b", strokeWidth: 1, pointerEvents: "none" }),
1930
+ /* @__PURE__ */ jsx6(
1931
+ "path",
1932
+ {
1933
+ d: helixD.join(""),
1934
+ fill: "none",
1935
+ stroke: color,
1936
+ strokeWidth: 1,
1937
+ strokeLinecap: "round",
1938
+ opacity: 0.35,
1939
+ pointerEvents: "none"
1940
+ }
1941
+ ),
1942
+ /* @__PURE__ */ jsx6(
1943
+ "line",
1944
+ {
1945
+ x1: fiberStart,
1946
+ y1: 0,
1947
+ x2: fiberEnd,
1948
+ y2: 0,
1949
+ stroke: "#64748b",
1950
+ strokeWidth: Math.max(1, strokeWidth * 0.45),
1951
+ strokeLinecap: "round",
1952
+ pointerEvents: "none"
1953
+ }
1954
+ ),
1955
+ /* @__PURE__ */ jsx6(
1956
+ "rect",
1957
+ {
1958
+ x: dist - halfT,
1959
+ y: -R,
1960
+ width: 2 * halfT,
1961
+ height: 2 * R,
1962
+ rx: halfT - 0.5,
1963
+ fill: "#f8fafc",
1964
+ stroke: color,
1965
+ strokeWidth,
1966
+ pointerEvents: "none"
1967
+ }
1968
+ ),
1969
+ /* @__PURE__ */ jsx6(
1970
+ "line",
1971
+ {
1972
+ x1: dist - halfT + 1,
1973
+ y1: 0,
1974
+ x2: dist + halfT - 1,
1975
+ y2: 0,
1976
+ stroke: "#cbd5e1",
1977
+ strokeWidth: 1,
1978
+ pointerEvents: "none"
1979
+ }
1980
+ ),
1981
+ /* @__PURE__ */ jsx6("circle", { cx: dist, cy: 0, r: Math.min(5, halfT - 1), fill: "#94a3b8", stroke: color, strokeWidth: 1, pointerEvents: "none" }),
1982
+ /* @__PURE__ */ jsx6(
1983
+ "line",
1984
+ {
1985
+ x1: dist,
1986
+ y1: 0,
1987
+ x2: dist,
1988
+ y2: -(R - 6),
1989
+ stroke: "#94a3b8",
1990
+ strokeWidth: 1.25,
1991
+ strokeDasharray: "3 3",
1992
+ opacity: 0.65,
1993
+ pointerEvents: "none"
1994
+ }
1995
+ ),
1996
+ /* @__PURE__ */ jsx6("g", { transform: `translate(${dist},0) rotate(${twistDeg})`, children: /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: 0, y2: -(R - 5), stroke: "#dc2626", strokeWidth: Math.max(1.5, strokeWidth * 0.75), strokeLinecap: "round", pointerEvents: "none" }) }),
1997
+ Math.abs(twistDeg) >= 2 ? /* @__PURE__ */ jsx6("g", { transform: `translate(${dist},0)`, children: /* @__PURE__ */ jsx6(
1998
+ "path",
1999
+ {
2000
+ d: `M 0 ${-rArc} A ${rArc} ${rArc} 0 0 ${twistDeg >= 0 ? 1 : 0} ${arcX2} ${arcY2}`,
2001
+ fill: "none",
2002
+ stroke: "#dc2626",
2003
+ strokeWidth: 1.25,
2004
+ pointerEvents: "none"
2005
+ }
2006
+ ) }) : null,
2007
+ el.label ? /* @__PURE__ */ jsx6("text", { x: dist, y: R + 22, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2008
+ ] }),
2009
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padX: pad, padY: pad })
2010
+ ] });
2011
+ });
2012
+ registerRenderer("vane_liquid", (el, ctx) => {
2013
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
2014
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
2015
+ const color = c3(el);
2016
+ const strokeWidth = sw3(el);
2017
+ const fontSize = font2(el);
2018
+ const beakerW = Math.max(40, dist * 0.4);
2019
+ const beakerH = Math.max(40, dist * 0.5);
2020
+ return /* @__PURE__ */ jsxs6("g", { children: [
2021
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
2022
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: dist, y2: 0, stroke: color, strokeWidth, pointerEvents: "none" }),
2023
+ /* @__PURE__ */ jsx6("line", { x1: dist, y1: -beakerW * 0.3, x2: dist, y2: beakerW * 0.3, stroke: color, strokeWidth: strokeWidth * 1.5, strokeLinecap: "round", pointerEvents: "none" }),
2024
+ /* @__PURE__ */ jsx6(
2025
+ "path",
2026
+ {
2027
+ d: `M ${dist - beakerH} ${-beakerW / 2} L ${dist + 10} ${-beakerW / 2} L ${dist + 10} ${beakerW / 2} L ${dist - beakerH} ${beakerW / 2}`,
2028
+ fill: "none",
2029
+ stroke: color,
2030
+ strokeWidth: 2,
2031
+ strokeLinecap: "round",
2032
+ strokeLinejoin: "round",
2033
+ pointerEvents: "none"
2034
+ }
2035
+ ),
2036
+ /* @__PURE__ */ jsx6(
2037
+ "rect",
2038
+ {
2039
+ x: dist - beakerH + 2,
2040
+ y: -beakerW / 2 + 2,
2041
+ width: beakerH + 8,
2042
+ height: beakerW - 4,
2043
+ fill: "#38bdf8",
2044
+ fillOpacity: 0.3,
2045
+ pointerEvents: "none"
2046
+ }
2047
+ ),
2048
+ el.label ? /* @__PURE__ */ jsx6("text", { x: dist / 2, y: -beakerW / 2 - 15, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2049
+ ] }),
2050
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padX: beakerH + 40, padY: beakerW + 40 })
2051
+ ] });
2052
+ });
2053
+ registerRenderer("wave_pulse", (el, ctx) => {
2054
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
2055
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
2056
+ const amp = el.amplitude || 50;
2057
+ const color = c3(el);
2058
+ const strokeWidth = sw3(el);
2059
+ const fontSize = font2(el);
2060
+ const mid = dist / 2;
2061
+ const spread = dist / 6;
2062
+ let d = "M 0 0";
2063
+ const step = Math.max(1, dist / 50);
2064
+ for (let i = 0; i <= dist; i += step) {
2065
+ const y = amp * Math.exp(-Math.pow(i - mid, 2) / (2 * spread * spread));
2066
+ d += ` L ${i} ${-y}`;
2067
+ }
2068
+ return /* @__PURE__ */ jsxs6("g", { children: [
2069
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
2070
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: dist, y2: 0, stroke: color, strokeWidth, strokeLinecap: "round", pointerEvents: "none" }),
2071
+ /* @__PURE__ */ jsx6(LocalFatPathHit, { dPath: d, strokeWidth: 22, strokeLinejoin: "round" }),
2072
+ /* @__PURE__ */ jsx6("path", { d, fill: color, fillOpacity: 0.1, stroke: color, strokeWidth, strokeLinejoin: "round", pointerEvents: "none" }),
2073
+ el.label ? /* @__PURE__ */ jsx6("text", { x: mid, y: -amp - 15, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2074
+ ] }),
2075
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padY: amp + 28 })
2076
+ ] });
2077
+ });
2078
+ registerRenderer("damped_wave", (el, ctx) => {
2079
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
2080
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
2081
+ const amp = el.amplitude || 50;
2082
+ const damp = el.damping || 0.015;
2083
+ const freq = el.frequency || 10;
2084
+ const color = c3(el);
2085
+ const strokeWidth = sw3(el);
2086
+ const fontSize = font2(el);
2087
+ let d = `M 0 ${-amp}`;
2088
+ let env1 = `M 0 ${-amp}`;
2089
+ let env2 = `M 0 ${amp}`;
2090
+ const step = Math.max(1, dist / 200);
2091
+ for (let i = 0; i <= dist; i += step) {
2092
+ const envY = amp * Math.exp(-damp * i);
2093
+ const y = envY * Math.cos(i / dist * freq * 2 * Math.PI);
2094
+ d += ` L ${i} ${-y}`;
2095
+ env1 += ` L ${i} ${-envY}`;
2096
+ env2 += ` L ${i} ${envY}`;
2097
+ }
2098
+ return /* @__PURE__ */ jsxs6("g", { children: [
2099
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
2100
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: dist, y2: 0, stroke: color, strokeWidth: 1, opacity: 0.3, pointerEvents: "none" }),
2101
+ /* @__PURE__ */ jsx6("path", { d: env1, fill: "none", stroke: color, strokeWidth: 1, strokeDasharray: "4 4", opacity: 0.5, pointerEvents: "none" }),
2102
+ /* @__PURE__ */ jsx6("path", { d: env2, fill: "none", stroke: color, strokeWidth: 1, strokeDasharray: "4 4", opacity: 0.5, pointerEvents: "none" }),
2103
+ /* @__PURE__ */ jsx6(LocalFatPathHit, { dPath: d, strokeWidth: 22, strokeLinejoin: "round" }),
2104
+ /* @__PURE__ */ jsx6("path", { d, fill: "none", stroke: color, strokeWidth, strokeLinejoin: "round", pointerEvents: "none" }),
2105
+ el.label ? /* @__PURE__ */ jsx6("text", { x: dist / 2, y: -amp - 15, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2106
+ ] }),
2107
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padY: amp + 28 })
2108
+ ] });
2109
+ });
2110
+ registerRenderer("beats_graph", (el, ctx) => {
2111
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
2112
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
2113
+ const amp = el.amplitude || 30;
2114
+ const freq = el.frequency || 20;
2115
+ const beatF = el.beatFreq || 2;
2116
+ const color = c3(el);
2117
+ const strokeWidth = sw3(el);
2118
+ const fontSize = font2(el);
2119
+ let d = "M 0 0";
2120
+ let env1 = "M 0 0";
2121
+ let env2 = "M 0 0";
2122
+ const step = Math.max(1, dist / 200);
2123
+ for (let i = 0; i <= dist; i += step) {
2124
+ const env = amp * Math.cos(i / dist * beatF * Math.PI);
2125
+ const y = env * Math.sin(i / dist * freq * 2 * Math.PI);
2126
+ d += ` L ${i} ${-y}`;
2127
+ env1 += ` L ${i} ${-Math.abs(env)}`;
2128
+ env2 += ` L ${i} ${Math.abs(env)}`;
2129
+ }
2130
+ return /* @__PURE__ */ jsxs6("g", { children: [
2131
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
2132
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: dist, y2: 0, stroke: color, strokeWidth: 1, opacity: 0.3, pointerEvents: "none" }),
2133
+ /* @__PURE__ */ jsx6("path", { d: env1, fill: "none", stroke: color, strokeWidth: 1, strokeDasharray: "2 4", opacity: 0.5, pointerEvents: "none" }),
2134
+ /* @__PURE__ */ jsx6("path", { d: env2, fill: "none", stroke: color, strokeWidth: 1, strokeDasharray: "2 4", opacity: 0.5, pointerEvents: "none" }),
2135
+ /* @__PURE__ */ jsx6(LocalFatPathHit, { dPath: d, strokeWidth: 22, strokeLinejoin: "round" }),
2136
+ /* @__PURE__ */ jsx6("path", { d, fill: "none", stroke: color, strokeWidth, strokeLinejoin: "round", pointerEvents: "none" }),
2137
+ el.label ? /* @__PURE__ */ jsx6("text", { x: dist / 2, y: -amp - 20, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2138
+ ] }),
2139
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padY: amp + 28 })
2140
+ ] });
2141
+ });
2142
+ registerRenderer("mach_cone", (el, ctx) => {
2143
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
2144
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
2145
+ const M = el.machNumber || 2;
2146
+ const coneAngle = Math.asin(1 / Math.max(1.01, M));
2147
+ const h = dist;
2148
+ const r = h * Math.tan(coneAngle);
2149
+ const color = c3(el);
2150
+ const strokeWidth = sw3(el);
2151
+ const fontSize = font2(el);
2152
+ return /* @__PURE__ */ jsxs6("g", { children: [
2153
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
2154
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: h, y2: 0, stroke: color, strokeWidth: 1, strokeDasharray: "4 4", pointerEvents: "none" }),
2155
+ /* @__PURE__ */ jsx6("line", { x1: h, y1: 0, x2: 0, y2: -r, stroke: color, strokeWidth, pointerEvents: "none" }),
2156
+ /* @__PURE__ */ jsx6("line", { x1: h, y1: 0, x2: 0, y2: r, stroke: color, strokeWidth, pointerEvents: "none" }),
2157
+ /* @__PURE__ */ jsx6("circle", { cx: h, cy: 0, r: 4, fill: color, pointerEvents: "none" }),
2158
+ [0.2, 0.5, 0.8].map((t) => /* @__PURE__ */ jsx6("circle", { cx: h * t, cy: 0, r: (1 - t) * r, fill: "none", stroke: color, strokeWidth: 1, opacity: 0.6, pointerEvents: "none" }, t)),
2159
+ el.label ? /* @__PURE__ */ jsx6("text", { x: h / 2, y: r + 20, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2160
+ ] }),
2161
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padY: r + 35 })
2162
+ ] });
2163
+ });
2164
+ registerRenderer("wall", (el, ctx) => {
2165
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
2166
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
2167
+ const color = c3(el);
2168
+ const strokeWidth = sw3(el);
2169
+ const fontSize = font2(el);
2170
+ const hashes = [];
2171
+ const numHashes = Math.max(2, Math.floor(dist / 10));
2172
+ for (let j = 0; j <= numHashes; j++) {
2173
+ const i = j / numHashes * dist;
2174
+ hashes.push(/* @__PURE__ */ jsx6("line", { x1: i, y1: 0, x2: i - 8, y2: -12, stroke: color, strokeWidth: 1, pointerEvents: "none" }, j));
2175
+ }
2176
+ return /* @__PURE__ */ jsxs6("g", { children: [
2177
+ /* @__PURE__ */ jsxs6("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
2178
+ /* @__PURE__ */ jsx6("line", { x1: 0, y1: 0, x2: dist, y2: 0, stroke: color, strokeWidth, pointerEvents: "none" }),
2179
+ hashes,
2180
+ el.label ? /* @__PURE__ */ jsx6("text", { x: dist / 2, y: 20, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2181
+ ] }),
2182
+ /* @__PURE__ */ jsx6(ChordInteractionOverlay, { el, ctx, padY: 35 })
2183
+ ] });
2184
+ });
2185
+
2186
+ // src/core/renderers/rotation.tsx
2187
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2188
+ var c4 = (el) => el.color || "#0f172a";
2189
+ var sw4 = (el) => el.strokeWidth || 2;
2190
+ var font3 = (el) => el.fontSize || 18;
2191
+ function generateSpringPath(dist, coils, width2 = 20) {
2192
+ const ccount = Math.max(3, coils ?? Math.floor(dist / 20));
2193
+ const step = dist / ccount;
2194
+ let d = `M 0 0 L ${step * 0.2} 0 `;
2195
+ for (let i = 0; i < ccount; i++) {
2196
+ const startX = i * step;
2197
+ if (i === ccount - 1) {
2198
+ d += `L ${startX + step * 0.25} ${-width2 / 2} L ${startX + step * 0.75} ${width2 / 2} L ${dist} 0 `;
2199
+ } else {
2200
+ d += `L ${startX + step * 0.25} ${-width2 / 2} L ${startX + step * 0.75} ${width2 / 2} L ${startX + step} 0 `;
2201
+ }
2202
+ }
2203
+ return d;
2204
+ }
2205
+ registerRenderer("point_mass", (el, ctx) => {
2206
+ const size = el.size ?? 15;
2207
+ const plate = Math.max(36, size + 22);
2208
+ return /* @__PURE__ */ jsx7("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs7(OriginHitPlate, { ctx, hitHalfSize: plate / 2, selectionHalfSize: plate / 2 + 8, children: [
2209
+ /* @__PURE__ */ jsx7("circle", { cx: 0, cy: 0, r: size, fill: c4(el), fillOpacity: 0.8, stroke: c4(el), strokeWidth: 2 }),
2210
+ /* @__PURE__ */ jsx7("circle", { cx: 0, cy: 0, r: size * 0.3, fill: "#fff", fillOpacity: 0.5 }),
2211
+ el.label ? /* @__PURE__ */ jsx7("text", { x: 0, y: size + 15, fontSize: font3(el), fill: c4(el), textAnchor: "middle", fontWeight: "bold", pointerEvents: "none", children: String(el.label) }) : null
2212
+ ] }) });
2213
+ });
2214
+ registerRenderer("com_indicator", (el, ctx) => {
2215
+ const size = el.size ?? 15;
2216
+ const plate = Math.max(40, size * 2 + 12);
2217
+ return /* @__PURE__ */ jsx7("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs7(OriginHitPlate, { ctx, hitHalfSize: plate / 2, selectionHalfSize: plate / 2 + 10, children: [
2218
+ /* @__PURE__ */ jsx7("circle", { cx: 0, cy: 0, r: size, fill: "none", stroke: c4(el), strokeWidth: sw4(el) }),
2219
+ /* @__PURE__ */ jsx7("line", { x1: -size * 1.3, y1: 0, x2: size * 1.3, y2: 0, stroke: c4(el), strokeWidth: sw4(el), pointerEvents: "none" }),
2220
+ /* @__PURE__ */ jsx7("line", { x1: 0, y1: -size * 1.3, x2: 0, y2: size * 1.3, stroke: c4(el), strokeWidth: sw4(el), pointerEvents: "none" }),
2221
+ /* @__PURE__ */ jsx7("path", { d: `M 0 0 L ${size} 0 A ${size} ${size} 0 0 0 0 ${-size} Z`, fill: c4(el), fillOpacity: 0.5, pointerEvents: "none" }),
2222
+ /* @__PURE__ */ jsx7("path", { d: `M 0 0 L ${-size} 0 A ${size} ${size} 0 0 0 0 ${size} Z`, fill: c4(el), fillOpacity: 0.5, pointerEvents: "none" }),
2223
+ el.label ? /* @__PURE__ */ jsx7("text", { x: 0, y: size + 15, fontSize: font3(el), fill: c4(el), textAnchor: "middle", fontWeight: "bold", pointerEvents: "none", children: String(el.label) }) : null
2224
+ ] }) });
2225
+ });
2226
+ registerRenderer("pivot", (el, ctx) => {
2227
+ const size = el.size ?? 20;
2228
+ const plate = Math.max(44, size * 2 + 24);
2229
+ return /* @__PURE__ */ jsx7("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs7(OriginHitPlate, { ctx, hitHalfSize: plate / 2, selectionHalfSize: plate / 2 + 10, children: [
2230
+ /* @__PURE__ */ jsx7(
2231
+ "polygon",
2232
+ {
2233
+ points: `0,0 ${-size},${size * 1.5} ${size},${size * 1.5}`,
2234
+ fill: "none",
2235
+ stroke: c4(el),
2236
+ strokeWidth: sw4(el),
2237
+ pointerEvents: "none"
2238
+ }
2239
+ ),
2240
+ /* @__PURE__ */ jsx7(
2241
+ "line",
2242
+ {
2243
+ x1: -size * 1.5,
2244
+ y1: size * 1.5,
2245
+ x2: size * 1.5,
2246
+ y2: size * 1.5,
2247
+ stroke: c4(el),
2248
+ strokeWidth: sw4(el) + 1,
2249
+ strokeLinecap: "round",
2250
+ pointerEvents: "none"
2251
+ }
2252
+ ),
2253
+ /* @__PURE__ */ jsx7("circle", { cx: 0, cy: 0, r: sw4(el) * 1.5, fill: c4(el), pointerEvents: "none" }),
2254
+ Array.from({ length: 5 }, (_, i) => /* @__PURE__ */ jsx7(
2255
+ "line",
2256
+ {
2257
+ x1: -size + i * size * 0.5,
2258
+ y1: size * 1.5,
2259
+ x2: -size + i * size * 0.5 - 5,
2260
+ y2: size * 1.5 + 8,
2261
+ stroke: c4(el),
2262
+ strokeWidth: 1,
2263
+ pointerEvents: "none"
2264
+ },
2265
+ i
2266
+ )),
2267
+ el.label ? /* @__PURE__ */ jsx7("text", { x: 0, y: size * 1.5 + 20, fontSize: font3(el), fill: c4(el), textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2268
+ ] }) });
2269
+ });
2270
+ registerRenderer("block_mass", (el, ctx) => {
2271
+ const minX = Math.min(el.x1, el.x2);
2272
+ const minY = Math.min(el.y1, el.y2);
2273
+ const w = Math.max(1, Math.abs(el.x2 - el.x1));
2274
+ const h = Math.max(1, Math.abs(el.y2 - el.y1));
2275
+ return /* @__PURE__ */ jsxs7("g", { children: [
2276
+ /* @__PURE__ */ jsxs7("g", { transform: `translate(${minX}, ${minY})`, children: [
2277
+ /* @__PURE__ */ jsx7("rect", { x: 0, y: 0, width: w, height: h, fill: c4(el), fillOpacity: 0.2, stroke: c4(el), strokeWidth: sw4(el), rx: 2 }),
2278
+ el.label ? /* @__PURE__ */ jsx7("text", { x: w / 2, y: h / 2, dominantBaseline: "central", textAnchor: "middle", fontSize: font3(el), fill: c4(el), fontWeight: "bold", pointerEvents: "none", children: String(el.label) }) : null
2279
+ ] }),
2280
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
2281
+ /* @__PURE__ */ jsx7(ChordInteractionOverlay, { el, ctx })
2282
+ ] });
2283
+ });
2284
+ registerRenderer("system_boundary", (el, ctx) => {
2285
+ const minX = Math.min(el.x1, el.x2);
2286
+ const minY = Math.min(el.y1, el.y2);
2287
+ const w = Math.max(1, Math.abs(el.x2 - el.x1));
2288
+ const h = Math.max(1, Math.abs(el.y2 - el.y1));
2289
+ return /* @__PURE__ */ jsxs7("g", { children: [
2290
+ /* @__PURE__ */ jsxs7("g", { transform: `translate(${minX}, ${minY})`, children: [
2291
+ /* @__PURE__ */ jsx7("rect", { x: 0, y: 0, width: w, height: h, fill: "none", stroke: c4(el), strokeWidth: sw4(el), strokeDasharray: "8 6", rx: 15 }),
2292
+ el.label ? /* @__PURE__ */ jsx7("text", { x: w / 2, y: -10, fontSize: font3(el), fill: c4(el), textAnchor: "middle", fontStyle: "italic", pointerEvents: "none", children: String(el.label) }) : null
2293
+ ] }),
2294
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
2295
+ /* @__PURE__ */ jsx7(ChordInteractionOverlay, { el, ctx })
2296
+ ] });
2297
+ });
2298
+ registerRenderer("rocket", (el, ctx) => {
2299
+ const dx = el.x2 - el.x1;
2300
+ const dy = el.y2 - el.y1;
2301
+ const dist = Math.hypot(dx, dy);
2302
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
2303
+ const minX = Math.min(el.x1, el.x2);
2304
+ const maxX = Math.max(el.x1, el.x2);
2305
+ const w = Math.max(1, maxX - minX);
2306
+ return /* @__PURE__ */ jsxs7("g", { children: [
2307
+ /* @__PURE__ */ jsxs7("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
2308
+ /* @__PURE__ */ jsx7(
2309
+ "path",
2310
+ {
2311
+ d: `M 0 ${-w / 4} L ${dist * 0.7} ${-w / 4} L ${dist} 0 L ${dist * 0.7} ${w / 4} L 0 ${w / 4} Z`,
2312
+ fill: "none",
2313
+ stroke: c4(el),
2314
+ strokeWidth: sw4(el),
2315
+ pointerEvents: "none"
2316
+ }
2317
+ ),
2318
+ /* @__PURE__ */ jsx7("polygon", { points: `0,${-w / 4} ${dist * 0.2},${-w / 4} 0,${-w / 2}`, fill: c4(el), fillOpacity: 0.8, pointerEvents: "none" }),
2319
+ /* @__PURE__ */ jsx7("polygon", { points: `0,${w / 4} ${dist * 0.2},${w / 4} 0,${w / 2}`, fill: c4(el), fillOpacity: 0.8, pointerEvents: "none" }),
2320
+ /* @__PURE__ */ jsx7("path", { d: `M 0 ${-w / 6} Q ${-dist * 0.3} 0 0 ${w / 6}`, fill: "#f97316", fillOpacity: 0.8, pointerEvents: "none" }),
2321
+ /* @__PURE__ */ jsx7("path", { d: `M 0 ${-w / 8} Q ${-dist * 0.5} 0 0 ${w / 8}`, fill: "#ef4444", fillOpacity: 0.6, pointerEvents: "none" }),
2322
+ el.label ? /* @__PURE__ */ jsx7("text", { x: dist / 2, y: -w / 2 - 10, fontSize: font3(el), fill: c4(el), textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2323
+ ] }),
2324
+ /* @__PURE__ */ jsx7(ChordInteractionOverlay, { el, ctx })
2325
+ ] });
2326
+ });
2327
+ registerRenderer("dashed_path", (el, ctx) => {
2328
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
2329
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
2330
+ return /* @__PURE__ */ jsxs7("g", { children: [
2331
+ /* @__PURE__ */ jsxs7("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
2332
+ /* @__PURE__ */ jsx7("line", { x1: 0, y1: 0, x2: d, y2: 0, stroke: c4(el), strokeWidth: sw4(el), strokeLinecap: "round", strokeDasharray: "8 6", pointerEvents: "none" }),
2333
+ el.label ? /* @__PURE__ */ jsx7("text", { x: d / 2, y: -10, fontSize: font3(el), fill: c4(el), textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2334
+ ] }),
2335
+ /* @__PURE__ */ jsx7(ChordInteractionOverlay, { el, ctx })
2336
+ ] });
2337
+ });
2338
+ registerRenderer("uniform_rod", (el, ctx) => {
2339
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
2340
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
2341
+ const strokeWidth = sw4(el);
2342
+ return /* @__PURE__ */ jsxs7("g", { children: [
2343
+ /* @__PURE__ */ jsxs7("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
2344
+ /* @__PURE__ */ jsx7("rect", { x: 0, y: -strokeWidth / 2, width: dist, height: strokeWidth, fill: c4(el), rx: strokeWidth / 4, pointerEvents: "none" }),
2345
+ /* @__PURE__ */ jsx7("circle", { cx: dist / 2, cy: 0, r: Math.max(2, strokeWidth / 4), fill: "#fff", pointerEvents: "none" }),
2346
+ el.label ? /* @__PURE__ */ jsx7("text", { x: dist / 2, y: -strokeWidth / 2 - 10, fontSize: font3(el), fill: c4(el), textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2347
+ ] }),
2348
+ /* @__PURE__ */ jsx7(ChordInteractionOverlay, { el, ctx })
2349
+ ] });
2350
+ });
2351
+ registerRenderer("solid_disk", (el, ctx) => {
2352
+ const dx = el.x2 - el.x1;
2353
+ const dy = el.y2 - el.y1;
2354
+ const radius = Math.hypot(dx, dy);
2355
+ return /* @__PURE__ */ jsxs7("g", { children: [
2356
+ /* @__PURE__ */ jsxs7("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
2357
+ /* @__PURE__ */ jsx7("circle", { cx: 0, cy: 0, r: radius, fill: c4(el), fillOpacity: 0.4, stroke: c4(el), strokeWidth: sw4(el), pointerEvents: "none" }),
2358
+ el.label ? /* @__PURE__ */ jsx7("text", { x: 0, y: radius + 20, fontSize: font3(el), fill: c4(el), textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2359
+ ] }),
2360
+ /* @__PURE__ */ jsx7(ChordInteractionOverlay, { el, ctx })
2361
+ ] });
2362
+ });
2363
+ registerRenderer("hoop_ring", (el, ctx) => {
2364
+ const dx = el.x2 - el.x1;
2365
+ const dy = el.y2 - el.y1;
2366
+ const radius = Math.hypot(dx, dy);
2367
+ return /* @__PURE__ */ jsxs7("g", { children: [
2368
+ /* @__PURE__ */ jsxs7("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
2369
+ /* @__PURE__ */ jsx7("circle", { cx: 0, cy: 0, r: radius, fill: "none", stroke: c4(el), strokeWidth: Math.max(4, sw4(el) * 2), pointerEvents: "none" }),
2370
+ el.label ? /* @__PURE__ */ jsx7("text", { x: 0, y: radius + 20, fontSize: font3(el), fill: c4(el), textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2371
+ ] }),
2372
+ /* @__PURE__ */ jsx7(ChordInteractionOverlay, { el, ctx })
2373
+ ] });
2374
+ });
2375
+ registerRenderer("rolling_body", (el, ctx) => {
2376
+ const dx = el.x2 - el.x1;
2377
+ const dy = el.y2 - el.y1;
2378
+ const radius = Math.hypot(dx, dy);
2379
+ return /* @__PURE__ */ jsxs7("g", { children: [
2380
+ /* @__PURE__ */ jsxs7("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
2381
+ /* @__PURE__ */ jsx7("circle", { cx: 0, cy: 0, r: radius, fill: c4(el), fillOpacity: 0.1, stroke: c4(el), strokeWidth: sw4(el), pointerEvents: "none" }),
2382
+ /* @__PURE__ */ jsx7("line", { x1: -radius, y1: 0, x2: radius, y2: 0, stroke: c4(el), strokeWidth: 1, strokeDasharray: "4 4", pointerEvents: "none" }),
2383
+ /* @__PURE__ */ jsx7("line", { x1: 0, y1: -radius, x2: 0, y2: radius, stroke: c4(el), strokeWidth: 1, strokeDasharray: "4 4", pointerEvents: "none" }),
2384
+ /* @__PURE__ */ jsx7("circle", { cx: 0, cy: 0, r: 3, fill: c4(el), pointerEvents: "none" }),
2385
+ el.showVelocity ? /* @__PURE__ */ jsxs7("g", { pointerEvents: "none", children: [
2386
+ /* @__PURE__ */ jsx7("line", { x1: 0, y1: 0, x2: radius * 1.5, y2: 0, stroke: "#ef4444", strokeWidth: 2 }),
2387
+ /* @__PURE__ */ jsx7("polygon", { points: `${radius * 1.5},0 ${radius * 1.5 - 8},-4 ${radius * 1.5 - 8},4`, fill: "#ef4444" }),
2388
+ /* @__PURE__ */ jsx7("text", { x: radius * 1.5 + 10, y: 5, fill: "#ef4444", fontSize: 14, fontStyle: "italic", children: "v" })
2389
+ ] }) : null,
2390
+ el.showOmega ? /* @__PURE__ */ jsxs7("g", { transform: `translate(0, ${-radius * 1.2})`, pointerEvents: "none", children: [
2391
+ /* @__PURE__ */ jsx7("path", { d: `M ${-radius * 0.4} 0 A ${radius * 0.4} ${radius * 0.4} 0 0 1 ${radius * 0.4} 0`, fill: "none", stroke: "#10b981", strokeWidth: 2 }),
2392
+ /* @__PURE__ */ jsx7("polygon", { points: `${radius * 0.4},0 ${radius * 0.4 - 4},-6 ${radius * 0.4 + 4},-6`, fill: "#10b981" }),
2393
+ /* @__PURE__ */ jsx7("text", { x: 0, y: -10, fill: "#10b981", fontSize: 14, fontStyle: "italic", textAnchor: "middle", children: "\u03C9" })
2394
+ ] }) : null,
2395
+ el.label ? /* @__PURE__ */ jsx7("text", { x: 0, y: radius + 20, fontSize: font3(el), fill: c4(el), textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2396
+ ] }),
2397
+ /* @__PURE__ */ jsx7(ChordInteractionOverlay, { el, ctx })
2398
+ ] });
2399
+ });
2400
+ registerRenderer("inclined_wedge", (el, ctx) => {
2401
+ const { x1, y1, x2, y2 } = el;
2402
+ const dx = x2 - x1;
2403
+ const dy = y2 - y1;
2404
+ const angle = Math.atan2(dy, dx) * (180 / Math.PI);
2405
+ const b = getBounds(x1, y1, x2, y2);
2406
+ const triMinX = Math.min(x1, x2);
2407
+ const triMinY = Math.min(y1, y2);
2408
+ const triW = b.width;
2409
+ const triH = b.height;
2410
+ return /* @__PURE__ */ jsxs7("g", { children: [
2411
+ /* @__PURE__ */ jsx7(
2412
+ "polygon",
2413
+ {
2414
+ points: `${x1},${y1} ${x1},${y2} ${x2},${y2}`,
2415
+ fill: c4(el),
2416
+ fillOpacity: 0.1,
2417
+ stroke: c4(el),
2418
+ strokeWidth: sw4(el),
2419
+ strokeLinejoin: "round",
2420
+ pointerEvents: "none"
2421
+ }
2422
+ ),
2423
+ el.label ? /* @__PURE__ */ jsx7("text", { x: (x1 + x2) / 2, y: y2 - 10, fontSize: font3(el), fill: c4(el), textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null,
2424
+ Math.abs(x2 - x1) > 20 && Math.abs(y2 - y1) > 20 ? /* @__PURE__ */ jsx7(
2425
+ "path",
2426
+ {
2427
+ d: `M ${x2 > x1 ? x2 - 20 : x2 + 20} ${y2} A 20 20 0 0 ${x2 > x1 ? 0 : 1} ${x2 > x1 ? x2 - 20 * Math.cos(angle * Math.PI / 180) : x2 + 20 * Math.cos(angle * Math.PI / 180)} ${y2 - 20 * Math.sin(Math.abs(angle) * Math.PI / 180)}`,
2428
+ fill: "none",
2429
+ stroke: c4(el),
2430
+ strokeWidth: 1,
2431
+ pointerEvents: "none"
2432
+ }
2433
+ ) : null,
2434
+ /* @__PURE__ */ jsx7(BBoxInteractionOverlay, { minX: triMinX, minY: triMinY, width: triW, height: triH, ctx }),
2435
+ /* @__PURE__ */ jsx7(ChordInteractionOverlay, { el, ctx })
2436
+ ] });
2437
+ });
2438
+ registerRenderer("pulley", (el, ctx) => {
2439
+ const dx = el.x2 - el.x1;
2440
+ const dy = el.y2 - el.y1;
2441
+ const radius = Math.hypot(dx, dy);
2442
+ return /* @__PURE__ */ jsxs7("g", { children: [
2443
+ /* @__PURE__ */ jsxs7("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
2444
+ /* @__PURE__ */ jsx7("path", { d: `M 0 0 L 0 ${-radius * 1.5}`, fill: "none", stroke: c4(el), strokeWidth: sw4(el), pointerEvents: "none" }),
2445
+ /* @__PURE__ */ jsx7("line", { x1: -radius * 0.8, y1: -radius * 1.5, x2: radius * 0.8, y2: -radius * 1.5, stroke: c4(el), strokeWidth: sw4(el), pointerEvents: "none" }),
2446
+ Array.from({ length: 5 }, (_, i) => /* @__PURE__ */ jsx7(
2447
+ "line",
2448
+ {
2449
+ x1: -radius * 0.8 + i * radius * 0.4,
2450
+ y1: -radius * 1.5,
2451
+ x2: -radius * 0.8 + i * radius * 0.4 + 5,
2452
+ y2: -radius * 1.5 - 5,
2453
+ stroke: c4(el),
2454
+ strokeWidth: 1,
2455
+ pointerEvents: "none"
2456
+ },
2457
+ i
2458
+ )),
2459
+ /* @__PURE__ */ jsx7("circle", { cx: 0, cy: 0, r: radius, fill: "#fff", stroke: c4(el), strokeWidth: sw4(el), pointerEvents: "none" }),
2460
+ /* @__PURE__ */ jsx7("circle", { cx: 0, cy: 0, r: radius * 0.8, fill: "none", stroke: c4(el), strokeWidth: 1, opacity: 0.3, pointerEvents: "none" }),
2461
+ /* @__PURE__ */ jsx7("circle", { cx: 0, cy: 0, r: 3, fill: c4(el), pointerEvents: "none" }),
2462
+ el.label ? /* @__PURE__ */ jsx7("text", { x: 0, y: -radius * 1.5 - 15, fontSize: font3(el), fill: c4(el), textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2463
+ ] }),
2464
+ /* @__PURE__ */ jsx7(ChordInteractionOverlay, { el, ctx })
2465
+ ] });
2466
+ });
2467
+ registerRenderer("curve_arrow", (el, ctx) => {
2468
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
2469
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
2470
+ const curvePath = `M 0 0 Q ${dist / 2} ${-dist * 0.5} ${dist} 0`;
2471
+ const bb = quadraticChordWorldBounds(el.x1, el.y1, el.x2, el.y2, dist / 4);
2472
+ return /* @__PURE__ */ jsxs7("g", { children: [
2473
+ /* @__PURE__ */ jsxs7("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
2474
+ /* @__PURE__ */ jsx7("path", { d: curvePath, fill: "none", stroke: c4(el), strokeWidth: sw4(el), strokeLinecap: "round", pointerEvents: "none" }),
2475
+ /* @__PURE__ */ jsx7(LocalFatPathHit, { dPath: curvePath, strokeWidth: 24, strokeLinecap: "round" }),
2476
+ /* @__PURE__ */ jsx7("polygon", { points: `${dist},0 ${dist - 8},-6 ${dist - 4},-1`, fill: c4(el), transform: `rotate(20 ${dist} 0)`, pointerEvents: "none" }),
2477
+ el.label ? /* @__PURE__ */ jsx7("text", { x: dist / 2, y: -dist * 0.3, fontSize: font3(el), fill: c4(el), textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2478
+ ] }),
2479
+ ctx.isSelected && !ctx.viewMode && !ctx.isGhost ? selectionDashRect(bb.x, bb.y, bb.width, bb.height) : null,
2480
+ endpointHandles(el, ctx)
2481
+ ] });
2482
+ });
2483
+ registerRenderer("spring", (el, ctx) => {
2484
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
2485
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
2486
+ const pathD = generateSpringPath(dist, el.coils);
2487
+ return /* @__PURE__ */ jsxs7("g", { children: [
2488
+ /* @__PURE__ */ jsxs7("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
2489
+ /* @__PURE__ */ jsx7(LocalFatPathHit, { dPath: pathD, strokeWidth: 26, strokeLinecap: "round", strokeLinejoin: "round" }),
2490
+ /* @__PURE__ */ jsx7(
2491
+ "path",
2492
+ {
2493
+ d: pathD,
2494
+ fill: "none",
2495
+ stroke: c4(el),
2496
+ strokeWidth: sw4(el),
2497
+ strokeLinecap: "round",
2498
+ strokeLinejoin: "round",
2499
+ pointerEvents: "none"
2500
+ }
2501
+ ),
2502
+ el.label ? /* @__PURE__ */ jsx7("text", { x: dist / 2, y: -20, fontSize: font3(el), fill: c4(el), textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2503
+ ] }),
2504
+ /* @__PURE__ */ jsx7(ChordInteractionOverlay, { el, ctx })
2505
+ ] });
2506
+ });
2507
+
2508
+ // src/core/renderers/circuit.tsx
2509
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2510
+ var drawColor = (el, ctx) => ctx?.isSelected ? "#2563eb" : el.color || "#111827";
2511
+ var drawStroke = (el, ctx) => ctx?.isSelected ? 2.5 : el.strokeWidth || 2;
2512
+ registerRenderer("wire", (el, ctx) => /* @__PURE__ */ jsxs8("g", { opacity: ctx?.isGhost ? 0.5 : 1, children: [
2513
+ /* @__PURE__ */ jsx8(
2514
+ "line",
2515
+ {
2516
+ x1: el.x1,
2517
+ y1: el.y1,
2518
+ x2: el.x2,
2519
+ y2: el.y2,
2520
+ stroke: drawColor(el, ctx),
2521
+ strokeWidth: drawStroke(el, ctx),
2522
+ strokeLinecap: "round",
2523
+ pointerEvents: "none"
2524
+ }
2525
+ ),
2526
+ /* @__PURE__ */ jsx8(ChordInteractionOverlay, { el, ctx, hitWidth: 20 })
2527
+ ] }));
2528
+ var rotated = (el, inner, ctx) => {
2529
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
2530
+ const a = angleDeg(el.x1, el.y1, el.x2, el.y2);
2531
+ const dx = el.x2 - el.x1;
2532
+ const dy = el.y2 - el.y1;
2533
+ const mx = (el.x1 + el.x2) / 2;
2534
+ const my = (el.y1 + el.y2) / 2;
2535
+ let nx = -dy / (d || 1);
2536
+ let ny = dx / (d || 1);
2537
+ if (ny > 0) {
2538
+ nx = -nx;
2539
+ ny = -ny;
2540
+ }
2541
+ const labelDist = 20;
2542
+ return /* @__PURE__ */ jsxs8("g", { opacity: ctx?.isGhost ? 0.5 : 1, children: [
2543
+ /* @__PURE__ */ jsx8("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${a})`, children: /* @__PURE__ */ jsx8("g", { children: inner }) }),
2544
+ el.label ? /* @__PURE__ */ jsx8(
2545
+ "text",
2546
+ {
2547
+ x: mx + nx * labelDist,
2548
+ y: my + ny * labelDist,
2549
+ dominantBaseline: "central",
2550
+ textAnchor: "middle",
2551
+ fill: drawColor(el, ctx),
2552
+ fontSize: "16",
2553
+ fontFamily: "serif",
2554
+ fontWeight: "bold",
2555
+ pointerEvents: "none",
2556
+ children: String(el.label)
2557
+ }
2558
+ ) : null,
2559
+ el.value ? /* @__PURE__ */ jsx8(
2560
+ "text",
2561
+ {
2562
+ x: mx - nx * labelDist,
2563
+ y: my - ny * labelDist,
2564
+ dominantBaseline: "central",
2565
+ textAnchor: "middle",
2566
+ fill: "#4b5563",
2567
+ fontSize: "14",
2568
+ fontFamily: "sans-serif",
2569
+ pointerEvents: "none",
2570
+ children: String(el.value)
2571
+ }
2572
+ ) : null,
2573
+ /* @__PURE__ */ jsx8(ChordInteractionOverlay, { el, ctx, hitWidth: 20 })
2574
+ ] });
2575
+ };
2576
+ registerRenderer("resistor", (el, ctx) => {
2577
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
2578
+ const start = d / 2 - 15;
2579
+ return rotated(el, /* @__PURE__ */ jsx8("path", { d: `M 0 0 L ${start} 0 l 2.5 -8 l 5 16 l 5 -16 l 5 16 l 5 -16 l 5 16 l 2.5 -8 L ${d} 0`, fill: "none", stroke: drawColor(el, ctx), strokeWidth: drawStroke(el, ctx), strokeLinejoin: "bevel" }), ctx);
2580
+ });
2581
+ registerRenderer("capacitor", (el, ctx) => {
2582
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
2583
+ const cx = d / 2;
2584
+ return rotated(el, /* @__PURE__ */ jsx8("path", { d: `M 0 0 L ${cx - 4} 0 M ${cx - 4} -12 L ${cx - 4} 12 M ${cx + 4} -12 L ${cx + 4} 12 M ${cx + 4} 0 L ${d} 0`, fill: "none", stroke: drawColor(el, ctx), strokeWidth: drawStroke(el, ctx) }), ctx);
2585
+ });
2586
+ registerRenderer("inductor", (el, ctx) => {
2587
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
2588
+ const s = d / 2 - 15;
2589
+ return rotated(el, /* @__PURE__ */ jsx8("path", { d: `M 0 0 L ${s} 0 A 5 5 0 0 0 ${s + 10} 0 A 5 5 0 0 0 ${s + 20} 0 A 5 5 0 0 0 ${s + 30} 0 L ${d} 0`, fill: "none", stroke: drawColor(el, ctx), strokeWidth: drawStroke(el, ctx) }), ctx);
2590
+ });
2591
+ registerRenderer("diode", (el, ctx) => {
2592
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
2593
+ const cx = d / 2;
2594
+ return rotated(el, /* @__PURE__ */ jsx8("path", { d: `M 0 0 L ${cx - 8} 0 M ${cx - 8} -8 L ${cx - 8} 8 L ${cx + 8} 0 Z M ${cx + 8} -8 L ${cx + 8} 8 M ${cx + 8} 0 L ${d} 0`, fill: "none", stroke: drawColor(el, ctx), strokeWidth: drawStroke(el, ctx) }), ctx);
2595
+ });
2596
+ registerRenderer("battery", (el, ctx) => {
2597
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
2598
+ const cx = d / 2;
2599
+ return rotated(el, /* @__PURE__ */ jsxs8(Fragment3, { children: [
2600
+ /* @__PURE__ */ jsx8("path", { d: `M 0 0 L ${cx - 4} 0 M ${cx - 4} -16 L ${cx - 4} 16 M ${cx + 4} -8 L ${cx + 4} 8 M ${cx + 4} 0 L ${d} 0`, fill: "none", stroke: drawColor(el, ctx), strokeWidth: drawStroke(el, ctx) }),
2601
+ /* @__PURE__ */ jsx8("line", { x1: cx + 4, y1: -8, x2: cx + 4, y2: 8, stroke: drawColor(el, ctx), strokeWidth: drawStroke(el, ctx) + 2 }),
2602
+ /* @__PURE__ */ jsx8("text", { x: cx - 12, y: -12, fontSize: "12", fill: drawColor(el, ctx), textAnchor: "middle", children: "+" })
2603
+ ] }), ctx);
2604
+ });
2605
+ ["meter_v", "meter_a"].forEach(
2606
+ (t) => registerRenderer(t, (el, ctx) => {
2607
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
2608
+ const cx = d / 2;
2609
+ const letter = t === "meter_v" ? "V" : "A";
2610
+ return rotated(el, /* @__PURE__ */ jsxs8(Fragment3, { children: [
2611
+ /* @__PURE__ */ jsx8("path", { d: `M 0 0 L ${cx - 14} 0 M ${cx + 14} 0 L ${d} 0`, fill: "none", stroke: drawColor(el, ctx), strokeWidth: drawStroke(el, ctx) }),
2612
+ /* @__PURE__ */ jsx8("circle", { cx, cy: 0, r: 14, fill: "#f8f9fa", stroke: drawColor(el, ctx), strokeWidth: drawStroke(el, ctx) }),
2613
+ /* @__PURE__ */ jsx8("text", { x: cx, y: 0, textAnchor: "middle", dominantBaseline: "central", fill: drawColor(el, ctx), fontSize: "14", fontWeight: "bold", children: letter })
2614
+ ] }), ctx);
2615
+ })
2616
+ );
2617
+ registerRenderer("ground", (el, ctx) => /* @__PURE__ */ jsx8("g", { transform: `translate(${el.x1}, ${el.y1})`, opacity: ctx?.isGhost ? 0.5 : 1, children: /* @__PURE__ */ jsx8(OriginHitPlate, { ctx, hitHalfSize: 28, selectionHalfSize: 34, children: /* @__PURE__ */ jsx8("path", { d: "M 0 0 L 0 15 M -12 15 L 12 15 M -8 20 L 8 20 M -4 25 L 4 25", fill: "none", stroke: drawColor(el, ctx), strokeWidth: drawStroke(el, ctx) }) }) }));
2618
+
2619
+ // src/core/renderers/circuitAutoNodes.ts
2620
+ var getCircuitAutoNodes = (elements) => {
2621
+ const pointMap = {};
2622
+ elements.forEach((el) => {
2623
+ if (el.type !== "wire") return;
2624
+ const p1 = `${el.x1},${el.y1}`;
2625
+ const p2 = `${el.x2},${el.y2}`;
2626
+ pointMap[p1] = (pointMap[p1] || 0) + 1;
2627
+ pointMap[p2] = (pointMap[p2] || 0) + 1;
2628
+ });
2629
+ return Object.entries(pointMap).filter(([, count]) => count > 2).map(([pt]) => pt.split(",").map(Number));
2630
+ };
2631
+
2632
+ // src/core/renderers/thermo.tsx
2633
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2634
+ var c5 = (el) => el.color || "#0f172a";
2635
+ var sw5 = (el) => el.strokeWidth || 2;
2636
+ var font4 = (el) => el.fontSize || 18;
2637
+ var seededRandom = (seed) => {
2638
+ const x = Math.sin(seed) * 1e4;
2639
+ return x - Math.floor(x);
2640
+ };
2641
+ var strokeDashFromLineStyle = (el) => {
2642
+ if (el.lineStyle === "dashed") return "6 6";
2643
+ if (el.lineStyle === "dotted") return "2 4";
2644
+ return void 0;
2645
+ };
2646
+ registerRenderer("pv_axes", (el, ctx) => {
2647
+ const minX = Math.min(el.x1, el.x2);
2648
+ const minY = Math.min(el.y1, el.y2);
2649
+ const w = Math.max(1, Math.abs(el.x2 - el.x1));
2650
+ const h = Math.max(1, Math.abs(el.y2 - el.y1));
2651
+ const yLabel = String(el.yLabel ?? "P");
2652
+ const xLabel = String(el.xLabel ?? "V");
2653
+ const color = c5(el);
2654
+ const strokeWidth = sw5(el);
2655
+ const fontSize = font4(el);
2656
+ return /* @__PURE__ */ jsxs9("g", { children: [
2657
+ /* @__PURE__ */ jsxs9("g", { transform: `translate(${minX}, ${minY})`, children: [
2658
+ /* @__PURE__ */ jsx9("line", { x1: 0, y1: 0, x2: 0, y2: h, stroke: color, strokeWidth }),
2659
+ /* @__PURE__ */ jsx9("polygon", { points: "0,0 -5,10 5,10", fill: color }),
2660
+ /* @__PURE__ */ jsx9("text", { x: -15, y: 10, fontSize, fill: color, fontWeight: "bold", children: yLabel }),
2661
+ /* @__PURE__ */ jsx9("line", { x1: 0, y1: h, x2: w, y2: h, stroke: color, strokeWidth }),
2662
+ /* @__PURE__ */ jsx9("polygon", { points: `${w},${h} ${w - 10},${h - 5} ${w - 10},${h + 5}`, fill: color }),
2663
+ /* @__PURE__ */ jsx9("text", { x: w - 10, y: h + 20, fontSize, fill: color, fontWeight: "bold", children: xLabel }),
2664
+ /* @__PURE__ */ jsx9("text", { x: -10, y: h + 15, fontSize: 12, fill: color, children: "0" }),
2665
+ el.label ? /* @__PURE__ */ jsx9("text", { x: w / 2, y: -10, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2666
+ ] }),
2667
+ /* @__PURE__ */ jsx9(BBoxInteractionOverlay, { minX, minY, width: w, height: h, ctx }),
2668
+ /* @__PURE__ */ jsx9(ChordInteractionOverlay, { el, ctx })
2669
+ ] });
2670
+ });
2671
+ registerRenderer("piston_cylinder", (el, ctx) => {
2672
+ const minX = Math.min(el.x1, el.x2);
2673
+ const minY = Math.min(el.y1, el.y2);
2674
+ const w = Math.max(1, Math.abs(el.x2 - el.x1));
2675
+ const h = Math.max(1, Math.abs(el.y2 - el.y1));
2676
+ const compression = el.compression ?? 0.5;
2677
+ const pistonY = h * compression;
2678
+ const color = c5(el);
2679
+ const strokeWidth = sw5(el);
2680
+ const particles = [];
2681
+ const numParticles = 40;
2682
+ for (let i = 0; i < numParticles; i++) {
2683
+ const px = seededRandom(Number(el.id) + i) * (w - 10) + 5;
2684
+ const py = pistonY + 10 + seededRandom(Number(el.id) + i * 100) * (h - pistonY - 15);
2685
+ particles.push(/* @__PURE__ */ jsx9("circle", { cx: px, cy: py, r: "2", fill: "#94a3b8" }, i));
2686
+ }
2687
+ return /* @__PURE__ */ jsxs9("g", { children: [
2688
+ /* @__PURE__ */ jsxs9("g", { transform: `translate(${minX}, ${minY})`, children: [
2689
+ /* @__PURE__ */ jsx9(
2690
+ "polyline",
2691
+ {
2692
+ points: `0,0 0,${h} ${w},${h} ${w},0`,
2693
+ fill: "#f8fafc",
2694
+ stroke: color,
2695
+ strokeWidth,
2696
+ strokeLinecap: "square",
2697
+ strokeLinejoin: "miter",
2698
+ pointerEvents: "none"
2699
+ }
2700
+ ),
2701
+ particles,
2702
+ /* @__PURE__ */ jsxs9("g", { transform: `translate(0, ${pistonY})`, children: [
2703
+ /* @__PURE__ */ jsx9("rect", { x: "2", y: "0", width: w - 4, height: "12", fill: "#64748b", rx: "2" }),
2704
+ /* @__PURE__ */ jsx9("line", { x1: w / 2, y1: "0", x2: w / 2, y2: "-40", stroke: "#64748b", strokeWidth: "4" }),
2705
+ /* @__PURE__ */ jsx9("line", { x1: w / 2 - 15, y1: "-40", x2: w / 2 + 15, y2: "-40", stroke: "#64748b", strokeWidth: "6", strokeLinecap: "round" }),
2706
+ /* @__PURE__ */ jsx9("line", { x1: w / 2, y1: "-50", x2: w / 2, y2: "-42", stroke: "#ef4444", strokeWidth: "2" }),
2707
+ /* @__PURE__ */ jsx9("polygon", { points: `${w / 2},-42 ${w / 2 - 4},-46 ${w / 2 + 4},-46`, fill: "#ef4444" })
2708
+ ] }),
2709
+ el.showHeat ? /* @__PURE__ */ jsxs9("g", { transform: `translate(${w / 2}, ${h + 15})`, children: [
2710
+ /* @__PURE__ */ jsx9("path", { d: "M-10,0 Q-15,-10 -5,-20 Q0,-10 10,-15 Q5,-5 10,0 Z", fill: "#ef4444", opacity: "0.8" }),
2711
+ /* @__PURE__ */ jsx9("path", { d: "M-5,0 Q-8,-6 0,-12 Q2,-5 5,0 Z", fill: "#f59e0b" }),
2712
+ /* @__PURE__ */ jsx9("text", { x: "25", y: "-5", fontSize: "14", fill: "#ef4444", fontWeight: "bold", children: "Q" })
2713
+ ] }) : null,
2714
+ el.label ? /* @__PURE__ */ jsx9("text", { x: w / 2, y: h + (el.showHeat ? 35 : 20), textAnchor: "middle", fill: color, fontSize: font4(el), pointerEvents: "none", children: String(el.label) }) : null
2715
+ ] }),
2716
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
2717
+ /* @__PURE__ */ jsx9(ChordInteractionOverlay, { el, ctx })
2718
+ ] });
2719
+ });
2720
+ registerRenderer("heat_engine", (el, ctx) => {
2721
+ const minX = Math.min(el.x1, el.x2);
2722
+ const minY = Math.min(el.y1, el.y2);
2723
+ const w = Math.max(1, Math.abs(el.x2 - el.x1));
2724
+ const h = Math.max(1, Math.abs(el.y2 - el.y1));
2725
+ const isEngine = el.engineType !== "refrigerator";
2726
+ const r = Math.min(w * 0.3, h * 0.2);
2727
+ const color = c5(el);
2728
+ const strokeWidth = sw5(el);
2729
+ const fontSize = font4(el);
2730
+ return /* @__PURE__ */ jsxs9("g", { children: [
2731
+ /* @__PURE__ */ jsxs9("g", { transform: `translate(${minX}, ${minY})`, children: [
2732
+ /* @__PURE__ */ jsx9("rect", { x: 0, y: 0, width: w, height: h * 0.2, fill: "#ef4444", rx: "4", pointerEvents: "none" }),
2733
+ /* @__PURE__ */ jsx9("text", { x: w / 2, y: h * 0.1, fontSize, fill: "#fff", textAnchor: "middle", dominantBaseline: "central", children: "T_H" }),
2734
+ /* @__PURE__ */ jsx9("rect", { x: 0, y: h * 0.8, width: w, height: h * 0.2, fill: "#3b82f6", rx: "4" }),
2735
+ /* @__PURE__ */ jsx9("text", { x: w / 2, y: h * 0.9, fontSize, fill: "#fff", textAnchor: "middle", dominantBaseline: "central", children: "T_C" }),
2736
+ /* @__PURE__ */ jsx9("circle", { cx: w / 2, cy: h / 2, r, fill: "#fff", stroke: color, strokeWidth }),
2737
+ /* @__PURE__ */ jsx9(
2738
+ "text",
2739
+ {
2740
+ x: w / 2,
2741
+ y: h / 2,
2742
+ fontSize: fontSize * 1.2,
2743
+ fill: color,
2744
+ textAnchor: "middle",
2745
+ dominantBaseline: "central",
2746
+ fontWeight: "bold",
2747
+ children: isEngine ? "E" : "R"
2748
+ }
2749
+ ),
2750
+ /* @__PURE__ */ jsxs9("g", { strokeWidth: 2, stroke: color, children: [
2751
+ /* @__PURE__ */ jsx9("line", { x1: w / 2, y1: h * 0.2, x2: w / 2, y2: h * 0.5 - r - 5 }),
2752
+ /* @__PURE__ */ jsx9(
2753
+ "polygon",
2754
+ {
2755
+ points: isEngine ? `${w / 2},${h * 0.5 - r - 2} ${w / 2 - 5},${h * 0.5 - r - 10} ${w / 2 + 5},${h * 0.5 - r - 10}` : `${w / 2},${h * 0.2 + 2} ${w / 2 - 5},${h * 0.2 + 10} ${w / 2 + 5},${h * 0.2 + 10}`,
2756
+ fill: color
2757
+ }
2758
+ ),
2759
+ /* @__PURE__ */ jsx9("text", { x: w / 2 + 10, y: h * 0.35, fontSize: 14, fill: color, dominantBaseline: "central", children: "Q_H" }),
2760
+ /* @__PURE__ */ jsx9("line", { x1: w / 2, y1: h * 0.5 + r + 5, x2: w / 2, y2: h * 0.8 }),
2761
+ /* @__PURE__ */ jsx9(
2762
+ "polygon",
2763
+ {
2764
+ points: isEngine ? `${w / 2},${h * 0.8 - 2} ${w / 2 - 5},${h * 0.8 - 10} ${w / 2 + 5},${h * 0.8 - 10}` : `${w / 2},${h * 0.5 + r + 2} ${w / 2 - 5},${h * 0.5 + r + 10} ${w / 2 + 5},${h * 0.5 + r + 10}`,
2765
+ fill: color
2766
+ }
2767
+ ),
2768
+ /* @__PURE__ */ jsx9("text", { x: w / 2 + 10, y: h * 0.65, fontSize: 14, fill: color, dominantBaseline: "central", children: "Q_C" }),
2769
+ /* @__PURE__ */ jsx9(
2770
+ "line",
2771
+ {
2772
+ x1: isEngine ? w / 2 + r + 5 : w,
2773
+ y1: h / 2,
2774
+ x2: isEngine ? w : w / 2 + r + 5,
2775
+ y2: h / 2
2776
+ }
2777
+ ),
2778
+ /* @__PURE__ */ jsx9(
2779
+ "polygon",
2780
+ {
2781
+ points: isEngine ? `${w},${h / 2} ${w - 8},${h / 2 - 5} ${w - 8},${h / 2 + 5}` : `${w / 2 + r + 5},${h / 2} ${w / 2 + r + 13},${h / 2 - 5} ${w / 2 + r + 13},${h / 2 + 5}`,
2782
+ fill: color
2783
+ }
2784
+ ),
2785
+ /* @__PURE__ */ jsx9("text", { x: w * 0.8, y: h / 2 - 15, fontSize: 14, fill: color, textAnchor: "middle", children: "W" })
2786
+ ] }),
2787
+ el.label ? /* @__PURE__ */ jsx9("text", { x: w / 2, y: h + 20, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2788
+ ] }),
2789
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
2790
+ /* @__PURE__ */ jsx9(ChordInteractionOverlay, { el, ctx })
2791
+ ] });
2792
+ });
2793
+ registerRenderer("conduction_rod", (el, ctx) => {
2794
+ const dist = distance(el.x1, el.y1, el.x2, el.y2);
2795
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
2796
+ const gradId = `grad-rod-${el.id}`;
2797
+ const color = c5(el);
2798
+ const strokeWidth = sw5(el);
2799
+ const fontSize = font4(el);
2800
+ return /* @__PURE__ */ jsxs9("g", { children: [
2801
+ /* @__PURE__ */ jsxs9("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
2802
+ /* @__PURE__ */ jsx9("defs", { children: /* @__PURE__ */ jsxs9("linearGradient", { id: gradId, x1: "0%", y1: "0%", x2: "100%", y2: "0%", children: [
2803
+ /* @__PURE__ */ jsx9("stop", { offset: "0%", stopColor: "#ef4444" }),
2804
+ /* @__PURE__ */ jsx9("stop", { offset: "100%", stopColor: "#3b82f6" })
2805
+ ] }) }),
2806
+ /* @__PURE__ */ jsx9("rect", { x: 0, y: -20, width: dist, height: 40, fill: `url(#${gradId})`, stroke: color, strokeWidth, rx: "4", pointerEvents: "none" }),
2807
+ /* @__PURE__ */ jsxs9("g", { stroke: "#fff", strokeWidth: "2", opacity: 0.6, children: [
2808
+ /* @__PURE__ */ jsx9("line", { x1: dist * 0.2, y1: 0, x2: dist * 0.4, y2: 0 }),
2809
+ /* @__PURE__ */ jsx9("polygon", { points: `${dist * 0.4},0 ${dist * 0.4 - 6},-4 ${dist * 0.4 - 6},4`, fill: "#fff" }),
2810
+ /* @__PURE__ */ jsx9("line", { x1: dist * 0.6, y1: 0, x2: dist * 0.8, y2: 0 }),
2811
+ /* @__PURE__ */ jsx9("polygon", { points: `${dist * 0.8},0 ${dist * 0.8 - 6},-4 ${dist * 0.8 - 6},4`, fill: "#fff" })
2812
+ ] }),
2813
+ /* @__PURE__ */ jsx9("text", { x: 0, y: -30, fontSize, fill: "#ef4444", textAnchor: "middle", fontWeight: "bold", children: "T_H" }),
2814
+ /* @__PURE__ */ jsx9("text", { x: dist, y: -30, fontSize, fill: "#3b82f6", textAnchor: "middle", fontWeight: "bold", children: "T_C" }),
2815
+ el.label ? /* @__PURE__ */ jsx9("text", { x: dist / 2, y: 35, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2816
+ ] }),
2817
+ /* @__PURE__ */ jsx9(ChordInteractionOverlay, { el, ctx, padY: 48 })
2818
+ ] });
2819
+ });
2820
+ registerRenderer("thermo_process", (el, ctx) => {
2821
+ const pType = el.processType || "isotherm";
2822
+ const dx = el.x2 - el.x1;
2823
+ const dy = el.y2 - el.y1;
2824
+ const pathD = pType === "isobaric" ? `M 0 0 L ${dx} 0` : pType === "isochoric" ? `M 0 0 L 0 ${dy}` : pType === "adiabatic" ? `M 0 0 Q ${dx * 0.1} ${dy * 0.9} ${dx} ${dy}` : `M 0 0 Q ${dx * 0.2} ${dy * 0.8} ${dx} ${dy}`;
2825
+ let midX = dx / 2;
2826
+ let midY = dy / 2;
2827
+ if (pType === "isotherm" || pType === "adiabatic") {
2828
+ const cx = dx * (pType === "adiabatic" ? 0.1 : 0.2);
2829
+ const cy = dy * (pType === "adiabatic" ? 0.9 : 0.8);
2830
+ midX = 0.25 * 0 + 0.5 * cx + 0.25 * dx;
2831
+ midY = 0.25 * 0 + 0.5 * cy + 0.25 * dy;
2832
+ }
2833
+ const arrowAngle = Math.atan2(dy, dx) * 180 / Math.PI;
2834
+ const dash = strokeDashFromLineStyle(el);
2835
+ const color = c5(el);
2836
+ const strokeWidth = sw5(el);
2837
+ const fontSize = font4(el);
2838
+ return /* @__PURE__ */ jsxs9("g", { children: [
2839
+ /* @__PURE__ */ jsxs9("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
2840
+ /* @__PURE__ */ jsx9(LocalFatPathHit, { dPath: pathD, strokeWidth: 22, strokeLinejoin: "round", strokeLinecap: "round" }),
2841
+ /* @__PURE__ */ jsx9("path", { d: pathD, fill: "none", stroke: color, strokeWidth, strokeDasharray: dash, pointerEvents: "none" }),
2842
+ /* @__PURE__ */ jsx9("g", { transform: `translate(${midX}, ${midY}) rotate(${arrowAngle})`, children: /* @__PURE__ */ jsx9("polygon", { points: "0,0 -8,-5 -8,5", fill: color, pointerEvents: "none" }) }),
2843
+ el.label ? /* @__PURE__ */ jsx9("text", { x: midX + 15, y: midY - 10, fontSize, fill: color, pointerEvents: "none", children: String(el.label) }) : null
2844
+ ] }),
2845
+ /* @__PURE__ */ jsx9(ChordInteractionOverlay, { el, ctx })
2846
+ ] });
2847
+ });
2848
+ registerRenderer("carnot_cycle", (el, ctx) => {
2849
+ const minX = Math.min(el.x1, el.x2);
2850
+ const minY = Math.min(el.y1, el.y2);
2851
+ const w = Math.max(1, Math.abs(el.x2 - el.x1));
2852
+ const h = Math.max(1, Math.abs(el.y2 - el.y1));
2853
+ const p1 = { x: w * 0.1, y: h * 0.1 };
2854
+ const p2 = { x: w * 0.6, y: h * 0.3 };
2855
+ const p3 = { x: w * 0.9, y: h * 0.9 };
2856
+ const p4 = { x: w * 0.4, y: h * 0.7 };
2857
+ const iso1 = `M ${p1.x} ${p1.y} Q ${w * 0.3} ${h * 0.15} ${p2.x} ${p2.y}`;
2858
+ const ad1 = `Q ${w * 0.7} ${h * 0.6} ${p3.x} ${p3.y}`;
2859
+ const iso2 = `Q ${w * 0.6} ${h * 0.85} ${p4.x} ${p4.y}`;
2860
+ const ad2 = `Q ${w * 0.2} ${h * 0.4} ${p1.x} ${p1.y}`;
2861
+ const color = c5(el);
2862
+ const strokeWidth = sw5(el);
2863
+ const fontSize = font4(el);
2864
+ return /* @__PURE__ */ jsxs9("g", { children: [
2865
+ /* @__PURE__ */ jsxs9("g", { transform: `translate(${minX}, ${minY})`, children: [
2866
+ /* @__PURE__ */ jsx9("path", { d: `${iso1} ${ad1} ${iso2} ${ad2} Z`, fill: color, fillOpacity: 0.1, pointerEvents: "none" }),
2867
+ /* @__PURE__ */ jsx9("path", { d: iso1, fill: "none", stroke: "#ef4444", strokeWidth, pointerEvents: "none" }),
2868
+ /* @__PURE__ */ jsx9("path", { d: `M ${p2.x} ${p2.y} ${ad1}`, fill: "none", stroke: "#64748b", strokeWidth, strokeDasharray: "4 4", pointerEvents: "none" }),
2869
+ /* @__PURE__ */ jsx9("path", { d: `M ${p3.x} ${p3.y} ${iso2}`, fill: "none", stroke: "#3b82f6", strokeWidth, pointerEvents: "none" }),
2870
+ /* @__PURE__ */ jsx9("path", { d: `M ${p4.x} ${p4.y} ${ad2}`, fill: "none", stroke: "#64748b", strokeWidth, strokeDasharray: "4 4", pointerEvents: "none" }),
2871
+ /* @__PURE__ */ jsx9("circle", { cx: p1.x, cy: p1.y, r: "3", fill: "#1e293b", pointerEvents: "none" }),
2872
+ /* @__PURE__ */ jsx9("circle", { cx: p2.x, cy: p2.y, r: "3", fill: "#1e293b", pointerEvents: "none" }),
2873
+ /* @__PURE__ */ jsx9("circle", { cx: p3.x, cy: p3.y, r: "3", fill: "#1e293b", pointerEvents: "none" }),
2874
+ /* @__PURE__ */ jsx9("circle", { cx: p4.x, cy: p4.y, r: "3", fill: "#1e293b", pointerEvents: "none" }),
2875
+ /* @__PURE__ */ jsx9("text", { x: w * 0.4, y: h * 0.15, fontSize: "14", fill: "#ef4444", fontWeight: "bold", children: "T_H" }),
2876
+ /* @__PURE__ */ jsx9("text", { x: w * 0.6, y: h * 0.95, fontSize: "14", fill: "#3b82f6", fontWeight: "bold", children: "T_C" }),
2877
+ /* @__PURE__ */ jsx9("text", { x: w / 2, y: h / 2 + 5, fontSize: 16, fill: color, textAnchor: "middle", fontWeight: "bold", children: "W" }),
2878
+ el.label ? /* @__PURE__ */ jsx9("text", { x: w / 2, y: -15, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2879
+ ] }),
2880
+ bboxInsetSelectionChrome(minX, minY, w, h, ctx),
2881
+ /* @__PURE__ */ jsx9(ChordInteractionOverlay, { el, ctx })
2882
+ ] });
2883
+ });
2884
+ registerRenderer("maxwell_boltzmann", (el, ctx) => {
2885
+ const minX = Math.min(el.x1, el.x2);
2886
+ const minY = Math.min(el.y1, el.y2);
2887
+ const w = Math.max(1, Math.abs(el.x2 - el.x1));
2888
+ const h = Math.max(1, Math.abs(el.y2 - el.y1));
2889
+ const t1 = el.t1 || 300;
2890
+ const t2 = el.t2 || 600;
2891
+ const strokeWidth = sw5(el);
2892
+ const fontSize = font4(el);
2893
+ const color = c5(el);
2894
+ const buildCurve = (temp, peakHeight, strokeCol, dash) => {
2895
+ let d = `M 0 ${h}`;
2896
+ const k = 1 / temp;
2897
+ const vmp = Math.sqrt(2 / k);
2898
+ const xMax = vmp * 3.5;
2899
+ for (let x = 0; x <= w; x += 2) {
2900
+ const v = x / w * xMax;
2901
+ const rawVal = v * v * Math.exp(-k * v * v);
2902
+ const maxRawVal = vmp * vmp * Math.exp(-1);
2903
+ const y = h - rawVal / maxRawVal * peakHeight;
2904
+ d += ` L ${x} ${y}`;
2905
+ }
2906
+ return /* @__PURE__ */ jsx9("path", { d, fill: "none", stroke: strokeCol, strokeWidth, strokeDasharray: dash, strokeLinejoin: "round", pointerEvents: "none" });
2907
+ };
2908
+ return /* @__PURE__ */ jsxs9("g", { children: [
2909
+ /* @__PURE__ */ jsxs9("g", { transform: `translate(${minX}, ${minY})`, children: [
2910
+ /* @__PURE__ */ jsx9("line", { x1: 0, y1: 0, x2: 0, y2: h, stroke: "#1e293b", strokeWidth: "2" }),
2911
+ /* @__PURE__ */ jsx9("line", { x1: 0, y1: h, x2: w, y2: h, stroke: "#1e293b", strokeWidth: "2" }),
2912
+ /* @__PURE__ */ jsx9("text", { x: -25, y: h / 2, fontSize: 14, fill: "#1e293b", transform: `rotate(-90, -25, ${h / 2})`, textAnchor: "middle", children: "N(v)" }),
2913
+ /* @__PURE__ */ jsx9("text", { x: w / 2, y: h + 20, fontSize: 14, fill: "#1e293b", textAnchor: "middle", children: "Speed (v)" }),
2914
+ buildCurve(t1, h * 0.8, "#ef4444"),
2915
+ buildCurve(t2, h * 0.5, "#3b82f6", "4 4"),
2916
+ /* @__PURE__ */ jsx9("rect", { x: w - 70, y: 10, width: 10, height: 10, fill: "#ef4444" }),
2917
+ /* @__PURE__ */ jsxs9("text", { x: w - 55, y: 20, fontSize: 12, fill: "#1e293b", children: [
2918
+ "T\u2081 = ",
2919
+ t1,
2920
+ "K"
2921
+ ] }),
2922
+ /* @__PURE__ */ jsx9("rect", { x: w - 70, y: 30, width: 10, height: 10, fill: "#3b82f6" }),
2923
+ /* @__PURE__ */ jsxs9("text", { x: w - 55, y: 40, fontSize: 12, fill: "#1e293b", children: [
2924
+ "T\u2082 = ",
2925
+ t2,
2926
+ "K"
2927
+ ] }),
2928
+ el.label ? /* @__PURE__ */ jsx9("text", { x: w / 2, y: -15, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2929
+ ] }),
2930
+ /* @__PURE__ */ jsx9(BBoxInteractionOverlay, { minX, minY, width: w, height: h, ctx }),
2931
+ /* @__PURE__ */ jsx9(ChordInteractionOverlay, { el, ctx })
2932
+ ] });
2933
+ });
2934
+ registerRenderer("diatomic_gas", (el, ctx) => {
2935
+ const color = c5(el);
2936
+ const fontSize = font4(el);
2937
+ const showRot = el.showRotations !== false;
2938
+ return /* @__PURE__ */ jsx9("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs9(OriginHitPlate, { ctx, hitHalfSize: 38, selectionHalfSize: 46, children: [
2939
+ /* @__PURE__ */ jsx9("circle", { cx: -15, cy: 0, r: 8, fill: color, stroke: "#fff", strokeWidth: 2, pointerEvents: "none" }),
2940
+ /* @__PURE__ */ jsx9("circle", { cx: 15, cy: 0, r: 8, fill: color, stroke: "#fff", strokeWidth: 2, pointerEvents: "none" }),
2941
+ /* @__PURE__ */ jsx9("line", { x1: -10, y1: 0, x2: 10, y2: 0, stroke: color, strokeWidth: 4, pointerEvents: "none" }),
2942
+ showRot ? /* @__PURE__ */ jsxs9("g", { stroke: "#94a3b8", strokeWidth: 1.5, fill: "none", pointerEvents: "none", children: [
2943
+ /* @__PURE__ */ jsx9("path", { d: "M -10 -15 A 15 15 0 0 1 10 -15", strokeDasharray: "2 2" }),
2944
+ /* @__PURE__ */ jsx9("polygon", { points: "10,-15 6,-18 6,-12", fill: "#94a3b8" }),
2945
+ /* @__PURE__ */ jsx9("path", { d: "M -25 0 A 25 10 0 0 1 0 10 A 25 10 0 0 1 25 0", strokeDasharray: "2 2" })
2946
+ ] }) : null,
2947
+ el.label ? /* @__PURE__ */ jsx9("text", { x: 0, y: 25, fontSize, fill: color, textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2948
+ ] }) });
2949
+ });
2950
+ registerRenderer("random_walk", (el, ctx) => {
2951
+ const steps = el.steps || 5;
2952
+ const dist = Math.hypot(el.x2 - el.x1, el.y2 - el.y1);
2953
+ let d = "M 0 0";
2954
+ let curX = 0;
2955
+ let curY = 0;
2956
+ const points = [/* @__PURE__ */ jsx9("circle", { cx: "0", cy: "0", r: "4", fill: "#22c55e", pointerEvents: "none" }, "start")];
2957
+ for (let i = 1; i <= steps; i++) {
2958
+ const angle = seededRandom(Number(el.id) + i * 7) * Math.PI * 2;
2959
+ const stepDist = dist / steps * (0.5 + seededRandom(Number(el.id) + i * 13));
2960
+ curX += Math.cos(angle) * stepDist;
2961
+ curY += Math.sin(angle) * stepDist;
2962
+ d += ` L ${curX} ${curY}`;
2963
+ points.push(
2964
+ /* @__PURE__ */ jsx9("circle", { cx: curX, cy: curY, r: i === steps ? 4 : 2, fill: i === steps ? "#ef4444" : c5(el), pointerEvents: "none" }, i)
2965
+ );
2966
+ }
2967
+ return /* @__PURE__ */ jsxs9("g", { children: [
2968
+ /* @__PURE__ */ jsxs9("g", { transform: `translate(${el.x1}, ${el.y1})`, children: [
2969
+ /* @__PURE__ */ jsx9(LocalFatPathHit, { dPath: d, strokeWidth: 20, strokeLinejoin: "round" }),
2970
+ /* @__PURE__ */ jsx9("path", { d, fill: "none", stroke: c5(el), strokeWidth: sw5(el), strokeLinejoin: "round", pointerEvents: "none" }),
2971
+ points,
2972
+ el.label ? /* @__PURE__ */ jsx9("text", { x: dist / 2, y: dist / 2, fontSize: font4(el), fill: c5(el), textAnchor: "middle", pointerEvents: "none", children: String(el.label) }) : null
2973
+ ] }),
2974
+ /* @__PURE__ */ jsx9(ChordInteractionOverlay, { el, ctx, padX: Math.max(40, dist), padY: Math.max(40, dist) })
2975
+ ] });
2976
+ });
2977
+
2978
+ // src/core/renderers/chemistry.tsx
2979
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2980
+ var colorOf3 = (el, isSelected) => {
2981
+ if (isSelected) return "#0d9488";
2982
+ return el.color || "#111827";
2983
+ };
2984
+ var strokeOf3 = (el, isSelected) => {
2985
+ const base = el.strokeWidth || 2;
2986
+ return isSelected ? Math.max(3, base) : base;
2987
+ };
2988
+ var fontOf3 = (el) => el.fontSize || 16;
2989
+ var ringPolygonPoints = (sides, radius, startDeg) => Array.from({ length: sides }).map((_, i) => {
2990
+ const angle = (i * 360 / sides + startDeg) * (Math.PI / 180);
2991
+ return `${radius * Math.cos(angle)},${radius * Math.sin(angle)}`;
2992
+ }).join(" ");
2993
+ registerRenderer("atom", (el, ctx) => /* @__PURE__ */ jsx10("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs10(OriginHitPlate, { ctx, hitHalfSize: 22, selectionHalfSize: 30, children: [
2994
+ /* @__PURE__ */ jsx10("rect", { x: "-20", y: "-15", width: "40", height: "30", fill: "white", rx: "6" }),
2995
+ /* @__PURE__ */ jsx10(
2996
+ "text",
2997
+ {
2998
+ x: "0",
2999
+ y: "0",
3000
+ dominantBaseline: "central",
3001
+ textAnchor: "middle",
3002
+ fill: colorOf3(el, ctx.isSelected),
3003
+ fontSize: Math.max(18, fontOf3(el)),
3004
+ fontFamily: "sans-serif",
3005
+ fontWeight: "bold",
3006
+ children: String(el.label || "C")
3007
+ }
3008
+ )
3009
+ ] }) }));
3010
+ registerRenderer("charge_plus", (el, ctx) => {
3011
+ const stroke2 = colorOf3(el, ctx.isSelected);
3012
+ const sw6 = strokeOf3(el, ctx.isSelected);
3013
+ const baseSw = el.strokeWidth || 2;
3014
+ const r = Math.max(12, baseSw * 4);
3015
+ const plate = Math.max(28, r + (el.label ? 26 : 8));
3016
+ return /* @__PURE__ */ jsx10("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs10(OriginHitPlate, { ctx, hitHalfSize: plate, selectionHalfSize: plate + 10, children: [
3017
+ /* @__PURE__ */ jsx10("circle", { cx: "0", cy: "0", r, fill: "white", stroke: stroke2, strokeWidth: sw6 }),
3018
+ /* @__PURE__ */ jsx10("line", { x1: -r / 2, y1: "0", x2: r / 2, y2: "0", stroke: stroke2, strokeWidth: sw6 }),
3019
+ /* @__PURE__ */ jsx10("line", { x1: "0", y1: -r / 2, x2: "0", y2: r / 2, stroke: stroke2, strokeWidth: sw6 }),
3020
+ el.label ? /* @__PURE__ */ jsx10(
3021
+ "text",
3022
+ {
3023
+ x: "0",
3024
+ y: r + 18,
3025
+ fontSize: fontOf3(el),
3026
+ fill: stroke2,
3027
+ fontFamily: "sans-serif",
3028
+ fontWeight: "bold",
3029
+ textAnchor: "middle",
3030
+ children: String(el.label)
3031
+ }
3032
+ ) : null
3033
+ ] }) });
3034
+ });
3035
+ registerRenderer("charge_minus", (el, ctx) => {
3036
+ const stroke2 = colorOf3(el, ctx.isSelected);
3037
+ const sw6 = strokeOf3(el, ctx.isSelected);
3038
+ const baseSw = el.strokeWidth || 2;
3039
+ const r = Math.max(12, baseSw * 4);
3040
+ const plate = Math.max(28, r + (el.label ? 26 : 8));
3041
+ return /* @__PURE__ */ jsx10("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs10(OriginHitPlate, { ctx, hitHalfSize: plate, selectionHalfSize: plate + 10, children: [
3042
+ /* @__PURE__ */ jsx10("circle", { cx: "0", cy: "0", r, fill: "white", stroke: stroke2, strokeWidth: sw6 }),
3043
+ /* @__PURE__ */ jsx10("line", { x1: -r / 2, y1: "0", x2: r / 2, y2: "0", stroke: stroke2, strokeWidth: sw6 }),
3044
+ el.label ? /* @__PURE__ */ jsx10(
3045
+ "text",
3046
+ {
3047
+ x: "0",
3048
+ y: r + 18,
3049
+ fontSize: fontOf3(el),
3050
+ fill: stroke2,
3051
+ fontFamily: "sans-serif",
3052
+ fontWeight: "bold",
3053
+ textAnchor: "middle",
3054
+ children: String(el.label)
3055
+ }
3056
+ ) : null
3057
+ ] }) });
3058
+ });
3059
+ registerRenderer("radical", (el, ctx) => /* @__PURE__ */ jsx10("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsx10(OriginHitPlate, { ctx, hitHalfSize: 14, selectionHalfSize: 22, children: /* @__PURE__ */ jsx10("circle", { cx: "0", cy: "0", r: "3.5", fill: colorOf3(el, ctx.isSelected) }) }) }));
3060
+ registerRenderer("lone_pair", (el, ctx) => /* @__PURE__ */ jsx10("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs10(OriginHitPlate, { ctx, hitHalfSize: 16, selectionHalfSize: 24, children: [
3061
+ /* @__PURE__ */ jsx10("circle", { cx: "-4", cy: "0", r: "2.5", fill: colorOf3(el, ctx.isSelected) }),
3062
+ /* @__PURE__ */ jsx10("circle", { cx: "4", cy: "0", r: "2.5", fill: colorOf3(el, ctx.isSelected) })
3063
+ ] }) }));
3064
+ registerRenderer("benzene", (el, ctx) => {
3065
+ const r = 30;
3066
+ const stroke2 = colorOf3(el, ctx.isSelected);
3067
+ const sw6 = strokeOf3(el, ctx.isSelected);
3068
+ return /* @__PURE__ */ jsx10("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsxs10(OriginHitPlate, { ctx, hitHalfSize: 36, selectionHalfSize: 44, children: [
3069
+ /* @__PURE__ */ jsx10("polygon", { points: ringPolygonPoints(6, r, -30), fill: "white", stroke: stroke2, strokeWidth: sw6, strokeLinejoin: "round" }),
3070
+ /* @__PURE__ */ jsx10("circle", { cx: "0", cy: "0", r: r - 10, fill: "none", stroke: stroke2, strokeWidth: sw6 })
3071
+ ] }) });
3072
+ });
3073
+ registerRenderer("cyclohexane", (el, ctx) => /* @__PURE__ */ jsx10("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsx10(OriginHitPlate, { ctx, hitHalfSize: 36, selectionHalfSize: 44, children: /* @__PURE__ */ jsx10(
3074
+ "polygon",
3075
+ {
3076
+ points: ringPolygonPoints(6, 30, -30),
3077
+ fill: "white",
3078
+ stroke: colorOf3(el, ctx.isSelected),
3079
+ strokeWidth: strokeOf3(el, ctx.isSelected),
3080
+ strokeLinejoin: "round"
3081
+ }
3082
+ ) }) }));
3083
+ registerRenderer("cyclopentane", (el, ctx) => /* @__PURE__ */ jsx10("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsx10(OriginHitPlate, { ctx, hitHalfSize: 36, selectionHalfSize: 44, children: /* @__PURE__ */ jsx10(
3084
+ "polygon",
3085
+ {
3086
+ points: ringPolygonPoints(5, 30, -90),
3087
+ fill: "white",
3088
+ stroke: colorOf3(el, ctx.isSelected),
3089
+ strokeWidth: strokeOf3(el, ctx.isSelected),
3090
+ strokeLinejoin: "round"
3091
+ }
3092
+ ) }) }));
3093
+ registerRenderer("cyclobutane", (el, ctx) => /* @__PURE__ */ jsx10("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsx10(OriginHitPlate, { ctx, hitHalfSize: 36, selectionHalfSize: 44, children: /* @__PURE__ */ jsx10(
3094
+ "polygon",
3095
+ {
3096
+ points: ringPolygonPoints(4, 30, -45),
3097
+ fill: "white",
3098
+ stroke: colorOf3(el, ctx.isSelected),
3099
+ strokeWidth: strokeOf3(el, ctx.isSelected),
3100
+ strokeLinejoin: "round"
3101
+ }
3102
+ ) }) }));
3103
+ registerRenderer("cyclopropane", (el, ctx) => /* @__PURE__ */ jsx10("g", { transform: `translate(${el.x1}, ${el.y1})`, children: /* @__PURE__ */ jsx10(OriginHitPlate, { ctx, hitHalfSize: 36, selectionHalfSize: 44, children: /* @__PURE__ */ jsx10(
3104
+ "polygon",
3105
+ {
3106
+ points: ringPolygonPoints(3, 30, -90),
3107
+ fill: "white",
3108
+ stroke: colorOf3(el, ctx.isSelected),
3109
+ strokeWidth: strokeOf3(el, ctx.isSelected),
3110
+ strokeLinejoin: "round"
3111
+ }
3112
+ ) }) }));
3113
+ var drawBond = (el, style, isSelected) => {
3114
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
3115
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
3116
+ const stroke2 = colorOf3(el, isSelected);
3117
+ const sw6 = strokeOf3(el, isSelected);
3118
+ return /* @__PURE__ */ jsxs10("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
3119
+ style === "single" && /* @__PURE__ */ jsx10("line", { x1: "0", y1: "0", x2: d, y2: "0", stroke: stroke2, strokeWidth: sw6, strokeLinecap: "round" }),
3120
+ style === "double" && /* @__PURE__ */ jsxs10(Fragment4, { children: [
3121
+ /* @__PURE__ */ jsx10("line", { x1: "0", y1: "-3.5", x2: d, y2: "-3.5", stroke: stroke2, strokeWidth: sw6, strokeLinecap: "round" }),
3122
+ /* @__PURE__ */ jsx10("line", { x1: "0", y1: "3.5", x2: d, y2: "3.5", stroke: stroke2, strokeWidth: sw6, strokeLinecap: "round" })
3123
+ ] }),
3124
+ style === "triple" && /* @__PURE__ */ jsxs10(Fragment4, { children: [
3125
+ /* @__PURE__ */ jsx10("line", { x1: "0", y1: "-6", x2: d, y2: "-6", stroke: stroke2, strokeWidth: sw6, strokeLinecap: "round" }),
3126
+ /* @__PURE__ */ jsx10("line", { x1: "0", y1: "0", x2: d, y2: "0", stroke: stroke2, strokeWidth: sw6, strokeLinecap: "round" }),
3127
+ /* @__PURE__ */ jsx10("line", { x1: "0", y1: "6", x2: d, y2: "6", stroke: stroke2, strokeWidth: sw6, strokeLinecap: "round" })
3128
+ ] }),
3129
+ style === "wedge" && /* @__PURE__ */ jsx10("polygon", { points: `0,0 ${d},-5 ${d},5`, fill: stroke2 }),
3130
+ style === "dash" && /* @__PURE__ */ jsx10("line", { x1: "0", y1: "0", x2: d, y2: "0", stroke: stroke2, strokeWidth: sw6 + 1, strokeDasharray: "5 5", strokeLinecap: "square" })
3131
+ ] });
3132
+ };
3133
+ registerRenderer("single_bond", (el, ctx) => /* @__PURE__ */ jsxs10("g", { children: [
3134
+ drawBond(el, "single", ctx.isSelected),
3135
+ /* @__PURE__ */ jsx10(ChordInteractionOverlay, { el, ctx })
3136
+ ] }));
3137
+ registerRenderer("double_bond", (el, ctx) => /* @__PURE__ */ jsxs10("g", { children: [
3138
+ drawBond(el, "double", ctx.isSelected),
3139
+ /* @__PURE__ */ jsx10(ChordInteractionOverlay, { el, ctx })
3140
+ ] }));
3141
+ registerRenderer("triple_bond", (el, ctx) => /* @__PURE__ */ jsxs10("g", { children: [
3142
+ drawBond(el, "triple", ctx.isSelected),
3143
+ /* @__PURE__ */ jsx10(ChordInteractionOverlay, { el, ctx })
3144
+ ] }));
3145
+ registerRenderer("wedge_bond", (el, ctx) => /* @__PURE__ */ jsxs10("g", { children: [
3146
+ drawBond(el, "wedge", ctx.isSelected),
3147
+ /* @__PURE__ */ jsx10(ChordInteractionOverlay, { el, ctx })
3148
+ ] }));
3149
+ registerRenderer("dash_bond", (el, ctx) => /* @__PURE__ */ jsxs10("g", { children: [
3150
+ drawBond(el, "dash", ctx.isSelected),
3151
+ /* @__PURE__ */ jsx10(ChordInteractionOverlay, { el, ctx })
3152
+ ] }));
3153
+ var lineMid = (el) => ({ x: (el.x1 + el.x2) / 2, y: (el.y1 + el.y2) / 2 });
3154
+ registerRenderer("reaction_arrow", (el, ctx) => {
3155
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
3156
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
3157
+ const stroke2 = colorOf3(el, ctx.isSelected);
3158
+ const sw6 = strokeOf3(el, ctx.isSelected);
3159
+ const mid = lineMid(el);
3160
+ return /* @__PURE__ */ jsxs10("g", { children: [
3161
+ /* @__PURE__ */ jsxs10("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
3162
+ /* @__PURE__ */ jsx10("line", { x1: "0", y1: "0", x2: d, y2: "0", stroke: stroke2, strokeWidth: sw6 }),
3163
+ /* @__PURE__ */ jsx10("polygon", { points: `${d},0 ${d - 12},-6 ${d - 12},6`, fill: stroke2 })
3164
+ ] }),
3165
+ /* @__PURE__ */ jsx10(ChordInteractionOverlay, { el, ctx }),
3166
+ el.label ? /* @__PURE__ */ jsx10("text", { x: mid.x, y: mid.y - 18, textAnchor: "middle", fill: "#0f766e", fontSize: "14", fontFamily: "sans-serif", fontWeight: "bold", pointerEvents: "none", children: String(el.label) }) : null,
3167
+ el.value ? /* @__PURE__ */ jsx10("text", { x: mid.x, y: mid.y + 18, textAnchor: "middle", fill: "#4b5563", fontSize: "13", fontFamily: "sans-serif", pointerEvents: "none", children: String(el.value) }) : null
3168
+ ] });
3169
+ });
3170
+ registerRenderer("equilibrium_arrow", (el, ctx) => {
3171
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
3172
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
3173
+ const stroke2 = colorOf3(el, ctx.isSelected);
3174
+ const sw6 = strokeOf3(el, ctx.isSelected);
3175
+ const mid = lineMid(el);
3176
+ return /* @__PURE__ */ jsxs10("g", { children: [
3177
+ /* @__PURE__ */ jsxs10("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
3178
+ /* @__PURE__ */ jsx10("line", { x1: "0", y1: "-4", x2: d, y2: "-4", stroke: stroke2, strokeWidth: sw6 }),
3179
+ /* @__PURE__ */ jsx10("polygon", { points: `${d},-4 ${d - 10},-10 ${d - 10},-4`, fill: stroke2 }),
3180
+ /* @__PURE__ */ jsx10("line", { x1: "0", y1: "4", x2: d, y2: "4", stroke: stroke2, strokeWidth: sw6 }),
3181
+ /* @__PURE__ */ jsx10("polygon", { points: `0,4 10,4 10,10`, fill: stroke2 })
3182
+ ] }),
3183
+ /* @__PURE__ */ jsx10(ChordInteractionOverlay, { el, ctx }),
3184
+ el.label ? /* @__PURE__ */ jsx10("text", { x: mid.x, y: mid.y - 18, textAnchor: "middle", fill: "#0f766e", fontSize: "14", fontFamily: "sans-serif", fontWeight: "bold", pointerEvents: "none", children: String(el.label) }) : null,
3185
+ el.value ? /* @__PURE__ */ jsx10("text", { x: mid.x, y: mid.y + 18, textAnchor: "middle", fill: "#4b5563", fontSize: "13", fontFamily: "sans-serif", pointerEvents: "none", children: String(el.value) }) : null
3186
+ ] });
3187
+ });
3188
+ registerRenderer("resonance_arrow", (el, ctx) => {
3189
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
3190
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
3191
+ const stroke2 = colorOf3(el, ctx.isSelected);
3192
+ const sw6 = strokeOf3(el, ctx.isSelected);
3193
+ return /* @__PURE__ */ jsxs10("g", { children: [
3194
+ /* @__PURE__ */ jsxs10("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
3195
+ /* @__PURE__ */ jsx10("line", { x1: "0", y1: "0", x2: d, y2: "0", stroke: stroke2, strokeWidth: sw6 }),
3196
+ /* @__PURE__ */ jsx10("polygon", { points: `0,0 12,-6 12,6`, fill: stroke2 }),
3197
+ /* @__PURE__ */ jsx10("polygon", { points: `${d},0 ${d - 12},-6 ${d - 12},6`, fill: stroke2 })
3198
+ ] }),
3199
+ /* @__PURE__ */ jsx10(ChordInteractionOverlay, { el, ctx })
3200
+ ] });
3201
+ });
3202
+ registerRenderer("curved_arrow", (el, ctx) => {
3203
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
3204
+ const angle = angleDeg(el.x1, el.y1, el.x2, el.y2);
3205
+ const h = d / 3;
3206
+ const arrowAngle = Math.atan2(h, d / 2) * (180 / Math.PI);
3207
+ const stroke2 = colorOf3(el, ctx.isSelected);
3208
+ const sw6 = strokeOf3(el, ctx.isSelected);
3209
+ const curvePath = `M 0 0 Q ${d / 2} ${-h} ${d} 0`;
3210
+ const bb = quadraticChordWorldBounds(el.x1, el.y1, el.x2, el.y2, h / 2);
3211
+ return /* @__PURE__ */ jsxs10("g", { children: [
3212
+ /* @__PURE__ */ jsxs10("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${angle})`, children: [
3213
+ /* @__PURE__ */ jsx10("path", { d: curvePath, fill: "none", stroke: stroke2, strokeWidth: sw6 }),
3214
+ /* @__PURE__ */ jsx10(LocalFatPathHit, { dPath: curvePath, strokeWidth: 24, strokeLinecap: "round" }),
3215
+ /* @__PURE__ */ jsx10("g", { transform: `translate(${d}, 0) rotate(${arrowAngle})`, children: /* @__PURE__ */ jsx10("polygon", { points: "0,0 -12,-6 -12,6", fill: stroke2 }) })
3216
+ ] }),
3217
+ ctx.isSelected && !ctx.viewMode && !ctx.isGhost ? selectionDashRect(bb.x, bb.y, bb.width, bb.height) : null,
3218
+ endpointHandles(el, ctx)
3219
+ ] });
3220
+ });
3221
+
3222
+ // src/core/renderers/electrostatics.tsx
3223
+ import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
3224
+ var stroke = (el, selected) => selected ? "#2563eb" : el.color || "#0f172a";
3225
+ var width = (el, selected) => selected ? 2.5 : el.strokeWidth || 2;
3226
+ var rotated2 = (el, content, selected) => {
3227
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
3228
+ const a = angleDeg(el.x1, el.y1, el.x2, el.y2);
3229
+ const dx = el.x2 - el.x1;
3230
+ const dy = el.y2 - el.y1;
3231
+ const mx = (el.x1 + el.x2) / 2;
3232
+ const my = (el.y1 + el.y2) / 2;
3233
+ let nx = -dy / (d || 1);
3234
+ let ny = dx / (d || 1);
3235
+ if (ny > 0) {
3236
+ nx = -nx;
3237
+ ny = -ny;
3238
+ }
3239
+ const labelDist = 18;
3240
+ const c6 = stroke(el, selected);
3241
+ return /* @__PURE__ */ jsxs11("g", { children: [
3242
+ /* @__PURE__ */ jsx11("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${a})`, children: content }),
3243
+ el.label && /* @__PURE__ */ jsx11("text", { x: mx + nx * labelDist, y: my + ny * labelDist, dominantBaseline: "central", textAnchor: "middle", fill: c6, fontSize: 14, fontFamily: "sans-serif", children: String(el.label) })
3244
+ ] });
3245
+ };
3246
+ registerRenderer("charge_pos", (el, ctx) => {
3247
+ const c6 = ctx.isSelected ? "#2563eb" : "#ef4444";
3248
+ return /* @__PURE__ */ jsx11("g", { transform: `translate(${el.x1}, ${el.y1})`, opacity: ctx?.isGhost ? 0.5 : 1, children: /* @__PURE__ */ jsxs11(OriginHitPlate, { ctx, hitHalfSize: 22, selectionHalfSize: 28, children: [
3249
+ /* @__PURE__ */ jsx11("circle", { cx: "0", cy: "0", r: "10", fill: "#fecaca", stroke: c6, strokeWidth: width(el, ctx.isSelected) }),
3250
+ /* @__PURE__ */ jsx11("line", { x1: "0", y1: "-4", x2: "0", y2: "4", stroke: c6, strokeWidth: 2 }),
3251
+ /* @__PURE__ */ jsx11("line", { x1: "-4", y1: "0", x2: "4", y2: "0", stroke: c6, strokeWidth: 2 }),
3252
+ el.label ? /* @__PURE__ */ jsx11("text", { x: 0, y: -16, textAnchor: "middle", fill: c6, fontSize: el.fontSize || 14, children: String(el.label) }) : null
3253
+ ] }) });
3254
+ });
3255
+ registerRenderer("charge_neg", (el, ctx) => {
3256
+ const c6 = ctx.isSelected ? "#2563eb" : "#3b82f6";
3257
+ return /* @__PURE__ */ jsx11("g", { transform: `translate(${el.x1}, ${el.y1})`, opacity: ctx?.isGhost ? 0.5 : 1, children: /* @__PURE__ */ jsxs11(OriginHitPlate, { ctx, hitHalfSize: 22, selectionHalfSize: 28, children: [
3258
+ /* @__PURE__ */ jsx11("circle", { cx: "0", cy: "0", r: "10", fill: "#bfdbfe", stroke: c6, strokeWidth: width(el, ctx.isSelected) }),
3259
+ /* @__PURE__ */ jsx11("line", { x1: "-4", y1: "0", x2: "4", y2: "0", stroke: c6, strokeWidth: 2 }),
3260
+ el.label ? /* @__PURE__ */ jsx11("text", { x: 0, y: -16, textAnchor: "middle", fill: c6, fontSize: el.fontSize || 14, children: String(el.label) }) : null
3261
+ ] }) });
3262
+ });
3263
+ registerRenderer("dipole", (el, ctx) => {
3264
+ const w = width(el, ctx.isSelected);
3265
+ return /* @__PURE__ */ jsxs11("g", { opacity: ctx?.isGhost ? 0.5 : 1, children: [
3266
+ /* @__PURE__ */ jsx11("line", { x1: el.x1, y1: el.y1, x2: el.x2, y2: el.y2, stroke: "#64748b", strokeWidth: w, strokeDasharray: "4 4" }),
3267
+ /* @__PURE__ */ jsx11("circle", { cx: el.x1, cy: el.y1, r: "8", fill: "#fecaca", stroke: ctx.isSelected ? "#2563eb" : "#ef4444", strokeWidth: w }),
3268
+ /* @__PURE__ */ jsx11("circle", { cx: el.x2, cy: el.y2, r: "8", fill: "#bfdbfe", stroke: ctx.isSelected ? "#2563eb" : "#3b82f6", strokeWidth: w }),
3269
+ el.label ? /* @__PURE__ */ jsx11("text", { x: (el.x1 + el.x2) / 2, y: el.y1 - 16, textAnchor: "middle", fill: "#475569", fontSize: 14, children: String(el.label) }) : null,
3270
+ /* @__PURE__ */ jsx11(ChordInteractionOverlay, { el, ctx })
3271
+ ] });
3272
+ });
3273
+ var plateGlyphs = (d, isPos) => {
3274
+ const n = Math.max(2, Math.floor(d / 25));
3275
+ const glyphs = [];
3276
+ for (let i = 0; i <= n; i++) {
3277
+ const pos = i / n * d;
3278
+ glyphs.push(
3279
+ /* @__PURE__ */ jsx11("g", { transform: `translate(${pos}, 0)`, children: isPos ? /* @__PURE__ */ jsx11(Fragment5, { children: /* @__PURE__ */ jsx11("path", { d: "M-4 0 h8 M0 -4 v8", stroke: "#ffffff", strokeWidth: "2", strokeLinecap: "round" }) }) : /* @__PURE__ */ jsx11("path", { d: "M-4 0 h8", stroke: "#ffffff", strokeWidth: "2", strokeLinecap: "round" }) }, i)
3280
+ );
3281
+ }
3282
+ return glyphs;
3283
+ };
3284
+ registerRenderer("charged_plate_pos", (el, ctx) => {
3285
+ const c6 = ctx.isSelected ? "#2563eb" : "#ef4444";
3286
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
3287
+ const a = angleDeg(el.x1, el.y1, el.x2, el.y2);
3288
+ const sw6 = Math.max(12, el.strokeWidth * 4 || 8);
3289
+ return /* @__PURE__ */ jsxs11("g", { opacity: ctx?.isGhost ? 0.5 : 1, children: [
3290
+ /* @__PURE__ */ jsxs11("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${a})`, children: [
3291
+ /* @__PURE__ */ jsx11("line", { x1: 0, y1: 0, x2: d, y2: 0, stroke: c6, strokeWidth: sw6, strokeLinecap: "round" }),
3292
+ plateGlyphs(d, true)
3293
+ ] }),
3294
+ /* @__PURE__ */ jsx11(ChordInteractionOverlay, { el, ctx })
3295
+ ] });
3296
+ });
3297
+ registerRenderer("charged_plate_neg", (el, ctx) => {
3298
+ const c6 = ctx.isSelected ? "#2563eb" : "#3b82f6";
3299
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
3300
+ const a = angleDeg(el.x1, el.y1, el.x2, el.y2);
3301
+ const sw6 = Math.max(12, el.strokeWidth * 4 || 8);
3302
+ return /* @__PURE__ */ jsxs11("g", { opacity: ctx?.isGhost ? 0.5 : 1, children: [
3303
+ /* @__PURE__ */ jsxs11("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${a})`, children: [
3304
+ /* @__PURE__ */ jsx11("line", { x1: 0, y1: 0, x2: d, y2: 0, stroke: c6, strokeWidth: sw6, strokeLinecap: "round" }),
3305
+ plateGlyphs(d, false)
3306
+ ] }),
3307
+ /* @__PURE__ */ jsx11(ChordInteractionOverlay, { el, ctx })
3308
+ ] });
3309
+ });
3310
+ registerRenderer("gaussian_sphere", (el, ctx) => {
3311
+ const r = Math.max(10, distance(el.x1, el.y1, el.x2, el.y2));
3312
+ const c6 = ctx.isSelected ? "#2563eb" : "#10b981";
3313
+ return /* @__PURE__ */ jsxs11("g", { opacity: ctx?.isGhost ? 0.5 : 1, children: [
3314
+ /* @__PURE__ */ jsx11("circle", { cx: el.x1, cy: el.y1, r, fill: "#d1fae5", fillOpacity: 0.35, stroke: c6, strokeWidth: width(el, ctx.isSelected), strokeDasharray: "5 5" }),
3315
+ el.label ? /* @__PURE__ */ jsx11("text", { x: el.x1, y: el.y1 - r - 12, textAnchor: "middle", fill: c6, fontSize: 13, children: String(el.label) }) : null,
3316
+ /* @__PURE__ */ jsx11(ChordInteractionOverlay, { el, ctx })
3317
+ ] });
3318
+ });
3319
+ registerRenderer("gaussian_cylinder", (el, ctx) => {
3320
+ const d = distance(el.x1, el.y1, el.x2, el.y2);
3321
+ const a = angleDeg(el.x1, el.y1, el.x2, el.y2);
3322
+ const r = el.curveHeight ?? 40;
3323
+ const capRx = 15;
3324
+ const c6 = ctx.isSelected ? "#2563eb" : "#10b981";
3325
+ const sw6 = width(el, ctx.isSelected);
3326
+ const dash = "6 6";
3327
+ return /* @__PURE__ */ jsxs11("g", { opacity: ctx?.isGhost ? 0.5 : 1, children: [
3328
+ /* @__PURE__ */ jsxs11("g", { transform: `translate(${el.x1}, ${el.y1}) rotate(${a})`, children: [
3329
+ /* @__PURE__ */ jsx11("line", { x1: 0, y1: -r, x2: d, y2: -r, stroke: c6, strokeWidth: sw6, strokeDasharray: dash }),
3330
+ /* @__PURE__ */ jsx11("line", { x1: 0, y1: r, x2: d, y2: r, stroke: c6, strokeWidth: sw6, strokeDasharray: dash }),
3331
+ /* @__PURE__ */ jsx11("ellipse", { cx: 0, cy: 0, rx: capRx, ry: r, fill: c6, fillOpacity: 0.05, stroke: c6, strokeWidth: sw6, strokeDasharray: dash }),
3332
+ /* @__PURE__ */ jsx11("ellipse", { cx: d, cy: 0, rx: capRx, ry: r, fill: c6, fillOpacity: 0.05, stroke: c6, strokeWidth: sw6, strokeDasharray: dash }),
3333
+ el.label ? /* @__PURE__ */ jsx11("text", { x: d / 2, y: -r - 10, textAnchor: "middle", fill: c6, fontSize: el.fontSize || 14, children: String(el.label) }) : null
3334
+ ] }),
3335
+ /* @__PURE__ */ jsx11(ChordInteractionOverlay, { el, ctx })
3336
+ ] });
3337
+ });
3338
+ registerRenderer("mirror_plane", (el, ctx) => /* @__PURE__ */ jsxs11("g", { children: [
3339
+ rotated2(
3340
+ el,
3341
+ /* @__PURE__ */ jsxs11(Fragment5, { children: [
3342
+ /* @__PURE__ */ jsx11("line", { x1: 0, y1: -24, x2: 0, y2: 24, stroke: stroke(el, ctx.isSelected), strokeWidth: width(el, ctx.isSelected) }),
3343
+ /* @__PURE__ */ jsx11("line", { x1: 0, y1: -18, x2: -8, y2: -12, stroke: stroke(el, ctx.isSelected), strokeWidth: 1.5 }),
3344
+ /* @__PURE__ */ jsx11("line", { x1: 0, y1: -6, x2: -8, y2: 0, stroke: stroke(el, ctx.isSelected), strokeWidth: 1.5 }),
3345
+ /* @__PURE__ */ jsx11("line", { x1: 0, y1: 6, x2: -8, y2: 12, stroke: stroke(el, ctx.isSelected), strokeWidth: 1.5 }),
3346
+ /* @__PURE__ */ jsx11("line", { x1: 0, y1: 18, x2: -8, y2: 24, stroke: stroke(el, ctx.isSelected), strokeWidth: 1.5 })
3347
+ ] }),
3348
+ ctx.isSelected
3349
+ ),
3350
+ /* @__PURE__ */ jsx11(ChordInteractionOverlay, { el, ctx })
3351
+ ] }));
3352
+ registerRenderer("energy_level", (el, ctx) => {
3353
+ const d = Math.max(24, distance(el.x1, el.y1, el.x2, el.y2));
3354
+ const c6 = stroke(el, ctx.isSelected);
3355
+ const energyText = el.energy != null ? String(el.energy) : "";
3356
+ return /* @__PURE__ */ jsxs11("g", { children: [
3357
+ rotated2(
3358
+ el,
3359
+ /* @__PURE__ */ jsxs11(Fragment5, { children: [
3360
+ /* @__PURE__ */ jsx11("line", { x1: 0, y1: -8, x2: d, y2: -8, stroke: c6, strokeWidth: width(el, ctx.isSelected) }),
3361
+ /* @__PURE__ */ jsx11("line", { x1: 0, y1: 8, x2: d, y2: 8, stroke: c6, strokeWidth: width(el, ctx.isSelected) }),
3362
+ energyText ? /* @__PURE__ */ jsx11("text", { x: d / 2, y: 22, textAnchor: "middle", fontSize: 10, fill: "#4b5563", children: energyText }) : null
3363
+ ] }),
3364
+ ctx.isSelected
3365
+ ),
3366
+ /* @__PURE__ */ jsx11(ChordInteractionOverlay, { el, ctx })
3367
+ ] });
3368
+ });
3369
+
3370
+ // src/core/Renderer.tsx
3371
+ import { Fragment as Fragment6, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
3372
+ var UnknownElement = ({ x, y, type }) => /* @__PURE__ */ jsxs12("g", { children: [
3373
+ /* @__PURE__ */ jsx12("rect", { x: x - 20, y: y - 12, width: 40, height: 24, fill: "#fee2e2", stroke: "#ef4444" }),
3374
+ /* @__PURE__ */ jsx12("text", { x, y, textAnchor: "middle", dominantBaseline: "central", fontSize: 10, fill: "#991b1b", children: type })
3375
+ ] });
3376
+ var Renderer = ({
3377
+ elements,
3378
+ selectedId,
3379
+ drawing,
3380
+ viewMode = false,
3381
+ showGrid = true,
3382
+ gridSize = GRID_SIZE,
3383
+ className,
3384
+ svgRef,
3385
+ onBackgroundMouseDown,
3386
+ onBackgroundMouseMove,
3387
+ onBackgroundMouseUp,
3388
+ onBackgroundMouseLeave,
3389
+ onElementMouseDown,
3390
+ onHandlePointerDown
3391
+ }) => {
3392
+ const autoNodes = useMemo(() => getCircuitAutoNodes(elements), [elements]);
3393
+ return /* @__PURE__ */ jsxs12(
3394
+ "svg",
3395
+ {
3396
+ ref: svgRef,
3397
+ className,
3398
+ onMouseDown: onBackgroundMouseDown,
3399
+ onMouseMove: onBackgroundMouseMove,
3400
+ onMouseUp: onBackgroundMouseUp,
3401
+ onMouseLeave: onBackgroundMouseLeave,
3402
+ children: [
3403
+ !viewMode && showGrid && /* @__PURE__ */ jsxs12(Fragment6, { children: [
3404
+ /* @__PURE__ */ jsx12("defs", { children: /* @__PURE__ */ jsx12("pattern", { id: "svg-engine-grid", width: gridSize, height: gridSize, patternUnits: "userSpaceOnUse", children: /* @__PURE__ */ jsx12("path", { d: `M ${gridSize} 0 L 0 0 0 ${gridSize}`, fill: "none", stroke: "#e2e8f0", strokeWidth: "1" }) }) }),
3405
+ /* @__PURE__ */ jsx12("rect", { width: "100%", height: "100%", fill: "url(#svg-engine-grid)", pointerEvents: "none" })
3406
+ ] }),
3407
+ elements.map((el) => {
3408
+ const renderer = getRenderer(String(el.type));
3409
+ const ctx = {
3410
+ isSelected: selectedId === el.id,
3411
+ viewMode,
3412
+ onHandlePointerDown: !viewMode && onHandlePointerDown ? (e, kind, cpIndex) => onHandlePointerDown(e, el, kind, cpIndex) : void 0
3413
+ };
3414
+ const content = renderer ? renderer(el, ctx) : /* @__PURE__ */ jsx12(UnknownElement, { x: el.x1, y: el.y1, type: String(el.type) });
3415
+ return /* @__PURE__ */ jsx12("g", { onMouseDown: (e) => onElementMouseDown?.(e, el), cursor: viewMode ? "default" : "move", children: content }, String(el.id));
3416
+ }),
3417
+ autoNodes.map(([nx, ny], idx) => /* @__PURE__ */ jsx12("circle", { cx: nx, cy: ny, r: 4, fill: "#111827", pointerEvents: "none" }, `junction-${idx}`)),
3418
+ drawing && (() => {
3419
+ const renderer = getRenderer(String(drawing.type));
3420
+ if (!renderer) return null;
3421
+ return /* @__PURE__ */ jsx12("g", { opacity: 0.5, children: renderer(drawing, { isGhost: true, viewMode, onHandlePointerDown: void 0 }) });
3422
+ })()
3423
+ ]
3424
+ }
3425
+ );
3426
+ };
3427
+
3428
+ export {
3429
+ GRID_SIZE,
3430
+ snap,
3431
+ distance,
3432
+ angleDeg,
3433
+ getBounds,
3434
+ getMouseCoords,
3435
+ registerRenderer,
3436
+ getRenderer,
3437
+ hasRenderer,
3438
+ Renderer
3439
+ };