@almadar/ui 5.149.0 → 5.150.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1156,7 +1156,7 @@ function useEventBus() {
1156
1156
  return {
1157
1157
  ...baseBus,
1158
1158
  emit: (type, payload, source) => {
1159
- if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".")) {
1159
+ if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".") && !source?.trait) {
1160
1160
  scopeLog.warn("emit:bare-key-no-scope", { type });
1161
1161
  }
1162
1162
  baseBus.emit(type, payload, source);
@@ -8482,6 +8482,14 @@ function shapeBounds(shape) {
8482
8482
  w: shape.radius * 2 + 8,
8483
8483
  h: shape.radius * 2 + 8
8484
8484
  };
8485
+ case "ellipse":
8486
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
8487
+ return {
8488
+ x: shape.x - shape.width / 2 - 4,
8489
+ y: shape.y - shape.height / 2 - 4,
8490
+ w: shape.width + 8,
8491
+ h: shape.height + 8
8492
+ };
8485
8493
  case "rect":
8486
8494
  if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
8487
8495
  return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
@@ -8515,13 +8523,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
8515
8523
  ctx.closePath();
8516
8524
  ctx.fill();
8517
8525
  }
8518
- function drawShape(ctx, shape, width, height) {
8526
+ function drawShape(ctx, shape, width, height, allShapes) {
8519
8527
  ctx.save();
8520
8528
  const opacity = shape.opacity ?? 1;
8521
8529
  ctx.globalAlpha = opacity;
8522
8530
  const stroke = resolveColor2(shape.color, ctx, "#333333");
8523
8531
  const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
8524
8532
  ctx.lineWidth = shape.lineWidth ?? 2;
8533
+ if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
8525
8534
  switch (shape.type) {
8526
8535
  case "grid": {
8527
8536
  const step = shape.step ?? 40;
@@ -8587,6 +8596,20 @@ function drawShape(ctx, shape, width, height) {
8587
8596
  ctx.stroke();
8588
8597
  break;
8589
8598
  }
8599
+ case "ellipse": {
8600
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
8601
+ const startAngle = (shape.startAngle ?? 0) * Math.PI / 180;
8602
+ const endAngle = (shape.endAngle ?? 360) * Math.PI / 180;
8603
+ ctx.beginPath();
8604
+ ctx.ellipse(shape.x, shape.y, shape.width / 2, shape.height / 2, 0, startAngle, endAngle);
8605
+ if (fill) {
8606
+ ctx.fillStyle = fill;
8607
+ ctx.fill();
8608
+ }
8609
+ ctx.strokeStyle = stroke;
8610
+ ctx.stroke();
8611
+ break;
8612
+ }
8590
8613
  case "rect": {
8591
8614
  if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
8592
8615
  if (fill) {
@@ -8633,21 +8656,153 @@ function drawShape(ctx, shape, width, height) {
8633
8656
  ctx.fillText(shape.text, shape.x, shape.y);
8634
8657
  break;
8635
8658
  }
8659
+ case "venn-region": {
8660
+ const resolveCircles = (ids) => (ids ?? []).flatMap((id) => {
8661
+ const c = allShapes.find((s) => s.type === "circle" && s.id === id);
8662
+ return c && c.x != null && c.y != null && c.radius != null ? [{ x: c.x, y: c.y, radius: c.radius }] : [];
8663
+ });
8664
+ const inside = resolveCircles(shape.inside);
8665
+ if (inside.length === 0) break;
8666
+ const outside = resolveCircles(shape.outside);
8667
+ const off = document.createElement("canvas");
8668
+ off.width = ctx.canvas.width;
8669
+ off.height = ctx.canvas.height;
8670
+ const octx = off.getContext("2d");
8671
+ if (!octx) break;
8672
+ octx.setTransform(ctx.getTransform());
8673
+ for (const c of inside) {
8674
+ const p = new Path2D();
8675
+ p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
8676
+ octx.clip(p);
8677
+ }
8678
+ octx.fillStyle = fill ?? stroke;
8679
+ octx.fillRect(0, 0, width, height);
8680
+ octx.globalCompositeOperation = "destination-out";
8681
+ for (const c of outside) {
8682
+ const p = new Path2D();
8683
+ p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
8684
+ octx.fill(p);
8685
+ }
8686
+ ctx.save();
8687
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
8688
+ ctx.drawImage(off, 0, 0);
8689
+ ctx.restore();
8690
+ break;
8691
+ }
8636
8692
  }
8637
8693
  ctx.restore();
8638
8694
  }
8639
- exports.LearningCanvas = void 0;
8695
+ function readoutShapes(readouts, width) {
8696
+ const out = [];
8697
+ const chipH = 18;
8698
+ const gap = 6;
8699
+ let rightEdge = width - 6;
8700
+ let rowY = 6;
8701
+ for (const readout of readouts) {
8702
+ const text = `${readout.label}: ${String(readout.value)}`;
8703
+ const chipW = Math.min(170, Math.max(34, text.length * 6 + 12));
8704
+ let chipX = rightEdge - chipW;
8705
+ if (chipX < 4) {
8706
+ rowY += chipH + 4;
8707
+ rightEdge = width - 6;
8708
+ chipX = rightEdge - chipW;
8709
+ }
8710
+ const color = readout.color ?? "#334155";
8711
+ out.push({ type: "rect", x: chipX, y: rowY, width: chipW, height: chipH, color, fill: color });
8712
+ out.push({
8713
+ type: "text",
8714
+ x: chipX + chipW / 2,
8715
+ y: rowY + chipH / 2,
8716
+ text,
8717
+ color: "#ffffff",
8718
+ fontSize: 10,
8719
+ align: "center"
8720
+ });
8721
+ rightEdge = chipX - gap;
8722
+ }
8723
+ return out;
8724
+ }
8725
+ function traceShapes(panel, k, width, height) {
8726
+ const w = panel.width ?? Math.round(width * 0.32);
8727
+ const h = panel.height ?? Math.round(height * 0.28);
8728
+ const x = panel.x ?? width - w - 8;
8729
+ const y = panel.y ?? height - h - 8 - k * (h + 8);
8730
+ const allSamples = panel.series.flatMap((series) => series.samples);
8731
+ let xLo = Math.min(...allSamples.map((p) => p.x));
8732
+ let xHi = Math.max(...allSamples.map((p) => p.x));
8733
+ let yLo = Math.min(...allSamples.map((p) => p.y));
8734
+ let yHi = Math.max(...allSamples.map((p) => p.y));
8735
+ if (xLo === xHi) {
8736
+ xLo -= 1;
8737
+ xHi += 1;
8738
+ }
8739
+ if (yLo === yHi) {
8740
+ yLo -= 1;
8741
+ yHi += 1;
8742
+ }
8743
+ const backgroundColor = panel.backgroundColor ?? "#ffffff";
8744
+ const frameColor = panel.frameColor ?? "#94a3b8";
8745
+ const out = [];
8746
+ out.push({
8747
+ type: "rect",
8748
+ x,
8749
+ y,
8750
+ width: w,
8751
+ height: h,
8752
+ color: backgroundColor,
8753
+ fill: backgroundColor,
8754
+ opacity: panel.backgroundOpacity ?? 0.85
8755
+ });
8756
+ out.push({ type: "rect", x, y, width: w, height: h, color: frameColor, lineWidth: 1 });
8757
+ panel.series.forEach((series, j) => {
8758
+ const color = series.color ?? TRACE_SERIES_COLORS[j % TRACE_SERIES_COLORS.length];
8759
+ const mapped = series.samples.map((p) => ({
8760
+ x: x + 4 + (p.x - xLo) / (xHi - xLo) * (w - 8),
8761
+ y: y + h - 4 - (p.y - yLo) / (yHi - yLo) * (h - 8)
8762
+ }));
8763
+ for (let i = 1; i < mapped.length; i++) {
8764
+ out.push({
8765
+ type: "line",
8766
+ x1: mapped[i - 1].x,
8767
+ y1: mapped[i - 1].y,
8768
+ x2: mapped[i].x,
8769
+ y2: mapped[i].y,
8770
+ color,
8771
+ lineWidth: 1.5
8772
+ });
8773
+ }
8774
+ if (mapped.length > 0) {
8775
+ const last = mapped[mapped.length - 1];
8776
+ out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
8777
+ }
8778
+ if (series.label) {
8779
+ out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
8780
+ }
8781
+ });
8782
+ if (panel.yLabel) {
8783
+ out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
8784
+ }
8785
+ if (panel.xLabel) {
8786
+ out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
8787
+ }
8788
+ return out;
8789
+ }
8790
+ var DASH_PATTERNS, TRACE_SERIES_COLORS; exports.LearningCanvas = void 0;
8640
8791
  var init_LearningCanvas = __esm({
8641
8792
  "components/learning/atoms/LearningCanvas.tsx"() {
8642
8793
  "use client";
8643
8794
  init_cn();
8644
8795
  init_useEventBus();
8796
+ DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
8797
+ TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
8645
8798
  exports.LearningCanvas = ({
8646
8799
  className,
8647
8800
  width = 600,
8648
8801
  height = 400,
8649
8802
  backgroundColor,
8650
8803
  shapes = [],
8804
+ readouts,
8805
+ traces,
8651
8806
  interactive = false,
8652
8807
  animate = false,
8653
8808
  onShapeClick,
@@ -8673,6 +8828,12 @@ var init_LearningCanvas = __esm({
8673
8828
  }
8674
8829
  return -1;
8675
8830
  }, [shapes]);
8831
+ const derivedShapes = React77.useMemo(() => {
8832
+ if (!traces?.length && !readouts?.length) return shapes;
8833
+ const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
8834
+ const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
8835
+ return [...shapes, ...traceOut, ...readoutOut];
8836
+ }, [shapes, traces, readouts, width, height]);
8676
8837
  const draw = React77.useCallback(() => {
8677
8838
  const canvas = canvasRef.current;
8678
8839
  if (!canvas) return;
@@ -8689,13 +8850,13 @@ var init_LearningCanvas = __esm({
8689
8850
  ctx.fillStyle = backgroundColor;
8690
8851
  ctx.fillRect(0, 0, width, height);
8691
8852
  }
8692
- for (const shape of shapes) {
8693
- if (shape.type !== "text") drawShape(ctx, shape, width, height);
8853
+ for (const shape of derivedShapes) {
8854
+ if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
8694
8855
  }
8695
- for (const shape of shapes) {
8696
- if (shape.type === "text") drawShape(ctx, shape, width, height);
8856
+ for (const shape of derivedShapes) {
8857
+ if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
8697
8858
  }
8698
- }, [width, height, backgroundColor, shapes]);
8859
+ }, [width, height, backgroundColor, derivedShapes]);
8699
8860
  React77.useEffect(() => {
8700
8861
  draw();
8701
8862
  }, [draw]);
@@ -8763,7 +8924,363 @@ var init_LearningCanvas = __esm({
8763
8924
  };
8764
8925
  }
8765
8926
  });
8766
- var DEFAULT_BAR_COLOR, DEFAULT_CELL_COLOR, DEFAULT_POINTER_COLOR, POINTER_BAND, TOP_PAD; exports.AlgorithmCanvas = void 0;
8927
+ function layoutCircle(nodes, width, height) {
8928
+ const cx = width / 2;
8929
+ const cy = height / 2;
8930
+ const radius = Math.max(10, Math.min(cx, cy) - 40);
8931
+ const positions = /* @__PURE__ */ new Map();
8932
+ const n = nodes.length;
8933
+ nodes.forEach((node, i) => {
8934
+ const angle = 2 * Math.PI * i / Math.max(n, 1) - Math.PI / 2;
8935
+ positions.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
8936
+ });
8937
+ return positions;
8938
+ }
8939
+ function layoutTree(nodes, edges, root, width, height) {
8940
+ const nodeIds = nodes.map((n) => n.id);
8941
+ const idSet = new Set(nodeIds);
8942
+ const childrenOf = /* @__PURE__ */ new Map();
8943
+ const hasIncoming = /* @__PURE__ */ new Set();
8944
+ for (const e of edges) {
8945
+ if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
8946
+ const list = childrenOf.get(e.from) ?? [];
8947
+ list.push(e.to);
8948
+ childrenOf.set(e.from, list);
8949
+ hasIncoming.add(e.to);
8950
+ }
8951
+ const depth = /* @__PURE__ */ new Map();
8952
+ const treeChildren = /* @__PURE__ */ new Map();
8953
+ const visited = /* @__PURE__ */ new Set();
8954
+ const bfsFrom = (start) => {
8955
+ if (visited.has(start)) return;
8956
+ visited.add(start);
8957
+ depth.set(start, 0);
8958
+ const queue = [start];
8959
+ while (queue.length > 0) {
8960
+ const u = queue.shift();
8961
+ for (const v of childrenOf.get(u) ?? []) {
8962
+ if (visited.has(v)) continue;
8963
+ visited.add(v);
8964
+ depth.set(v, (depth.get(u) ?? 0) + 1);
8965
+ const list = treeChildren.get(u) ?? [];
8966
+ list.push(v);
8967
+ treeChildren.set(u, list);
8968
+ queue.push(v);
8969
+ }
8970
+ }
8971
+ };
8972
+ const primaryRoot = root && idSet.has(root) ? root : nodeIds.find((id) => !hasIncoming.has(id)) ?? nodeIds[0];
8973
+ const rootsOrder = [];
8974
+ if (primaryRoot !== void 0) {
8975
+ bfsFrom(primaryRoot);
8976
+ rootsOrder.push(primaryRoot);
8977
+ }
8978
+ for (const id of nodeIds) {
8979
+ if (!visited.has(id)) {
8980
+ bfsFrom(id);
8981
+ rootsOrder.push(id);
8982
+ }
8983
+ }
8984
+ let leafCounter = 0;
8985
+ const xSlot = /* @__PURE__ */ new Map();
8986
+ const assignXSlot = (u) => {
8987
+ const children = treeChildren.get(u) ?? [];
8988
+ if (children.length === 0) {
8989
+ const slot = leafCounter++;
8990
+ xSlot.set(u, slot);
8991
+ return slot;
8992
+ }
8993
+ const childSlots = children.map(assignXSlot);
8994
+ const avg = childSlots.reduce((a, b) => a + b, 0) / childSlots.length;
8995
+ xSlot.set(u, avg);
8996
+ return avg;
8997
+ };
8998
+ for (const r of rootsOrder) assignXSlot(r);
8999
+ let maxDepth = 0;
9000
+ for (const d of depth.values()) maxDepth = Math.max(maxDepth, d);
9001
+ const colWidth = width / Math.max(1, leafCounter);
9002
+ const rowHeight = height / (maxDepth + 1);
9003
+ const positions = /* @__PURE__ */ new Map();
9004
+ for (const id of nodeIds) {
9005
+ const slot = xSlot.get(id) ?? 0;
9006
+ const d = depth.get(id) ?? 0;
9007
+ positions.set(id, { x: slot * colWidth + colWidth / 2, y: d * rowHeight + rowHeight / 2 });
9008
+ }
9009
+ return positions;
9010
+ }
9011
+ function layoutLayered(nodes, edges, width, height) {
9012
+ const nodeIds = nodes.map((n) => n.id);
9013
+ const idSet = new Set(nodeIds);
9014
+ const adj = /* @__PURE__ */ new Map();
9015
+ const remainingIndegree = /* @__PURE__ */ new Map();
9016
+ for (const id of nodeIds) remainingIndegree.set(id, 0);
9017
+ for (const e of edges) {
9018
+ if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
9019
+ const list = adj.get(e.from) ?? [];
9020
+ list.push(e.to);
9021
+ adj.set(e.from, list);
9022
+ remainingIndegree.set(e.to, (remainingIndegree.get(e.to) ?? 0) + 1);
9023
+ }
9024
+ const layer = /* @__PURE__ */ new Map();
9025
+ const dequeued = /* @__PURE__ */ new Set();
9026
+ const queue = [];
9027
+ for (const id of nodeIds) {
9028
+ if ((remainingIndegree.get(id) ?? 0) === 0) {
9029
+ layer.set(id, 0);
9030
+ queue.push(id);
9031
+ }
9032
+ }
9033
+ while (queue.length > 0) {
9034
+ const u = queue.shift();
9035
+ dequeued.add(u);
9036
+ for (const v of adj.get(u) ?? []) {
9037
+ const candidate = (layer.get(u) ?? 0) + 1;
9038
+ layer.set(v, Math.max(layer.get(v) ?? 0, candidate));
9039
+ remainingIndegree.set(v, (remainingIndegree.get(v) ?? 0) - 1);
9040
+ if ((remainingIndegree.get(v) ?? 0) === 0 && !dequeued.has(v)) {
9041
+ queue.push(v);
9042
+ }
9043
+ }
9044
+ }
9045
+ let baseMaxLayer = 0;
9046
+ for (const id of nodeIds) {
9047
+ if (dequeued.has(id)) baseMaxLayer = Math.max(baseMaxLayer, layer.get(id) ?? 0);
9048
+ }
9049
+ const cycleLayer = baseMaxLayer + 1;
9050
+ let maxLayer = baseMaxLayer;
9051
+ for (const id of nodeIds) {
9052
+ if (!dequeued.has(id)) {
9053
+ layer.set(id, cycleLayer);
9054
+ maxLayer = cycleLayer;
9055
+ }
9056
+ }
9057
+ const colWidth = width / Math.max(1, maxLayer + 1);
9058
+ const byLayer = /* @__PURE__ */ new Map();
9059
+ for (const id of nodeIds) {
9060
+ const l = layer.get(id) ?? 0;
9061
+ const list = byLayer.get(l) ?? [];
9062
+ list.push(id);
9063
+ byLayer.set(l, list);
9064
+ }
9065
+ const positions = /* @__PURE__ */ new Map();
9066
+ for (const [l, ids] of byLayer) {
9067
+ const rowHeight = height / ids.length;
9068
+ ids.forEach((id, i) => {
9069
+ positions.set(id, { x: l * colWidth + colWidth / 2, y: i * rowHeight + rowHeight / 2 });
9070
+ });
9071
+ }
9072
+ return positions;
9073
+ }
9074
+ function computePositions(nodes, edges, layout, root, width, height) {
9075
+ switch (layout) {
9076
+ case "circle":
9077
+ return layoutCircle(nodes, width, height);
9078
+ case "tree":
9079
+ return layoutTree(nodes, edges, root, width, height);
9080
+ case "layered":
9081
+ return layoutLayered(nodes, edges, width, height);
9082
+ case "manual":
9083
+ default: {
9084
+ const positions = /* @__PURE__ */ new Map();
9085
+ for (const n of nodes) positions.set(n.id, { x: n.x ?? 0, y: n.y ?? 0 });
9086
+ return positions;
9087
+ }
9088
+ }
9089
+ }
9090
+ var NODE_STATE_COLOR, EDGE_STATE_COLOR, DEFAULT_NODE_RADIUS; exports.AlgoGraphCanvas = void 0;
9091
+ var init_AlgoGraphCanvas = __esm({
9092
+ "components/learning/molecules/AlgoGraphCanvas.tsx"() {
9093
+ "use client";
9094
+ init_atoms();
9095
+ init_Stack();
9096
+ init_LearningCanvas();
9097
+ NODE_STATE_COLOR = {
9098
+ unvisited: "#cbd5e1",
9099
+ frontier: "#f59e0b",
9100
+ current: "#ef4444",
9101
+ visited: "#22c55e",
9102
+ goal: "#8b5cf6",
9103
+ path: "#0ea5e9"
9104
+ };
9105
+ EDGE_STATE_COLOR = {
9106
+ default: "#9ca3af",
9107
+ tree: "#16a34a",
9108
+ relaxed: "#f59e0b",
9109
+ candidate: "#38bdf8",
9110
+ path: "#dc2626"
9111
+ };
9112
+ DEFAULT_NODE_RADIUS = 18;
9113
+ exports.AlgoGraphCanvas = ({
9114
+ className,
9115
+ width = 600,
9116
+ height = 400,
9117
+ title,
9118
+ backgroundColor,
9119
+ nodes = [],
9120
+ edges = [],
9121
+ layout = "manual",
9122
+ root,
9123
+ shapes = [],
9124
+ interactive = false,
9125
+ animate = false,
9126
+ onShapeClick,
9127
+ onNodeClick,
9128
+ isLoading,
9129
+ error
9130
+ }) => {
9131
+ const nodeById = React77.useMemo(() => {
9132
+ const m = /* @__PURE__ */ new Map();
9133
+ for (const n of nodes) m.set(n.id, n);
9134
+ return m;
9135
+ }, [nodes]);
9136
+ const nodeIndexById = React77.useMemo(() => {
9137
+ const m = /* @__PURE__ */ new Map();
9138
+ nodes.forEach((n, i) => m.set(n.id, i));
9139
+ return m;
9140
+ }, [nodes]);
9141
+ const derivedShapes = React77.useMemo(() => {
9142
+ const out = [];
9143
+ const positions = computePositions(nodes, edges, layout, root, width, height);
9144
+ const edgeGeoms = [];
9145
+ for (const e of edges) {
9146
+ const a = nodeById.get(e.from);
9147
+ const b = nodeById.get(e.to);
9148
+ const posA = positions.get(e.from);
9149
+ const posB = positions.get(e.to);
9150
+ if (!a || !b || !posA || !posB) continue;
9151
+ const rA = a.radius ?? DEFAULT_NODE_RADIUS;
9152
+ const rB = b.radius ?? DEFAULT_NODE_RADIUS;
9153
+ const dx = posB.x - posA.x;
9154
+ const dy = posB.y - posA.y;
9155
+ const dist = Math.max(1e-6, Math.hypot(dx, dy));
9156
+ const ux = dx / dist;
9157
+ const uy = dy / dist;
9158
+ edgeGeoms.push({
9159
+ directed: e.directed ?? false,
9160
+ x1: posA.x + ux * rA,
9161
+ y1: posA.y + uy * rA,
9162
+ x2: posB.x - ux * rB,
9163
+ y2: posB.y - uy * rB,
9164
+ color: e.color ?? EDGE_STATE_COLOR[e.state ?? "default"],
9165
+ label: e.label ?? (e.weight != null ? String(e.weight) : void 0)
9166
+ });
9167
+ }
9168
+ for (const g of edgeGeoms) {
9169
+ out.push({
9170
+ type: g.directed ? "arrow" : "line",
9171
+ x1: g.x1,
9172
+ y1: g.y1,
9173
+ x2: g.x2,
9174
+ y2: g.y2,
9175
+ color: g.color,
9176
+ lineWidth: 2
9177
+ });
9178
+ }
9179
+ for (const g of edgeGeoms) {
9180
+ if (g.label === void 0) continue;
9181
+ const midX = (g.x1 + g.x2) / 2;
9182
+ const midY = (g.y1 + g.y2) / 2;
9183
+ const dx = g.x2 - g.x1;
9184
+ const dy = g.y2 - g.y1;
9185
+ const dist = Math.max(1e-6, Math.hypot(dx, dy));
9186
+ const perpX = -(dy / dist);
9187
+ const perpY = dx / dist;
9188
+ out.push({
9189
+ type: "text",
9190
+ x: midX + perpX * 10,
9191
+ y: midY + perpY * 10,
9192
+ text: g.label,
9193
+ fontSize: 11,
9194
+ align: "center",
9195
+ color: "#374151"
9196
+ });
9197
+ }
9198
+ const nodeGeoms = [];
9199
+ for (const n of nodes) {
9200
+ const pos = positions.get(n.id);
9201
+ if (!pos) continue;
9202
+ nodeGeoms.push({
9203
+ id: n.id,
9204
+ x: pos.x,
9205
+ y: pos.y,
9206
+ radius: n.radius ?? DEFAULT_NODE_RADIUS,
9207
+ color: n.color ?? NODE_STATE_COLOR[n.state ?? "unvisited"],
9208
+ label: n.label,
9209
+ badge: n.badge
9210
+ });
9211
+ }
9212
+ for (const g of nodeGeoms) {
9213
+ out.push({ type: "circle", id: g.id, x: g.x, y: g.y, radius: g.radius, color: g.color, fill: `${g.color}33` });
9214
+ }
9215
+ const badgeGeoms = [];
9216
+ for (const g of nodeGeoms) {
9217
+ if (!g.badge) continue;
9218
+ const w = Math.min(42, Math.max(18, g.badge.text.length * 6 + 10));
9219
+ badgeGeoms.push({
9220
+ cx: g.x + g.radius * 0.75,
9221
+ cy: g.y - g.radius * 0.75,
9222
+ w,
9223
+ h: 14,
9224
+ // Borderless pill: same color drives both stroke and fill.
9225
+ color: g.badge.color ?? "#1e293b",
9226
+ text: g.badge.text
9227
+ });
9228
+ }
9229
+ for (const b of badgeGeoms) {
9230
+ out.push({ type: "rect", x: b.cx - b.w / 2, y: b.cy - b.h / 2, width: b.w, height: b.h, color: b.color, fill: b.color });
9231
+ }
9232
+ for (const b of badgeGeoms) {
9233
+ out.push({ type: "text", x: b.cx, y: b.cy, text: b.text, fontSize: 9, align: "center", color: "#ffffff" });
9234
+ }
9235
+ for (const g of nodeGeoms) {
9236
+ if (g.label === void 0) continue;
9237
+ out.push({
9238
+ type: "text",
9239
+ x: g.x,
9240
+ y: g.y + g.radius + 14,
9241
+ text: g.label,
9242
+ fontSize: 12,
9243
+ align: "center",
9244
+ color: "#111827"
9245
+ });
9246
+ }
9247
+ out.push(...shapes);
9248
+ return out;
9249
+ }, [nodes, edges, layout, root, width, height, nodeById, shapes]);
9250
+ const handleShapeClick = React77.useCallback(
9251
+ (payload) => {
9252
+ if (payload.type === "circle" && payload.id) {
9253
+ const node = nodeById.get(payload.id);
9254
+ const idx = nodeIndexById.get(payload.id);
9255
+ if (node && idx !== void 0) {
9256
+ onNodeClick?.({ id: node.id, label: node.label, index: idx });
9257
+ }
9258
+ }
9259
+ onShapeClick?.(payload);
9260
+ },
9261
+ [nodeById, nodeIndexById, onNodeClick, onShapeClick]
9262
+ );
9263
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
9264
+ title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
9265
+ /* @__PURE__ */ jsxRuntime.jsx(
9266
+ exports.LearningCanvas,
9267
+ {
9268
+ width,
9269
+ height,
9270
+ backgroundColor,
9271
+ shapes: derivedShapes,
9272
+ interactive,
9273
+ animate,
9274
+ onShapeClick: onShapeClick || onNodeClick ? handleShapeClick : void 0,
9275
+ isLoading,
9276
+ error
9277
+ }
9278
+ )
9279
+ ] }) });
9280
+ };
9281
+ }
9282
+ });
9283
+ var DEFAULT_BAR_COLOR, DEFAULT_CELL_COLOR, DEFAULT_POINTER_COLOR, POINTER_BAND, TOP_PAD, PANEL_FAMILY_ORDER, RANGE_COLOR_DEFAULT, RANGE_FILL_OPACITY, BRACKET_TOP_OFFSET, BRACKET_ROW_H, BRACKET_TICK_H, BRACKET_LABEL_OFFSET, SLOT_EMPTY_FILL, SLOT_EMPTY_STROKE, SLOT_FILLED_STROKE, SLOT_HIGHLIGHT_DEFAULT, SLOT_VALUE_TEXT_COLOR, FRAME_ACTIVE_COLOR, FRAME_RETURNING_COLOR, FRAME_DONE_COLOR, FRAME_LABEL_COLOR, FRAME_DETAIL_COLOR, FRAME_TWO_LINE_MIN_H, BUCKET_INDEX_FILL, BUCKET_INDEX_STROKE, BUCKET_INDEX_TEXT, BUCKET_ENTRY_TEXT, BUCKET_ENTRY_DEFAULT, BUCKET_ENTRY_HIGHLIGHT, BUCKET_ENTRY_PROBING, BUCKET_ENTRY_MIN_W, BUCKET_ENTRY_MAX_W, AXIS_LABEL_COLOR, AXIS_LABEL_FONT_SIZE, CORNER_TEXT_COLOR, CORNER_FONT_SIZE, CORNER_MIN_CELL, CORNER_INSET_X, CORNER_INSET_Y, AUX_PRIMARY_RATIO, AUX_LABEL_BAND, AUX_BASELINE_PAD; exports.AlgorithmCanvas = void 0;
8767
9284
  var init_AlgorithmCanvas = __esm({
8768
9285
  "components/learning/molecules/AlgorithmCanvas.tsx"() {
8769
9286
  "use client";
@@ -8775,6 +9292,43 @@ var init_AlgorithmCanvas = __esm({
8775
9292
  DEFAULT_POINTER_COLOR = "#dc2626";
8776
9293
  POINTER_BAND = 34;
8777
9294
  TOP_PAD = 26;
9295
+ PANEL_FAMILY_ORDER = ["bars", "slots", "cells", "buckets", "frames"];
9296
+ RANGE_COLOR_DEFAULT = "#3b82f6";
9297
+ RANGE_FILL_OPACITY = 0.15;
9298
+ BRACKET_TOP_OFFSET = 16;
9299
+ BRACKET_ROW_H = 14;
9300
+ BRACKET_TICK_H = 6;
9301
+ BRACKET_LABEL_OFFSET = 6;
9302
+ SLOT_EMPTY_FILL = "#f1f5f9";
9303
+ SLOT_EMPTY_STROKE = "#cbd5e1";
9304
+ SLOT_FILLED_STROKE = "#9ca3af";
9305
+ SLOT_HIGHLIGHT_DEFAULT = "#f59e0b";
9306
+ SLOT_VALUE_TEXT_COLOR = "#ffffff";
9307
+ FRAME_ACTIVE_COLOR = "#3b82f6";
9308
+ FRAME_RETURNING_COLOR = "#f59e0b";
9309
+ FRAME_DONE_COLOR = "#94a3b8";
9310
+ FRAME_LABEL_COLOR = "#ffffff";
9311
+ FRAME_DETAIL_COLOR = "#e2e8f0";
9312
+ FRAME_TWO_LINE_MIN_H = 22;
9313
+ BUCKET_INDEX_FILL = "#e2e8f0";
9314
+ BUCKET_INDEX_STROKE = "#9ca3af";
9315
+ BUCKET_INDEX_TEXT = "#374151";
9316
+ BUCKET_ENTRY_TEXT = "#ffffff";
9317
+ BUCKET_ENTRY_DEFAULT = "#3b82f6";
9318
+ BUCKET_ENTRY_HIGHLIGHT = "#f59e0b";
9319
+ BUCKET_ENTRY_PROBING = "#38bdf8";
9320
+ BUCKET_ENTRY_MIN_W = 24;
9321
+ BUCKET_ENTRY_MAX_W = 64;
9322
+ AXIS_LABEL_COLOR = "#6b7280";
9323
+ AXIS_LABEL_FONT_SIZE = 10;
9324
+ CORNER_TEXT_COLOR = "#111827";
9325
+ CORNER_FONT_SIZE = 7;
9326
+ CORNER_MIN_CELL = 28;
9327
+ CORNER_INSET_X = 3;
9328
+ CORNER_INSET_Y = 6;
9329
+ AUX_PRIMARY_RATIO = 0.6;
9330
+ AUX_LABEL_BAND = 18;
9331
+ AUX_BASELINE_PAD = 8;
8778
9332
  exports.AlgorithmCanvas = ({
8779
9333
  className,
8780
9334
  width = 600,
@@ -8784,6 +9338,14 @@ var init_AlgorithmCanvas = __esm({
8784
9338
  bars = [],
8785
9339
  cells = [],
8786
9340
  pointers = [],
9341
+ ranges = [],
9342
+ slots = [],
9343
+ slotOrientation = "horizontal",
9344
+ frames = [],
9345
+ buckets = [],
9346
+ auxBars = [],
9347
+ rowLabels = [],
9348
+ colLabels = [],
8787
9349
  shapes = [],
8788
9350
  interactive = false,
8789
9351
  animate = false,
@@ -8793,12 +9355,35 @@ var init_AlgorithmCanvas = __esm({
8793
9355
  }) => {
8794
9356
  const derivedShapes = React77.useMemo(() => {
8795
9357
  const out = [];
9358
+ const presence = {
9359
+ bars: bars.length > 0,
9360
+ slots: slots.length > 0,
9361
+ cells: cells.length > 0,
9362
+ buckets: buckets.length > 0,
9363
+ frames: frames.length > 0
9364
+ };
9365
+ const panelCount = PANEL_FAMILY_ORDER.filter((f3) => presence[f3]).length;
9366
+ const panelHeight = height / Math.max(1, panelCount);
9367
+ const panelY = { bars: 0, slots: 0, cells: 0, buckets: 0, frames: 0 };
9368
+ let compactIndex = 0;
9369
+ PANEL_FAMILY_ORDER.forEach((f3) => {
9370
+ if (presence[f3]) {
9371
+ panelY[f3] = compactIndex * panelHeight;
9372
+ compactIndex += 1;
9373
+ }
9374
+ });
8796
9375
  if (bars.length > 0) {
9376
+ const panelYBars = panelY.bars;
8797
9377
  const slot = width / bars.length;
8798
9378
  const barW = slot * 0.8;
8799
9379
  const gap = slot * 0.1;
8800
- const baseline = height - POINTER_BAND;
8801
- const usableH = baseline - TOP_PAD;
9380
+ const bracketRanges = ranges.filter((r) => r.kind === "bracket");
9381
+ const bracketCount = bracketRanges.length;
9382
+ const bracketHeadroom = bracketCount > 0 ? BRACKET_TOP_OFFSET + bracketCount * BRACKET_ROW_H : 0;
9383
+ const hasAux = auxBars.length > 0;
9384
+ const primaryH = hasAux ? panelHeight * AUX_PRIMARY_RATIO : panelHeight;
9385
+ const baseline = panelYBars + primaryH - POINTER_BAND;
9386
+ const usableH = baseline - (panelYBars + TOP_PAD + bracketHeadroom);
8802
9387
  const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
8803
9388
  bars.forEach((bar, i) => {
8804
9389
  const v = Number.isFinite(bar.value) ? bar.value : 0;
@@ -8828,6 +9413,89 @@ var init_AlgorithmCanvas = __esm({
8828
9413
  });
8829
9414
  }
8830
9415
  });
9416
+ ranges.forEach((r) => {
9417
+ const kind = r.kind ?? "fill";
9418
+ if (kind !== "fill") return;
9419
+ const color = r.color ?? RANGE_COLOR_DEFAULT;
9420
+ out.push({
9421
+ type: "rect",
9422
+ x: r.from * slot,
9423
+ y: panelYBars,
9424
+ width: (r.to - r.from + 1) * slot,
9425
+ height: primaryH,
9426
+ color,
9427
+ fill: color,
9428
+ opacity: RANGE_FILL_OPACITY
9429
+ });
9430
+ if (r.label) {
9431
+ out.push({
9432
+ type: "text",
9433
+ x: r.from * slot + 4,
9434
+ // Sits below the bracket block (if any) so fill and bracket labels never collide.
9435
+ y: panelYBars + 10 + bracketHeadroom,
9436
+ text: r.label,
9437
+ color,
9438
+ fontSize: 10,
9439
+ align: "left"
9440
+ });
9441
+ }
9442
+ });
9443
+ bracketRanges.forEach((r, i) => {
9444
+ const bracketY = panelYBars + BRACKET_TOP_OFFSET + i * BRACKET_ROW_H;
9445
+ const x1 = r.from * slot + slot * 0.1;
9446
+ const x2 = (r.to + 1) * slot - slot * 0.1;
9447
+ const color = r.color ?? RANGE_COLOR_DEFAULT;
9448
+ out.push({ type: "line", x1, y1: bracketY, x2, y2: bracketY, color, lineWidth: 2 });
9449
+ out.push({ type: "line", x1, y1: bracketY, x2: x1, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
9450
+ out.push({ type: "line", x1: x2, y1: bracketY, x2, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
9451
+ if (r.label) {
9452
+ out.push({
9453
+ type: "text",
9454
+ x: (x1 + x2) / 2,
9455
+ y: bracketY - BRACKET_LABEL_OFFSET,
9456
+ text: r.label,
9457
+ color,
9458
+ fontSize: 10,
9459
+ align: "center"
9460
+ });
9461
+ }
9462
+ });
9463
+ if (hasAux) {
9464
+ const auxH = panelHeight - primaryH;
9465
+ const slot2 = width / auxBars.length;
9466
+ const auxBaseline = panelYBars + primaryH + auxH - AUX_BASELINE_PAD;
9467
+ const auxUsableH = auxBaseline - (panelYBars + primaryH + AUX_LABEL_BAND);
9468
+ const maxAuxV = Math.max(1, ...auxBars.map((b) => Number.isFinite(b.value) ? b.value : 0));
9469
+ auxBars.forEach((bar, i) => {
9470
+ const v = Number.isFinite(bar.value) ? bar.value : 0;
9471
+ const bh = Math.max(0, v / maxAuxV * auxUsableH);
9472
+ const x = i * slot2 + slot2 * 0.1;
9473
+ const w = slot2 * 0.8;
9474
+ const color = bar.color ?? DEFAULT_BAR_COLOR;
9475
+ out.push({
9476
+ type: "rect",
9477
+ id: `auxbar-${i}`,
9478
+ x,
9479
+ y: auxBaseline - bh,
9480
+ width: w,
9481
+ height: bh,
9482
+ color,
9483
+ fill: color
9484
+ });
9485
+ const label = bar.label ?? (auxBars.length <= 24 ? String(v) : void 0);
9486
+ if (label) {
9487
+ out.push({
9488
+ type: "text",
9489
+ x: x + w / 2,
9490
+ y: auxBaseline - bh - 8,
9491
+ text: label,
9492
+ color: "#374151",
9493
+ fontSize: 11,
9494
+ align: "center"
9495
+ });
9496
+ }
9497
+ });
9498
+ }
8831
9499
  pointers.forEach((p) => {
8832
9500
  if (p.index < 0 || p.index >= bars.length) return;
8833
9501
  const cx = p.index * slot + slot / 2;
@@ -8835,7 +9503,7 @@ var init_AlgorithmCanvas = __esm({
8835
9503
  out.push({
8836
9504
  type: "arrow",
8837
9505
  x1: cx,
8838
- y1: height - 6,
9506
+ y1: panelYBars + primaryH - 18,
8839
9507
  x2: cx,
8840
9508
  y2: baseline + 4,
8841
9509
  color,
@@ -8845,7 +9513,7 @@ var init_AlgorithmCanvas = __esm({
8845
9513
  out.push({
8846
9514
  type: "text",
8847
9515
  x: cx,
8848
- y: height - 22,
9516
+ y: panelYBars + primaryH - 8,
8849
9517
  text: p.label,
8850
9518
  color,
8851
9519
  fontSize: 11,
@@ -8854,14 +9522,111 @@ var init_AlgorithmCanvas = __esm({
8854
9522
  }
8855
9523
  });
8856
9524
  }
9525
+ if (slots.length > 0) {
9526
+ const panelYSlots = panelY.slots;
9527
+ const n = slots.length;
9528
+ const vertical = slotOrientation === "vertical";
9529
+ const vBoxH = panelHeight / n;
9530
+ const vBoxW = Math.min(width * 0.5, 120);
9531
+ const vBoxX = (width - vBoxW) / 2;
9532
+ const hCellW = width / n;
9533
+ const hBoxW = hCellW * 0.82;
9534
+ const hBoxH = Math.min(panelHeight * 0.6, 48);
9535
+ const hBoxY = panelYSlots + (panelHeight - hBoxH) / 2;
9536
+ const slotBox = (i) => vertical ? { x: vBoxX, y: panelYSlots + panelHeight - (i + 1) * vBoxH, width: vBoxW, height: vBoxH } : { x: i * hCellW + (hCellW - hBoxW) / 2, y: hBoxY, width: hBoxW, height: hBoxH };
9537
+ slots.forEach((s, i) => {
9538
+ const box = slotBox(i);
9539
+ const state = s.state ?? "filled";
9540
+ const fill = state === "empty" ? SLOT_EMPTY_FILL : state === "highlight" ? s.color ?? SLOT_HIGHLIGHT_DEFAULT : s.color ?? DEFAULT_BAR_COLOR;
9541
+ const stroke = state === "empty" ? SLOT_EMPTY_STROKE : SLOT_FILLED_STROKE;
9542
+ out.push({
9543
+ type: "rect",
9544
+ id: `slot-${i}`,
9545
+ x: box.x,
9546
+ y: box.y,
9547
+ width: box.width,
9548
+ height: box.height,
9549
+ color: stroke,
9550
+ fill
9551
+ });
9552
+ if (s.value != null && state !== "empty") {
9553
+ out.push({
9554
+ type: "text",
9555
+ x: box.x + box.width / 2,
9556
+ y: box.y + box.height / 2,
9557
+ text: String(s.value),
9558
+ color: SLOT_VALUE_TEXT_COLOR,
9559
+ fontSize: 12,
9560
+ align: "center"
9561
+ });
9562
+ }
9563
+ });
9564
+ if (bars.length === 0) {
9565
+ pointers.forEach((p) => {
9566
+ if (p.index < 0 || p.index >= slots.length) return;
9567
+ const box = slotBox(p.index);
9568
+ const color = p.color ?? DEFAULT_POINTER_COLOR;
9569
+ if (vertical) {
9570
+ const cy = box.y + box.height / 2;
9571
+ out.push({
9572
+ type: "arrow",
9573
+ x1: box.x + box.width + 34,
9574
+ y1: cy,
9575
+ x2: box.x + box.width + 4,
9576
+ y2: cy,
9577
+ color,
9578
+ lineWidth: 2
9579
+ });
9580
+ if (p.label) {
9581
+ out.push({
9582
+ type: "text",
9583
+ x: box.x + box.width + 38,
9584
+ y: cy,
9585
+ text: p.label,
9586
+ color,
9587
+ fontSize: 11,
9588
+ align: "left"
9589
+ });
9590
+ }
9591
+ } else {
9592
+ const cx = box.x + box.width / 2;
9593
+ out.push({
9594
+ type: "arrow",
9595
+ x1: cx,
9596
+ y1: panelYSlots + panelHeight - 18,
9597
+ x2: cx,
9598
+ y2: box.y + box.height + 4,
9599
+ color,
9600
+ lineWidth: 2
9601
+ });
9602
+ if (p.label) {
9603
+ out.push({
9604
+ type: "text",
9605
+ x: cx,
9606
+ y: panelYSlots + panelHeight - 8,
9607
+ text: p.label,
9608
+ color,
9609
+ fontSize: 11,
9610
+ align: "center"
9611
+ });
9612
+ }
9613
+ }
9614
+ });
9615
+ }
9616
+ }
8857
9617
  if (cells.length > 0) {
9618
+ const panelYCells = panelY.cells;
8858
9619
  const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
8859
9620
  const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
8860
- const cw = width / maxCol;
8861
- const ch = height / maxRow;
9621
+ const colLabelH = colLabels.length > 0 ? 16 : 0;
9622
+ const rowLabelW = rowLabels.length > 0 ? 20 : 0;
9623
+ const gridX0 = rowLabelW;
9624
+ const gridY0 = panelYCells + colLabelH;
9625
+ const cw = (width - rowLabelW) / maxCol;
9626
+ const ch = (panelHeight - colLabelH) / maxRow;
8862
9627
  cells.forEach((c, i) => {
8863
- const x = c.col * cw;
8864
- const y = c.row * ch;
9628
+ const x = gridX0 + c.col * cw;
9629
+ const y = gridY0 + c.row * ch;
8865
9630
  const color = c.color ?? DEFAULT_CELL_COLOR;
8866
9631
  out.push({
8867
9632
  type: "rect",
@@ -8885,11 +9650,207 @@ var init_AlgorithmCanvas = __esm({
8885
9650
  align: "center"
8886
9651
  });
8887
9652
  }
9653
+ if (c.corner && cw >= CORNER_MIN_CELL && ch >= CORNER_MIN_CELL) {
9654
+ const { tl, tr, bl, br } = c.corner;
9655
+ if (tl) {
9656
+ out.push({
9657
+ type: "text",
9658
+ x: x + CORNER_INSET_X,
9659
+ y: y + CORNER_INSET_Y,
9660
+ text: tl,
9661
+ color: CORNER_TEXT_COLOR,
9662
+ fontSize: CORNER_FONT_SIZE,
9663
+ align: "left"
9664
+ });
9665
+ }
9666
+ if (tr) {
9667
+ out.push({
9668
+ type: "text",
9669
+ x: x + cw - CORNER_INSET_X,
9670
+ y: y + CORNER_INSET_Y,
9671
+ text: tr,
9672
+ color: CORNER_TEXT_COLOR,
9673
+ fontSize: CORNER_FONT_SIZE,
9674
+ align: "right"
9675
+ });
9676
+ }
9677
+ if (bl) {
9678
+ out.push({
9679
+ type: "text",
9680
+ x: x + CORNER_INSET_X,
9681
+ y: y + ch - CORNER_INSET_Y,
9682
+ text: bl,
9683
+ color: CORNER_TEXT_COLOR,
9684
+ fontSize: CORNER_FONT_SIZE,
9685
+ align: "left"
9686
+ });
9687
+ }
9688
+ if (br) {
9689
+ out.push({
9690
+ type: "text",
9691
+ x: x + cw - CORNER_INSET_X,
9692
+ y: y + ch - CORNER_INSET_Y,
9693
+ text: br,
9694
+ color: CORNER_TEXT_COLOR,
9695
+ fontSize: CORNER_FONT_SIZE,
9696
+ align: "right"
9697
+ });
9698
+ }
9699
+ }
9700
+ });
9701
+ colLabels.forEach((l) => {
9702
+ out.push({
9703
+ type: "text",
9704
+ x: gridX0 + l.index * cw + cw / 2,
9705
+ y: panelYCells + colLabelH / 2,
9706
+ text: l.text,
9707
+ color: l.color ?? AXIS_LABEL_COLOR,
9708
+ fontSize: AXIS_LABEL_FONT_SIZE,
9709
+ align: "center"
9710
+ });
9711
+ });
9712
+ rowLabels.forEach((l) => {
9713
+ out.push({
9714
+ type: "text",
9715
+ x: rowLabelW - 6,
9716
+ y: gridY0 + l.index * ch + ch / 2,
9717
+ text: l.text,
9718
+ color: l.color ?? AXIS_LABEL_COLOR,
9719
+ fontSize: AXIS_LABEL_FONT_SIZE,
9720
+ align: "right"
9721
+ });
9722
+ });
9723
+ }
9724
+ if (buckets.length > 0) {
9725
+ const panelYBuckets = panelY.buckets;
9726
+ const bucketCount = Math.max(0, ...buckets.map((b) => b.index)) + 1;
9727
+ const rowH = panelHeight / bucketCount;
9728
+ const indexColW = Math.min(width * 0.12, 40);
9729
+ const maxChainLen = Math.max(1, ...buckets.map((b) => b.entries.length));
9730
+ const entryW = Math.min(BUCKET_ENTRY_MAX_W, Math.max(BUCKET_ENTRY_MIN_W, (width - indexColW - 8) / maxChainLen));
9731
+ const maxVisible = Math.floor((width - indexColW - 4) / entryW);
9732
+ buckets.forEach((b) => {
9733
+ const rowY = panelYBuckets + b.index * rowH;
9734
+ out.push({
9735
+ type: "rect",
9736
+ id: `bucket-index-${b.index}`,
9737
+ x: 2,
9738
+ y: rowY + 2,
9739
+ width: indexColW - 4,
9740
+ height: rowH - 4,
9741
+ color: BUCKET_INDEX_STROKE,
9742
+ fill: BUCKET_INDEX_FILL
9743
+ });
9744
+ out.push({
9745
+ type: "text",
9746
+ x: 2 + (indexColW - 4) / 2,
9747
+ y: rowY + rowH / 2,
9748
+ text: String(b.index),
9749
+ color: BUCKET_INDEX_TEXT,
9750
+ fontSize: 10,
9751
+ align: "center"
9752
+ });
9753
+ const overflow = b.entries.length > maxVisible;
9754
+ const visibleCount = overflow ? Math.max(0, maxVisible - 1) : b.entries.length;
9755
+ for (let j = 0; j < visibleCount; j++) {
9756
+ const entry = b.entries[j];
9757
+ const ex = indexColW + 4 + j * entryW;
9758
+ const state = entry.state ?? "default";
9759
+ const fill = state === "highlight" ? entry.color ?? BUCKET_ENTRY_HIGHLIGHT : state === "probing" ? entry.color ?? BUCKET_ENTRY_PROBING : entry.color ?? BUCKET_ENTRY_DEFAULT;
9760
+ out.push({
9761
+ type: "rect",
9762
+ id: `bucket-${b.index}-${j}`,
9763
+ x: ex,
9764
+ y: rowY + 2,
9765
+ width: entryW - 2,
9766
+ height: rowH - 4,
9767
+ color: fill,
9768
+ fill
9769
+ });
9770
+ if (entryW >= 20 && rowH >= 16) {
9771
+ out.push({
9772
+ type: "text",
9773
+ x: ex + (entryW - 2) / 2,
9774
+ y: rowY + rowH / 2,
9775
+ text: entry.label,
9776
+ color: BUCKET_ENTRY_TEXT,
9777
+ fontSize: 10,
9778
+ align: "center"
9779
+ });
9780
+ }
9781
+ }
9782
+ if (overflow) {
9783
+ const ex = indexColW + 4 + visibleCount * entryW;
9784
+ out.push({
9785
+ type: "rect",
9786
+ id: `bucket-${b.index}-overflow`,
9787
+ x: ex,
9788
+ y: rowY + 2,
9789
+ width: entryW - 2,
9790
+ height: rowH - 4,
9791
+ color: BUCKET_ENTRY_DEFAULT,
9792
+ fill: BUCKET_ENTRY_DEFAULT
9793
+ });
9794
+ out.push({
9795
+ type: "text",
9796
+ x: ex + (entryW - 2) / 2,
9797
+ y: rowY + rowH / 2,
9798
+ text: `+${b.entries.length - visibleCount}`,
9799
+ color: BUCKET_ENTRY_TEXT,
9800
+ fontSize: 10,
9801
+ align: "center"
9802
+ });
9803
+ }
9804
+ });
9805
+ }
9806
+ if (frames.length > 0) {
9807
+ const panelYFrames = panelY.frames;
9808
+ const n = frames.length;
9809
+ const frameH = panelHeight / n;
9810
+ const x = 8;
9811
+ const w = width - 16;
9812
+ frames.forEach((f3, i) => {
9813
+ const y = panelYFrames + panelHeight - (i + 1) * frameH;
9814
+ const state = f3.state ?? "active";
9815
+ const fill = state === "returning" ? f3.color ?? FRAME_RETURNING_COLOR : state === "done" ? f3.color ?? FRAME_DONE_COLOR : f3.color ?? FRAME_ACTIVE_COLOR;
9816
+ out.push({ type: "rect", id: `frame-${i}`, x, y, width: w, height: frameH, color: fill, fill });
9817
+ if (frameH >= FRAME_TWO_LINE_MIN_H) {
9818
+ out.push({
9819
+ type: "text",
9820
+ x: 16,
9821
+ y: y + frameH * 0.35,
9822
+ text: f3.label,
9823
+ color: FRAME_LABEL_COLOR,
9824
+ fontSize: 10,
9825
+ align: "left"
9826
+ });
9827
+ if (f3.detail) {
9828
+ out.push({
9829
+ type: "text",
9830
+ x: 16,
9831
+ y: y + frameH * 0.7,
9832
+ text: f3.detail,
9833
+ color: FRAME_DETAIL_COLOR,
9834
+ fontSize: 10,
9835
+ align: "left"
9836
+ });
9837
+ }
9838
+ } else {
9839
+ out.push({
9840
+ type: "text",
9841
+ x: 16,
9842
+ y: y + frameH / 2,
9843
+ text: f3.label,
9844
+ color: FRAME_LABEL_COLOR,
9845
+ fontSize: 10,
9846
+ align: "left"
9847
+ });
9848
+ }
8888
9849
  });
8889
9850
  }
8890
9851
  out.push(...shapes);
8891
9852
  return out;
8892
- }, [bars, cells, pointers, shapes, width, height]);
9853
+ }, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
8893
9854
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
8894
9855
  title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
8895
9856
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -9903,6 +10864,12 @@ function LearningScene3D({
9903
10864
  const unitId = event.payload?.unitId;
9904
10865
  if (typeof unitId === "string") onItemClickRef.current?.(unitId);
9905
10866
  });
10867
+ if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && post?.bloom) {
10868
+ const unknownKeys = Object.keys(post.bloom).filter((k) => !KNOWN_BLOOM_KEYS.has(k));
10869
+ if (unknownKeys.length > 0) {
10870
+ sceneLog.debug("post.bloom has unrecognized keys \u2014 only intensity/threshold/smoothing are read", { unknownKeys });
10871
+ }
10872
+ }
9906
10873
  const props3d = {
9907
10874
  drawables,
9908
10875
  isLoading,
@@ -9968,7 +10935,7 @@ function cylinderBetween(from, to, radius, color) {
9968
10935
  material: { color }
9969
10936
  };
9970
10937
  }
9971
- function arrowBetween(from, to, color, shaftRadius = 0.08) {
10938
+ function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
9972
10939
  const len = segmentLength(from, to);
9973
10940
  if (len < 1e-6) return null;
9974
10941
  const tipLen = Math.min(shaftRadius * 8, len * 0.35);
@@ -9996,6 +10963,7 @@ function arrowBetween(from, to, color, shaftRadius = 0.08) {
9996
10963
  };
9997
10964
  return {
9998
10965
  type: "draw-group",
10966
+ ...id !== void 0 ? { id } : {},
9999
10967
  position: { x: from[0], y: from[1], z: from[2] },
10000
10968
  items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
10001
10969
  };
@@ -10022,20 +10990,228 @@ function get3DClickPayload(onShapeClick, idToIndex) {
10022
10990
  if (!onShapeClick) return void 0;
10023
10991
  return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
10024
10992
  }
10025
- var Canvas3DHost;
10993
+ function polylineTube(points, radius, color, opts) {
10994
+ const maxSegments = opts?.maxSegments ?? 128;
10995
+ let pts = points;
10996
+ if (pts.length - 1 > maxSegments) {
10997
+ const step = (pts.length - 1) / maxSegments;
10998
+ const kept = [pts[0]];
10999
+ for (let s = 1; s < maxSegments; s++) kept.push(pts[Math.round(s * step)]);
11000
+ kept.push(pts[pts.length - 1]);
11001
+ pts = kept;
11002
+ }
11003
+ const out = [];
11004
+ for (let i = 0; i < pts.length - 1; i++) {
11005
+ const seg = cylinderBetween(pts[i], pts[i + 1], radius, color);
11006
+ if (seg) out.push(opts?.opacity !== void 0 ? { ...seg, opacity: opts.opacity } : seg);
11007
+ }
11008
+ return out;
11009
+ }
11010
+ function heightFieldMesh(spec) {
11011
+ const { nx, ny, heights, spacing = 1, x = 0, y = 0 } = spec;
11012
+ const flatShading = spec.flatShading ?? true;
11013
+ const vertices = [];
11014
+ for (let iy = 0; iy < ny; iy++) {
11015
+ for (let ix = 0; ix < nx; ix++) {
11016
+ vertices.push([
11017
+ x + (ix - (nx - 1) / 2) * spacing,
11018
+ y + (iy - (ny - 1) / 2) * spacing,
11019
+ heights[iy * nx + ix] ?? 0
11020
+ ]);
11021
+ }
11022
+ }
11023
+ const bands = [...spec.bands ?? []].sort((a, b) => (a.min ?? -Infinity) - (b.min ?? -Infinity));
11024
+ const facesByBand = /* @__PURE__ */ new Map();
11025
+ for (let iy = 0; iy < ny - 1; iy++) {
11026
+ for (let ix = 0; ix < nx - 1; ix++) {
11027
+ const v00 = iy * nx + ix;
11028
+ const v10 = iy * nx + ix + 1;
11029
+ const v01 = (iy + 1) * nx + ix;
11030
+ const v11 = (iy + 1) * nx + ix + 1;
11031
+ for (const face of [[v00, v10, v01], [v10, v11, v01]]) {
11032
+ const centroid = (vertices[face[0]][2] + vertices[face[1]][2] + vertices[face[2]][2]) / 3;
11033
+ let band = null;
11034
+ for (const b of bands) {
11035
+ if ((b.min ?? -Infinity) <= centroid) band = b;
11036
+ }
11037
+ const key = bands.length > 0 ? band : null;
11038
+ const list = facesByBand.get(key) ?? [];
11039
+ list.push(face);
11040
+ facesByBand.set(key, list);
11041
+ }
11042
+ }
11043
+ }
11044
+ const out = [];
11045
+ for (const [band, faces] of facesByBand) {
11046
+ if (faces.length === 0) continue;
11047
+ out.push({
11048
+ type: "draw-mesh",
11049
+ shape: "polyhedron",
11050
+ position: { x: 0, y: 0, z: 0 },
11051
+ vertices,
11052
+ faces,
11053
+ pivot: "center",
11054
+ material: { color: band?.color ?? spec.color ?? "#64748b", flatShading, side: "double" },
11055
+ ...spec.opacity !== void 0 ? { opacity: spec.opacity } : {}
11056
+ });
11057
+ }
11058
+ return out;
11059
+ }
11060
+ function arrowField(vectors, opts) {
11061
+ const scale = opts?.scale ?? 1;
11062
+ const out = [];
11063
+ for (const v of vectors) {
11064
+ const to = [
11065
+ v.from[0] + v.delta[0] * scale,
11066
+ v.from[1] + v.delta[1] * scale,
11067
+ v.from[2] + v.delta[2] * scale
11068
+ ];
11069
+ const arrow = arrowBetween(v.from, to, v.color ?? "#dc2626", v.width, v.id);
11070
+ if (arrow) out.push(arrow);
11071
+ if (v.label) out.push(billboardLabel(v.label, to[0], to[1], to[2], { color: opts?.labelColor }));
11072
+ }
11073
+ return out;
11074
+ }
11075
+ function helixDrawables(spec, opts) {
11076
+ const count = spec.count ?? spec.rungs?.length ?? 0;
11077
+ const rungs = Array.from({ length: count }, (_, i) => spec.rungs?.[i] ?? {});
11078
+ const radius = spec.radius ?? 1;
11079
+ const rise = spec.rise ?? 0.34;
11080
+ const twistRad = (spec.twistDeg ?? 36) * (Math.PI / 180);
11081
+ const strandAColor = spec.strandAColor ?? "#38bdf8";
11082
+ const strandBColor = spec.strandBColor ?? "#fb923c";
11083
+ const backboneRadius = spec.backboneRadius ?? 0.16;
11084
+ const rungRadius = spec.rungRadius ?? 0.12;
11085
+ const cx = spec.x ?? 0;
11086
+ const cy = spec.y ?? 0;
11087
+ const cz = spec.z ?? 0;
11088
+ const unwoundCount = spec.unwoundCount ?? 0;
11089
+ const unwindSpread = spec.unwindSpread ?? 1.8;
11090
+ const strandA = [];
11091
+ const strandB = [];
11092
+ for (let i = 0; i < count; i++) {
11093
+ const yi = cy + (i - (count - 1) / 2) * rise;
11094
+ const theta = i * twistRad;
11095
+ const s = i < unwoundCount ? unwindSpread : 1;
11096
+ strandA.push([cx + s * radius * Math.cos(theta), yi, cz + s * radius * Math.sin(theta)]);
11097
+ strandB.push([cx + s * radius * Math.cos(theta + Math.PI), yi, cz + s * radius * Math.sin(theta + Math.PI)]);
11098
+ }
11099
+ const out = [];
11100
+ for (let i = 0; i < count; i++) {
11101
+ out.push(meshSphere(`hx-a-${i}`, strandA[i][0], strandA[i][1], strandA[i][2], backboneRadius, strandAColor));
11102
+ out.push(meshSphere(`hx-b-${i}`, strandB[i][0], strandB[i][1], strandB[i][2], backboneRadius, strandBColor));
11103
+ if (i > 0) {
11104
+ const segA = cylinderBetween(strandA[i - 1], strandA[i], backboneRadius, strandAColor);
11105
+ if (segA) out.push(segA);
11106
+ const segB = cylinderBetween(strandB[i - 1], strandB[i], backboneRadius, strandBColor);
11107
+ if (segB) out.push(segB);
11108
+ }
11109
+ const rung = rungs[i];
11110
+ const rungColor = rung.color ?? "#94a3b8";
11111
+ const rod = cylinderBetween(strandA[i], strandB[i], rungRadius, rungColor);
11112
+ if (rod) out.push(rod);
11113
+ const mid = [
11114
+ (strandA[i][0] + strandB[i][0]) / 2,
11115
+ (strandA[i][1] + strandB[i][1]) / 2,
11116
+ (strandA[i][2] + strandB[i][2]) / 2
11117
+ ];
11118
+ const markerRadius = rung.radius ?? rungRadius;
11119
+ out.push(meshSphere(rung.id, mid[0], mid[1], mid[2], markerRadius, rungColor));
11120
+ if (rung.label) out.push(billboardLabel(rung.label, mid[0], mid[1], mid[2] + markerRadius, { color: opts?.labelColor }));
11121
+ }
11122
+ return out;
11123
+ }
11124
+ function latticeDrawables(spec, opts) {
11125
+ const nx = spec.nx ?? 2;
11126
+ const ny = spec.ny ?? 2;
11127
+ const nz = spec.nz ?? 2;
11128
+ const latticeConstant = spec.latticeConstant ?? 2;
11129
+ const bondRadius = spec.bondRadius ?? 0.06;
11130
+ const highlightCell = spec.highlightCell ?? false;
11131
+ const dimColor = spec.dimColor ?? "#475569";
11132
+ const showLabels = spec.showLabels ?? false;
11133
+ const selectedColor = spec.selectedColor ?? "#f59e0b";
11134
+ const posByKey = /* @__PURE__ */ new Map();
11135
+ const inCellByKey = /* @__PURE__ */ new Map();
11136
+ const out = [];
11137
+ for (const site of spec.basis) {
11138
+ const snx = site.xEdge ? nx + 1 : nx;
11139
+ const sny = site.yEdge ? ny + 1 : ny;
11140
+ const snz = site.zEdge ? nz + 1 : nz;
11141
+ for (let i = 0; i < snx; i++) {
11142
+ for (let j = 0; j < sny; j++) {
11143
+ for (let k = 0; k < snz; k++) {
11144
+ const key = `${site.key}-${i}-${j}-${k}`;
11145
+ const inCell = i + site.dx <= 1 && j + site.dy <= 1 && k + site.dz <= 1;
11146
+ const pos = [
11147
+ (i + site.dx) * latticeConstant - nx * latticeConstant / 2,
11148
+ (j + site.dy) * latticeConstant - ny * latticeConstant / 2,
11149
+ (k + site.dz) * latticeConstant - nz * latticeConstant / 2
11150
+ ];
11151
+ posByKey.set(key, pos);
11152
+ inCellByKey.set(key, inCell);
11153
+ const isSelected = spec.selectedId === `lat-${key}`;
11154
+ const color = isSelected ? selectedColor : highlightCell && !inCell ? dimColor : site.color ?? "#2563eb";
11155
+ const radius = (site.radius ?? 0.3) * (isSelected ? 1.4 : 1);
11156
+ out.push(meshSphere(`lat-${key}`, pos[0], pos[1], pos[2], radius, color));
11157
+ if (showLabels && site.element) {
11158
+ out.push(billboardLabel(site.element, pos[0], pos[1], pos[2] + radius, { color: opts?.labelColor }));
11159
+ }
11160
+ }
11161
+ }
11162
+ }
11163
+ }
11164
+ const basisByKey = new Map(spec.basis.map((s) => [s.key, s]));
11165
+ for (const bond of spec.bonds ?? []) {
11166
+ const fromSite = basisByKey.get(bond.from);
11167
+ const toSite = basisByKey.get(bond.to);
11168
+ if (!fromSite || !toSite) continue;
11169
+ const fnx = fromSite.xEdge ? nx + 1 : nx;
11170
+ const fny = fromSite.yEdge ? ny + 1 : ny;
11171
+ const fnz = fromSite.zEdge ? nz + 1 : nz;
11172
+ const tnx = toSite.xEdge ? nx + 1 : nx;
11173
+ const tny = toSite.yEdge ? ny + 1 : ny;
11174
+ const tnz = toSite.zEdge ? nz + 1 : nz;
11175
+ const bdx = bond.dx ?? 0;
11176
+ const bdy = bond.dy ?? 0;
11177
+ const bdz = bond.dz ?? 0;
11178
+ for (let i = 0; i < fnx; i++) {
11179
+ for (let j = 0; j < fny; j++) {
11180
+ for (let k = 0; k < fnz; k++) {
11181
+ const ti = i + bdx;
11182
+ const tj = j + bdy;
11183
+ const tk = k + bdz;
11184
+ if (ti < 0 || ti >= tnx || tj < 0 || tj >= tny || tk < 0 || tk >= tnz) continue;
11185
+ const fromKey = `${fromSite.key}-${i}-${j}-${k}`;
11186
+ const toKey = `${toSite.key}-${ti}-${tj}-${tk}`;
11187
+ const fromPos = posByKey.get(fromKey);
11188
+ const toPos = posByKey.get(toKey);
11189
+ if (!fromPos || !toPos) continue;
11190
+ const dimmed = highlightCell && !(inCellByKey.get(fromKey) && inCellByKey.get(toKey));
11191
+ const seg = cylinderBetween(fromPos, toPos, bondRadius, dimmed ? dimColor : bond.color ?? "#6b7280");
11192
+ if (seg) out.push(seg);
11193
+ }
11194
+ }
11195
+ }
11196
+ }
11197
+ return out;
11198
+ }
11199
+ var sceneLog, KNOWN_BLOOM_KEYS, Canvas3DHost;
10026
11200
  var init_learningScene3D = __esm({
10027
11201
  "components/learning/molecules/learningScene3D.tsx"() {
10028
11202
  "use client";
10029
11203
  init_atoms();
10030
11204
  init_Stack();
10031
11205
  init_useEventBus();
11206
+ sceneLog = logger.createLogger("almadar:ui:learning-scene-3d");
11207
+ KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
10032
11208
  Canvas3DHost = React77.lazy(
10033
11209
  () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
10034
11210
  );
10035
11211
  LearningScene3D.displayName = "LearningScene3D";
10036
11212
  }
10037
11213
  });
10038
- var biologyLog; exports.BiologyCanvas = void 0;
11214
+ var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT; exports.BiologyCanvas = void 0;
10039
11215
  var init_BiologyCanvas = __esm({
10040
11216
  "components/learning/molecules/BiologyCanvas.tsx"() {
10041
11217
  "use client";
@@ -10044,6 +11220,17 @@ var init_BiologyCanvas = __esm({
10044
11220
  init_LearningCanvas();
10045
11221
  init_learningScene3D();
10046
11222
  biologyLog = logger.createLogger("almadar:ui:biology-canvas");
11223
+ BIO_BAND_COLORS = ["#dcfce7", "#fef9c3", "#fee2e2", "#e0e7ff"];
11224
+ BIO_STAGE_FILL = {
11225
+ pending: "#e2e8f0",
11226
+ active: "#3b82f6",
11227
+ done: "#94a3b8"
11228
+ };
11229
+ BIO_STAGE_TEXT = {
11230
+ pending: "#64748b",
11231
+ active: "#ffffff",
11232
+ done: "#ffffff"
11233
+ };
10047
11234
  exports.BiologyCanvas = ({
10048
11235
  className,
10049
11236
  width = 600,
@@ -10056,7 +11243,15 @@ var init_BiologyCanvas = __esm({
10056
11243
  post,
10057
11244
  nodes = [],
10058
11245
  edges = [],
11246
+ compartments = [],
11247
+ bands = [],
11248
+ stages = [],
11249
+ stageStyle = "timeline",
11250
+ helix,
11251
+ helix3d,
10059
11252
  shapes = [],
11253
+ readouts,
11254
+ traces,
10060
11255
  showGrid,
10061
11256
  shadows,
10062
11257
  interactive,
@@ -10071,19 +11266,148 @@ var init_BiologyCanvas = __esm({
10071
11266
  for (const n of nodes) {
10072
11267
  if (n.id) nodeById.set(n.id, n);
10073
11268
  }
11269
+ const bandCount = bands.length;
11270
+ for (let i = 0; i < bandCount; i++) {
11271
+ const band = bands[i];
11272
+ const bandColor = band.color ?? BIO_BAND_COLORS[i % BIO_BAND_COLORS.length];
11273
+ const bandY = i * height / bandCount;
11274
+ const bandH = height / bandCount;
11275
+ out.push({
11276
+ type: "rect",
11277
+ x: 0,
11278
+ y: bandY,
11279
+ width,
11280
+ height: bandH,
11281
+ color: bandColor,
11282
+ fill: bandColor,
11283
+ opacity: 0.45
11284
+ });
11285
+ if (band.label) {
11286
+ out.push({
11287
+ type: "text",
11288
+ x: 8,
11289
+ y: bandY + 14,
11290
+ text: band.label,
11291
+ color: "#6b7280",
11292
+ fontSize: 10
11293
+ });
11294
+ }
11295
+ }
11296
+ for (const c of compartments) {
11297
+ const color = c.color ?? "#16a34a";
11298
+ out.push({
11299
+ type: "ellipse",
11300
+ x: c.x,
11301
+ y: c.y,
11302
+ width: c.width,
11303
+ height: c.height,
11304
+ color,
11305
+ fill: c.fill ?? `${color}1A`,
11306
+ lineWidth: c.lineWidth ?? 2,
11307
+ ...c.dash ? { dash: c.dash } : {}
11308
+ });
11309
+ if (c.label) {
11310
+ out.push({
11311
+ type: "text",
11312
+ x: c.x,
11313
+ y: c.y - c.height / 2 + 14,
11314
+ text: c.label,
11315
+ color: "#111827",
11316
+ fontSize: 11,
11317
+ align: "center"
11318
+ });
11319
+ }
11320
+ }
11321
+ if (helix) {
11322
+ const hx = helix.x ?? 24;
11323
+ const hy = helix.y ?? height * 0.25;
11324
+ const hw = helix.width ?? width - 48;
11325
+ const hh = helix.height ?? height * 0.5;
11326
+ const rungs = helix.rungs;
11327
+ const n = rungs.length;
11328
+ const cy = hy + hh / 2;
11329
+ const colorA = helix.colorA ?? "#2563eb";
11330
+ const colorB = helix.colorB ?? "#dc2626";
11331
+ const rungColor = helix.rungColor ?? "#94a3b8";
11332
+ const fork = helix.fork ?? 0;
11333
+ const maxSep = Math.min(hh - 8, 96);
11334
+ const strandA = [];
11335
+ const strandB = [];
11336
+ const rungGeoms = [];
11337
+ for (let i = 0; i < n; i++) {
11338
+ const rx = hx + (i + 0.5) * hw / n;
11339
+ const t = (i + 0.5) / n;
11340
+ const paired = t >= fork;
11341
+ const sep = paired ? 28 : 28 + (maxSep - 28) * ((fork - t) / fork);
11342
+ strandA.push({ x: rx, y: cy - sep / 2 });
11343
+ strandB.push({ x: rx, y: cy + sep / 2 });
11344
+ rungGeoms.push({ rx, sep, rung: rungs[i], paired });
11345
+ }
11346
+ for (let i = 1; i < n; i++) {
11347
+ out.push({ type: "line", x1: strandA[i - 1].x, y1: strandA[i - 1].y, x2: strandA[i].x, y2: strandA[i].y, color: colorA, lineWidth: 3 });
11348
+ }
11349
+ for (let i = 1; i < n; i++) {
11350
+ out.push({ type: "line", x1: strandB[i - 1].x, y1: strandB[i - 1].y, x2: strandB[i].x, y2: strandB[i].y, color: colorB, lineWidth: 3 });
11351
+ }
11352
+ for (const g of rungGeoms) {
11353
+ const rColor = g.rung.color ?? (g.rung.state === "new" ? "#16a34a" : rungColor);
11354
+ const topY = cy - g.sep / 2;
11355
+ const bottomY = cy + g.sep / 2;
11356
+ if (g.paired) {
11357
+ out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: bottomY, color: rColor });
11358
+ if (g.rung.a) {
11359
+ out.push({ type: "text", x: g.rx, y: cy - g.sep / 4, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
11360
+ }
11361
+ if (g.rung.b) {
11362
+ out.push({ type: "text", x: g.rx, y: cy + g.sep / 4, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
11363
+ }
11364
+ } else {
11365
+ const stubTopY = topY + 8;
11366
+ const stubBottomY = bottomY - 8;
11367
+ out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: stubTopY, color: rColor });
11368
+ out.push({ type: "line", x1: g.rx, y1: bottomY, x2: g.rx, y2: stubBottomY, color: rColor });
11369
+ if (g.rung.a) {
11370
+ out.push({ type: "text", x: g.rx, y: stubTopY + 6, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
11371
+ }
11372
+ if (g.rung.b) {
11373
+ out.push({ type: "text", x: g.rx, y: stubBottomY - 6, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
11374
+ }
11375
+ }
11376
+ }
11377
+ }
10074
11378
  for (const e of edges) {
10075
11379
  const a = nodeById.get(e.from);
10076
11380
  const b = nodeById.get(e.to);
10077
11381
  if (!a || !b) continue;
10078
- out.push({
10079
- type: "line",
10080
- x1: a.x,
10081
- y1: a.y,
10082
- x2: b.x,
10083
- y2: b.y,
10084
- color: e.color ?? "#9ca3af",
10085
- lineWidth: 2
10086
- });
11382
+ const color = e.color ?? "#9ca3af";
11383
+ if (e.directed) {
11384
+ const rA = a.radius ?? 16;
11385
+ const rB = b.radius ?? 16;
11386
+ const dx = b.x - a.x;
11387
+ const dy = b.y - a.y;
11388
+ const dist = Math.max(1e-6, Math.hypot(dx, dy));
11389
+ const ux = dx / dist;
11390
+ const uy = dy / dist;
11391
+ out.push({
11392
+ type: "arrow",
11393
+ x1: a.x + ux * rA,
11394
+ y1: a.y + uy * rA,
11395
+ x2: b.x - ux * rB,
11396
+ y2: b.y - uy * rB,
11397
+ color,
11398
+ lineWidth: 2
11399
+ });
11400
+ } else {
11401
+ out.push({
11402
+ type: "line",
11403
+ x1: a.x,
11404
+ y1: a.y,
11405
+ x2: b.x,
11406
+ y2: b.y,
11407
+ color,
11408
+ lineWidth: 2
11409
+ });
11410
+ }
10087
11411
  if (e.label) {
10088
11412
  out.push({
10089
11413
  type: "text",
@@ -10096,6 +11420,8 @@ var init_BiologyCanvas = __esm({
10096
11420
  }
10097
11421
  }
10098
11422
  for (const n of nodes) {
11423
+ const state = n.state ?? "default";
11424
+ const muted = state === "muted";
10099
11425
  out.push({
10100
11426
  type: "circle",
10101
11427
  x: n.x,
@@ -10103,28 +11429,127 @@ var init_BiologyCanvas = __esm({
10103
11429
  radius: n.radius ?? 16,
10104
11430
  color: n.color ?? "#16a34a",
10105
11431
  fill: `${n.color ?? "#16a34a"}33`,
10106
- id: n.id
11432
+ id: n.id,
11433
+ ...muted ? { opacity: 0.35 } : {}
10107
11434
  });
11435
+ if (state === "highlight") {
11436
+ out.push({
11437
+ type: "circle",
11438
+ x: n.x,
11439
+ y: n.y,
11440
+ radius: (n.radius ?? 16) + 4,
11441
+ color: "#f59e0b",
11442
+ lineWidth: 2
11443
+ });
11444
+ }
10108
11445
  if (n.label) {
10109
11446
  out.push({
10110
11447
  type: "text",
10111
11448
  x: n.x,
10112
11449
  y: n.y + (n.radius ?? 16) + 14,
10113
11450
  text: n.label,
11451
+ ...muted ? { opacity: 0.35 } : {},
10114
11452
  color: "#111827",
10115
11453
  fontSize: 12,
10116
11454
  align: "center"
10117
11455
  });
10118
11456
  }
10119
11457
  }
11458
+ const stageCount = stages.length;
11459
+ if (stageCount > 0) {
11460
+ if (stageStyle === "ring") {
11461
+ const cx = width / 2;
11462
+ const cy = height / 2;
11463
+ const R = Math.min(width, height) / 2 - 48;
11464
+ const ringPoints = [];
11465
+ for (let i = 0; i < stageCount; i++) {
11466
+ const angleRad = (-90 + 360 * i / stageCount) * Math.PI / 180;
11467
+ ringPoints.push({ x: cx + R * Math.cos(angleRad), y: cy + R * Math.sin(angleRad) });
11468
+ }
11469
+ if (stageCount >= 2) {
11470
+ for (let i = 0; i < stageCount - 1; i++) {
11471
+ const p1 = ringPoints[i];
11472
+ const p2 = ringPoints[i + 1];
11473
+ const dx = p2.x - p1.x;
11474
+ const dy = p2.y - p1.y;
11475
+ const dist = Math.max(1e-6, Math.hypot(dx, dy));
11476
+ const ux = dx / dist;
11477
+ const uy = dy / dist;
11478
+ out.push({
11479
+ type: "arrow",
11480
+ x1: p1.x + ux * 46,
11481
+ y1: p1.y + uy * 46,
11482
+ x2: p2.x - ux * 46,
11483
+ y2: p2.y - uy * 46,
11484
+ color: "#94a3b8"
11485
+ });
11486
+ }
11487
+ }
11488
+ for (let i = 0; i < stageCount; i++) {
11489
+ const stage = stages[i];
11490
+ const state = stage.state ?? "pending";
11491
+ const fill = stage.color ?? BIO_STAGE_FILL[state];
11492
+ const w = Math.max(26, Math.min(84, stage.label.length * 6 + 10));
11493
+ const h = 18;
11494
+ const p = ringPoints[i];
11495
+ out.push({ type: "rect", x: p.x - w / 2, y: p.y - h / 2, width: w, height: h, color: fill, fill });
11496
+ out.push({ type: "text", x: p.x, y: p.y, text: stage.label, color: BIO_STAGE_TEXT[state], fontSize: 10, align: "center" });
11497
+ }
11498
+ } else {
11499
+ const stripY = height - 32;
11500
+ const slotW = (width - 16) / stageCount;
11501
+ const chipGeoms = [];
11502
+ for (let i = 0; i < stageCount; i++) {
11503
+ chipGeoms.push({ x: 8 + i * slotW + 5, w: slotW - 10 });
11504
+ }
11505
+ for (let i = 0; i < stageCount - 1; i++) {
11506
+ const midY = stripY + 13;
11507
+ out.push({
11508
+ type: "arrow",
11509
+ x1: chipGeoms[i].x + chipGeoms[i].w,
11510
+ y1: midY,
11511
+ x2: chipGeoms[i + 1].x,
11512
+ y2: midY,
11513
+ color: "#94a3b8"
11514
+ });
11515
+ }
11516
+ for (let i = 0; i < stageCount; i++) {
11517
+ const stage = stages[i];
11518
+ const state = stage.state ?? "pending";
11519
+ const fill = stage.color ?? BIO_STAGE_FILL[state];
11520
+ const g = chipGeoms[i];
11521
+ out.push({ type: "rect", x: g.x, y: stripY, width: g.w, height: 26, color: fill, fill });
11522
+ out.push({
11523
+ type: "text",
11524
+ x: g.x + g.w / 2,
11525
+ y: stripY + 13,
11526
+ text: stage.label,
11527
+ color: BIO_STAGE_TEXT[state],
11528
+ fontSize: 10,
11529
+ align: "center"
11530
+ });
11531
+ }
11532
+ }
11533
+ }
10120
11534
  out.push(...shapes);
10121
11535
  return out;
10122
- }, [nodes, edges, shapes]);
11536
+ }, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
10123
11537
  const drawables3D = React77.useMemo(() => {
10124
11538
  if (mode !== "3d") return [];
10125
11539
  if (shapes.length > 0) {
10126
11540
  biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
10127
11541
  }
11542
+ if (compartments.length > 0 || bands.length > 0 || stages.length > 0 || helix) {
11543
+ biologyLog.debug("2D-only families ignored in 3D mode (pixel-authored 2D vocabulary)", {
11544
+ compartments: compartments.length,
11545
+ bands: bands.length,
11546
+ stages: stages.length,
11547
+ helix: helix != null
11548
+ });
11549
+ }
11550
+ if (animate) {
11551
+ biologyLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
11552
+ }
10128
11553
  const out = [];
10129
11554
  const labelColor = labelColorForBackground(backgroundColor);
10130
11555
  const nodeById = /* @__PURE__ */ new Map();
@@ -10158,15 +11583,21 @@ var init_BiologyCanvas = __esm({
10158
11583
  out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
10159
11584
  }
10160
11585
  }
11586
+ if (helix3d) {
11587
+ out.push(...helixDrawables(helix3d, { labelColor }));
11588
+ }
10161
11589
  return out;
10162
- }, [mode, nodes, edges, shapes, backgroundColor]);
11590
+ }, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
10163
11591
  const nodeIndexById = React77.useMemo(() => {
10164
11592
  const m = /* @__PURE__ */ new Map();
11593
+ (helix3d?.rungs ?? []).forEach((rung, i) => {
11594
+ if (rung.id) m.set(rung.id, i);
11595
+ });
10165
11596
  nodes.forEach((n, i) => {
10166
11597
  if (n.id) m.set(n.id, i);
10167
11598
  });
10168
11599
  return m;
10169
- }, [nodes]);
11600
+ }, [nodes, helix3d]);
10170
11601
  if (mode === "3d") {
10171
11602
  return /* @__PURE__ */ jsxRuntime.jsx(
10172
11603
  LearningScene3D,
@@ -10198,6 +11629,8 @@ var init_BiologyCanvas = __esm({
10198
11629
  height,
10199
11630
  backgroundColor,
10200
11631
  shapes: derivedShapes,
11632
+ readouts,
11633
+ traces,
10201
11634
  interactive: interactive ?? false,
10202
11635
  animate,
10203
11636
  onShapeClick,
@@ -19388,7 +20821,7 @@ function bondPerpendicular(a, b) {
19388
20821
  if (len < 1e-6) return [1, 0, 0];
19389
20822
  return [px / len, py / len, 0];
19390
20823
  }
19391
- var chemistryLog; exports.ChemistryCanvas = void 0;
20824
+ var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES; exports.ChemistryCanvas = void 0;
19392
20825
  var init_ChemistryCanvas = __esm({
19393
20826
  "components/learning/molecules/ChemistryCanvas.tsx"() {
19394
20827
  "use client";
@@ -19397,6 +20830,13 @@ var init_ChemistryCanvas = __esm({
19397
20830
  init_LearningCanvas();
19398
20831
  init_learningScene3D();
19399
20832
  chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
20833
+ CHEM_BOND_STATE_COLOR = {
20834
+ default: "#6b7280",
20835
+ forming: "#16a34a",
20836
+ breaking: "#dc2626",
20837
+ highlight: "#f59e0b"
20838
+ };
20839
+ LONE_PAIR_ANGLES = [-90, 0, 90, 180];
19400
20840
  exports.ChemistryCanvas = ({
19401
20841
  className,
19402
20842
  width = 600,
@@ -19410,7 +20850,14 @@ var init_ChemistryCanvas = __esm({
19410
20850
  atoms = [],
19411
20851
  bonds = [],
19412
20852
  arrows = [],
20853
+ bondStyle = "thick",
20854
+ containers = [],
20855
+ equation,
20856
+ equationColor,
20857
+ lattice3d,
19413
20858
  shapes = [],
20859
+ readouts,
20860
+ traces,
19414
20861
  showGrid,
19415
20862
  shadows,
19416
20863
  interactive,
@@ -19425,21 +20872,118 @@ var init_ChemistryCanvas = __esm({
19425
20872
  for (const a of atoms) {
19426
20873
  if (a.id) atomById.set(a.id, a);
19427
20874
  }
20875
+ for (const c of containers) {
20876
+ const color = c.color ?? "#64748b";
20877
+ if (c.level != null) {
20878
+ const lv = c.level;
20879
+ out.push({
20880
+ type: "rect",
20881
+ x: c.x + 1,
20882
+ y: c.y + c.height * (1 - lv),
20883
+ width: c.width - 2,
20884
+ height: c.height * lv - 1,
20885
+ color: c.levelColor ?? "#60a5fa",
20886
+ fill: c.levelColor ?? "#60a5fa",
20887
+ opacity: 0.5
20888
+ });
20889
+ }
20890
+ out.push({
20891
+ type: "rect",
20892
+ x: c.x,
20893
+ y: c.y,
20894
+ width: c.width,
20895
+ height: c.height,
20896
+ color,
20897
+ fill: c.fill,
20898
+ lineWidth: c.lineWidth ?? 2
20899
+ });
20900
+ const divider = c.divider ?? "none";
20901
+ if (divider !== "none") {
20902
+ out.push({
20903
+ type: "line",
20904
+ x1: c.x + c.width / 2,
20905
+ y1: c.y,
20906
+ x2: c.x + c.width / 2,
20907
+ y2: c.y + c.height,
20908
+ color: c.dividerColor ?? color,
20909
+ ...divider === "dashed" || divider === "dotted" ? { dash: divider } : {}
20910
+ });
20911
+ }
20912
+ if (c.leftLabel) {
20913
+ out.push({
20914
+ type: "text",
20915
+ x: c.x + c.width * 0.25,
20916
+ y: c.y + 12,
20917
+ text: c.leftLabel,
20918
+ color: "#374151",
20919
+ fontSize: 11,
20920
+ align: "center"
20921
+ });
20922
+ }
20923
+ if (c.rightLabel) {
20924
+ out.push({
20925
+ type: "text",
20926
+ x: c.x + c.width * 0.75,
20927
+ y: c.y + 12,
20928
+ text: c.rightLabel,
20929
+ color: "#374151",
20930
+ fontSize: 11,
20931
+ align: "center"
20932
+ });
20933
+ }
20934
+ if (c.label) {
20935
+ out.push({
20936
+ type: "text",
20937
+ x: c.x + c.width / 2,
20938
+ y: c.y + c.height + 12,
20939
+ text: c.label,
20940
+ color: "#111827",
20941
+ fontSize: 12,
20942
+ align: "center"
20943
+ });
20944
+ }
20945
+ }
19428
20946
  for (const b of bonds) {
19429
20947
  const a = atomById.get(b.from);
19430
20948
  const c = atomById.get(b.to);
19431
20949
  if (!a || !c) continue;
19432
- const color = b.color ?? "#6b7280";
19433
- const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
19434
- out.push({
19435
- type: "line",
19436
- x1: a.x,
19437
- y1: a.y,
19438
- x2: c.x,
19439
- y2: c.y,
19440
- color,
19441
- lineWidth: strokeWidth
19442
- });
20950
+ const state = b.state ?? "default";
20951
+ const color = b.color ?? CHEM_BOND_STATE_COLOR[state];
20952
+ const dash = state === "forming" || state === "breaking" ? "dashed" : void 0;
20953
+ if (bondStyle === "parallel") {
20954
+ const dx = c.x - a.x;
20955
+ const dy = c.y - a.y;
20956
+ const dist = Math.max(1e-6, Math.hypot(dx, dy));
20957
+ const ux = dx / dist;
20958
+ const uy = dy / dist;
20959
+ const px = -uy;
20960
+ const py = ux;
20961
+ const offsets = b.type === "double" ? [-3, 3] : b.type === "triple" ? [-4, 0, 4] : [0];
20962
+ for (const off of offsets) {
20963
+ out.push({
20964
+ type: "line",
20965
+ x1: a.x + px * off,
20966
+ y1: a.y + py * off,
20967
+ x2: c.x + px * off,
20968
+ y2: c.y + py * off,
20969
+ color,
20970
+ lineWidth: 2,
20971
+ ...dash ? { dash } : {}
20972
+ });
20973
+ }
20974
+ } else {
20975
+ const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
20976
+ out.push({
20977
+ type: "line",
20978
+ x1: a.x,
20979
+ y1: a.y,
20980
+ x2: c.x,
20981
+ y2: c.y,
20982
+ color,
20983
+ lineWidth: strokeWidth,
20984
+ ...dash ? { dash } : {}
20985
+ });
20986
+ }
19443
20987
  }
19444
20988
  for (const a of arrows) {
19445
20989
  const angle = (a.angle ?? 0) * (Math.PI / 180);
@@ -19488,15 +21032,62 @@ var init_ChemistryCanvas = __esm({
19488
21032
  align: "center"
19489
21033
  });
19490
21034
  }
21035
+ const r = a.radius ?? 14;
21036
+ if (a.charge) {
21037
+ out.push({
21038
+ type: "text",
21039
+ x: a.x + r * 0.85,
21040
+ y: a.y - r * 0.85,
21041
+ text: a.charge,
21042
+ color: "#111827",
21043
+ fontSize: 9,
21044
+ align: "left"
21045
+ });
21046
+ }
21047
+ const lonePairs = Math.max(0, Math.min(4, a.lonePairs ?? 0));
21048
+ for (let k = 0; k < lonePairs; k++) {
21049
+ const angleRad = LONE_PAIR_ANGLES[k] * Math.PI / 180;
21050
+ const cx = a.x + (r + 6) * Math.cos(angleRad);
21051
+ const cy = a.y + (r + 6) * Math.sin(angleRad);
21052
+ const perpX = -Math.sin(angleRad);
21053
+ const perpY = Math.cos(angleRad);
21054
+ for (const sign of [1, -1]) {
21055
+ out.push({
21056
+ type: "circle",
21057
+ x: cx + perpX * 2.5 * sign,
21058
+ y: cy + perpY * 2.5 * sign,
21059
+ radius: 1.5,
21060
+ color: "#374151",
21061
+ fill: "#374151"
21062
+ });
21063
+ }
21064
+ }
21065
+ }
21066
+ if (equation) {
21067
+ out.push({
21068
+ type: "text",
21069
+ x: width / 2,
21070
+ y: 14,
21071
+ text: equation,
21072
+ color: equationColor ?? "#111827",
21073
+ fontSize: 13,
21074
+ align: "center"
21075
+ });
19491
21076
  }
19492
21077
  out.push(...shapes);
19493
21078
  return out;
19494
- }, [atoms, bonds, arrows, shapes]);
21079
+ }, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
19495
21080
  const drawables3D = React77.useMemo(() => {
19496
21081
  if (mode !== "3d") return [];
19497
21082
  if (shapes.length > 0) {
19498
21083
  chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
19499
21084
  }
21085
+ if (containers.length > 0) {
21086
+ chemistryLog.debug("containers ignored in 3D mode (pixel-authored 2D vocabulary)", { count: containers.length });
21087
+ }
21088
+ if (animate) {
21089
+ chemistryLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
21090
+ }
19500
21091
  const out = [];
19501
21092
  const labelColor = labelColorForBackground(backgroundColor);
19502
21093
  const atomById = /* @__PURE__ */ new Map();
@@ -19543,8 +21134,11 @@ var init_ChemistryCanvas = __esm({
19543
21134
  out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
19544
21135
  }
19545
21136
  }
21137
+ if (lattice3d) {
21138
+ out.push(...latticeDrawables(lattice3d, { labelColor }));
21139
+ }
19546
21140
  return out;
19547
- }, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
21141
+ }, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
19548
21142
  const atomIndexById = React77.useMemo(() => {
19549
21143
  const m = /* @__PURE__ */ new Map();
19550
21144
  atoms.forEach((a, i) => {
@@ -19583,6 +21177,8 @@ var init_ChemistryCanvas = __esm({
19583
21177
  height,
19584
21178
  backgroundColor,
19585
21179
  shapes: derivedShapes,
21180
+ readouts,
21181
+ traces,
19586
21182
  interactive: interactive ?? false,
19587
21183
  animate,
19588
21184
  onShapeClick,
@@ -29495,6 +31091,10 @@ var init_molecules = __esm({
29495
31091
  init_GameShell();
29496
31092
  }
29497
31093
  });
31094
+ function formatTick(v) {
31095
+ if (Number.isInteger(v)) return String(v);
31096
+ return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
31097
+ }
29498
31098
  exports.MathCanvas = void 0;
29499
31099
  var init_MathCanvas = __esm({
29500
31100
  "components/learning/molecules/MathCanvas.tsx"() {
@@ -29514,10 +31114,19 @@ var init_MathCanvas = __esm({
29514
31114
  showAxes = true,
29515
31115
  showGrid = true,
29516
31116
  gridStep = 1,
31117
+ showTickLabels = false,
31118
+ showCurveLabels = false,
29517
31119
  curves = [],
29518
31120
  points = [],
29519
31121
  vectors = [],
31122
+ regions = [],
31123
+ bars = [],
31124
+ guides = [],
31125
+ angles = [],
31126
+ hops = [],
29520
31127
  shapes = [],
31128
+ readouts,
31129
+ traces,
29521
31130
  interactive = false,
29522
31131
  animate = false,
29523
31132
  onShapeClick,
@@ -29531,6 +31140,8 @@ var init_MathCanvas = __esm({
29531
31140
  const plotH = height - margin * 2;
29532
31141
  const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
29533
31142
  const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
31143
+ const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
31144
+ const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
29534
31145
  if (showGrid) {
29535
31146
  for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
29536
31147
  const px = mapX(x);
@@ -29541,14 +31152,99 @@ var init_MathCanvas = __esm({
29541
31152
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
29542
31153
  }
29543
31154
  }
31155
+ if (showTickLabels) {
31156
+ const labelEveryX = Math.max(1, Math.ceil((xMax - xMin) / gridStep / Math.floor(plotW / 40)));
31157
+ let kx = 0;
31158
+ for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
31159
+ if (kx % labelEveryX === 0 && x !== 0) {
31160
+ out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: 10, align: "center" });
31161
+ }
31162
+ }
31163
+ const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
31164
+ let ky = 0;
31165
+ for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
31166
+ if (ky % labelEveryY === 0 && y !== 0) {
31167
+ out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: 10, align: "right" });
31168
+ }
31169
+ }
31170
+ if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
31171
+ out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: 10, align: "right" });
31172
+ }
31173
+ }
31174
+ for (const region of regions) {
31175
+ if (!region.samples || region.samples.length === 0) continue;
31176
+ const baseline = region.baseline ?? 0;
31177
+ const clampedPoint = (p) => ({
31178
+ x: mapX(Math.min(xMax, Math.max(xMin, p.x))),
31179
+ y: mapY(Math.min(yMax, Math.max(yMin, p.y)))
31180
+ });
31181
+ const upper = region.samples.map(clampedPoint);
31182
+ const first = region.samples[0];
31183
+ const last = region.samples[region.samples.length - 1];
31184
+ const closing = region.samples2 && region.samples2.length > 0 ? [...region.samples2].reverse().map(clampedPoint) : [clampedPoint({ x: last.x, y: baseline }), clampedPoint({ x: first.x, y: baseline })];
31185
+ const color = region.color ?? "#2563eb";
31186
+ out.push({
31187
+ type: "polygon",
31188
+ points: [...upper, ...closing],
31189
+ fill: color,
31190
+ color,
31191
+ opacity: region.opacity ?? 0.2,
31192
+ lineWidth: 1
31193
+ });
31194
+ if (region.label) {
31195
+ const mid = Math.floor(region.samples.length / 2);
31196
+ out.push({
31197
+ type: "text",
31198
+ x: mapX((first.x + last.x) / 2),
31199
+ y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
31200
+ text: region.label,
31201
+ color: "#111827",
31202
+ fontSize: 11
31203
+ });
31204
+ }
31205
+ }
31206
+ for (const bar of bars) {
31207
+ if (bar.x + bar.width < xMin || bar.x > xMax) continue;
31208
+ const y0 = bar.y0 ?? 0;
31209
+ const color = bar.color ?? "#93c5fd";
31210
+ out.push({
31211
+ type: "rect",
31212
+ x: mapX(bar.x),
31213
+ y: mapY(Math.max(y0, bar.y1)),
31214
+ width: mapX(bar.x + bar.width) - mapX(bar.x),
31215
+ height: Math.abs(mapY(bar.y1) - mapY(y0)),
31216
+ color,
31217
+ fill: color,
31218
+ opacity: bar.opacity ?? 0.5,
31219
+ lineWidth: 1
31220
+ });
31221
+ }
29544
31222
  if (showAxes) {
29545
- const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
29546
- const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
29547
31223
  out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
29548
31224
  out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
29549
31225
  }
31226
+ for (const guide of guides) {
31227
+ const color = guide.color ?? "#9ca3af";
31228
+ const dash = guide.dash ?? "dashed";
31229
+ if (guide.kind === "vline") {
31230
+ if (guide.at < xMin || guide.at > xMax) continue;
31231
+ const px = mapX(guide.at);
31232
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
31233
+ if (guide.label) {
31234
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
31235
+ }
31236
+ } else {
31237
+ if (guide.at < yMin || guide.at > yMax) continue;
31238
+ const py = mapY(guide.at);
31239
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
31240
+ if (guide.label) {
31241
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
31242
+ }
31243
+ }
31244
+ }
29550
31245
  for (const curve of curves) {
29551
31246
  if (!curve.samples || curve.samples.length < 2) continue;
31247
+ let lastInRange;
29552
31248
  for (let i = 1; i < curve.samples.length; i++) {
29553
31249
  const a = curve.samples[i - 1];
29554
31250
  const b = curve.samples[i];
@@ -29560,19 +31256,97 @@ var init_MathCanvas = __esm({
29560
31256
  x2: mapX(b.x),
29561
31257
  y2: mapY(b.y),
29562
31258
  color: curve.color ?? "#2563eb",
29563
- lineWidth: 2
31259
+ lineWidth: 2,
31260
+ dash: curve.dash
31261
+ });
31262
+ lastInRange = b;
31263
+ }
31264
+ if (showCurveLabels && curve.label && lastInRange) {
31265
+ out.push({
31266
+ type: "text",
31267
+ x: mapX(lastInRange.x) + 6,
31268
+ y: mapY(lastInRange.y) - 6,
31269
+ text: curve.label,
31270
+ color: curve.color ?? "#2563eb",
31271
+ fontSize: 11
31272
+ });
31273
+ }
31274
+ }
31275
+ for (const hop of hops) {
31276
+ const x1 = mapX(hop.from);
31277
+ const x2 = mapX(hop.to);
31278
+ const peak = Math.min(36, plotH * 0.3);
31279
+ const color = hop.color ?? "#7c3aed";
31280
+ out.push({
31281
+ type: "ellipse",
31282
+ x: (x1 + x2) / 2,
31283
+ y: xAxisY,
31284
+ width: Math.abs(x2 - x1),
31285
+ height: 2 * peak,
31286
+ startAngle: 180,
31287
+ endAngle: 360,
31288
+ color
31289
+ });
31290
+ const s = Math.sign(hop.to - hop.from);
31291
+ out.push({
31292
+ type: "polygon",
31293
+ points: [
31294
+ { x: x2, y: xAxisY },
31295
+ { x: x2 - 4 * s, y: xAxisY - 7 },
31296
+ { x: x2 + 2 * s, y: xAxisY - 7 }
31297
+ ],
31298
+ fill: color,
31299
+ color
31300
+ });
31301
+ if (hop.label) {
31302
+ out.push({
31303
+ type: "text",
31304
+ x: (x1 + x2) / 2,
31305
+ y: xAxisY - peak - 8,
31306
+ text: hop.label,
31307
+ color: "#111827",
31308
+ fontSize: 10,
31309
+ align: "center"
31310
+ });
31311
+ }
31312
+ }
31313
+ for (const angle of angles) {
31314
+ const radius = angle.radius ?? 0.8;
31315
+ const color = angle.color ?? "#0ea5e9";
31316
+ out.push({
31317
+ type: "ellipse",
31318
+ x: mapX(angle.x),
31319
+ y: mapY(angle.y),
31320
+ width: 2 * radius * plotW / (xMax - xMin),
31321
+ height: 2 * radius * plotH / (yMax - yMin),
31322
+ startAngle: -angle.to,
31323
+ endAngle: -angle.from,
31324
+ color
31325
+ });
31326
+ if (angle.label) {
31327
+ const mid = (angle.from + angle.to) / 2;
31328
+ const rad = mid * Math.PI / 180;
31329
+ out.push({
31330
+ type: "text",
31331
+ x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
31332
+ y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
31333
+ text: angle.label,
31334
+ color: "#111827",
31335
+ fontSize: 11,
31336
+ align: "center"
29564
31337
  });
29565
31338
  }
29566
31339
  }
29567
31340
  for (const p of points) {
29568
31341
  if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
31342
+ const isOpen = p.style === "open";
29569
31343
  out.push({
29570
31344
  type: "circle",
29571
31345
  x: mapX(p.x),
29572
31346
  y: mapY(p.y),
29573
31347
  radius: p.radius ?? 4,
29574
31348
  color: p.color ?? "#dc2626",
29575
- fill: p.color ?? "#dc2626"
31349
+ fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
29576
31350
  });
29577
31351
  if (p.label) {
29578
31352
  out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
@@ -29591,7 +31365,28 @@ var init_MathCanvas = __esm({
29591
31365
  }
29592
31366
  out.push(...shapes);
29593
31367
  return out;
29594
- }, [width, height, xMin, xMax, yMin, yMax, showAxes, showGrid, gridStep, curves, points, vectors, shapes]);
31368
+ }, [
31369
+ width,
31370
+ height,
31371
+ xMin,
31372
+ xMax,
31373
+ yMin,
31374
+ yMax,
31375
+ showAxes,
31376
+ showGrid,
31377
+ gridStep,
31378
+ showTickLabels,
31379
+ showCurveLabels,
31380
+ curves,
31381
+ points,
31382
+ vectors,
31383
+ regions,
31384
+ bars,
31385
+ guides,
31386
+ angles,
31387
+ hops,
31388
+ shapes
31389
+ ]);
29595
31390
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
29596
31391
  title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
29597
31392
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -29600,6 +31395,8 @@ var init_MathCanvas = __esm({
29600
31395
  width,
29601
31396
  height,
29602
31397
  shapes: derivedShapes,
31398
+ readouts,
31399
+ traces,
29603
31400
  interactive,
29604
31401
  animate,
29605
31402
  onShapeClick,
@@ -29611,7 +31408,315 @@ var init_MathCanvas = __esm({
29611
31408
  };
29612
31409
  }
29613
31410
  });
29614
- var physicsLog2; exports.PhysicsCanvas = void 0;
31411
+ function formatMeterValue(v) {
31412
+ return Number.isInteger(v) ? String(v) : String(Number(v.toFixed(2)));
31413
+ }
31414
+ function sceneObjectShapes(obj, canvasWidth, canvasHeight) {
31415
+ const out = [];
31416
+ const color = obj.color ?? "#334155";
31417
+ switch (obj.kind) {
31418
+ case "ground": {
31419
+ const xStart = obj.x1 ?? 0;
31420
+ const xEnd = obj.x2 ?? canvasWidth;
31421
+ const y = obj.y ?? 0;
31422
+ out.push({ type: "line", x1: xStart, y1: y, x2: xEnd, y2: y, color, lineWidth: 2 });
31423
+ for (let hx = xStart + 7; hx <= xEnd; hx += 14) {
31424
+ out.push({ type: "line", x1: hx, y1: y, x2: hx - 7, y2: y + 7, color, lineWidth: 1 });
31425
+ }
31426
+ if (obj.label) {
31427
+ out.push({
31428
+ type: "text",
31429
+ x: (xStart + xEnd) / 2,
31430
+ y: y - 10,
31431
+ text: obj.label,
31432
+ color: PHYSICS_LABEL_COLOR,
31433
+ fontSize: 11,
31434
+ align: "center"
31435
+ });
31436
+ }
31437
+ break;
31438
+ }
31439
+ case "wall": {
31440
+ const yStart = obj.y1 ?? 0;
31441
+ const yEnd = obj.y2 ?? canvasHeight;
31442
+ const x = obj.x ?? 0;
31443
+ out.push({ type: "line", x1: x, y1: yStart, x2: x, y2: yEnd, color, lineWidth: 2 });
31444
+ for (let hy = yStart + 7; hy <= yEnd; hy += 14) {
31445
+ out.push({ type: "line", x1: x, y1: hy, x2: x - 7, y2: hy + 7, color, lineWidth: 1 });
31446
+ }
31447
+ if (obj.label) {
31448
+ out.push({
31449
+ type: "text",
31450
+ x: x + 12,
31451
+ y: (yStart + yEnd) / 2,
31452
+ text: obj.label,
31453
+ color: PHYSICS_LABEL_COLOR,
31454
+ fontSize: 11,
31455
+ align: "left"
31456
+ });
31457
+ }
31458
+ break;
31459
+ }
31460
+ case "ramp": {
31461
+ const x1 = obj.x1 ?? 0;
31462
+ const y1 = obj.y1 ?? 0;
31463
+ const x2 = obj.x2 ?? canvasWidth;
31464
+ const y2 = obj.y2 ?? canvasHeight;
31465
+ out.push({
31466
+ type: "polygon",
31467
+ points: [
31468
+ { x: x1, y: y1 },
31469
+ { x: x2, y: y2 },
31470
+ { x: x1, y: y2 }
31471
+ ],
31472
+ color,
31473
+ fill: obj.fill ?? "#e2e8f0",
31474
+ lineWidth: 2
31475
+ });
31476
+ if (obj.label) {
31477
+ out.push({
31478
+ type: "text",
31479
+ x: (2 * x1 + x2) / 3,
31480
+ y: (y1 + 2 * y2) / 3,
31481
+ text: obj.label,
31482
+ color: PHYSICS_LABEL_COLOR,
31483
+ fontSize: 11,
31484
+ align: "center"
31485
+ });
31486
+ }
31487
+ break;
31488
+ }
31489
+ case "box": {
31490
+ const x = obj.x ?? 0;
31491
+ const y = obj.y ?? 0;
31492
+ const w = obj.width ?? 40;
31493
+ const h = obj.height ?? 40;
31494
+ out.push({ type: "rect", x, y, width: w, height: h, color, fill: obj.fill, lineWidth: 2 });
31495
+ if (obj.label) {
31496
+ out.push({
31497
+ type: "text",
31498
+ x: x + w / 2,
31499
+ y: y + h / 2,
31500
+ text: obj.label,
31501
+ color: PHYSICS_LABEL_COLOR,
31502
+ fontSize: 11,
31503
+ align: "center"
31504
+ });
31505
+ }
31506
+ break;
31507
+ }
31508
+ case "pivot": {
31509
+ const x = obj.x ?? 0;
31510
+ const y = obj.y ?? 0;
31511
+ out.push({ type: "circle", x, y, radius: 5, color, fill: color });
31512
+ out.push({ type: "line", x1: x - 14, y1: y - 8, x2: x + 14, y2: y - 8, color, lineWidth: 1 });
31513
+ for (let k = 0; k < 5; k++) {
31514
+ const hx = x - 14 + 7 * k;
31515
+ out.push({ type: "line", x1: hx, y1: y - 8, x2: hx - 6, y2: y - 14, color, lineWidth: 1 });
31516
+ }
31517
+ if (obj.label) {
31518
+ out.push({
31519
+ type: "text",
31520
+ x,
31521
+ y: y - 20,
31522
+ text: obj.label,
31523
+ color: PHYSICS_LABEL_COLOR,
31524
+ fontSize: 11,
31525
+ align: "center"
31526
+ });
31527
+ }
31528
+ break;
31529
+ }
31530
+ }
31531
+ return out;
31532
+ }
31533
+ function trailShapes(trail) {
31534
+ const n = trail.points.length;
31535
+ if (n < 2) return [];
31536
+ const color = trail.color ?? "#94a3b8";
31537
+ const lineWidth = trail.width ?? 2;
31538
+ const fade = trail.fade ?? true;
31539
+ const globalOpacity = trail.opacity ?? 1;
31540
+ const out = [];
31541
+ for (let i = 0; i < n - 1; i++) {
31542
+ const a = trail.points[i];
31543
+ const b = trail.points[i + 1];
31544
+ const segmentOpacity = fade ? 0.12 + 0.68 * i / (n - 1) : 0.6;
31545
+ out.push({
31546
+ type: "line",
31547
+ x1: a.x,
31548
+ y1: a.y,
31549
+ x2: b.x,
31550
+ y2: b.y,
31551
+ color,
31552
+ lineWidth,
31553
+ opacity: segmentOpacity * globalOpacity
31554
+ });
31555
+ }
31556
+ return out;
31557
+ }
31558
+ function constraintShapes(c, a, b) {
31559
+ const color = c.color ?? "#9ca3af";
31560
+ const kind = c.kind ?? "rod";
31561
+ if (kind === "rod") {
31562
+ return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2 }];
31563
+ }
31564
+ if (kind === "string") {
31565
+ return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2, dash: "dashed" }];
31566
+ }
31567
+ const COILS = 8;
31568
+ const AMP = 7;
31569
+ const LEAD = 10;
31570
+ const dx = b.x - a.x;
31571
+ const dy = b.y - a.y;
31572
+ const dist = Math.max(1e-6, Math.hypot(dx, dy));
31573
+ const ux = dx / dist;
31574
+ const uy = dy / dist;
31575
+ const perpX = -uy;
31576
+ const perpY = ux;
31577
+ const aPrime = { x: a.x + LEAD * ux, y: a.y + LEAD * uy };
31578
+ const bPrime = { x: b.x - LEAD * ux, y: b.y - LEAD * uy };
31579
+ const m = 2 * COILS;
31580
+ const polyline = [{ x: a.x, y: a.y }, aPrime];
31581
+ for (let j = 1; j <= m; j++) {
31582
+ const t = j / (m + 1);
31583
+ const baseX = aPrime.x + t * (bPrime.x - aPrime.x);
31584
+ const baseY = aPrime.y + t * (bPrime.y - aPrime.y);
31585
+ const sign = j % 2 === 0 ? 1 : -1;
31586
+ polyline.push({ x: baseX + sign * AMP * perpX, y: baseY + sign * AMP * perpY });
31587
+ }
31588
+ polyline.push(bPrime, { x: b.x, y: b.y });
31589
+ const out = [];
31590
+ for (let i = 1; i < polyline.length; i++) {
31591
+ out.push({
31592
+ type: "line",
31593
+ x1: polyline[i - 1].x,
31594
+ y1: polyline[i - 1].y,
31595
+ x2: polyline[i].x,
31596
+ y2: polyline[i].y,
31597
+ color,
31598
+ lineWidth: 2
31599
+ });
31600
+ }
31601
+ return out;
31602
+ }
31603
+ function vectorShapes(v, bodyById) {
31604
+ let ax;
31605
+ let ay;
31606
+ if (v.body) {
31607
+ const anchor = bodyById.get(v.body);
31608
+ if (!anchor) return [];
31609
+ ax = anchor.x;
31610
+ ay = anchor.y;
31611
+ } else {
31612
+ ax = v.x ?? 0;
31613
+ ay = v.y ?? 0;
31614
+ }
31615
+ const scale = v.scale ?? 1;
31616
+ const color = v.color ?? "#dc2626";
31617
+ const tx = ax + v.dx * scale;
31618
+ const ty = ay + v.dy * scale;
31619
+ const out = [{ type: "arrow", x1: ax, y1: ay, x2: tx, y2: ty, color, lineWidth: 2, dash: v.dash }];
31620
+ if (v.label) {
31621
+ const dist = Math.max(1e-6, Math.hypot(tx - ax, ty - ay));
31622
+ const ux = (tx - ax) / dist;
31623
+ const uy = (ty - ay) / dist;
31624
+ out.push({
31625
+ type: "text",
31626
+ x: tx + 8 * ux,
31627
+ y: ty + 8 * uy,
31628
+ text: v.label,
31629
+ color,
31630
+ fontSize: 11,
31631
+ align: "center"
31632
+ });
31633
+ }
31634
+ return out;
31635
+ }
31636
+ function angleMarkerShapes(a) {
31637
+ const radius = a.radius ?? 26;
31638
+ const color = a.color ?? "#0ea5e9";
31639
+ const out = [
31640
+ {
31641
+ type: "ellipse",
31642
+ x: a.x,
31643
+ y: a.y,
31644
+ width: radius * 2,
31645
+ height: radius * 2,
31646
+ startAngle: a.from,
31647
+ endAngle: a.to,
31648
+ color,
31649
+ lineWidth: 2
31650
+ }
31651
+ ];
31652
+ if (a.label) {
31653
+ const mid = (a.from + a.to) / 2 * (Math.PI / 180);
31654
+ out.push({
31655
+ type: "text",
31656
+ x: a.x + (radius + 13) * Math.cos(mid),
31657
+ y: a.y + (radius + 13) * Math.sin(mid),
31658
+ text: a.label,
31659
+ color,
31660
+ fontSize: 11,
31661
+ align: "center"
31662
+ });
31663
+ }
31664
+ return out;
31665
+ }
31666
+ function fieldShapes(field, canvasWidth, canvasHeight) {
31667
+ const spacing = field.spacing ?? 48;
31668
+ const size = field.size ?? 14;
31669
+ const color = field.color ?? "#94a3b8";
31670
+ const regionX = field.x ?? 0;
31671
+ const regionY = field.y ?? 0;
31672
+ const regionW = field.width ?? canvasWidth;
31673
+ const regionH = field.height ?? canvasHeight;
31674
+ const out = [];
31675
+ for (let gx = regionX + spacing / 2; gx < regionX + regionW; gx += spacing) {
31676
+ for (let gy = regionY + spacing / 2; gy < regionY + regionH; gy += spacing) {
31677
+ if (field.kind === "arrows") {
31678
+ const rad = (field.angle ?? 0) * Math.PI / 180;
31679
+ const hx = Math.cos(rad) * size / 2;
31680
+ const hy = Math.sin(rad) * size / 2;
31681
+ out.push({ type: "arrow", x1: gx - hx, y1: gy - hy, x2: gx + hx, y2: gy + hy, color, lineWidth: 2 });
31682
+ } else if (field.kind === "into") {
31683
+ const r = size / 3;
31684
+ const d = 0.6 * r * Math.SQRT1_2;
31685
+ out.push({ type: "circle", x: gx, y: gy, radius: r, color });
31686
+ out.push({ type: "line", x1: gx - d, y1: gy - d, x2: gx + d, y2: gy + d, color, lineWidth: 1 });
31687
+ out.push({ type: "line", x1: gx - d, y1: gy + d, x2: gx + d, y2: gy - d, color, lineWidth: 1 });
31688
+ } else {
31689
+ const r = size / 3;
31690
+ out.push({ type: "circle", x: gx, y: gy, radius: r, color });
31691
+ out.push({ type: "circle", x: gx, y: gy, radius: 1.5, color, fill: color });
31692
+ }
31693
+ }
31694
+ }
31695
+ return out;
31696
+ }
31697
+ function meterShapes(meters, canvasHeight) {
31698
+ const n = meters.length;
31699
+ const out = [];
31700
+ const sharedMax = Math.max(1e-6, ...meters.map((m) => m.value));
31701
+ meters.forEach((meter, i) => {
31702
+ const rowY = canvasHeight - 10 - 16 * (n - i);
31703
+ const color = meter.color ?? "#3b82f6";
31704
+ const M = meter.max ?? sharedMax;
31705
+ const w = Math.round(Math.min(1, Math.max(0, meter.value / M)) * 110);
31706
+ out.push({ type: "text", x: 8, y: rowY + 8, text: meter.label, color: PHYSICS_LABEL_COLOR, fontSize: 10 });
31707
+ out.push({ type: "rect", x: 52, y: rowY, width: w, height: 10, color, fill: color });
31708
+ out.push({
31709
+ type: "text",
31710
+ x: 166,
31711
+ y: rowY + 8,
31712
+ text: formatMeterValue(meter.value),
31713
+ color: "#6b7280",
31714
+ fontSize: 9
31715
+ });
31716
+ });
31717
+ return out;
31718
+ }
31719
+ var physicsLog2, PHYSICS_LABEL_COLOR; exports.PhysicsCanvas = void 0;
29615
31720
  var init_PhysicsCanvas = __esm({
29616
31721
  "components/learning/molecules/PhysicsCanvas.tsx"() {
29617
31722
  "use client";
@@ -29620,6 +31725,7 @@ var init_PhysicsCanvas = __esm({
29620
31725
  init_LearningCanvas();
29621
31726
  init_learningScene3D();
29622
31727
  physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
31728
+ PHYSICS_LABEL_COLOR = "#374151";
29623
31729
  exports.PhysicsCanvas = ({
29624
31730
  className,
29625
31731
  width = 600,
@@ -29636,7 +31742,18 @@ var init_PhysicsCanvas = __esm({
29636
31742
  showForces = false,
29637
31743
  velocityScale = 20,
29638
31744
  forceScale = 20,
31745
+ sceneObjects = [],
31746
+ trails = [],
31747
+ vectors = [],
31748
+ surface3d,
31749
+ vectors3d = [],
31750
+ vectorScale = 1,
31751
+ angles = [],
31752
+ field,
31753
+ meters = [],
29639
31754
  shapes = [],
31755
+ readouts,
31756
+ traces,
29640
31757
  showGrid,
29641
31758
  shadows,
29642
31759
  interactive,
@@ -29651,19 +31768,14 @@ var init_PhysicsCanvas = __esm({
29651
31768
  for (const b of bodies) {
29652
31769
  if (b.id) bodyById.set(b.id, b);
29653
31770
  }
31771
+ if (field) out.push(...fieldShapes(field, width, height));
31772
+ for (const obj of sceneObjects) out.push(...sceneObjectShapes(obj, width, height));
31773
+ for (const trail of trails) out.push(...trailShapes(trail));
29654
31774
  for (const c of constraints) {
29655
31775
  const a = bodyById.get(c.from);
29656
31776
  const b = bodyById.get(c.to);
29657
31777
  if (!a || !b) continue;
29658
- out.push({
29659
- type: "line",
29660
- x1: a.x,
29661
- y1: a.y,
29662
- x2: b.x,
29663
- y2: b.y,
29664
- color: c.color ?? "#9ca3af",
29665
- lineWidth: 2
29666
- });
31778
+ out.push(...constraintShapes(c, a, b));
29667
31779
  }
29668
31780
  for (const b of bodies) {
29669
31781
  out.push({
@@ -29708,14 +31820,51 @@ var init_PhysicsCanvas = __esm({
29708
31820
  });
29709
31821
  }
29710
31822
  }
31823
+ for (const v of vectors) out.push(...vectorShapes(v, bodyById));
31824
+ for (const a of angles) out.push(...angleMarkerShapes(a));
31825
+ if (meters.length > 0) out.push(...meterShapes(meters, height));
29711
31826
  out.push(...shapes);
29712
31827
  return out;
29713
- }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
31828
+ }, [
31829
+ bodies,
31830
+ constraints,
31831
+ showVelocity,
31832
+ showForces,
31833
+ velocityScale,
31834
+ forceScale,
31835
+ sceneObjects,
31836
+ trails,
31837
+ vectors,
31838
+ angles,
31839
+ field,
31840
+ meters,
31841
+ shapes,
31842
+ width,
31843
+ height
31844
+ ]);
29714
31845
  const drawables3D = React77.useMemo(() => {
29715
31846
  if (mode !== "3d") return [];
29716
31847
  if (shapes.length > 0) {
29717
31848
  physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
29718
31849
  }
31850
+ if (sceneObjects.length > 0) {
31851
+ physicsLog2.debug("sceneObjects ignored in 3D mode (pixel-authored 2D vocabulary)", { count: sceneObjects.length });
31852
+ }
31853
+ if (vectors.length > 0) {
31854
+ physicsLog2.debug("vectors ignored in 3D mode (pixel-authored 2D vocabulary)", { count: vectors.length });
31855
+ }
31856
+ if (angles.length > 0) {
31857
+ physicsLog2.debug("angles ignored in 3D mode (pixel-authored 2D vocabulary)", { count: angles.length });
31858
+ }
31859
+ if (field) {
31860
+ physicsLog2.debug("field ignored in 3D mode (pixel-authored 2D vocabulary)");
31861
+ }
31862
+ if (meters.length > 0) {
31863
+ physicsLog2.debug("meters ignored in 3D mode (pixel-authored 2D vocabulary)", { count: meters.length });
31864
+ }
31865
+ if (animate) {
31866
+ physicsLog2.debug("animate ignored in 3D mode (motion is entity-state driven)");
31867
+ }
29719
31868
  const out = [];
29720
31869
  const labelColor = labelColorForBackground(backgroundColor);
29721
31870
  const bodyById = /* @__PURE__ */ new Map();
@@ -29763,15 +31912,67 @@ var init_PhysicsCanvas = __esm({
29763
31912
  if (arrow) out.push(arrow);
29764
31913
  }
29765
31914
  }
31915
+ for (const trail of trails) {
31916
+ if (trail.fade !== void 0) {
31917
+ physicsLog2.debug("trail.fade ignored in 3D mode (2D-only fade curve \u2014 3D draws an opaque tube)", { id: trail.id });
31918
+ }
31919
+ const points = trail.points.map((p) => [p.x, p.y, p.z ?? 0]);
31920
+ out.push(
31921
+ ...polylineTube(points, trail.width ?? 0.05, trail.color ?? "#94a3b8", {
31922
+ ...trail.opacity !== void 0 ? { opacity: trail.opacity } : {}
31923
+ })
31924
+ );
31925
+ }
31926
+ if (surface3d) {
31927
+ out.push(...heightFieldMesh(surface3d));
31928
+ }
31929
+ if (vectors3d.length > 0) {
31930
+ out.push(
31931
+ ...arrowField(
31932
+ vectors3d.map((v) => ({
31933
+ id: v.id,
31934
+ from: [v.x, v.y, v.z ?? 0],
31935
+ delta: [v.dx, v.dy, v.dz ?? 0],
31936
+ color: v.color,
31937
+ label: v.label,
31938
+ width: v.width
31939
+ })),
31940
+ { scale: vectorScale, labelColor }
31941
+ )
31942
+ );
31943
+ }
29766
31944
  return out;
29767
- }, [mode, bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes, backgroundColor]);
31945
+ }, [
31946
+ mode,
31947
+ bodies,
31948
+ constraints,
31949
+ showVelocity,
31950
+ showForces,
31951
+ velocityScale,
31952
+ forceScale,
31953
+ shapes,
31954
+ sceneObjects,
31955
+ trails,
31956
+ vectors,
31957
+ surface3d,
31958
+ vectors3d,
31959
+ vectorScale,
31960
+ angles,
31961
+ field,
31962
+ meters,
31963
+ animate,
31964
+ backgroundColor
31965
+ ]);
29768
31966
  const bodyIndexById = React77.useMemo(() => {
29769
31967
  const m = /* @__PURE__ */ new Map();
31968
+ vectors3d.forEach((v, i) => {
31969
+ if (v.id) m.set(v.id, i);
31970
+ });
29770
31971
  bodies.forEach((b, i) => {
29771
31972
  if (b.id) m.set(b.id, i);
29772
31973
  });
29773
31974
  return m;
29774
- }, [bodies]);
31975
+ }, [bodies, vectors3d]);
29775
31976
  if (mode === "3d") {
29776
31977
  return /* @__PURE__ */ jsxRuntime.jsx(
29777
31978
  LearningScene3D,
@@ -29803,6 +32004,8 @@ var init_PhysicsCanvas = __esm({
29803
32004
  height,
29804
32005
  backgroundColor,
29805
32006
  shapes: derivedShapes,
32007
+ readouts,
32008
+ traces,
29806
32009
  interactive: interactive ?? false,
29807
32010
  animate,
29808
32011
  onShapeClick,
@@ -29953,7 +32156,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
29953
32156
  }
29954
32157
  return nodeIds.map((id) => positions.get(id));
29955
32158
  }
29956
- function layoutTree(nodeIds, adjacency, roots, width, height, margin) {
32159
+ function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
29957
32160
  const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
29958
32161
  const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
29959
32162
  const maxLayer = Math.max(...Array.from(layers.values()));
@@ -30009,7 +32212,7 @@ function computeStaticLayout(mode, input) {
30009
32212
  const adjacency = buildAdjacency(nodeIds, edges);
30010
32213
  const roots = findRoots(nodeIds, adjacency);
30011
32214
  if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
30012
- if (mode === "tree") return layoutTree(nodeIds, adjacency, roots, width, height, margin);
32215
+ if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
30013
32216
  return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
30014
32217
  }
30015
32218
  var init_graphViewLayouts = __esm({
@@ -30421,7 +32624,7 @@ var init_MapView = __esm({
30421
32624
  shadowSize: [41, 41]
30422
32625
  });
30423
32626
  L.Marker.prototype.options.icon = defaultIcon;
30424
- const { useEffect: useEffect66, useRef: useRef65, useCallback: useCallback107, useState: useState104 } = React77__namespace.default;
32627
+ const { useEffect: useEffect66, useRef: useRef65, useCallback: useCallback108, useState: useState104 } = React77__namespace.default;
30425
32628
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
30426
32629
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
30427
32630
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -30467,7 +32670,7 @@ var init_MapView = __esm({
30467
32670
  }) {
30468
32671
  const eventBus = useEventBus2();
30469
32672
  const [clickedPosition, setClickedPosition] = useState104(null);
30470
- const handleMapClick = useCallback107((lat, lng) => {
32673
+ const handleMapClick = useCallback108((lat, lng) => {
30471
32674
  if (showClickedPin) {
30472
32675
  setClickedPosition({ lat, lng });
30473
32676
  }
@@ -30476,7 +32679,7 @@ var init_MapView = __esm({
30476
32679
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
30477
32680
  }
30478
32681
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
30479
- const handleMarkerClick = useCallback107((marker) => {
32682
+ const handleMarkerClick = useCallback108((marker) => {
30480
32683
  onMarkerClick?.(marker);
30481
32684
  if (markerClickEvent) {
30482
32685
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -31252,26 +33455,6 @@ var init_Lightbox = __esm({
31252
33455
  exports.Lightbox.displayName = "Lightbox";
31253
33456
  }
31254
33457
  });
31255
- function useMediaQuery(query) {
31256
- const subscribe = React77.useCallback(
31257
- (onChange) => {
31258
- const mql = window.matchMedia(query);
31259
- mql.addEventListener("change", onChange);
31260
- return () => mql.removeEventListener("change", onChange);
31261
- },
31262
- [query]
31263
- );
31264
- return React77.useSyncExternalStore(
31265
- subscribe,
31266
- () => window.matchMedia(query).matches,
31267
- () => false
31268
- );
31269
- }
31270
- var init_useMediaQuery = __esm({
31271
- "hooks/useMediaQuery.ts"() {
31272
- "use client";
31273
- }
31274
- });
31275
33458
  function renderIconInput3(icon, props) {
31276
33459
  return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { icon, ...props });
31277
33460
  }
@@ -31310,8 +33493,8 @@ function TableView({
31310
33493
  columns,
31311
33494
  fields,
31312
33495
  itemActions,
31313
- maxInlineActions,
31314
- itemClickEvent,
33496
+ maxInlineActions: _maxInlineActions,
33497
+ itemClickEvent = "",
31315
33498
  selectable = false,
31316
33499
  selectEvent,
31317
33500
  selectedIds,
@@ -31361,7 +33544,6 @@ function TableView({
31361
33544
  const hasMore = pageSize > 0 && visibleCount < ordered2.length;
31362
33545
  const hasRenderProp = typeof children === "function";
31363
33546
  const idField = dndItemIdField ?? "id";
31364
- const isCoarsePointer = useMediaQuery("(pointer: coarse)");
31365
33547
  React77__namespace.default.useEffect(() => {
31366
33548
  tableViewLog.debug("render", {
31367
33549
  rowCount: data.length,
@@ -31403,21 +33585,14 @@ function TableView({
31403
33585
  const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
31404
33586
  eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
31405
33587
  };
31406
- const handleActionClick = (action, row) => (e) => {
31407
- e.stopPropagation();
31408
- const payload = {
31409
- id: row.id,
31410
- row
31411
- };
31412
- eventBus.emit(`UI:${action.event}`, payload);
31413
- };
33588
+ const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
31414
33589
  const handleRowClick = (row) => () => {
31415
- if (!itemClickEvent) return;
33590
+ if (!rowClickEvent) return;
31416
33591
  const payload = {
31417
33592
  id: row.id,
31418
33593
  row
31419
33594
  };
31420
- eventBus.emit(`UI:${itemClickEvent}`, payload);
33595
+ eventBus.emit(`UI:${rowClickEvent}`, payload);
31421
33596
  };
31422
33597
  const colFloors = React77__namespace.default.useMemo(
31423
33598
  () => colDefs.map((col) => {
@@ -31433,10 +33608,7 @@ function TableView({
31433
33608
  const statusNode = isLoading ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "body", color: "secondary", children: t("loading.items") }) }) : error ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "body", color: "error", children: error.message }) }) : data.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) }) : null;
31434
33609
  const lk = LOOKS[look];
31435
33610
  const hasActions = actionDefs.length > 0;
31436
- const effectiveMaxInline = isCoarsePointer ? 0 : maxInlineActions;
31437
- const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
31438
- const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
31439
- const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
33611
+ const actionsTrack = hasActions ? "3rem" : null;
31440
33612
  const gridTemplateColumns = [
31441
33613
  selectable ? "auto" : null,
31442
33614
  ...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
@@ -31483,7 +33655,7 @@ function TableView({
31483
33655
  col.key
31484
33656
  );
31485
33657
  }),
31486
- hasActions && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)]" })
33658
+ hasActions && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)] border-l border-[var(--color-border)] h-full" })
31487
33659
  ]
31488
33660
  }
31489
33661
  );
@@ -31495,12 +33667,12 @@ function TableView({
31495
33667
  role: "row",
31496
33668
  "data-entity-row": true,
31497
33669
  "data-entity-id": id,
31498
- onClick: itemClickEvent ? handleRowClick(row) : void 0,
33670
+ onClick: rowClickEvent ? handleRowClick(row) : void 0,
31499
33671
  style: !hasRenderProp ? { gridTemplateColumns } : void 0,
31500
33672
  className: cn(
31501
33673
  "group items-center gap-3 transition-colors duration-fast",
31502
33674
  hasRenderProp ? "flex" : "grid",
31503
- itemClickEvent && "cursor-pointer",
33675
+ rowClickEvent && "cursor-pointer",
31504
33676
  lk.rowPad,
31505
33677
  lk.divider && "border-b border-[var(--color-border)]",
31506
33678
  lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
@@ -31508,7 +33680,7 @@ function TableView({
31508
33680
  look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
31509
33681
  ),
31510
33682
  children: [
31511
- selectable && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "flex items-center", onClick: itemClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
33683
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "flex items-center", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
31512
33684
  exports.Checkbox,
31513
33685
  {
31514
33686
  checked: selected.has(id),
@@ -31529,53 +33701,37 @@ function TableView({
31529
33701
  }
31530
33702
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
31531
33703
  }),
31532
- hasActions && /* @__PURE__ */ jsxRuntime.jsxs(
33704
+ hasActions && /* @__PURE__ */ jsxRuntime.jsx(
31533
33705
  exports.HStack,
31534
33706
  {
31535
33707
  gap: "xs",
31536
- onClick: itemClickEvent ? (e) => e.stopPropagation() : void 0,
33708
+ onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
31537
33709
  className: cn(
31538
33710
  // Pinned: the fixed column tracks routinely overflow the caller's
31539
- // scroll container, which used to leave the actions off-screen.
31540
- // Opaque so scrolled cells pass underneath it.
33711
+ // scroll container, which would leave the kebab off-screen.
33712
+ // Opaque + hairline edge so it reads as a pinned column, not a
33713
+ // floating control, while scrolled cells pass underneath.
31541
33714
  "justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
33715
+ "border-l border-[var(--color-border)]",
31542
33716
  lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
31543
33717
  ),
31544
- children: [
31545
- (effectiveMaxInline != null ? actionDefs.slice(0, effectiveMaxInline) : actionDefs).map((action, i) => /* @__PURE__ */ jsxRuntime.jsxs(
31546
- exports.Button,
31547
- {
31548
- variant: action.variant === "primary" ? "primary" : "ghost",
31549
- size: "sm",
31550
- onClick: handleActionClick(action, row),
31551
- "data-testid": `action-${action.event}`,
31552
- "data-row-id": String(row.id),
31553
- className: cn(action.variant === "danger" && "text-error hover:text-error hover:bg-error/10"),
31554
- children: [
31555
- action.icon && renderIconInput3(action.icon, { size: "xs", className: "mr-1" }),
31556
- action.label
31557
- ]
31558
- },
31559
- i
31560
- )),
31561
- effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
31562
- exports.Menu,
31563
- {
31564
- position: "bottom-end",
31565
- trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
31566
- items: actionDefs.slice(effectiveMaxInline).map((action) => ({
31567
- label: action.label,
31568
- icon: action.icon,
31569
- event: action.event,
31570
- variant: action.variant === "danger" ? "danger" : "default",
31571
- onClick: () => eventBus.emit(`UI:${action.event}`, {
31572
- id: row.id,
31573
- row
31574
- })
31575
- }))
31576
- }
31577
- )
31578
- ]
33718
+ children: /* @__PURE__ */ jsxRuntime.jsx(
33719
+ exports.Menu,
33720
+ {
33721
+ position: "bottom-end",
33722
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", "data-row-id": String(row.id), children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
33723
+ items: actionDefs.map((action) => ({
33724
+ label: action.label,
33725
+ icon: action.icon,
33726
+ event: action.event,
33727
+ variant: action.variant === "danger" ? "danger" : "default",
33728
+ onClick: () => eventBus.emit(`UI:${action.event}`, {
33729
+ id: row.id,
33730
+ row
33731
+ })
33732
+ }))
33733
+ }
33734
+ )
31579
33735
  }
31580
33736
  )
31581
33737
  ]
@@ -31618,7 +33774,6 @@ var init_TableView = __esm({
31618
33774
  init_format();
31619
33775
  init_getNestedValue();
31620
33776
  init_useEventBus();
31621
- init_useMediaQuery();
31622
33777
  init_Box();
31623
33778
  init_Stack();
31624
33779
  init_Typography();
@@ -40218,6 +42373,7 @@ var init_molecules2 = __esm({
40218
42373
  init_BiologyCanvas();
40219
42374
  init_ChemistryCanvas();
40220
42375
  init_AlgorithmCanvas();
42376
+ init_AlgoGraphCanvas();
40221
42377
  init_learningScene3D();
40222
42378
  init_GraphView();
40223
42379
  init_MapView();
@@ -46900,6 +49056,7 @@ var init_component_registry_generated = __esm({
46900
49056
  init_ActionTile();
46901
49057
  init_ActivationBlock();
46902
49058
  init_ComponentPatterns();
49059
+ init_AlgoGraphCanvas();
46903
49060
  init_AlgorithmCanvas();
46904
49061
  init_AnimatedCounter();
46905
49062
  init_AnimatedGraphic();
@@ -47163,6 +49320,7 @@ var init_component_registry_generated = __esm({
47163
49320
  "ActivationBlock": exports.ActivationBlock,
47164
49321
  "Alert": AlertPattern,
47165
49322
  "AlertPattern": AlertPattern,
49323
+ "AlgoGraphCanvas": exports.AlgoGraphCanvas,
47166
49324
  "AlgorithmCanvas": exports.AlgorithmCanvas,
47167
49325
  "AnimatedCounter": exports.AnimatedCounter,
47168
49326
  "AnimatedGraphic": exports.AnimatedGraphic,
@@ -50635,7 +52793,23 @@ init_useAuthContext();
50635
52793
  init_useSwipeGesture();
50636
52794
  init_useLongPress();
50637
52795
  init_useDragReorder();
50638
- init_useMediaQuery();
52796
+ function useMediaQuery(query) {
52797
+ const subscribe = React77.useCallback(
52798
+ (onChange) => {
52799
+ const mql = window.matchMedia(query);
52800
+ mql.addEventListener("change", onChange);
52801
+ return () => mql.removeEventListener("change", onChange);
52802
+ },
52803
+ [query]
52804
+ );
52805
+ return React77.useSyncExternalStore(
52806
+ subscribe,
52807
+ () => window.matchMedia(query).matches,
52808
+ () => false
52809
+ );
52810
+ }
52811
+
52812
+ // hooks/index.ts
50639
52813
  init_useInfiniteScroll();
50640
52814
  init_usePullToRefresh();
50641
52815
  init_useCanvasGestures();