@almadar/ui 5.148.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.
Files changed (35) hide show
  1. package/dist/{GameAudioProvider-CPGwD49P.d.cts → GameAudioProvider-B48iXwz3.d.ts} +2 -50
  2. package/dist/{GameAudioProvider-CPGwD49P.d.ts → GameAudioProvider-CQAdPreB.d.cts} +2 -50
  3. package/dist/avl/index.cjs +2741 -158
  4. package/dist/avl/index.js +2741 -158
  5. package/dist/{avl-schema-parser-DVmrgdwc.d.cts → avl-schema-parser-CVzkNzg7.d.cts} +18 -9
  6. package/dist/{avl-schema-parser-BRJ77Yze.d.ts → avl-schema-parser-Cx_4SVg9.d.ts} +18 -9
  7. package/dist/{cn-BAn68sNO.d.cts → cn-BnAJZcNb.d.cts} +2 -8
  8. package/dist/{cn-CWxxLkri.d.ts → cn-DJTUjk1M.d.ts} +2 -8
  9. package/dist/components/index.cjs +2764 -158
  10. package/dist/components/index.d.cts +1077 -107
  11. package/dist/components/index.d.ts +1077 -107
  12. package/dist/components/index.js +2760 -160
  13. package/dist/hooks/index.cjs +1 -1
  14. package/dist/hooks/index.js +1 -1
  15. package/dist/lib/drawable/three/index.cjs +57 -30
  16. package/dist/lib/drawable/three/index.d.cts +4 -3
  17. package/dist/lib/drawable/three/index.d.ts +4 -3
  18. package/dist/lib/drawable/three/index.js +57 -30
  19. package/dist/lib/index.d.cts +3 -2
  20. package/dist/lib/index.d.ts +3 -2
  21. package/dist/marketing/index.cjs +1 -1
  22. package/dist/marketing/index.js +1 -1
  23. package/dist/{paintDispatch-Cb_hQj4Y.d.ts → paintDispatch-BjZjUbcb.d.cts} +63 -69
  24. package/dist/{paintDispatch-Cb_hQj4Y.d.cts → paintDispatch-CxdLjHQq.d.ts} +63 -69
  25. package/dist/providers/index.cjs +2734 -152
  26. package/dist/providers/index.d.cts +2 -1
  27. package/dist/providers/index.d.ts +2 -1
  28. package/dist/providers/index.js +2734 -152
  29. package/dist/runtime/index.cjs +2741 -158
  30. package/dist/runtime/index.d.cts +2 -2
  31. package/dist/runtime/index.d.ts +2 -2
  32. package/dist/runtime/index.js +2741 -158
  33. package/dist/types-B3nHgnMG.d.cts +51 -0
  34. package/dist/types-B3nHgnMG.d.ts +51 -0
  35. package/package.json +3 -3
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import * as React77 from 'react';
3
- import React77__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, useState, useLayoutEffect, Suspense, useSyncExternalStore, lazy, useId } from 'react';
3
+ import React77__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, useState, useLayoutEffect, useId, Suspense, lazy, useSyncExternalStore } from 'react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { EventBusContext, useTraitScopeChain, useCurrentPagePath, useGameAudioContextOptional, useEntitySchemaOptional, useEntityBindingSnapshot, useTraitScope, TraitScopeProvider } from '@almadar/ui/providers';
@@ -1081,7 +1081,7 @@ function useEventBus() {
1081
1081
  return {
1082
1082
  ...baseBus,
1083
1083
  emit: (type, payload, source) => {
1084
- if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".")) {
1084
+ if (typeof type === "string" && type.startsWith("UI:") && !type.slice(3).includes(".") && !source?.trait) {
1085
1085
  scopeLog.warn("emit:bare-key-no-scope", { type });
1086
1086
  }
1087
1087
  baseBus.emit(type, payload, source);
@@ -8407,6 +8407,14 @@ function shapeBounds(shape) {
8407
8407
  w: shape.radius * 2 + 8,
8408
8408
  h: shape.radius * 2 + 8
8409
8409
  };
8410
+ case "ellipse":
8411
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
8412
+ return {
8413
+ x: shape.x - shape.width / 2 - 4,
8414
+ y: shape.y - shape.height / 2 - 4,
8415
+ w: shape.width + 8,
8416
+ h: shape.height + 8
8417
+ };
8410
8418
  case "rect":
8411
8419
  if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
8412
8420
  return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
@@ -8440,13 +8448,14 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
8440
8448
  ctx.closePath();
8441
8449
  ctx.fill();
8442
8450
  }
8443
- function drawShape(ctx, shape, width, height) {
8451
+ function drawShape(ctx, shape, width, height, allShapes) {
8444
8452
  ctx.save();
8445
8453
  const opacity = shape.opacity ?? 1;
8446
8454
  ctx.globalAlpha = opacity;
8447
8455
  const stroke = resolveColor2(shape.color, ctx, "#333333");
8448
8456
  const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
8449
8457
  ctx.lineWidth = shape.lineWidth ?? 2;
8458
+ if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
8450
8459
  switch (shape.type) {
8451
8460
  case "grid": {
8452
8461
  const step = shape.step ?? 40;
@@ -8512,6 +8521,20 @@ function drawShape(ctx, shape, width, height) {
8512
8521
  ctx.stroke();
8513
8522
  break;
8514
8523
  }
8524
+ case "ellipse": {
8525
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
8526
+ const startAngle = (shape.startAngle ?? 0) * Math.PI / 180;
8527
+ const endAngle = (shape.endAngle ?? 360) * Math.PI / 180;
8528
+ ctx.beginPath();
8529
+ ctx.ellipse(shape.x, shape.y, shape.width / 2, shape.height / 2, 0, startAngle, endAngle);
8530
+ if (fill) {
8531
+ ctx.fillStyle = fill;
8532
+ ctx.fill();
8533
+ }
8534
+ ctx.strokeStyle = stroke;
8535
+ ctx.stroke();
8536
+ break;
8537
+ }
8515
8538
  case "rect": {
8516
8539
  if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
8517
8540
  if (fill) {
@@ -8558,21 +8581,153 @@ function drawShape(ctx, shape, width, height) {
8558
8581
  ctx.fillText(shape.text, shape.x, shape.y);
8559
8582
  break;
8560
8583
  }
8584
+ case "venn-region": {
8585
+ const resolveCircles = (ids) => (ids ?? []).flatMap((id) => {
8586
+ const c = allShapes.find((s) => s.type === "circle" && s.id === id);
8587
+ return c && c.x != null && c.y != null && c.radius != null ? [{ x: c.x, y: c.y, radius: c.radius }] : [];
8588
+ });
8589
+ const inside = resolveCircles(shape.inside);
8590
+ if (inside.length === 0) break;
8591
+ const outside = resolveCircles(shape.outside);
8592
+ const off = document.createElement("canvas");
8593
+ off.width = ctx.canvas.width;
8594
+ off.height = ctx.canvas.height;
8595
+ const octx = off.getContext("2d");
8596
+ if (!octx) break;
8597
+ octx.setTransform(ctx.getTransform());
8598
+ for (const c of inside) {
8599
+ const p = new Path2D();
8600
+ p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
8601
+ octx.clip(p);
8602
+ }
8603
+ octx.fillStyle = fill ?? stroke;
8604
+ octx.fillRect(0, 0, width, height);
8605
+ octx.globalCompositeOperation = "destination-out";
8606
+ for (const c of outside) {
8607
+ const p = new Path2D();
8608
+ p.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
8609
+ octx.fill(p);
8610
+ }
8611
+ ctx.save();
8612
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
8613
+ ctx.drawImage(off, 0, 0);
8614
+ ctx.restore();
8615
+ break;
8616
+ }
8561
8617
  }
8562
8618
  ctx.restore();
8563
8619
  }
8564
- var LearningCanvas;
8620
+ function readoutShapes(readouts, width) {
8621
+ const out = [];
8622
+ const chipH = 18;
8623
+ const gap = 6;
8624
+ let rightEdge = width - 6;
8625
+ let rowY = 6;
8626
+ for (const readout of readouts) {
8627
+ const text = `${readout.label}: ${String(readout.value)}`;
8628
+ const chipW = Math.min(170, Math.max(34, text.length * 6 + 12));
8629
+ let chipX = rightEdge - chipW;
8630
+ if (chipX < 4) {
8631
+ rowY += chipH + 4;
8632
+ rightEdge = width - 6;
8633
+ chipX = rightEdge - chipW;
8634
+ }
8635
+ const color = readout.color ?? "#334155";
8636
+ out.push({ type: "rect", x: chipX, y: rowY, width: chipW, height: chipH, color, fill: color });
8637
+ out.push({
8638
+ type: "text",
8639
+ x: chipX + chipW / 2,
8640
+ y: rowY + chipH / 2,
8641
+ text,
8642
+ color: "#ffffff",
8643
+ fontSize: 10,
8644
+ align: "center"
8645
+ });
8646
+ rightEdge = chipX - gap;
8647
+ }
8648
+ return out;
8649
+ }
8650
+ function traceShapes(panel, k, width, height) {
8651
+ const w = panel.width ?? Math.round(width * 0.32);
8652
+ const h = panel.height ?? Math.round(height * 0.28);
8653
+ const x = panel.x ?? width - w - 8;
8654
+ const y = panel.y ?? height - h - 8 - k * (h + 8);
8655
+ const allSamples = panel.series.flatMap((series) => series.samples);
8656
+ let xLo = Math.min(...allSamples.map((p) => p.x));
8657
+ let xHi = Math.max(...allSamples.map((p) => p.x));
8658
+ let yLo = Math.min(...allSamples.map((p) => p.y));
8659
+ let yHi = Math.max(...allSamples.map((p) => p.y));
8660
+ if (xLo === xHi) {
8661
+ xLo -= 1;
8662
+ xHi += 1;
8663
+ }
8664
+ if (yLo === yHi) {
8665
+ yLo -= 1;
8666
+ yHi += 1;
8667
+ }
8668
+ const backgroundColor = panel.backgroundColor ?? "#ffffff";
8669
+ const frameColor = panel.frameColor ?? "#94a3b8";
8670
+ const out = [];
8671
+ out.push({
8672
+ type: "rect",
8673
+ x,
8674
+ y,
8675
+ width: w,
8676
+ height: h,
8677
+ color: backgroundColor,
8678
+ fill: backgroundColor,
8679
+ opacity: panel.backgroundOpacity ?? 0.85
8680
+ });
8681
+ out.push({ type: "rect", x, y, width: w, height: h, color: frameColor, lineWidth: 1 });
8682
+ panel.series.forEach((series, j) => {
8683
+ const color = series.color ?? TRACE_SERIES_COLORS[j % TRACE_SERIES_COLORS.length];
8684
+ const mapped = series.samples.map((p) => ({
8685
+ x: x + 4 + (p.x - xLo) / (xHi - xLo) * (w - 8),
8686
+ y: y + h - 4 - (p.y - yLo) / (yHi - yLo) * (h - 8)
8687
+ }));
8688
+ for (let i = 1; i < mapped.length; i++) {
8689
+ out.push({
8690
+ type: "line",
8691
+ x1: mapped[i - 1].x,
8692
+ y1: mapped[i - 1].y,
8693
+ x2: mapped[i].x,
8694
+ y2: mapped[i].y,
8695
+ color,
8696
+ lineWidth: 1.5
8697
+ });
8698
+ }
8699
+ if (mapped.length > 0) {
8700
+ const last = mapped[mapped.length - 1];
8701
+ out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
8702
+ }
8703
+ if (series.label) {
8704
+ out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
8705
+ }
8706
+ });
8707
+ if (panel.yLabel) {
8708
+ out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
8709
+ }
8710
+ if (panel.xLabel) {
8711
+ out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
8712
+ }
8713
+ return out;
8714
+ }
8715
+ var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
8565
8716
  var init_LearningCanvas = __esm({
8566
8717
  "components/learning/atoms/LearningCanvas.tsx"() {
8567
8718
  "use client";
8568
8719
  init_cn();
8569
8720
  init_useEventBus();
8721
+ DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
8722
+ TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
8570
8723
  LearningCanvas = ({
8571
8724
  className,
8572
8725
  width = 600,
8573
8726
  height = 400,
8574
8727
  backgroundColor,
8575
8728
  shapes = [],
8729
+ readouts,
8730
+ traces,
8576
8731
  interactive = false,
8577
8732
  animate = false,
8578
8733
  onShapeClick,
@@ -8598,6 +8753,12 @@ var init_LearningCanvas = __esm({
8598
8753
  }
8599
8754
  return -1;
8600
8755
  }, [shapes]);
8756
+ const derivedShapes = useMemo(() => {
8757
+ if (!traces?.length && !readouts?.length) return shapes;
8758
+ const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
8759
+ const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
8760
+ return [...shapes, ...traceOut, ...readoutOut];
8761
+ }, [shapes, traces, readouts, width, height]);
8601
8762
  const draw = useCallback(() => {
8602
8763
  const canvas = canvasRef.current;
8603
8764
  if (!canvas) return;
@@ -8614,13 +8775,13 @@ var init_LearningCanvas = __esm({
8614
8775
  ctx.fillStyle = backgroundColor;
8615
8776
  ctx.fillRect(0, 0, width, height);
8616
8777
  }
8617
- for (const shape of shapes) {
8618
- if (shape.type !== "text") drawShape(ctx, shape, width, height);
8778
+ for (const shape of derivedShapes) {
8779
+ if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
8619
8780
  }
8620
- for (const shape of shapes) {
8621
- if (shape.type === "text") drawShape(ctx, shape, width, height);
8781
+ for (const shape of derivedShapes) {
8782
+ if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
8622
8783
  }
8623
- }, [width, height, backgroundColor, shapes]);
8784
+ }, [width, height, backgroundColor, derivedShapes]);
8624
8785
  useEffect(() => {
8625
8786
  draw();
8626
8787
  }, [draw]);
@@ -8688,7 +8849,363 @@ var init_LearningCanvas = __esm({
8688
8849
  };
8689
8850
  }
8690
8851
  });
8691
- var DEFAULT_BAR_COLOR, DEFAULT_CELL_COLOR, DEFAULT_POINTER_COLOR, POINTER_BAND, TOP_PAD, AlgorithmCanvas;
8852
+ function layoutCircle(nodes, width, height) {
8853
+ const cx = width / 2;
8854
+ const cy = height / 2;
8855
+ const radius = Math.max(10, Math.min(cx, cy) - 40);
8856
+ const positions = /* @__PURE__ */ new Map();
8857
+ const n = nodes.length;
8858
+ nodes.forEach((node, i) => {
8859
+ const angle = 2 * Math.PI * i / Math.max(n, 1) - Math.PI / 2;
8860
+ positions.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
8861
+ });
8862
+ return positions;
8863
+ }
8864
+ function layoutTree(nodes, edges, root, width, height) {
8865
+ const nodeIds = nodes.map((n) => n.id);
8866
+ const idSet = new Set(nodeIds);
8867
+ const childrenOf = /* @__PURE__ */ new Map();
8868
+ const hasIncoming = /* @__PURE__ */ new Set();
8869
+ for (const e of edges) {
8870
+ if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
8871
+ const list = childrenOf.get(e.from) ?? [];
8872
+ list.push(e.to);
8873
+ childrenOf.set(e.from, list);
8874
+ hasIncoming.add(e.to);
8875
+ }
8876
+ const depth = /* @__PURE__ */ new Map();
8877
+ const treeChildren = /* @__PURE__ */ new Map();
8878
+ const visited = /* @__PURE__ */ new Set();
8879
+ const bfsFrom = (start) => {
8880
+ if (visited.has(start)) return;
8881
+ visited.add(start);
8882
+ depth.set(start, 0);
8883
+ const queue = [start];
8884
+ while (queue.length > 0) {
8885
+ const u = queue.shift();
8886
+ for (const v of childrenOf.get(u) ?? []) {
8887
+ if (visited.has(v)) continue;
8888
+ visited.add(v);
8889
+ depth.set(v, (depth.get(u) ?? 0) + 1);
8890
+ const list = treeChildren.get(u) ?? [];
8891
+ list.push(v);
8892
+ treeChildren.set(u, list);
8893
+ queue.push(v);
8894
+ }
8895
+ }
8896
+ };
8897
+ const primaryRoot = root && idSet.has(root) ? root : nodeIds.find((id) => !hasIncoming.has(id)) ?? nodeIds[0];
8898
+ const rootsOrder = [];
8899
+ if (primaryRoot !== void 0) {
8900
+ bfsFrom(primaryRoot);
8901
+ rootsOrder.push(primaryRoot);
8902
+ }
8903
+ for (const id of nodeIds) {
8904
+ if (!visited.has(id)) {
8905
+ bfsFrom(id);
8906
+ rootsOrder.push(id);
8907
+ }
8908
+ }
8909
+ let leafCounter = 0;
8910
+ const xSlot = /* @__PURE__ */ new Map();
8911
+ const assignXSlot = (u) => {
8912
+ const children = treeChildren.get(u) ?? [];
8913
+ if (children.length === 0) {
8914
+ const slot = leafCounter++;
8915
+ xSlot.set(u, slot);
8916
+ return slot;
8917
+ }
8918
+ const childSlots = children.map(assignXSlot);
8919
+ const avg = childSlots.reduce((a, b) => a + b, 0) / childSlots.length;
8920
+ xSlot.set(u, avg);
8921
+ return avg;
8922
+ };
8923
+ for (const r of rootsOrder) assignXSlot(r);
8924
+ let maxDepth = 0;
8925
+ for (const d of depth.values()) maxDepth = Math.max(maxDepth, d);
8926
+ const colWidth = width / Math.max(1, leafCounter);
8927
+ const rowHeight = height / (maxDepth + 1);
8928
+ const positions = /* @__PURE__ */ new Map();
8929
+ for (const id of nodeIds) {
8930
+ const slot = xSlot.get(id) ?? 0;
8931
+ const d = depth.get(id) ?? 0;
8932
+ positions.set(id, { x: slot * colWidth + colWidth / 2, y: d * rowHeight + rowHeight / 2 });
8933
+ }
8934
+ return positions;
8935
+ }
8936
+ function layoutLayered(nodes, edges, width, height) {
8937
+ const nodeIds = nodes.map((n) => n.id);
8938
+ const idSet = new Set(nodeIds);
8939
+ const adj = /* @__PURE__ */ new Map();
8940
+ const remainingIndegree = /* @__PURE__ */ new Map();
8941
+ for (const id of nodeIds) remainingIndegree.set(id, 0);
8942
+ for (const e of edges) {
8943
+ if (!idSet.has(e.from) || !idSet.has(e.to)) continue;
8944
+ const list = adj.get(e.from) ?? [];
8945
+ list.push(e.to);
8946
+ adj.set(e.from, list);
8947
+ remainingIndegree.set(e.to, (remainingIndegree.get(e.to) ?? 0) + 1);
8948
+ }
8949
+ const layer = /* @__PURE__ */ new Map();
8950
+ const dequeued = /* @__PURE__ */ new Set();
8951
+ const queue = [];
8952
+ for (const id of nodeIds) {
8953
+ if ((remainingIndegree.get(id) ?? 0) === 0) {
8954
+ layer.set(id, 0);
8955
+ queue.push(id);
8956
+ }
8957
+ }
8958
+ while (queue.length > 0) {
8959
+ const u = queue.shift();
8960
+ dequeued.add(u);
8961
+ for (const v of adj.get(u) ?? []) {
8962
+ const candidate = (layer.get(u) ?? 0) + 1;
8963
+ layer.set(v, Math.max(layer.get(v) ?? 0, candidate));
8964
+ remainingIndegree.set(v, (remainingIndegree.get(v) ?? 0) - 1);
8965
+ if ((remainingIndegree.get(v) ?? 0) === 0 && !dequeued.has(v)) {
8966
+ queue.push(v);
8967
+ }
8968
+ }
8969
+ }
8970
+ let baseMaxLayer = 0;
8971
+ for (const id of nodeIds) {
8972
+ if (dequeued.has(id)) baseMaxLayer = Math.max(baseMaxLayer, layer.get(id) ?? 0);
8973
+ }
8974
+ const cycleLayer = baseMaxLayer + 1;
8975
+ let maxLayer = baseMaxLayer;
8976
+ for (const id of nodeIds) {
8977
+ if (!dequeued.has(id)) {
8978
+ layer.set(id, cycleLayer);
8979
+ maxLayer = cycleLayer;
8980
+ }
8981
+ }
8982
+ const colWidth = width / Math.max(1, maxLayer + 1);
8983
+ const byLayer = /* @__PURE__ */ new Map();
8984
+ for (const id of nodeIds) {
8985
+ const l = layer.get(id) ?? 0;
8986
+ const list = byLayer.get(l) ?? [];
8987
+ list.push(id);
8988
+ byLayer.set(l, list);
8989
+ }
8990
+ const positions = /* @__PURE__ */ new Map();
8991
+ for (const [l, ids] of byLayer) {
8992
+ const rowHeight = height / ids.length;
8993
+ ids.forEach((id, i) => {
8994
+ positions.set(id, { x: l * colWidth + colWidth / 2, y: i * rowHeight + rowHeight / 2 });
8995
+ });
8996
+ }
8997
+ return positions;
8998
+ }
8999
+ function computePositions(nodes, edges, layout, root, width, height) {
9000
+ switch (layout) {
9001
+ case "circle":
9002
+ return layoutCircle(nodes, width, height);
9003
+ case "tree":
9004
+ return layoutTree(nodes, edges, root, width, height);
9005
+ case "layered":
9006
+ return layoutLayered(nodes, edges, width, height);
9007
+ case "manual":
9008
+ default: {
9009
+ const positions = /* @__PURE__ */ new Map();
9010
+ for (const n of nodes) positions.set(n.id, { x: n.x ?? 0, y: n.y ?? 0 });
9011
+ return positions;
9012
+ }
9013
+ }
9014
+ }
9015
+ var NODE_STATE_COLOR, EDGE_STATE_COLOR, DEFAULT_NODE_RADIUS, AlgoGraphCanvas;
9016
+ var init_AlgoGraphCanvas = __esm({
9017
+ "components/learning/molecules/AlgoGraphCanvas.tsx"() {
9018
+ "use client";
9019
+ init_atoms();
9020
+ init_Stack();
9021
+ init_LearningCanvas();
9022
+ NODE_STATE_COLOR = {
9023
+ unvisited: "#cbd5e1",
9024
+ frontier: "#f59e0b",
9025
+ current: "#ef4444",
9026
+ visited: "#22c55e",
9027
+ goal: "#8b5cf6",
9028
+ path: "#0ea5e9"
9029
+ };
9030
+ EDGE_STATE_COLOR = {
9031
+ default: "#9ca3af",
9032
+ tree: "#16a34a",
9033
+ relaxed: "#f59e0b",
9034
+ candidate: "#38bdf8",
9035
+ path: "#dc2626"
9036
+ };
9037
+ DEFAULT_NODE_RADIUS = 18;
9038
+ AlgoGraphCanvas = ({
9039
+ className,
9040
+ width = 600,
9041
+ height = 400,
9042
+ title,
9043
+ backgroundColor,
9044
+ nodes = [],
9045
+ edges = [],
9046
+ layout = "manual",
9047
+ root,
9048
+ shapes = [],
9049
+ interactive = false,
9050
+ animate = false,
9051
+ onShapeClick,
9052
+ onNodeClick,
9053
+ isLoading,
9054
+ error
9055
+ }) => {
9056
+ const nodeById = useMemo(() => {
9057
+ const m = /* @__PURE__ */ new Map();
9058
+ for (const n of nodes) m.set(n.id, n);
9059
+ return m;
9060
+ }, [nodes]);
9061
+ const nodeIndexById = useMemo(() => {
9062
+ const m = /* @__PURE__ */ new Map();
9063
+ nodes.forEach((n, i) => m.set(n.id, i));
9064
+ return m;
9065
+ }, [nodes]);
9066
+ const derivedShapes = useMemo(() => {
9067
+ const out = [];
9068
+ const positions = computePositions(nodes, edges, layout, root, width, height);
9069
+ const edgeGeoms = [];
9070
+ for (const e of edges) {
9071
+ const a = nodeById.get(e.from);
9072
+ const b = nodeById.get(e.to);
9073
+ const posA = positions.get(e.from);
9074
+ const posB = positions.get(e.to);
9075
+ if (!a || !b || !posA || !posB) continue;
9076
+ const rA = a.radius ?? DEFAULT_NODE_RADIUS;
9077
+ const rB = b.radius ?? DEFAULT_NODE_RADIUS;
9078
+ const dx = posB.x - posA.x;
9079
+ const dy = posB.y - posA.y;
9080
+ const dist = Math.max(1e-6, Math.hypot(dx, dy));
9081
+ const ux = dx / dist;
9082
+ const uy = dy / dist;
9083
+ edgeGeoms.push({
9084
+ directed: e.directed ?? false,
9085
+ x1: posA.x + ux * rA,
9086
+ y1: posA.y + uy * rA,
9087
+ x2: posB.x - ux * rB,
9088
+ y2: posB.y - uy * rB,
9089
+ color: e.color ?? EDGE_STATE_COLOR[e.state ?? "default"],
9090
+ label: e.label ?? (e.weight != null ? String(e.weight) : void 0)
9091
+ });
9092
+ }
9093
+ for (const g of edgeGeoms) {
9094
+ out.push({
9095
+ type: g.directed ? "arrow" : "line",
9096
+ x1: g.x1,
9097
+ y1: g.y1,
9098
+ x2: g.x2,
9099
+ y2: g.y2,
9100
+ color: g.color,
9101
+ lineWidth: 2
9102
+ });
9103
+ }
9104
+ for (const g of edgeGeoms) {
9105
+ if (g.label === void 0) continue;
9106
+ const midX = (g.x1 + g.x2) / 2;
9107
+ const midY = (g.y1 + g.y2) / 2;
9108
+ const dx = g.x2 - g.x1;
9109
+ const dy = g.y2 - g.y1;
9110
+ const dist = Math.max(1e-6, Math.hypot(dx, dy));
9111
+ const perpX = -(dy / dist);
9112
+ const perpY = dx / dist;
9113
+ out.push({
9114
+ type: "text",
9115
+ x: midX + perpX * 10,
9116
+ y: midY + perpY * 10,
9117
+ text: g.label,
9118
+ fontSize: 11,
9119
+ align: "center",
9120
+ color: "#374151"
9121
+ });
9122
+ }
9123
+ const nodeGeoms = [];
9124
+ for (const n of nodes) {
9125
+ const pos = positions.get(n.id);
9126
+ if (!pos) continue;
9127
+ nodeGeoms.push({
9128
+ id: n.id,
9129
+ x: pos.x,
9130
+ y: pos.y,
9131
+ radius: n.radius ?? DEFAULT_NODE_RADIUS,
9132
+ color: n.color ?? NODE_STATE_COLOR[n.state ?? "unvisited"],
9133
+ label: n.label,
9134
+ badge: n.badge
9135
+ });
9136
+ }
9137
+ for (const g of nodeGeoms) {
9138
+ out.push({ type: "circle", id: g.id, x: g.x, y: g.y, radius: g.radius, color: g.color, fill: `${g.color}33` });
9139
+ }
9140
+ const badgeGeoms = [];
9141
+ for (const g of nodeGeoms) {
9142
+ if (!g.badge) continue;
9143
+ const w = Math.min(42, Math.max(18, g.badge.text.length * 6 + 10));
9144
+ badgeGeoms.push({
9145
+ cx: g.x + g.radius * 0.75,
9146
+ cy: g.y - g.radius * 0.75,
9147
+ w,
9148
+ h: 14,
9149
+ // Borderless pill: same color drives both stroke and fill.
9150
+ color: g.badge.color ?? "#1e293b",
9151
+ text: g.badge.text
9152
+ });
9153
+ }
9154
+ for (const b of badgeGeoms) {
9155
+ 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 });
9156
+ }
9157
+ for (const b of badgeGeoms) {
9158
+ out.push({ type: "text", x: b.cx, y: b.cy, text: b.text, fontSize: 9, align: "center", color: "#ffffff" });
9159
+ }
9160
+ for (const g of nodeGeoms) {
9161
+ if (g.label === void 0) continue;
9162
+ out.push({
9163
+ type: "text",
9164
+ x: g.x,
9165
+ y: g.y + g.radius + 14,
9166
+ text: g.label,
9167
+ fontSize: 12,
9168
+ align: "center",
9169
+ color: "#111827"
9170
+ });
9171
+ }
9172
+ out.push(...shapes);
9173
+ return out;
9174
+ }, [nodes, edges, layout, root, width, height, nodeById, shapes]);
9175
+ const handleShapeClick = useCallback(
9176
+ (payload) => {
9177
+ if (payload.type === "circle" && payload.id) {
9178
+ const node = nodeById.get(payload.id);
9179
+ const idx = nodeIndexById.get(payload.id);
9180
+ if (node && idx !== void 0) {
9181
+ onNodeClick?.({ id: node.id, label: node.label, index: idx });
9182
+ }
9183
+ }
9184
+ onShapeClick?.(payload);
9185
+ },
9186
+ [nodeById, nodeIndexById, onNodeClick, onShapeClick]
9187
+ );
9188
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
9189
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
9190
+ /* @__PURE__ */ jsx(
9191
+ LearningCanvas,
9192
+ {
9193
+ width,
9194
+ height,
9195
+ backgroundColor,
9196
+ shapes: derivedShapes,
9197
+ interactive,
9198
+ animate,
9199
+ onShapeClick: onShapeClick || onNodeClick ? handleShapeClick : void 0,
9200
+ isLoading,
9201
+ error
9202
+ }
9203
+ )
9204
+ ] }) });
9205
+ };
9206
+ }
9207
+ });
9208
+ 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, AlgorithmCanvas;
8692
9209
  var init_AlgorithmCanvas = __esm({
8693
9210
  "components/learning/molecules/AlgorithmCanvas.tsx"() {
8694
9211
  "use client";
@@ -8700,6 +9217,43 @@ var init_AlgorithmCanvas = __esm({
8700
9217
  DEFAULT_POINTER_COLOR = "#dc2626";
8701
9218
  POINTER_BAND = 34;
8702
9219
  TOP_PAD = 26;
9220
+ PANEL_FAMILY_ORDER = ["bars", "slots", "cells", "buckets", "frames"];
9221
+ RANGE_COLOR_DEFAULT = "#3b82f6";
9222
+ RANGE_FILL_OPACITY = 0.15;
9223
+ BRACKET_TOP_OFFSET = 16;
9224
+ BRACKET_ROW_H = 14;
9225
+ BRACKET_TICK_H = 6;
9226
+ BRACKET_LABEL_OFFSET = 6;
9227
+ SLOT_EMPTY_FILL = "#f1f5f9";
9228
+ SLOT_EMPTY_STROKE = "#cbd5e1";
9229
+ SLOT_FILLED_STROKE = "#9ca3af";
9230
+ SLOT_HIGHLIGHT_DEFAULT = "#f59e0b";
9231
+ SLOT_VALUE_TEXT_COLOR = "#ffffff";
9232
+ FRAME_ACTIVE_COLOR = "#3b82f6";
9233
+ FRAME_RETURNING_COLOR = "#f59e0b";
9234
+ FRAME_DONE_COLOR = "#94a3b8";
9235
+ FRAME_LABEL_COLOR = "#ffffff";
9236
+ FRAME_DETAIL_COLOR = "#e2e8f0";
9237
+ FRAME_TWO_LINE_MIN_H = 22;
9238
+ BUCKET_INDEX_FILL = "#e2e8f0";
9239
+ BUCKET_INDEX_STROKE = "#9ca3af";
9240
+ BUCKET_INDEX_TEXT = "#374151";
9241
+ BUCKET_ENTRY_TEXT = "#ffffff";
9242
+ BUCKET_ENTRY_DEFAULT = "#3b82f6";
9243
+ BUCKET_ENTRY_HIGHLIGHT = "#f59e0b";
9244
+ BUCKET_ENTRY_PROBING = "#38bdf8";
9245
+ BUCKET_ENTRY_MIN_W = 24;
9246
+ BUCKET_ENTRY_MAX_W = 64;
9247
+ AXIS_LABEL_COLOR = "#6b7280";
9248
+ AXIS_LABEL_FONT_SIZE = 10;
9249
+ CORNER_TEXT_COLOR = "#111827";
9250
+ CORNER_FONT_SIZE = 7;
9251
+ CORNER_MIN_CELL = 28;
9252
+ CORNER_INSET_X = 3;
9253
+ CORNER_INSET_Y = 6;
9254
+ AUX_PRIMARY_RATIO = 0.6;
9255
+ AUX_LABEL_BAND = 18;
9256
+ AUX_BASELINE_PAD = 8;
8703
9257
  AlgorithmCanvas = ({
8704
9258
  className,
8705
9259
  width = 600,
@@ -8709,6 +9263,14 @@ var init_AlgorithmCanvas = __esm({
8709
9263
  bars = [],
8710
9264
  cells = [],
8711
9265
  pointers = [],
9266
+ ranges = [],
9267
+ slots = [],
9268
+ slotOrientation = "horizontal",
9269
+ frames = [],
9270
+ buckets = [],
9271
+ auxBars = [],
9272
+ rowLabels = [],
9273
+ colLabels = [],
8712
9274
  shapes = [],
8713
9275
  interactive = false,
8714
9276
  animate = false,
@@ -8718,12 +9280,35 @@ var init_AlgorithmCanvas = __esm({
8718
9280
  }) => {
8719
9281
  const derivedShapes = useMemo(() => {
8720
9282
  const out = [];
9283
+ const presence = {
9284
+ bars: bars.length > 0,
9285
+ slots: slots.length > 0,
9286
+ cells: cells.length > 0,
9287
+ buckets: buckets.length > 0,
9288
+ frames: frames.length > 0
9289
+ };
9290
+ const panelCount = PANEL_FAMILY_ORDER.filter((f3) => presence[f3]).length;
9291
+ const panelHeight = height / Math.max(1, panelCount);
9292
+ const panelY = { bars: 0, slots: 0, cells: 0, buckets: 0, frames: 0 };
9293
+ let compactIndex = 0;
9294
+ PANEL_FAMILY_ORDER.forEach((f3) => {
9295
+ if (presence[f3]) {
9296
+ panelY[f3] = compactIndex * panelHeight;
9297
+ compactIndex += 1;
9298
+ }
9299
+ });
8721
9300
  if (bars.length > 0) {
9301
+ const panelYBars = panelY.bars;
8722
9302
  const slot = width / bars.length;
8723
9303
  const barW = slot * 0.8;
8724
9304
  const gap = slot * 0.1;
8725
- const baseline = height - POINTER_BAND;
8726
- const usableH = baseline - TOP_PAD;
9305
+ const bracketRanges = ranges.filter((r) => r.kind === "bracket");
9306
+ const bracketCount = bracketRanges.length;
9307
+ const bracketHeadroom = bracketCount > 0 ? BRACKET_TOP_OFFSET + bracketCount * BRACKET_ROW_H : 0;
9308
+ const hasAux = auxBars.length > 0;
9309
+ const primaryH = hasAux ? panelHeight * AUX_PRIMARY_RATIO : panelHeight;
9310
+ const baseline = panelYBars + primaryH - POINTER_BAND;
9311
+ const usableH = baseline - (panelYBars + TOP_PAD + bracketHeadroom);
8727
9312
  const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
8728
9313
  bars.forEach((bar, i) => {
8729
9314
  const v = Number.isFinite(bar.value) ? bar.value : 0;
@@ -8753,6 +9338,89 @@ var init_AlgorithmCanvas = __esm({
8753
9338
  });
8754
9339
  }
8755
9340
  });
9341
+ ranges.forEach((r) => {
9342
+ const kind = r.kind ?? "fill";
9343
+ if (kind !== "fill") return;
9344
+ const color = r.color ?? RANGE_COLOR_DEFAULT;
9345
+ out.push({
9346
+ type: "rect",
9347
+ x: r.from * slot,
9348
+ y: panelYBars,
9349
+ width: (r.to - r.from + 1) * slot,
9350
+ height: primaryH,
9351
+ color,
9352
+ fill: color,
9353
+ opacity: RANGE_FILL_OPACITY
9354
+ });
9355
+ if (r.label) {
9356
+ out.push({
9357
+ type: "text",
9358
+ x: r.from * slot + 4,
9359
+ // Sits below the bracket block (if any) so fill and bracket labels never collide.
9360
+ y: panelYBars + 10 + bracketHeadroom,
9361
+ text: r.label,
9362
+ color,
9363
+ fontSize: 10,
9364
+ align: "left"
9365
+ });
9366
+ }
9367
+ });
9368
+ bracketRanges.forEach((r, i) => {
9369
+ const bracketY = panelYBars + BRACKET_TOP_OFFSET + i * BRACKET_ROW_H;
9370
+ const x1 = r.from * slot + slot * 0.1;
9371
+ const x2 = (r.to + 1) * slot - slot * 0.1;
9372
+ const color = r.color ?? RANGE_COLOR_DEFAULT;
9373
+ out.push({ type: "line", x1, y1: bracketY, x2, y2: bracketY, color, lineWidth: 2 });
9374
+ out.push({ type: "line", x1, y1: bracketY, x2: x1, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
9375
+ out.push({ type: "line", x1: x2, y1: bracketY, x2, y2: bracketY + BRACKET_TICK_H, color, lineWidth: 2 });
9376
+ if (r.label) {
9377
+ out.push({
9378
+ type: "text",
9379
+ x: (x1 + x2) / 2,
9380
+ y: bracketY - BRACKET_LABEL_OFFSET,
9381
+ text: r.label,
9382
+ color,
9383
+ fontSize: 10,
9384
+ align: "center"
9385
+ });
9386
+ }
9387
+ });
9388
+ if (hasAux) {
9389
+ const auxH = panelHeight - primaryH;
9390
+ const slot2 = width / auxBars.length;
9391
+ const auxBaseline = panelYBars + primaryH + auxH - AUX_BASELINE_PAD;
9392
+ const auxUsableH = auxBaseline - (panelYBars + primaryH + AUX_LABEL_BAND);
9393
+ const maxAuxV = Math.max(1, ...auxBars.map((b) => Number.isFinite(b.value) ? b.value : 0));
9394
+ auxBars.forEach((bar, i) => {
9395
+ const v = Number.isFinite(bar.value) ? bar.value : 0;
9396
+ const bh = Math.max(0, v / maxAuxV * auxUsableH);
9397
+ const x = i * slot2 + slot2 * 0.1;
9398
+ const w = slot2 * 0.8;
9399
+ const color = bar.color ?? DEFAULT_BAR_COLOR;
9400
+ out.push({
9401
+ type: "rect",
9402
+ id: `auxbar-${i}`,
9403
+ x,
9404
+ y: auxBaseline - bh,
9405
+ width: w,
9406
+ height: bh,
9407
+ color,
9408
+ fill: color
9409
+ });
9410
+ const label = bar.label ?? (auxBars.length <= 24 ? String(v) : void 0);
9411
+ if (label) {
9412
+ out.push({
9413
+ type: "text",
9414
+ x: x + w / 2,
9415
+ y: auxBaseline - bh - 8,
9416
+ text: label,
9417
+ color: "#374151",
9418
+ fontSize: 11,
9419
+ align: "center"
9420
+ });
9421
+ }
9422
+ });
9423
+ }
8756
9424
  pointers.forEach((p) => {
8757
9425
  if (p.index < 0 || p.index >= bars.length) return;
8758
9426
  const cx = p.index * slot + slot / 2;
@@ -8760,7 +9428,7 @@ var init_AlgorithmCanvas = __esm({
8760
9428
  out.push({
8761
9429
  type: "arrow",
8762
9430
  x1: cx,
8763
- y1: height - 6,
9431
+ y1: panelYBars + primaryH - 18,
8764
9432
  x2: cx,
8765
9433
  y2: baseline + 4,
8766
9434
  color,
@@ -8770,7 +9438,7 @@ var init_AlgorithmCanvas = __esm({
8770
9438
  out.push({
8771
9439
  type: "text",
8772
9440
  x: cx,
8773
- y: height - 22,
9441
+ y: panelYBars + primaryH - 8,
8774
9442
  text: p.label,
8775
9443
  color,
8776
9444
  fontSize: 11,
@@ -8779,14 +9447,111 @@ var init_AlgorithmCanvas = __esm({
8779
9447
  }
8780
9448
  });
8781
9449
  }
9450
+ if (slots.length > 0) {
9451
+ const panelYSlots = panelY.slots;
9452
+ const n = slots.length;
9453
+ const vertical = slotOrientation === "vertical";
9454
+ const vBoxH = panelHeight / n;
9455
+ const vBoxW = Math.min(width * 0.5, 120);
9456
+ const vBoxX = (width - vBoxW) / 2;
9457
+ const hCellW = width / n;
9458
+ const hBoxW = hCellW * 0.82;
9459
+ const hBoxH = Math.min(panelHeight * 0.6, 48);
9460
+ const hBoxY = panelYSlots + (panelHeight - hBoxH) / 2;
9461
+ 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 };
9462
+ slots.forEach((s, i) => {
9463
+ const box = slotBox(i);
9464
+ const state = s.state ?? "filled";
9465
+ const fill = state === "empty" ? SLOT_EMPTY_FILL : state === "highlight" ? s.color ?? SLOT_HIGHLIGHT_DEFAULT : s.color ?? DEFAULT_BAR_COLOR;
9466
+ const stroke = state === "empty" ? SLOT_EMPTY_STROKE : SLOT_FILLED_STROKE;
9467
+ out.push({
9468
+ type: "rect",
9469
+ id: `slot-${i}`,
9470
+ x: box.x,
9471
+ y: box.y,
9472
+ width: box.width,
9473
+ height: box.height,
9474
+ color: stroke,
9475
+ fill
9476
+ });
9477
+ if (s.value != null && state !== "empty") {
9478
+ out.push({
9479
+ type: "text",
9480
+ x: box.x + box.width / 2,
9481
+ y: box.y + box.height / 2,
9482
+ text: String(s.value),
9483
+ color: SLOT_VALUE_TEXT_COLOR,
9484
+ fontSize: 12,
9485
+ align: "center"
9486
+ });
9487
+ }
9488
+ });
9489
+ if (bars.length === 0) {
9490
+ pointers.forEach((p) => {
9491
+ if (p.index < 0 || p.index >= slots.length) return;
9492
+ const box = slotBox(p.index);
9493
+ const color = p.color ?? DEFAULT_POINTER_COLOR;
9494
+ if (vertical) {
9495
+ const cy = box.y + box.height / 2;
9496
+ out.push({
9497
+ type: "arrow",
9498
+ x1: box.x + box.width + 34,
9499
+ y1: cy,
9500
+ x2: box.x + box.width + 4,
9501
+ y2: cy,
9502
+ color,
9503
+ lineWidth: 2
9504
+ });
9505
+ if (p.label) {
9506
+ out.push({
9507
+ type: "text",
9508
+ x: box.x + box.width + 38,
9509
+ y: cy,
9510
+ text: p.label,
9511
+ color,
9512
+ fontSize: 11,
9513
+ align: "left"
9514
+ });
9515
+ }
9516
+ } else {
9517
+ const cx = box.x + box.width / 2;
9518
+ out.push({
9519
+ type: "arrow",
9520
+ x1: cx,
9521
+ y1: panelYSlots + panelHeight - 18,
9522
+ x2: cx,
9523
+ y2: box.y + box.height + 4,
9524
+ color,
9525
+ lineWidth: 2
9526
+ });
9527
+ if (p.label) {
9528
+ out.push({
9529
+ type: "text",
9530
+ x: cx,
9531
+ y: panelYSlots + panelHeight - 8,
9532
+ text: p.label,
9533
+ color,
9534
+ fontSize: 11,
9535
+ align: "center"
9536
+ });
9537
+ }
9538
+ }
9539
+ });
9540
+ }
9541
+ }
8782
9542
  if (cells.length > 0) {
9543
+ const panelYCells = panelY.cells;
8783
9544
  const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
8784
9545
  const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
8785
- const cw = width / maxCol;
8786
- const ch = height / maxRow;
9546
+ const colLabelH = colLabels.length > 0 ? 16 : 0;
9547
+ const rowLabelW = rowLabels.length > 0 ? 20 : 0;
9548
+ const gridX0 = rowLabelW;
9549
+ const gridY0 = panelYCells + colLabelH;
9550
+ const cw = (width - rowLabelW) / maxCol;
9551
+ const ch = (panelHeight - colLabelH) / maxRow;
8787
9552
  cells.forEach((c, i) => {
8788
- const x = c.col * cw;
8789
- const y = c.row * ch;
9553
+ const x = gridX0 + c.col * cw;
9554
+ const y = gridY0 + c.row * ch;
8790
9555
  const color = c.color ?? DEFAULT_CELL_COLOR;
8791
9556
  out.push({
8792
9557
  type: "rect",
@@ -8810,11 +9575,207 @@ var init_AlgorithmCanvas = __esm({
8810
9575
  align: "center"
8811
9576
  });
8812
9577
  }
9578
+ if (c.corner && cw >= CORNER_MIN_CELL && ch >= CORNER_MIN_CELL) {
9579
+ const { tl, tr, bl, br } = c.corner;
9580
+ if (tl) {
9581
+ out.push({
9582
+ type: "text",
9583
+ x: x + CORNER_INSET_X,
9584
+ y: y + CORNER_INSET_Y,
9585
+ text: tl,
9586
+ color: CORNER_TEXT_COLOR,
9587
+ fontSize: CORNER_FONT_SIZE,
9588
+ align: "left"
9589
+ });
9590
+ }
9591
+ if (tr) {
9592
+ out.push({
9593
+ type: "text",
9594
+ x: x + cw - CORNER_INSET_X,
9595
+ y: y + CORNER_INSET_Y,
9596
+ text: tr,
9597
+ color: CORNER_TEXT_COLOR,
9598
+ fontSize: CORNER_FONT_SIZE,
9599
+ align: "right"
9600
+ });
9601
+ }
9602
+ if (bl) {
9603
+ out.push({
9604
+ type: "text",
9605
+ x: x + CORNER_INSET_X,
9606
+ y: y + ch - CORNER_INSET_Y,
9607
+ text: bl,
9608
+ color: CORNER_TEXT_COLOR,
9609
+ fontSize: CORNER_FONT_SIZE,
9610
+ align: "left"
9611
+ });
9612
+ }
9613
+ if (br) {
9614
+ out.push({
9615
+ type: "text",
9616
+ x: x + cw - CORNER_INSET_X,
9617
+ y: y + ch - CORNER_INSET_Y,
9618
+ text: br,
9619
+ color: CORNER_TEXT_COLOR,
9620
+ fontSize: CORNER_FONT_SIZE,
9621
+ align: "right"
9622
+ });
9623
+ }
9624
+ }
9625
+ });
9626
+ colLabels.forEach((l) => {
9627
+ out.push({
9628
+ type: "text",
9629
+ x: gridX0 + l.index * cw + cw / 2,
9630
+ y: panelYCells + colLabelH / 2,
9631
+ text: l.text,
9632
+ color: l.color ?? AXIS_LABEL_COLOR,
9633
+ fontSize: AXIS_LABEL_FONT_SIZE,
9634
+ align: "center"
9635
+ });
9636
+ });
9637
+ rowLabels.forEach((l) => {
9638
+ out.push({
9639
+ type: "text",
9640
+ x: rowLabelW - 6,
9641
+ y: gridY0 + l.index * ch + ch / 2,
9642
+ text: l.text,
9643
+ color: l.color ?? AXIS_LABEL_COLOR,
9644
+ fontSize: AXIS_LABEL_FONT_SIZE,
9645
+ align: "right"
9646
+ });
9647
+ });
9648
+ }
9649
+ if (buckets.length > 0) {
9650
+ const panelYBuckets = panelY.buckets;
9651
+ const bucketCount = Math.max(0, ...buckets.map((b) => b.index)) + 1;
9652
+ const rowH = panelHeight / bucketCount;
9653
+ const indexColW = Math.min(width * 0.12, 40);
9654
+ const maxChainLen = Math.max(1, ...buckets.map((b) => b.entries.length));
9655
+ const entryW = Math.min(BUCKET_ENTRY_MAX_W, Math.max(BUCKET_ENTRY_MIN_W, (width - indexColW - 8) / maxChainLen));
9656
+ const maxVisible = Math.floor((width - indexColW - 4) / entryW);
9657
+ buckets.forEach((b) => {
9658
+ const rowY = panelYBuckets + b.index * rowH;
9659
+ out.push({
9660
+ type: "rect",
9661
+ id: `bucket-index-${b.index}`,
9662
+ x: 2,
9663
+ y: rowY + 2,
9664
+ width: indexColW - 4,
9665
+ height: rowH - 4,
9666
+ color: BUCKET_INDEX_STROKE,
9667
+ fill: BUCKET_INDEX_FILL
9668
+ });
9669
+ out.push({
9670
+ type: "text",
9671
+ x: 2 + (indexColW - 4) / 2,
9672
+ y: rowY + rowH / 2,
9673
+ text: String(b.index),
9674
+ color: BUCKET_INDEX_TEXT,
9675
+ fontSize: 10,
9676
+ align: "center"
9677
+ });
9678
+ const overflow = b.entries.length > maxVisible;
9679
+ const visibleCount = overflow ? Math.max(0, maxVisible - 1) : b.entries.length;
9680
+ for (let j = 0; j < visibleCount; j++) {
9681
+ const entry = b.entries[j];
9682
+ const ex = indexColW + 4 + j * entryW;
9683
+ const state = entry.state ?? "default";
9684
+ const fill = state === "highlight" ? entry.color ?? BUCKET_ENTRY_HIGHLIGHT : state === "probing" ? entry.color ?? BUCKET_ENTRY_PROBING : entry.color ?? BUCKET_ENTRY_DEFAULT;
9685
+ out.push({
9686
+ type: "rect",
9687
+ id: `bucket-${b.index}-${j}`,
9688
+ x: ex,
9689
+ y: rowY + 2,
9690
+ width: entryW - 2,
9691
+ height: rowH - 4,
9692
+ color: fill,
9693
+ fill
9694
+ });
9695
+ if (entryW >= 20 && rowH >= 16) {
9696
+ out.push({
9697
+ type: "text",
9698
+ x: ex + (entryW - 2) / 2,
9699
+ y: rowY + rowH / 2,
9700
+ text: entry.label,
9701
+ color: BUCKET_ENTRY_TEXT,
9702
+ fontSize: 10,
9703
+ align: "center"
9704
+ });
9705
+ }
9706
+ }
9707
+ if (overflow) {
9708
+ const ex = indexColW + 4 + visibleCount * entryW;
9709
+ out.push({
9710
+ type: "rect",
9711
+ id: `bucket-${b.index}-overflow`,
9712
+ x: ex,
9713
+ y: rowY + 2,
9714
+ width: entryW - 2,
9715
+ height: rowH - 4,
9716
+ color: BUCKET_ENTRY_DEFAULT,
9717
+ fill: BUCKET_ENTRY_DEFAULT
9718
+ });
9719
+ out.push({
9720
+ type: "text",
9721
+ x: ex + (entryW - 2) / 2,
9722
+ y: rowY + rowH / 2,
9723
+ text: `+${b.entries.length - visibleCount}`,
9724
+ color: BUCKET_ENTRY_TEXT,
9725
+ fontSize: 10,
9726
+ align: "center"
9727
+ });
9728
+ }
9729
+ });
9730
+ }
9731
+ if (frames.length > 0) {
9732
+ const panelYFrames = panelY.frames;
9733
+ const n = frames.length;
9734
+ const frameH = panelHeight / n;
9735
+ const x = 8;
9736
+ const w = width - 16;
9737
+ frames.forEach((f3, i) => {
9738
+ const y = panelYFrames + panelHeight - (i + 1) * frameH;
9739
+ const state = f3.state ?? "active";
9740
+ const fill = state === "returning" ? f3.color ?? FRAME_RETURNING_COLOR : state === "done" ? f3.color ?? FRAME_DONE_COLOR : f3.color ?? FRAME_ACTIVE_COLOR;
9741
+ out.push({ type: "rect", id: `frame-${i}`, x, y, width: w, height: frameH, color: fill, fill });
9742
+ if (frameH >= FRAME_TWO_LINE_MIN_H) {
9743
+ out.push({
9744
+ type: "text",
9745
+ x: 16,
9746
+ y: y + frameH * 0.35,
9747
+ text: f3.label,
9748
+ color: FRAME_LABEL_COLOR,
9749
+ fontSize: 10,
9750
+ align: "left"
9751
+ });
9752
+ if (f3.detail) {
9753
+ out.push({
9754
+ type: "text",
9755
+ x: 16,
9756
+ y: y + frameH * 0.7,
9757
+ text: f3.detail,
9758
+ color: FRAME_DETAIL_COLOR,
9759
+ fontSize: 10,
9760
+ align: "left"
9761
+ });
9762
+ }
9763
+ } else {
9764
+ out.push({
9765
+ type: "text",
9766
+ x: 16,
9767
+ y: y + frameH / 2,
9768
+ text: f3.label,
9769
+ color: FRAME_LABEL_COLOR,
9770
+ fontSize: 10,
9771
+ align: "left"
9772
+ });
9773
+ }
8813
9774
  });
8814
9775
  }
8815
9776
  out.push(...shapes);
8816
9777
  return out;
8817
- }, [bars, cells, pointers, shapes, width, height]);
9778
+ }, [bars, cells, pointers, ranges, slots, slotOrientation, frames, buckets, auxBars, rowLabels, colLabels, shapes, width, height]);
8818
9779
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
8819
9780
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
8820
9781
  /* @__PURE__ */ jsx(
@@ -9803,23 +10764,422 @@ var init_BehaviorView = __esm({
9803
10764
  BehaviorView.displayName = "BehaviorView";
9804
10765
  }
9805
10766
  });
9806
- var BiologyCanvas;
10767
+ function LearningScene3D({
10768
+ className,
10769
+ width = 600,
10770
+ height = 400,
10771
+ title,
10772
+ backgroundColor,
10773
+ drawables,
10774
+ camera,
10775
+ lighting,
10776
+ post,
10777
+ showGrid = false,
10778
+ shadows,
10779
+ interactive,
10780
+ isLoading,
10781
+ error,
10782
+ onItemClick
10783
+ }) {
10784
+ const instanceId = useId().replace(/[^a-zA-Z0-9]/g, "");
10785
+ const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
10786
+ const onItemClickRef = useRef(onItemClick);
10787
+ onItemClickRef.current = onItemClick;
10788
+ useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
10789
+ const unitId = event.payload?.unitId;
10790
+ if (typeof unitId === "string") onItemClickRef.current?.(unitId);
10791
+ });
10792
+ if (typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production" && post?.bloom) {
10793
+ const unknownKeys = Object.keys(post.bloom).filter((k) => !KNOWN_BLOOM_KEYS.has(k));
10794
+ if (unknownKeys.length > 0) {
10795
+ sceneLog.debug("post.bloom has unrecognized keys \u2014 only intensity/threshold/smoothing are read", { unknownKeys });
10796
+ }
10797
+ }
10798
+ const props3d = {
10799
+ drawables,
10800
+ isLoading,
10801
+ error: error ? error.message : null,
10802
+ cameraMode: camera?.mode ?? "perspective",
10803
+ ...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
10804
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
10805
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
10806
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
10807
+ ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
10808
+ backgroundColor,
10809
+ showGrid,
10810
+ ...shadows !== void 0 ? { shadows } : {},
10811
+ ...interactive !== void 0 ? { controlsEnabled: interactive } : {},
10812
+ lighting,
10813
+ post,
10814
+ ...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
10815
+ };
10816
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
10817
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
10818
+ /* @__PURE__ */ jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost, { ...props3d }) }) })
10819
+ ] }) });
10820
+ }
10821
+ function meshSphere(id, x, y, z, radius, color, opts) {
10822
+ return {
10823
+ type: "draw-mesh",
10824
+ ...id !== void 0 ? { id } : {},
10825
+ shape: opts?.shape ?? "sphere",
10826
+ position: { x, y, z },
10827
+ radius,
10828
+ pivot: "center",
10829
+ segments: opts?.segments ?? 24,
10830
+ material: { color, ...opts?.material },
10831
+ ...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
10832
+ };
10833
+ }
10834
+ function axisRotation(from, to) {
10835
+ const wx = to[0] - from[0];
10836
+ const wy = to[2] - from[2];
10837
+ const wz = to[1] - from[1];
10838
+ const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
10839
+ const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
10840
+ return [0, Math.atan2(wz, -wx), tilt];
10841
+ }
10842
+ function segmentLength(from, to) {
10843
+ const dx = to[0] - from[0];
10844
+ const dy = to[1] - from[1];
10845
+ const dz = to[2] - from[2];
10846
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
10847
+ }
10848
+ function cylinderBetween(from, to, radius, color) {
10849
+ const len = segmentLength(from, to);
10850
+ if (len < 1e-6) return null;
10851
+ return {
10852
+ type: "draw-mesh",
10853
+ shape: "cylinder",
10854
+ position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
10855
+ radius,
10856
+ height: len,
10857
+ rotation: axisRotation(from, to),
10858
+ pivot: "center",
10859
+ segments: 12,
10860
+ material: { color }
10861
+ };
10862
+ }
10863
+ function arrowBetween(from, to, color, shaftRadius = 0.08, id) {
10864
+ const len = segmentLength(from, to);
10865
+ if (len < 1e-6) return null;
10866
+ const tipLen = Math.min(shaftRadius * 8, len * 0.35);
10867
+ const k = (len - tipLen) / len;
10868
+ const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
10869
+ const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
10870
+ const tipStart = shaftEnd;
10871
+ const tipEnd = delta;
10872
+ const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
10873
+ const tipLenActual = segmentLength(tipStart, tipEnd);
10874
+ const tip = {
10875
+ type: "draw-mesh",
10876
+ shape: "cone",
10877
+ position: {
10878
+ x: (tipStart[0] + tipEnd[0]) / 2,
10879
+ y: (tipStart[1] + tipEnd[1]) / 2,
10880
+ z: (tipStart[2] + tipEnd[2]) / 2
10881
+ },
10882
+ radius: shaftRadius * 3,
10883
+ height: tipLenActual,
10884
+ rotation: axisRotation(tipStart, tipEnd),
10885
+ pivot: "center",
10886
+ segments: 12,
10887
+ material: { color }
10888
+ };
10889
+ return {
10890
+ type: "draw-group",
10891
+ ...id !== void 0 ? { id } : {},
10892
+ position: { x: from[0], y: from[1], z: from[2] },
10893
+ items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
10894
+ };
10895
+ }
10896
+ function billboardLabel(text, x, y, z, opts) {
10897
+ return {
10898
+ type: "draw-text",
10899
+ text,
10900
+ position: { x, y, z },
10901
+ color: opts?.color ?? "#111827"
10902
+ };
10903
+ }
10904
+ function labelColorForBackground(backgroundColor) {
10905
+ const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
10906
+ if (!m) return "#111827";
10907
+ const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
10908
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
10909
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
10910
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
10911
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
10912
+ return luminance < 0.5 ? "#e5e7eb" : "#111827";
10913
+ }
10914
+ function get3DClickPayload(onShapeClick, idToIndex) {
10915
+ if (!onShapeClick) return void 0;
10916
+ return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
10917
+ }
10918
+ function polylineTube(points, radius, color, opts) {
10919
+ const maxSegments = opts?.maxSegments ?? 128;
10920
+ let pts = points;
10921
+ if (pts.length - 1 > maxSegments) {
10922
+ const step = (pts.length - 1) / maxSegments;
10923
+ const kept = [pts[0]];
10924
+ for (let s = 1; s < maxSegments; s++) kept.push(pts[Math.round(s * step)]);
10925
+ kept.push(pts[pts.length - 1]);
10926
+ pts = kept;
10927
+ }
10928
+ const out = [];
10929
+ for (let i = 0; i < pts.length - 1; i++) {
10930
+ const seg = cylinderBetween(pts[i], pts[i + 1], radius, color);
10931
+ if (seg) out.push(opts?.opacity !== void 0 ? { ...seg, opacity: opts.opacity } : seg);
10932
+ }
10933
+ return out;
10934
+ }
10935
+ function heightFieldMesh(spec) {
10936
+ const { nx, ny, heights, spacing = 1, x = 0, y = 0 } = spec;
10937
+ const flatShading = spec.flatShading ?? true;
10938
+ const vertices = [];
10939
+ for (let iy = 0; iy < ny; iy++) {
10940
+ for (let ix = 0; ix < nx; ix++) {
10941
+ vertices.push([
10942
+ x + (ix - (nx - 1) / 2) * spacing,
10943
+ y + (iy - (ny - 1) / 2) * spacing,
10944
+ heights[iy * nx + ix] ?? 0
10945
+ ]);
10946
+ }
10947
+ }
10948
+ const bands = [...spec.bands ?? []].sort((a, b) => (a.min ?? -Infinity) - (b.min ?? -Infinity));
10949
+ const facesByBand = /* @__PURE__ */ new Map();
10950
+ for (let iy = 0; iy < ny - 1; iy++) {
10951
+ for (let ix = 0; ix < nx - 1; ix++) {
10952
+ const v00 = iy * nx + ix;
10953
+ const v10 = iy * nx + ix + 1;
10954
+ const v01 = (iy + 1) * nx + ix;
10955
+ const v11 = (iy + 1) * nx + ix + 1;
10956
+ for (const face of [[v00, v10, v01], [v10, v11, v01]]) {
10957
+ const centroid = (vertices[face[0]][2] + vertices[face[1]][2] + vertices[face[2]][2]) / 3;
10958
+ let band = null;
10959
+ for (const b of bands) {
10960
+ if ((b.min ?? -Infinity) <= centroid) band = b;
10961
+ }
10962
+ const key = bands.length > 0 ? band : null;
10963
+ const list = facesByBand.get(key) ?? [];
10964
+ list.push(face);
10965
+ facesByBand.set(key, list);
10966
+ }
10967
+ }
10968
+ }
10969
+ const out = [];
10970
+ for (const [band, faces] of facesByBand) {
10971
+ if (faces.length === 0) continue;
10972
+ out.push({
10973
+ type: "draw-mesh",
10974
+ shape: "polyhedron",
10975
+ position: { x: 0, y: 0, z: 0 },
10976
+ vertices,
10977
+ faces,
10978
+ pivot: "center",
10979
+ material: { color: band?.color ?? spec.color ?? "#64748b", flatShading, side: "double" },
10980
+ ...spec.opacity !== void 0 ? { opacity: spec.opacity } : {}
10981
+ });
10982
+ }
10983
+ return out;
10984
+ }
10985
+ function arrowField(vectors, opts) {
10986
+ const scale = opts?.scale ?? 1;
10987
+ const out = [];
10988
+ for (const v of vectors) {
10989
+ const to = [
10990
+ v.from[0] + v.delta[0] * scale,
10991
+ v.from[1] + v.delta[1] * scale,
10992
+ v.from[2] + v.delta[2] * scale
10993
+ ];
10994
+ const arrow = arrowBetween(v.from, to, v.color ?? "#dc2626", v.width, v.id);
10995
+ if (arrow) out.push(arrow);
10996
+ if (v.label) out.push(billboardLabel(v.label, to[0], to[1], to[2], { color: opts?.labelColor }));
10997
+ }
10998
+ return out;
10999
+ }
11000
+ function helixDrawables(spec, opts) {
11001
+ const count = spec.count ?? spec.rungs?.length ?? 0;
11002
+ const rungs = Array.from({ length: count }, (_, i) => spec.rungs?.[i] ?? {});
11003
+ const radius = spec.radius ?? 1;
11004
+ const rise = spec.rise ?? 0.34;
11005
+ const twistRad = (spec.twistDeg ?? 36) * (Math.PI / 180);
11006
+ const strandAColor = spec.strandAColor ?? "#38bdf8";
11007
+ const strandBColor = spec.strandBColor ?? "#fb923c";
11008
+ const backboneRadius = spec.backboneRadius ?? 0.16;
11009
+ const rungRadius = spec.rungRadius ?? 0.12;
11010
+ const cx = spec.x ?? 0;
11011
+ const cy = spec.y ?? 0;
11012
+ const cz = spec.z ?? 0;
11013
+ const unwoundCount = spec.unwoundCount ?? 0;
11014
+ const unwindSpread = spec.unwindSpread ?? 1.8;
11015
+ const strandA = [];
11016
+ const strandB = [];
11017
+ for (let i = 0; i < count; i++) {
11018
+ const yi = cy + (i - (count - 1) / 2) * rise;
11019
+ const theta = i * twistRad;
11020
+ const s = i < unwoundCount ? unwindSpread : 1;
11021
+ strandA.push([cx + s * radius * Math.cos(theta), yi, cz + s * radius * Math.sin(theta)]);
11022
+ strandB.push([cx + s * radius * Math.cos(theta + Math.PI), yi, cz + s * radius * Math.sin(theta + Math.PI)]);
11023
+ }
11024
+ const out = [];
11025
+ for (let i = 0; i < count; i++) {
11026
+ out.push(meshSphere(`hx-a-${i}`, strandA[i][0], strandA[i][1], strandA[i][2], backboneRadius, strandAColor));
11027
+ out.push(meshSphere(`hx-b-${i}`, strandB[i][0], strandB[i][1], strandB[i][2], backboneRadius, strandBColor));
11028
+ if (i > 0) {
11029
+ const segA = cylinderBetween(strandA[i - 1], strandA[i], backboneRadius, strandAColor);
11030
+ if (segA) out.push(segA);
11031
+ const segB = cylinderBetween(strandB[i - 1], strandB[i], backboneRadius, strandBColor);
11032
+ if (segB) out.push(segB);
11033
+ }
11034
+ const rung = rungs[i];
11035
+ const rungColor = rung.color ?? "#94a3b8";
11036
+ const rod = cylinderBetween(strandA[i], strandB[i], rungRadius, rungColor);
11037
+ if (rod) out.push(rod);
11038
+ const mid = [
11039
+ (strandA[i][0] + strandB[i][0]) / 2,
11040
+ (strandA[i][1] + strandB[i][1]) / 2,
11041
+ (strandA[i][2] + strandB[i][2]) / 2
11042
+ ];
11043
+ const markerRadius = rung.radius ?? rungRadius;
11044
+ out.push(meshSphere(rung.id, mid[0], mid[1], mid[2], markerRadius, rungColor));
11045
+ if (rung.label) out.push(billboardLabel(rung.label, mid[0], mid[1], mid[2] + markerRadius, { color: opts?.labelColor }));
11046
+ }
11047
+ return out;
11048
+ }
11049
+ function latticeDrawables(spec, opts) {
11050
+ const nx = spec.nx ?? 2;
11051
+ const ny = spec.ny ?? 2;
11052
+ const nz = spec.nz ?? 2;
11053
+ const latticeConstant = spec.latticeConstant ?? 2;
11054
+ const bondRadius = spec.bondRadius ?? 0.06;
11055
+ const highlightCell = spec.highlightCell ?? false;
11056
+ const dimColor = spec.dimColor ?? "#475569";
11057
+ const showLabels = spec.showLabels ?? false;
11058
+ const selectedColor = spec.selectedColor ?? "#f59e0b";
11059
+ const posByKey = /* @__PURE__ */ new Map();
11060
+ const inCellByKey = /* @__PURE__ */ new Map();
11061
+ const out = [];
11062
+ for (const site of spec.basis) {
11063
+ const snx = site.xEdge ? nx + 1 : nx;
11064
+ const sny = site.yEdge ? ny + 1 : ny;
11065
+ const snz = site.zEdge ? nz + 1 : nz;
11066
+ for (let i = 0; i < snx; i++) {
11067
+ for (let j = 0; j < sny; j++) {
11068
+ for (let k = 0; k < snz; k++) {
11069
+ const key = `${site.key}-${i}-${j}-${k}`;
11070
+ const inCell = i + site.dx <= 1 && j + site.dy <= 1 && k + site.dz <= 1;
11071
+ const pos = [
11072
+ (i + site.dx) * latticeConstant - nx * latticeConstant / 2,
11073
+ (j + site.dy) * latticeConstant - ny * latticeConstant / 2,
11074
+ (k + site.dz) * latticeConstant - nz * latticeConstant / 2
11075
+ ];
11076
+ posByKey.set(key, pos);
11077
+ inCellByKey.set(key, inCell);
11078
+ const isSelected = spec.selectedId === `lat-${key}`;
11079
+ const color = isSelected ? selectedColor : highlightCell && !inCell ? dimColor : site.color ?? "#2563eb";
11080
+ const radius = (site.radius ?? 0.3) * (isSelected ? 1.4 : 1);
11081
+ out.push(meshSphere(`lat-${key}`, pos[0], pos[1], pos[2], radius, color));
11082
+ if (showLabels && site.element) {
11083
+ out.push(billboardLabel(site.element, pos[0], pos[1], pos[2] + radius, { color: opts?.labelColor }));
11084
+ }
11085
+ }
11086
+ }
11087
+ }
11088
+ }
11089
+ const basisByKey = new Map(spec.basis.map((s) => [s.key, s]));
11090
+ for (const bond of spec.bonds ?? []) {
11091
+ const fromSite = basisByKey.get(bond.from);
11092
+ const toSite = basisByKey.get(bond.to);
11093
+ if (!fromSite || !toSite) continue;
11094
+ const fnx = fromSite.xEdge ? nx + 1 : nx;
11095
+ const fny = fromSite.yEdge ? ny + 1 : ny;
11096
+ const fnz = fromSite.zEdge ? nz + 1 : nz;
11097
+ const tnx = toSite.xEdge ? nx + 1 : nx;
11098
+ const tny = toSite.yEdge ? ny + 1 : ny;
11099
+ const tnz = toSite.zEdge ? nz + 1 : nz;
11100
+ const bdx = bond.dx ?? 0;
11101
+ const bdy = bond.dy ?? 0;
11102
+ const bdz = bond.dz ?? 0;
11103
+ for (let i = 0; i < fnx; i++) {
11104
+ for (let j = 0; j < fny; j++) {
11105
+ for (let k = 0; k < fnz; k++) {
11106
+ const ti = i + bdx;
11107
+ const tj = j + bdy;
11108
+ const tk = k + bdz;
11109
+ if (ti < 0 || ti >= tnx || tj < 0 || tj >= tny || tk < 0 || tk >= tnz) continue;
11110
+ const fromKey = `${fromSite.key}-${i}-${j}-${k}`;
11111
+ const toKey = `${toSite.key}-${ti}-${tj}-${tk}`;
11112
+ const fromPos = posByKey.get(fromKey);
11113
+ const toPos = posByKey.get(toKey);
11114
+ if (!fromPos || !toPos) continue;
11115
+ const dimmed = highlightCell && !(inCellByKey.get(fromKey) && inCellByKey.get(toKey));
11116
+ const seg = cylinderBetween(fromPos, toPos, bondRadius, dimmed ? dimColor : bond.color ?? "#6b7280");
11117
+ if (seg) out.push(seg);
11118
+ }
11119
+ }
11120
+ }
11121
+ }
11122
+ return out;
11123
+ }
11124
+ var sceneLog, KNOWN_BLOOM_KEYS, Canvas3DHost;
11125
+ var init_learningScene3D = __esm({
11126
+ "components/learning/molecules/learningScene3D.tsx"() {
11127
+ "use client";
11128
+ init_atoms();
11129
+ init_Stack();
11130
+ init_useEventBus();
11131
+ sceneLog = createLogger("almadar:ui:learning-scene-3d");
11132
+ KNOWN_BLOOM_KEYS = /* @__PURE__ */ new Set(["intensity", "threshold", "smoothing"]);
11133
+ Canvas3DHost = lazy(
11134
+ () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
11135
+ );
11136
+ LearningScene3D.displayName = "LearningScene3D";
11137
+ }
11138
+ });
11139
+ var biologyLog, BIO_BAND_COLORS, BIO_STAGE_FILL, BIO_STAGE_TEXT, BiologyCanvas;
9807
11140
  var init_BiologyCanvas = __esm({
9808
11141
  "components/learning/molecules/BiologyCanvas.tsx"() {
9809
11142
  "use client";
9810
11143
  init_atoms();
9811
11144
  init_Stack();
9812
11145
  init_LearningCanvas();
11146
+ init_learningScene3D();
11147
+ biologyLog = createLogger("almadar:ui:biology-canvas");
11148
+ BIO_BAND_COLORS = ["#dcfce7", "#fef9c3", "#fee2e2", "#e0e7ff"];
11149
+ BIO_STAGE_FILL = {
11150
+ pending: "#e2e8f0",
11151
+ active: "#3b82f6",
11152
+ done: "#94a3b8"
11153
+ };
11154
+ BIO_STAGE_TEXT = {
11155
+ pending: "#64748b",
11156
+ active: "#ffffff",
11157
+ done: "#ffffff"
11158
+ };
9813
11159
  BiologyCanvas = ({
9814
11160
  className,
9815
11161
  width = 600,
9816
11162
  height = 400,
9817
11163
  title,
9818
11164
  backgroundColor,
11165
+ mode = "2d",
11166
+ camera,
11167
+ lighting,
11168
+ post,
9819
11169
  nodes = [],
9820
11170
  edges = [],
11171
+ compartments = [],
11172
+ bands = [],
11173
+ stages = [],
11174
+ stageStyle = "timeline",
11175
+ helix,
11176
+ helix3d,
9821
11177
  shapes = [],
9822
- interactive = false,
11178
+ readouts,
11179
+ traces,
11180
+ showGrid,
11181
+ shadows,
11182
+ interactive,
9823
11183
  animate = false,
9824
11184
  onShapeClick,
9825
11185
  isLoading,
@@ -9831,19 +11191,148 @@ var init_BiologyCanvas = __esm({
9831
11191
  for (const n of nodes) {
9832
11192
  if (n.id) nodeById.set(n.id, n);
9833
11193
  }
11194
+ const bandCount = bands.length;
11195
+ for (let i = 0; i < bandCount; i++) {
11196
+ const band = bands[i];
11197
+ const bandColor = band.color ?? BIO_BAND_COLORS[i % BIO_BAND_COLORS.length];
11198
+ const bandY = i * height / bandCount;
11199
+ const bandH = height / bandCount;
11200
+ out.push({
11201
+ type: "rect",
11202
+ x: 0,
11203
+ y: bandY,
11204
+ width,
11205
+ height: bandH,
11206
+ color: bandColor,
11207
+ fill: bandColor,
11208
+ opacity: 0.45
11209
+ });
11210
+ if (band.label) {
11211
+ out.push({
11212
+ type: "text",
11213
+ x: 8,
11214
+ y: bandY + 14,
11215
+ text: band.label,
11216
+ color: "#6b7280",
11217
+ fontSize: 10
11218
+ });
11219
+ }
11220
+ }
11221
+ for (const c of compartments) {
11222
+ const color = c.color ?? "#16a34a";
11223
+ out.push({
11224
+ type: "ellipse",
11225
+ x: c.x,
11226
+ y: c.y,
11227
+ width: c.width,
11228
+ height: c.height,
11229
+ color,
11230
+ fill: c.fill ?? `${color}1A`,
11231
+ lineWidth: c.lineWidth ?? 2,
11232
+ ...c.dash ? { dash: c.dash } : {}
11233
+ });
11234
+ if (c.label) {
11235
+ out.push({
11236
+ type: "text",
11237
+ x: c.x,
11238
+ y: c.y - c.height / 2 + 14,
11239
+ text: c.label,
11240
+ color: "#111827",
11241
+ fontSize: 11,
11242
+ align: "center"
11243
+ });
11244
+ }
11245
+ }
11246
+ if (helix) {
11247
+ const hx = helix.x ?? 24;
11248
+ const hy = helix.y ?? height * 0.25;
11249
+ const hw = helix.width ?? width - 48;
11250
+ const hh = helix.height ?? height * 0.5;
11251
+ const rungs = helix.rungs;
11252
+ const n = rungs.length;
11253
+ const cy = hy + hh / 2;
11254
+ const colorA = helix.colorA ?? "#2563eb";
11255
+ const colorB = helix.colorB ?? "#dc2626";
11256
+ const rungColor = helix.rungColor ?? "#94a3b8";
11257
+ const fork = helix.fork ?? 0;
11258
+ const maxSep = Math.min(hh - 8, 96);
11259
+ const strandA = [];
11260
+ const strandB = [];
11261
+ const rungGeoms = [];
11262
+ for (let i = 0; i < n; i++) {
11263
+ const rx = hx + (i + 0.5) * hw / n;
11264
+ const t = (i + 0.5) / n;
11265
+ const paired = t >= fork;
11266
+ const sep = paired ? 28 : 28 + (maxSep - 28) * ((fork - t) / fork);
11267
+ strandA.push({ x: rx, y: cy - sep / 2 });
11268
+ strandB.push({ x: rx, y: cy + sep / 2 });
11269
+ rungGeoms.push({ rx, sep, rung: rungs[i], paired });
11270
+ }
11271
+ for (let i = 1; i < n; i++) {
11272
+ 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 });
11273
+ }
11274
+ for (let i = 1; i < n; i++) {
11275
+ 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 });
11276
+ }
11277
+ for (const g of rungGeoms) {
11278
+ const rColor = g.rung.color ?? (g.rung.state === "new" ? "#16a34a" : rungColor);
11279
+ const topY = cy - g.sep / 2;
11280
+ const bottomY = cy + g.sep / 2;
11281
+ if (g.paired) {
11282
+ out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: bottomY, color: rColor });
11283
+ if (g.rung.a) {
11284
+ out.push({ type: "text", x: g.rx, y: cy - g.sep / 4, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
11285
+ }
11286
+ if (g.rung.b) {
11287
+ out.push({ type: "text", x: g.rx, y: cy + g.sep / 4, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
11288
+ }
11289
+ } else {
11290
+ const stubTopY = topY + 8;
11291
+ const stubBottomY = bottomY - 8;
11292
+ out.push({ type: "line", x1: g.rx, y1: topY, x2: g.rx, y2: stubTopY, color: rColor });
11293
+ out.push({ type: "line", x1: g.rx, y1: bottomY, x2: g.rx, y2: stubBottomY, color: rColor });
11294
+ if (g.rung.a) {
11295
+ out.push({ type: "text", x: g.rx, y: stubTopY + 6, text: g.rung.a, fontSize: 9, align: "center", color: "#374151" });
11296
+ }
11297
+ if (g.rung.b) {
11298
+ out.push({ type: "text", x: g.rx, y: stubBottomY - 6, text: g.rung.b, fontSize: 9, align: "center", color: "#374151" });
11299
+ }
11300
+ }
11301
+ }
11302
+ }
9834
11303
  for (const e of edges) {
9835
11304
  const a = nodeById.get(e.from);
9836
11305
  const b = nodeById.get(e.to);
9837
11306
  if (!a || !b) continue;
9838
- out.push({
9839
- type: "line",
9840
- x1: a.x,
9841
- y1: a.y,
9842
- x2: b.x,
9843
- y2: b.y,
9844
- color: e.color ?? "#9ca3af",
9845
- lineWidth: 2
9846
- });
11307
+ const color = e.color ?? "#9ca3af";
11308
+ if (e.directed) {
11309
+ const rA = a.radius ?? 16;
11310
+ const rB = b.radius ?? 16;
11311
+ const dx = b.x - a.x;
11312
+ const dy = b.y - a.y;
11313
+ const dist = Math.max(1e-6, Math.hypot(dx, dy));
11314
+ const ux = dx / dist;
11315
+ const uy = dy / dist;
11316
+ out.push({
11317
+ type: "arrow",
11318
+ x1: a.x + ux * rA,
11319
+ y1: a.y + uy * rA,
11320
+ x2: b.x - ux * rB,
11321
+ y2: b.y - uy * rB,
11322
+ color,
11323
+ lineWidth: 2
11324
+ });
11325
+ } else {
11326
+ out.push({
11327
+ type: "line",
11328
+ x1: a.x,
11329
+ y1: a.y,
11330
+ x2: b.x,
11331
+ y2: b.y,
11332
+ color,
11333
+ lineWidth: 2
11334
+ });
11335
+ }
9847
11336
  if (e.label) {
9848
11337
  out.push({
9849
11338
  type: "text",
@@ -9856,6 +11345,8 @@ var init_BiologyCanvas = __esm({
9856
11345
  }
9857
11346
  }
9858
11347
  for (const n of nodes) {
11348
+ const state = n.state ?? "default";
11349
+ const muted = state === "muted";
9859
11350
  out.push({
9860
11351
  type: "circle",
9861
11352
  x: n.x,
@@ -9863,23 +11354,197 @@ var init_BiologyCanvas = __esm({
9863
11354
  radius: n.radius ?? 16,
9864
11355
  color: n.color ?? "#16a34a",
9865
11356
  fill: `${n.color ?? "#16a34a"}33`,
9866
- id: n.id
11357
+ id: n.id,
11358
+ ...muted ? { opacity: 0.35 } : {}
9867
11359
  });
11360
+ if (state === "highlight") {
11361
+ out.push({
11362
+ type: "circle",
11363
+ x: n.x,
11364
+ y: n.y,
11365
+ radius: (n.radius ?? 16) + 4,
11366
+ color: "#f59e0b",
11367
+ lineWidth: 2
11368
+ });
11369
+ }
9868
11370
  if (n.label) {
9869
11371
  out.push({
9870
11372
  type: "text",
9871
11373
  x: n.x,
9872
11374
  y: n.y + (n.radius ?? 16) + 14,
9873
11375
  text: n.label,
11376
+ ...muted ? { opacity: 0.35 } : {},
9874
11377
  color: "#111827",
9875
11378
  fontSize: 12,
9876
11379
  align: "center"
9877
11380
  });
9878
11381
  }
9879
11382
  }
11383
+ const stageCount = stages.length;
11384
+ if (stageCount > 0) {
11385
+ if (stageStyle === "ring") {
11386
+ const cx = width / 2;
11387
+ const cy = height / 2;
11388
+ const R = Math.min(width, height) / 2 - 48;
11389
+ const ringPoints = [];
11390
+ for (let i = 0; i < stageCount; i++) {
11391
+ const angleRad = (-90 + 360 * i / stageCount) * Math.PI / 180;
11392
+ ringPoints.push({ x: cx + R * Math.cos(angleRad), y: cy + R * Math.sin(angleRad) });
11393
+ }
11394
+ if (stageCount >= 2) {
11395
+ for (let i = 0; i < stageCount - 1; i++) {
11396
+ const p1 = ringPoints[i];
11397
+ const p2 = ringPoints[i + 1];
11398
+ const dx = p2.x - p1.x;
11399
+ const dy = p2.y - p1.y;
11400
+ const dist = Math.max(1e-6, Math.hypot(dx, dy));
11401
+ const ux = dx / dist;
11402
+ const uy = dy / dist;
11403
+ out.push({
11404
+ type: "arrow",
11405
+ x1: p1.x + ux * 46,
11406
+ y1: p1.y + uy * 46,
11407
+ x2: p2.x - ux * 46,
11408
+ y2: p2.y - uy * 46,
11409
+ color: "#94a3b8"
11410
+ });
11411
+ }
11412
+ }
11413
+ for (let i = 0; i < stageCount; i++) {
11414
+ const stage = stages[i];
11415
+ const state = stage.state ?? "pending";
11416
+ const fill = stage.color ?? BIO_STAGE_FILL[state];
11417
+ const w = Math.max(26, Math.min(84, stage.label.length * 6 + 10));
11418
+ const h = 18;
11419
+ const p = ringPoints[i];
11420
+ out.push({ type: "rect", x: p.x - w / 2, y: p.y - h / 2, width: w, height: h, color: fill, fill });
11421
+ out.push({ type: "text", x: p.x, y: p.y, text: stage.label, color: BIO_STAGE_TEXT[state], fontSize: 10, align: "center" });
11422
+ }
11423
+ } else {
11424
+ const stripY = height - 32;
11425
+ const slotW = (width - 16) / stageCount;
11426
+ const chipGeoms = [];
11427
+ for (let i = 0; i < stageCount; i++) {
11428
+ chipGeoms.push({ x: 8 + i * slotW + 5, w: slotW - 10 });
11429
+ }
11430
+ for (let i = 0; i < stageCount - 1; i++) {
11431
+ const midY = stripY + 13;
11432
+ out.push({
11433
+ type: "arrow",
11434
+ x1: chipGeoms[i].x + chipGeoms[i].w,
11435
+ y1: midY,
11436
+ x2: chipGeoms[i + 1].x,
11437
+ y2: midY,
11438
+ color: "#94a3b8"
11439
+ });
11440
+ }
11441
+ for (let i = 0; i < stageCount; i++) {
11442
+ const stage = stages[i];
11443
+ const state = stage.state ?? "pending";
11444
+ const fill = stage.color ?? BIO_STAGE_FILL[state];
11445
+ const g = chipGeoms[i];
11446
+ out.push({ type: "rect", x: g.x, y: stripY, width: g.w, height: 26, color: fill, fill });
11447
+ out.push({
11448
+ type: "text",
11449
+ x: g.x + g.w / 2,
11450
+ y: stripY + 13,
11451
+ text: stage.label,
11452
+ color: BIO_STAGE_TEXT[state],
11453
+ fontSize: 10,
11454
+ align: "center"
11455
+ });
11456
+ }
11457
+ }
11458
+ }
9880
11459
  out.push(...shapes);
9881
11460
  return out;
9882
- }, [nodes, edges, shapes]);
11461
+ }, [nodes, edges, compartments, bands, stages, stageStyle, helix, shapes, width, height]);
11462
+ const drawables3D = useMemo(() => {
11463
+ if (mode !== "3d") return [];
11464
+ if (shapes.length > 0) {
11465
+ biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
11466
+ }
11467
+ if (compartments.length > 0 || bands.length > 0 || stages.length > 0 || helix) {
11468
+ biologyLog.debug("2D-only families ignored in 3D mode (pixel-authored 2D vocabulary)", {
11469
+ compartments: compartments.length,
11470
+ bands: bands.length,
11471
+ stages: stages.length,
11472
+ helix: helix != null
11473
+ });
11474
+ }
11475
+ if (animate) {
11476
+ biologyLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
11477
+ }
11478
+ const out = [];
11479
+ const labelColor = labelColorForBackground(backgroundColor);
11480
+ const nodeById = /* @__PURE__ */ new Map();
11481
+ for (const n of nodes) {
11482
+ if (n.id) nodeById.set(n.id, n);
11483
+ }
11484
+ for (const e of edges) {
11485
+ const a = nodeById.get(e.from);
11486
+ const b = nodeById.get(e.to);
11487
+ if (!a || !b) continue;
11488
+ const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
11489
+ const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
11490
+ if (edge) out.push(edge);
11491
+ if (e.label) {
11492
+ out.push(
11493
+ billboardLabel(
11494
+ e.label,
11495
+ (a.x + b.x) / 2,
11496
+ (a.y + b.y) / 2,
11497
+ ((a.z ?? 0) + (b.z ?? 0)) / 2,
11498
+ { color: labelColor }
11499
+ )
11500
+ );
11501
+ }
11502
+ }
11503
+ for (const n of nodes) {
11504
+ const radius = n.radius ?? 0.5;
11505
+ const nz = n.z ?? 0;
11506
+ out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
11507
+ if (n.label) {
11508
+ out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
11509
+ }
11510
+ }
11511
+ if (helix3d) {
11512
+ out.push(...helixDrawables(helix3d, { labelColor }));
11513
+ }
11514
+ return out;
11515
+ }, [mode, nodes, edges, shapes, compartments, bands, stages, helix, helix3d, animate, backgroundColor]);
11516
+ const nodeIndexById = useMemo(() => {
11517
+ const m = /* @__PURE__ */ new Map();
11518
+ (helix3d?.rungs ?? []).forEach((rung, i) => {
11519
+ if (rung.id) m.set(rung.id, i);
11520
+ });
11521
+ nodes.forEach((n, i) => {
11522
+ if (n.id) m.set(n.id, i);
11523
+ });
11524
+ return m;
11525
+ }, [nodes, helix3d]);
11526
+ if (mode === "3d") {
11527
+ return /* @__PURE__ */ jsx(
11528
+ LearningScene3D,
11529
+ {
11530
+ className,
11531
+ width,
11532
+ height,
11533
+ title,
11534
+ backgroundColor,
11535
+ drawables: drawables3D,
11536
+ camera,
11537
+ lighting,
11538
+ post,
11539
+ showGrid,
11540
+ shadows,
11541
+ interactive,
11542
+ isLoading,
11543
+ error,
11544
+ onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
11545
+ }
11546
+ );
11547
+ }
9883
11548
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
9884
11549
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
9885
11550
  /* @__PURE__ */ jsx(
@@ -9889,7 +11554,9 @@ var init_BiologyCanvas = __esm({
9889
11554
  height,
9890
11555
  backgroundColor,
9891
11556
  shapes: derivedShapes,
9892
- interactive,
11557
+ readouts,
11558
+ traces,
11559
+ interactive: interactive ?? false,
9893
11560
  animate,
9894
11561
  onShapeClick,
9895
11562
  isLoading,
@@ -17281,9 +18948,10 @@ function Canvas({
17281
18948
  drawables: [...drawables ?? [], ...childDrawables],
17282
18949
  isLoading,
17283
18950
  cameraMode: to3DCameraMode(camera?.mode),
17284
- ...zoom !== void 0 ? { scale: zoom } : {},
18951
+ ...zoom !== void 0 ? { zoom } : {},
17285
18952
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
17286
18953
  ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
18954
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
17287
18955
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
17288
18956
  unitScale,
17289
18957
  backgroundColor,
@@ -17306,7 +18974,7 @@ function Canvas({
17306
18974
  keyUpMap
17307
18975
  };
17308
18976
  return /* @__PURE__ */ jsxs(Fragment, { children: [
17309
- /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost, { ...props3d }) }),
18977
+ /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost2, { ...props3d }) }),
17310
18978
  React77.Children.count(children) > 0 && /* @__PURE__ */ jsx(DrawableRegistryContext.Provider, { value: registerChildDrawable, children: /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children }) })
17311
18979
  ] });
17312
18980
  }
@@ -17336,13 +19004,13 @@ function Canvas({
17336
19004
  }
17337
19005
  );
17338
19006
  }
17339
- var Canvas3DHost, canvasLog;
19007
+ var Canvas3DHost2, canvasLog;
17340
19008
  var init_Canvas = __esm({
17341
19009
  "components/game/molecules/Canvas.tsx"() {
17342
19010
  "use client";
17343
19011
  init_registry();
17344
19012
  init_Canvas2D();
17345
- Canvas3DHost = lazy(
19013
+ Canvas3DHost2 = lazy(
17346
19014
  () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
17347
19015
  );
17348
19016
  canvasLog = createLogger("almadar:ui:game-canvas");
@@ -19069,24 +20737,55 @@ var init_ChatBar = __esm({
19069
20737
  ChatBar.displayName = "ChatBar";
19070
20738
  }
19071
20739
  });
19072
- var ChemistryCanvas;
20740
+ function bondPerpendicular(a, b) {
20741
+ const dx = b[0] - a[0];
20742
+ const dy = b[1] - a[1];
20743
+ const px = dy;
20744
+ const py = -dx;
20745
+ const len = Math.sqrt(px * px + py * py);
20746
+ if (len < 1e-6) return [1, 0, 0];
20747
+ return [px / len, py / len, 0];
20748
+ }
20749
+ var chemistryLog, CHEM_BOND_STATE_COLOR, LONE_PAIR_ANGLES, ChemistryCanvas;
19073
20750
  var init_ChemistryCanvas = __esm({
19074
20751
  "components/learning/molecules/ChemistryCanvas.tsx"() {
19075
20752
  "use client";
19076
20753
  init_atoms();
19077
20754
  init_Stack();
19078
20755
  init_LearningCanvas();
20756
+ init_learningScene3D();
20757
+ chemistryLog = createLogger("almadar:ui:chemistry-canvas");
20758
+ CHEM_BOND_STATE_COLOR = {
20759
+ default: "#6b7280",
20760
+ forming: "#16a34a",
20761
+ breaking: "#dc2626",
20762
+ highlight: "#f59e0b"
20763
+ };
20764
+ LONE_PAIR_ANGLES = [-90, 0, 90, 180];
19079
20765
  ChemistryCanvas = ({
19080
20766
  className,
19081
20767
  width = 600,
19082
20768
  height = 400,
19083
20769
  title,
19084
20770
  backgroundColor,
20771
+ mode = "2d",
20772
+ camera,
20773
+ lighting,
20774
+ post,
19085
20775
  atoms = [],
19086
20776
  bonds = [],
19087
20777
  arrows = [],
20778
+ bondStyle = "thick",
20779
+ containers = [],
20780
+ equation,
20781
+ equationColor,
20782
+ lattice3d,
19088
20783
  shapes = [],
19089
- interactive = false,
20784
+ readouts,
20785
+ traces,
20786
+ showGrid,
20787
+ shadows,
20788
+ interactive,
19090
20789
  animate = false,
19091
20790
  onShapeClick,
19092
20791
  isLoading,
@@ -19098,21 +20797,118 @@ var init_ChemistryCanvas = __esm({
19098
20797
  for (const a of atoms) {
19099
20798
  if (a.id) atomById.set(a.id, a);
19100
20799
  }
20800
+ for (const c of containers) {
20801
+ const color = c.color ?? "#64748b";
20802
+ if (c.level != null) {
20803
+ const lv = c.level;
20804
+ out.push({
20805
+ type: "rect",
20806
+ x: c.x + 1,
20807
+ y: c.y + c.height * (1 - lv),
20808
+ width: c.width - 2,
20809
+ height: c.height * lv - 1,
20810
+ color: c.levelColor ?? "#60a5fa",
20811
+ fill: c.levelColor ?? "#60a5fa",
20812
+ opacity: 0.5
20813
+ });
20814
+ }
20815
+ out.push({
20816
+ type: "rect",
20817
+ x: c.x,
20818
+ y: c.y,
20819
+ width: c.width,
20820
+ height: c.height,
20821
+ color,
20822
+ fill: c.fill,
20823
+ lineWidth: c.lineWidth ?? 2
20824
+ });
20825
+ const divider = c.divider ?? "none";
20826
+ if (divider !== "none") {
20827
+ out.push({
20828
+ type: "line",
20829
+ x1: c.x + c.width / 2,
20830
+ y1: c.y,
20831
+ x2: c.x + c.width / 2,
20832
+ y2: c.y + c.height,
20833
+ color: c.dividerColor ?? color,
20834
+ ...divider === "dashed" || divider === "dotted" ? { dash: divider } : {}
20835
+ });
20836
+ }
20837
+ if (c.leftLabel) {
20838
+ out.push({
20839
+ type: "text",
20840
+ x: c.x + c.width * 0.25,
20841
+ y: c.y + 12,
20842
+ text: c.leftLabel,
20843
+ color: "#374151",
20844
+ fontSize: 11,
20845
+ align: "center"
20846
+ });
20847
+ }
20848
+ if (c.rightLabel) {
20849
+ out.push({
20850
+ type: "text",
20851
+ x: c.x + c.width * 0.75,
20852
+ y: c.y + 12,
20853
+ text: c.rightLabel,
20854
+ color: "#374151",
20855
+ fontSize: 11,
20856
+ align: "center"
20857
+ });
20858
+ }
20859
+ if (c.label) {
20860
+ out.push({
20861
+ type: "text",
20862
+ x: c.x + c.width / 2,
20863
+ y: c.y + c.height + 12,
20864
+ text: c.label,
20865
+ color: "#111827",
20866
+ fontSize: 12,
20867
+ align: "center"
20868
+ });
20869
+ }
20870
+ }
19101
20871
  for (const b of bonds) {
19102
20872
  const a = atomById.get(b.from);
19103
20873
  const c = atomById.get(b.to);
19104
20874
  if (!a || !c) continue;
19105
- const color = b.color ?? "#6b7280";
19106
- const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
19107
- out.push({
19108
- type: "line",
19109
- x1: a.x,
19110
- y1: a.y,
19111
- x2: c.x,
19112
- y2: c.y,
19113
- color,
19114
- lineWidth: strokeWidth
19115
- });
20875
+ const state = b.state ?? "default";
20876
+ const color = b.color ?? CHEM_BOND_STATE_COLOR[state];
20877
+ const dash = state === "forming" || state === "breaking" ? "dashed" : void 0;
20878
+ if (bondStyle === "parallel") {
20879
+ const dx = c.x - a.x;
20880
+ const dy = c.y - a.y;
20881
+ const dist = Math.max(1e-6, Math.hypot(dx, dy));
20882
+ const ux = dx / dist;
20883
+ const uy = dy / dist;
20884
+ const px = -uy;
20885
+ const py = ux;
20886
+ const offsets = b.type === "double" ? [-3, 3] : b.type === "triple" ? [-4, 0, 4] : [0];
20887
+ for (const off of offsets) {
20888
+ out.push({
20889
+ type: "line",
20890
+ x1: a.x + px * off,
20891
+ y1: a.y + py * off,
20892
+ x2: c.x + px * off,
20893
+ y2: c.y + py * off,
20894
+ color,
20895
+ lineWidth: 2,
20896
+ ...dash ? { dash } : {}
20897
+ });
20898
+ }
20899
+ } else {
20900
+ const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
20901
+ out.push({
20902
+ type: "line",
20903
+ x1: a.x,
20904
+ y1: a.y,
20905
+ x2: c.x,
20906
+ y2: c.y,
20907
+ color,
20908
+ lineWidth: strokeWidth,
20909
+ ...dash ? { dash } : {}
20910
+ });
20911
+ }
19116
20912
  }
19117
20913
  for (const a of arrows) {
19118
20914
  const angle = (a.angle ?? 0) * (Math.PI / 180);
@@ -19161,10 +20957,142 @@ var init_ChemistryCanvas = __esm({
19161
20957
  align: "center"
19162
20958
  });
19163
20959
  }
20960
+ const r = a.radius ?? 14;
20961
+ if (a.charge) {
20962
+ out.push({
20963
+ type: "text",
20964
+ x: a.x + r * 0.85,
20965
+ y: a.y - r * 0.85,
20966
+ text: a.charge,
20967
+ color: "#111827",
20968
+ fontSize: 9,
20969
+ align: "left"
20970
+ });
20971
+ }
20972
+ const lonePairs = Math.max(0, Math.min(4, a.lonePairs ?? 0));
20973
+ for (let k = 0; k < lonePairs; k++) {
20974
+ const angleRad = LONE_PAIR_ANGLES[k] * Math.PI / 180;
20975
+ const cx = a.x + (r + 6) * Math.cos(angleRad);
20976
+ const cy = a.y + (r + 6) * Math.sin(angleRad);
20977
+ const perpX = -Math.sin(angleRad);
20978
+ const perpY = Math.cos(angleRad);
20979
+ for (const sign of [1, -1]) {
20980
+ out.push({
20981
+ type: "circle",
20982
+ x: cx + perpX * 2.5 * sign,
20983
+ y: cy + perpY * 2.5 * sign,
20984
+ radius: 1.5,
20985
+ color: "#374151",
20986
+ fill: "#374151"
20987
+ });
20988
+ }
20989
+ }
20990
+ }
20991
+ if (equation) {
20992
+ out.push({
20993
+ type: "text",
20994
+ x: width / 2,
20995
+ y: 14,
20996
+ text: equation,
20997
+ color: equationColor ?? "#111827",
20998
+ fontSize: 13,
20999
+ align: "center"
21000
+ });
19164
21001
  }
19165
21002
  out.push(...shapes);
19166
21003
  return out;
19167
- }, [atoms, bonds, arrows, shapes]);
21004
+ }, [atoms, bonds, arrows, bondStyle, containers, equation, equationColor, shapes, width]);
21005
+ const drawables3D = useMemo(() => {
21006
+ if (mode !== "3d") return [];
21007
+ if (shapes.length > 0) {
21008
+ chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
21009
+ }
21010
+ if (containers.length > 0) {
21011
+ chemistryLog.debug("containers ignored in 3D mode (pixel-authored 2D vocabulary)", { count: containers.length });
21012
+ }
21013
+ if (animate) {
21014
+ chemistryLog.debug("animate ignored in 3D mode (motion is entity-state driven)");
21015
+ }
21016
+ const out = [];
21017
+ const labelColor = labelColorForBackground(backgroundColor);
21018
+ const atomById = /* @__PURE__ */ new Map();
21019
+ for (const a of atoms) {
21020
+ if (a.id) atomById.set(a.id, a);
21021
+ }
21022
+ for (const b of bonds) {
21023
+ const a = atomById.get(b.from);
21024
+ const c = atomById.get(b.to);
21025
+ if (!a || !c) continue;
21026
+ const color = b.color ?? "#6b7280";
21027
+ const from = [a.x, a.y, a.z ?? 0];
21028
+ const to = [c.x, c.y, c.z ?? 0];
21029
+ const perp = bondPerpendicular(from, to);
21030
+ const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
21031
+ const step = bondRadius * 2.2;
21032
+ const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
21033
+ for (const off of offsets) {
21034
+ const bond = cylinderBetween(
21035
+ [from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
21036
+ [to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
21037
+ bondRadius,
21038
+ color
21039
+ );
21040
+ if (bond) out.push(bond);
21041
+ }
21042
+ }
21043
+ for (const a of arrows) {
21044
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
21045
+ const len = a.length ?? 60;
21046
+ const x2 = a.x + Math.cos(angle) * len;
21047
+ const y2 = a.y + Math.sin(angle) * len;
21048
+ const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
21049
+ if (arrow) out.push(arrow);
21050
+ if (a.label) {
21051
+ out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
21052
+ }
21053
+ }
21054
+ for (const a of atoms) {
21055
+ const radius = a.radius ?? 0.45;
21056
+ const az = a.z ?? 0;
21057
+ out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
21058
+ if (a.element) {
21059
+ out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
21060
+ }
21061
+ }
21062
+ if (lattice3d) {
21063
+ out.push(...latticeDrawables(lattice3d, { labelColor }));
21064
+ }
21065
+ return out;
21066
+ }, [mode, atoms, bonds, arrows, shapes, containers, lattice3d, animate, backgroundColor]);
21067
+ const atomIndexById = useMemo(() => {
21068
+ const m = /* @__PURE__ */ new Map();
21069
+ atoms.forEach((a, i) => {
21070
+ if (a.id) m.set(a.id, i);
21071
+ });
21072
+ return m;
21073
+ }, [atoms]);
21074
+ if (mode === "3d") {
21075
+ return /* @__PURE__ */ jsx(
21076
+ LearningScene3D,
21077
+ {
21078
+ className,
21079
+ width,
21080
+ height,
21081
+ title,
21082
+ backgroundColor,
21083
+ drawables: drawables3D,
21084
+ camera,
21085
+ lighting,
21086
+ post,
21087
+ showGrid,
21088
+ shadows,
21089
+ interactive,
21090
+ isLoading,
21091
+ error,
21092
+ onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
21093
+ }
21094
+ );
21095
+ }
19168
21096
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
19169
21097
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
19170
21098
  /* @__PURE__ */ jsx(
@@ -19174,7 +21102,9 @@ var init_ChemistryCanvas = __esm({
19174
21102
  height,
19175
21103
  backgroundColor,
19176
21104
  shapes: derivedShapes,
19177
- interactive,
21105
+ readouts,
21106
+ traces,
21107
+ interactive: interactive ?? false,
19178
21108
  animate,
19179
21109
  onShapeClick,
19180
21110
  isLoading,
@@ -29086,6 +31016,10 @@ var init_molecules = __esm({
29086
31016
  init_GameShell();
29087
31017
  }
29088
31018
  });
31019
+ function formatTick(v) {
31020
+ if (Number.isInteger(v)) return String(v);
31021
+ return v.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
31022
+ }
29089
31023
  var MathCanvas;
29090
31024
  var init_MathCanvas = __esm({
29091
31025
  "components/learning/molecules/MathCanvas.tsx"() {
@@ -29105,10 +31039,19 @@ var init_MathCanvas = __esm({
29105
31039
  showAxes = true,
29106
31040
  showGrid = true,
29107
31041
  gridStep = 1,
31042
+ showTickLabels = false,
31043
+ showCurveLabels = false,
29108
31044
  curves = [],
29109
31045
  points = [],
29110
31046
  vectors = [],
31047
+ regions = [],
31048
+ bars = [],
31049
+ guides = [],
31050
+ angles = [],
31051
+ hops = [],
29111
31052
  shapes = [],
31053
+ readouts,
31054
+ traces,
29112
31055
  interactive = false,
29113
31056
  animate = false,
29114
31057
  onShapeClick,
@@ -29122,6 +31065,8 @@ var init_MathCanvas = __esm({
29122
31065
  const plotH = height - margin * 2;
29123
31066
  const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
29124
31067
  const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
31068
+ const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
31069
+ const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
29125
31070
  if (showGrid) {
29126
31071
  for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
29127
31072
  const px = mapX(x);
@@ -29132,14 +31077,99 @@ var init_MathCanvas = __esm({
29132
31077
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
29133
31078
  }
29134
31079
  }
31080
+ if (showTickLabels) {
31081
+ const labelEveryX = Math.max(1, Math.ceil((xMax - xMin) / gridStep / Math.floor(plotW / 40)));
31082
+ let kx = 0;
31083
+ for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
31084
+ if (kx % labelEveryX === 0 && x !== 0) {
31085
+ out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: 10, align: "center" });
31086
+ }
31087
+ }
31088
+ const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
31089
+ let ky = 0;
31090
+ for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
31091
+ if (ky % labelEveryY === 0 && y !== 0) {
31092
+ out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: 10, align: "right" });
31093
+ }
31094
+ }
31095
+ if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
31096
+ out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: 10, align: "right" });
31097
+ }
31098
+ }
31099
+ for (const region of regions) {
31100
+ if (!region.samples || region.samples.length === 0) continue;
31101
+ const baseline = region.baseline ?? 0;
31102
+ const clampedPoint = (p) => ({
31103
+ x: mapX(Math.min(xMax, Math.max(xMin, p.x))),
31104
+ y: mapY(Math.min(yMax, Math.max(yMin, p.y)))
31105
+ });
31106
+ const upper = region.samples.map(clampedPoint);
31107
+ const first = region.samples[0];
31108
+ const last = region.samples[region.samples.length - 1];
31109
+ 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 })];
31110
+ const color = region.color ?? "#2563eb";
31111
+ out.push({
31112
+ type: "polygon",
31113
+ points: [...upper, ...closing],
31114
+ fill: color,
31115
+ color,
31116
+ opacity: region.opacity ?? 0.2,
31117
+ lineWidth: 1
31118
+ });
31119
+ if (region.label) {
31120
+ const mid = Math.floor(region.samples.length / 2);
31121
+ out.push({
31122
+ type: "text",
31123
+ x: mapX((first.x + last.x) / 2),
31124
+ y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
31125
+ text: region.label,
31126
+ color: "#111827",
31127
+ fontSize: 11
31128
+ });
31129
+ }
31130
+ }
31131
+ for (const bar of bars) {
31132
+ if (bar.x + bar.width < xMin || bar.x > xMax) continue;
31133
+ const y0 = bar.y0 ?? 0;
31134
+ const color = bar.color ?? "#93c5fd";
31135
+ out.push({
31136
+ type: "rect",
31137
+ x: mapX(bar.x),
31138
+ y: mapY(Math.max(y0, bar.y1)),
31139
+ width: mapX(bar.x + bar.width) - mapX(bar.x),
31140
+ height: Math.abs(mapY(bar.y1) - mapY(y0)),
31141
+ color,
31142
+ fill: color,
31143
+ opacity: bar.opacity ?? 0.5,
31144
+ lineWidth: 1
31145
+ });
31146
+ }
29135
31147
  if (showAxes) {
29136
- const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
29137
- const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
29138
31148
  out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
29139
31149
  out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
29140
31150
  }
31151
+ for (const guide of guides) {
31152
+ const color = guide.color ?? "#9ca3af";
31153
+ const dash = guide.dash ?? "dashed";
31154
+ if (guide.kind === "vline") {
31155
+ if (guide.at < xMin || guide.at > xMax) continue;
31156
+ const px = mapX(guide.at);
31157
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
31158
+ if (guide.label) {
31159
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
31160
+ }
31161
+ } else {
31162
+ if (guide.at < yMin || guide.at > yMax) continue;
31163
+ const py = mapY(guide.at);
31164
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
31165
+ if (guide.label) {
31166
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
31167
+ }
31168
+ }
31169
+ }
29141
31170
  for (const curve of curves) {
29142
31171
  if (!curve.samples || curve.samples.length < 2) continue;
31172
+ let lastInRange;
29143
31173
  for (let i = 1; i < curve.samples.length; i++) {
29144
31174
  const a = curve.samples[i - 1];
29145
31175
  const b = curve.samples[i];
@@ -29151,19 +31181,97 @@ var init_MathCanvas = __esm({
29151
31181
  x2: mapX(b.x),
29152
31182
  y2: mapY(b.y),
29153
31183
  color: curve.color ?? "#2563eb",
29154
- lineWidth: 2
31184
+ lineWidth: 2,
31185
+ dash: curve.dash
31186
+ });
31187
+ lastInRange = b;
31188
+ }
31189
+ if (showCurveLabels && curve.label && lastInRange) {
31190
+ out.push({
31191
+ type: "text",
31192
+ x: mapX(lastInRange.x) + 6,
31193
+ y: mapY(lastInRange.y) - 6,
31194
+ text: curve.label,
31195
+ color: curve.color ?? "#2563eb",
31196
+ fontSize: 11
31197
+ });
31198
+ }
31199
+ }
31200
+ for (const hop of hops) {
31201
+ const x1 = mapX(hop.from);
31202
+ const x2 = mapX(hop.to);
31203
+ const peak = Math.min(36, plotH * 0.3);
31204
+ const color = hop.color ?? "#7c3aed";
31205
+ out.push({
31206
+ type: "ellipse",
31207
+ x: (x1 + x2) / 2,
31208
+ y: xAxisY,
31209
+ width: Math.abs(x2 - x1),
31210
+ height: 2 * peak,
31211
+ startAngle: 180,
31212
+ endAngle: 360,
31213
+ color
31214
+ });
31215
+ const s = Math.sign(hop.to - hop.from);
31216
+ out.push({
31217
+ type: "polygon",
31218
+ points: [
31219
+ { x: x2, y: xAxisY },
31220
+ { x: x2 - 4 * s, y: xAxisY - 7 },
31221
+ { x: x2 + 2 * s, y: xAxisY - 7 }
31222
+ ],
31223
+ fill: color,
31224
+ color
31225
+ });
31226
+ if (hop.label) {
31227
+ out.push({
31228
+ type: "text",
31229
+ x: (x1 + x2) / 2,
31230
+ y: xAxisY - peak - 8,
31231
+ text: hop.label,
31232
+ color: "#111827",
31233
+ fontSize: 10,
31234
+ align: "center"
31235
+ });
31236
+ }
31237
+ }
31238
+ for (const angle of angles) {
31239
+ const radius = angle.radius ?? 0.8;
31240
+ const color = angle.color ?? "#0ea5e9";
31241
+ out.push({
31242
+ type: "ellipse",
31243
+ x: mapX(angle.x),
31244
+ y: mapY(angle.y),
31245
+ width: 2 * radius * plotW / (xMax - xMin),
31246
+ height: 2 * radius * plotH / (yMax - yMin),
31247
+ startAngle: -angle.to,
31248
+ endAngle: -angle.from,
31249
+ color
31250
+ });
31251
+ if (angle.label) {
31252
+ const mid = (angle.from + angle.to) / 2;
31253
+ const rad = mid * Math.PI / 180;
31254
+ out.push({
31255
+ type: "text",
31256
+ x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
31257
+ y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
31258
+ text: angle.label,
31259
+ color: "#111827",
31260
+ fontSize: 11,
31261
+ align: "center"
29155
31262
  });
29156
31263
  }
29157
31264
  }
29158
31265
  for (const p of points) {
29159
31266
  if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
31267
+ const isOpen = p.style === "open";
29160
31268
  out.push({
29161
31269
  type: "circle",
29162
31270
  x: mapX(p.x),
29163
31271
  y: mapY(p.y),
29164
31272
  radius: p.radius ?? 4,
29165
31273
  color: p.color ?? "#dc2626",
29166
- fill: p.color ?? "#dc2626"
31274
+ fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
29167
31275
  });
29168
31276
  if (p.label) {
29169
31277
  out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
@@ -29182,7 +31290,28 @@ var init_MathCanvas = __esm({
29182
31290
  }
29183
31291
  out.push(...shapes);
29184
31292
  return out;
29185
- }, [width, height, xMin, xMax, yMin, yMax, showAxes, showGrid, gridStep, curves, points, vectors, shapes]);
31293
+ }, [
31294
+ width,
31295
+ height,
31296
+ xMin,
31297
+ xMax,
31298
+ yMin,
31299
+ yMax,
31300
+ showAxes,
31301
+ showGrid,
31302
+ gridStep,
31303
+ showTickLabels,
31304
+ showCurveLabels,
31305
+ curves,
31306
+ points,
31307
+ vectors,
31308
+ regions,
31309
+ bars,
31310
+ guides,
31311
+ angles,
31312
+ hops,
31313
+ shapes
31314
+ ]);
29186
31315
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
29187
31316
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
29188
31317
  /* @__PURE__ */ jsx(
@@ -29191,6 +31320,8 @@ var init_MathCanvas = __esm({
29191
31320
  width,
29192
31321
  height,
29193
31322
  shapes: derivedShapes,
31323
+ readouts,
31324
+ traces,
29194
31325
  interactive,
29195
31326
  animate,
29196
31327
  onShapeClick,
@@ -29202,27 +31333,355 @@ var init_MathCanvas = __esm({
29202
31333
  };
29203
31334
  }
29204
31335
  });
29205
- var PhysicsCanvas;
31336
+ function formatMeterValue(v) {
31337
+ return Number.isInteger(v) ? String(v) : String(Number(v.toFixed(2)));
31338
+ }
31339
+ function sceneObjectShapes(obj, canvasWidth, canvasHeight) {
31340
+ const out = [];
31341
+ const color = obj.color ?? "#334155";
31342
+ switch (obj.kind) {
31343
+ case "ground": {
31344
+ const xStart = obj.x1 ?? 0;
31345
+ const xEnd = obj.x2 ?? canvasWidth;
31346
+ const y = obj.y ?? 0;
31347
+ out.push({ type: "line", x1: xStart, y1: y, x2: xEnd, y2: y, color, lineWidth: 2 });
31348
+ for (let hx = xStart + 7; hx <= xEnd; hx += 14) {
31349
+ out.push({ type: "line", x1: hx, y1: y, x2: hx - 7, y2: y + 7, color, lineWidth: 1 });
31350
+ }
31351
+ if (obj.label) {
31352
+ out.push({
31353
+ type: "text",
31354
+ x: (xStart + xEnd) / 2,
31355
+ y: y - 10,
31356
+ text: obj.label,
31357
+ color: PHYSICS_LABEL_COLOR,
31358
+ fontSize: 11,
31359
+ align: "center"
31360
+ });
31361
+ }
31362
+ break;
31363
+ }
31364
+ case "wall": {
31365
+ const yStart = obj.y1 ?? 0;
31366
+ const yEnd = obj.y2 ?? canvasHeight;
31367
+ const x = obj.x ?? 0;
31368
+ out.push({ type: "line", x1: x, y1: yStart, x2: x, y2: yEnd, color, lineWidth: 2 });
31369
+ for (let hy = yStart + 7; hy <= yEnd; hy += 14) {
31370
+ out.push({ type: "line", x1: x, y1: hy, x2: x - 7, y2: hy + 7, color, lineWidth: 1 });
31371
+ }
31372
+ if (obj.label) {
31373
+ out.push({
31374
+ type: "text",
31375
+ x: x + 12,
31376
+ y: (yStart + yEnd) / 2,
31377
+ text: obj.label,
31378
+ color: PHYSICS_LABEL_COLOR,
31379
+ fontSize: 11,
31380
+ align: "left"
31381
+ });
31382
+ }
31383
+ break;
31384
+ }
31385
+ case "ramp": {
31386
+ const x1 = obj.x1 ?? 0;
31387
+ const y1 = obj.y1 ?? 0;
31388
+ const x2 = obj.x2 ?? canvasWidth;
31389
+ const y2 = obj.y2 ?? canvasHeight;
31390
+ out.push({
31391
+ type: "polygon",
31392
+ points: [
31393
+ { x: x1, y: y1 },
31394
+ { x: x2, y: y2 },
31395
+ { x: x1, y: y2 }
31396
+ ],
31397
+ color,
31398
+ fill: obj.fill ?? "#e2e8f0",
31399
+ lineWidth: 2
31400
+ });
31401
+ if (obj.label) {
31402
+ out.push({
31403
+ type: "text",
31404
+ x: (2 * x1 + x2) / 3,
31405
+ y: (y1 + 2 * y2) / 3,
31406
+ text: obj.label,
31407
+ color: PHYSICS_LABEL_COLOR,
31408
+ fontSize: 11,
31409
+ align: "center"
31410
+ });
31411
+ }
31412
+ break;
31413
+ }
31414
+ case "box": {
31415
+ const x = obj.x ?? 0;
31416
+ const y = obj.y ?? 0;
31417
+ const w = obj.width ?? 40;
31418
+ const h = obj.height ?? 40;
31419
+ out.push({ type: "rect", x, y, width: w, height: h, color, fill: obj.fill, lineWidth: 2 });
31420
+ if (obj.label) {
31421
+ out.push({
31422
+ type: "text",
31423
+ x: x + w / 2,
31424
+ y: y + h / 2,
31425
+ text: obj.label,
31426
+ color: PHYSICS_LABEL_COLOR,
31427
+ fontSize: 11,
31428
+ align: "center"
31429
+ });
31430
+ }
31431
+ break;
31432
+ }
31433
+ case "pivot": {
31434
+ const x = obj.x ?? 0;
31435
+ const y = obj.y ?? 0;
31436
+ out.push({ type: "circle", x, y, radius: 5, color, fill: color });
31437
+ out.push({ type: "line", x1: x - 14, y1: y - 8, x2: x + 14, y2: y - 8, color, lineWidth: 1 });
31438
+ for (let k = 0; k < 5; k++) {
31439
+ const hx = x - 14 + 7 * k;
31440
+ out.push({ type: "line", x1: hx, y1: y - 8, x2: hx - 6, y2: y - 14, color, lineWidth: 1 });
31441
+ }
31442
+ if (obj.label) {
31443
+ out.push({
31444
+ type: "text",
31445
+ x,
31446
+ y: y - 20,
31447
+ text: obj.label,
31448
+ color: PHYSICS_LABEL_COLOR,
31449
+ fontSize: 11,
31450
+ align: "center"
31451
+ });
31452
+ }
31453
+ break;
31454
+ }
31455
+ }
31456
+ return out;
31457
+ }
31458
+ function trailShapes(trail) {
31459
+ const n = trail.points.length;
31460
+ if (n < 2) return [];
31461
+ const color = trail.color ?? "#94a3b8";
31462
+ const lineWidth = trail.width ?? 2;
31463
+ const fade = trail.fade ?? true;
31464
+ const globalOpacity = trail.opacity ?? 1;
31465
+ const out = [];
31466
+ for (let i = 0; i < n - 1; i++) {
31467
+ const a = trail.points[i];
31468
+ const b = trail.points[i + 1];
31469
+ const segmentOpacity = fade ? 0.12 + 0.68 * i / (n - 1) : 0.6;
31470
+ out.push({
31471
+ type: "line",
31472
+ x1: a.x,
31473
+ y1: a.y,
31474
+ x2: b.x,
31475
+ y2: b.y,
31476
+ color,
31477
+ lineWidth,
31478
+ opacity: segmentOpacity * globalOpacity
31479
+ });
31480
+ }
31481
+ return out;
31482
+ }
31483
+ function constraintShapes(c, a, b) {
31484
+ const color = c.color ?? "#9ca3af";
31485
+ const kind = c.kind ?? "rod";
31486
+ if (kind === "rod") {
31487
+ return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2 }];
31488
+ }
31489
+ if (kind === "string") {
31490
+ return [{ type: "line", x1: a.x, y1: a.y, x2: b.x, y2: b.y, color, lineWidth: 2, dash: "dashed" }];
31491
+ }
31492
+ const COILS = 8;
31493
+ const AMP = 7;
31494
+ const LEAD = 10;
31495
+ const dx = b.x - a.x;
31496
+ const dy = b.y - a.y;
31497
+ const dist = Math.max(1e-6, Math.hypot(dx, dy));
31498
+ const ux = dx / dist;
31499
+ const uy = dy / dist;
31500
+ const perpX = -uy;
31501
+ const perpY = ux;
31502
+ const aPrime = { x: a.x + LEAD * ux, y: a.y + LEAD * uy };
31503
+ const bPrime = { x: b.x - LEAD * ux, y: b.y - LEAD * uy };
31504
+ const m = 2 * COILS;
31505
+ const polyline = [{ x: a.x, y: a.y }, aPrime];
31506
+ for (let j = 1; j <= m; j++) {
31507
+ const t = j / (m + 1);
31508
+ const baseX = aPrime.x + t * (bPrime.x - aPrime.x);
31509
+ const baseY = aPrime.y + t * (bPrime.y - aPrime.y);
31510
+ const sign = j % 2 === 0 ? 1 : -1;
31511
+ polyline.push({ x: baseX + sign * AMP * perpX, y: baseY + sign * AMP * perpY });
31512
+ }
31513
+ polyline.push(bPrime, { x: b.x, y: b.y });
31514
+ const out = [];
31515
+ for (let i = 1; i < polyline.length; i++) {
31516
+ out.push({
31517
+ type: "line",
31518
+ x1: polyline[i - 1].x,
31519
+ y1: polyline[i - 1].y,
31520
+ x2: polyline[i].x,
31521
+ y2: polyline[i].y,
31522
+ color,
31523
+ lineWidth: 2
31524
+ });
31525
+ }
31526
+ return out;
31527
+ }
31528
+ function vectorShapes(v, bodyById) {
31529
+ let ax;
31530
+ let ay;
31531
+ if (v.body) {
31532
+ const anchor = bodyById.get(v.body);
31533
+ if (!anchor) return [];
31534
+ ax = anchor.x;
31535
+ ay = anchor.y;
31536
+ } else {
31537
+ ax = v.x ?? 0;
31538
+ ay = v.y ?? 0;
31539
+ }
31540
+ const scale = v.scale ?? 1;
31541
+ const color = v.color ?? "#dc2626";
31542
+ const tx = ax + v.dx * scale;
31543
+ const ty = ay + v.dy * scale;
31544
+ const out = [{ type: "arrow", x1: ax, y1: ay, x2: tx, y2: ty, color, lineWidth: 2, dash: v.dash }];
31545
+ if (v.label) {
31546
+ const dist = Math.max(1e-6, Math.hypot(tx - ax, ty - ay));
31547
+ const ux = (tx - ax) / dist;
31548
+ const uy = (ty - ay) / dist;
31549
+ out.push({
31550
+ type: "text",
31551
+ x: tx + 8 * ux,
31552
+ y: ty + 8 * uy,
31553
+ text: v.label,
31554
+ color,
31555
+ fontSize: 11,
31556
+ align: "center"
31557
+ });
31558
+ }
31559
+ return out;
31560
+ }
31561
+ function angleMarkerShapes(a) {
31562
+ const radius = a.radius ?? 26;
31563
+ const color = a.color ?? "#0ea5e9";
31564
+ const out = [
31565
+ {
31566
+ type: "ellipse",
31567
+ x: a.x,
31568
+ y: a.y,
31569
+ width: radius * 2,
31570
+ height: radius * 2,
31571
+ startAngle: a.from,
31572
+ endAngle: a.to,
31573
+ color,
31574
+ lineWidth: 2
31575
+ }
31576
+ ];
31577
+ if (a.label) {
31578
+ const mid = (a.from + a.to) / 2 * (Math.PI / 180);
31579
+ out.push({
31580
+ type: "text",
31581
+ x: a.x + (radius + 13) * Math.cos(mid),
31582
+ y: a.y + (radius + 13) * Math.sin(mid),
31583
+ text: a.label,
31584
+ color,
31585
+ fontSize: 11,
31586
+ align: "center"
31587
+ });
31588
+ }
31589
+ return out;
31590
+ }
31591
+ function fieldShapes(field, canvasWidth, canvasHeight) {
31592
+ const spacing = field.spacing ?? 48;
31593
+ const size = field.size ?? 14;
31594
+ const color = field.color ?? "#94a3b8";
31595
+ const regionX = field.x ?? 0;
31596
+ const regionY = field.y ?? 0;
31597
+ const regionW = field.width ?? canvasWidth;
31598
+ const regionH = field.height ?? canvasHeight;
31599
+ const out = [];
31600
+ for (let gx = regionX + spacing / 2; gx < regionX + regionW; gx += spacing) {
31601
+ for (let gy = regionY + spacing / 2; gy < regionY + regionH; gy += spacing) {
31602
+ if (field.kind === "arrows") {
31603
+ const rad = (field.angle ?? 0) * Math.PI / 180;
31604
+ const hx = Math.cos(rad) * size / 2;
31605
+ const hy = Math.sin(rad) * size / 2;
31606
+ out.push({ type: "arrow", x1: gx - hx, y1: gy - hy, x2: gx + hx, y2: gy + hy, color, lineWidth: 2 });
31607
+ } else if (field.kind === "into") {
31608
+ const r = size / 3;
31609
+ const d = 0.6 * r * Math.SQRT1_2;
31610
+ out.push({ type: "circle", x: gx, y: gy, radius: r, color });
31611
+ out.push({ type: "line", x1: gx - d, y1: gy - d, x2: gx + d, y2: gy + d, color, lineWidth: 1 });
31612
+ out.push({ type: "line", x1: gx - d, y1: gy + d, x2: gx + d, y2: gy - d, color, lineWidth: 1 });
31613
+ } else {
31614
+ const r = size / 3;
31615
+ out.push({ type: "circle", x: gx, y: gy, radius: r, color });
31616
+ out.push({ type: "circle", x: gx, y: gy, radius: 1.5, color, fill: color });
31617
+ }
31618
+ }
31619
+ }
31620
+ return out;
31621
+ }
31622
+ function meterShapes(meters, canvasHeight) {
31623
+ const n = meters.length;
31624
+ const out = [];
31625
+ const sharedMax = Math.max(1e-6, ...meters.map((m) => m.value));
31626
+ meters.forEach((meter, i) => {
31627
+ const rowY = canvasHeight - 10 - 16 * (n - i);
31628
+ const color = meter.color ?? "#3b82f6";
31629
+ const M = meter.max ?? sharedMax;
31630
+ const w = Math.round(Math.min(1, Math.max(0, meter.value / M)) * 110);
31631
+ out.push({ type: "text", x: 8, y: rowY + 8, text: meter.label, color: PHYSICS_LABEL_COLOR, fontSize: 10 });
31632
+ out.push({ type: "rect", x: 52, y: rowY, width: w, height: 10, color, fill: color });
31633
+ out.push({
31634
+ type: "text",
31635
+ x: 166,
31636
+ y: rowY + 8,
31637
+ text: formatMeterValue(meter.value),
31638
+ color: "#6b7280",
31639
+ fontSize: 9
31640
+ });
31641
+ });
31642
+ return out;
31643
+ }
31644
+ var physicsLog2, PHYSICS_LABEL_COLOR, PhysicsCanvas;
29206
31645
  var init_PhysicsCanvas = __esm({
29207
31646
  "components/learning/molecules/PhysicsCanvas.tsx"() {
29208
31647
  "use client";
29209
31648
  init_atoms();
29210
31649
  init_Stack();
29211
31650
  init_LearningCanvas();
31651
+ init_learningScene3D();
31652
+ physicsLog2 = createLogger("almadar:ui:physics-canvas");
31653
+ PHYSICS_LABEL_COLOR = "#374151";
29212
31654
  PhysicsCanvas = ({
29213
31655
  className,
29214
31656
  width = 600,
29215
31657
  height = 400,
29216
31658
  title,
29217
31659
  backgroundColor,
31660
+ mode = "2d",
31661
+ camera,
31662
+ lighting,
31663
+ post,
29218
31664
  bodies = [],
29219
31665
  constraints = [],
29220
31666
  showVelocity = true,
29221
31667
  showForces = false,
29222
31668
  velocityScale = 20,
29223
31669
  forceScale = 20,
31670
+ sceneObjects = [],
31671
+ trails = [],
31672
+ vectors = [],
31673
+ surface3d,
31674
+ vectors3d = [],
31675
+ vectorScale = 1,
31676
+ angles = [],
31677
+ field,
31678
+ meters = [],
29224
31679
  shapes = [],
29225
- interactive = false,
31680
+ readouts,
31681
+ traces,
31682
+ showGrid,
31683
+ shadows,
31684
+ interactive,
29226
31685
  animate = false,
29227
31686
  onShapeClick,
29228
31687
  isLoading,
@@ -29234,19 +31693,14 @@ var init_PhysicsCanvas = __esm({
29234
31693
  for (const b of bodies) {
29235
31694
  if (b.id) bodyById.set(b.id, b);
29236
31695
  }
31696
+ if (field) out.push(...fieldShapes(field, width, height));
31697
+ for (const obj of sceneObjects) out.push(...sceneObjectShapes(obj, width, height));
31698
+ for (const trail of trails) out.push(...trailShapes(trail));
29237
31699
  for (const c of constraints) {
29238
31700
  const a = bodyById.get(c.from);
29239
31701
  const b = bodyById.get(c.to);
29240
31702
  if (!a || !b) continue;
29241
- out.push({
29242
- type: "line",
29243
- x1: a.x,
29244
- y1: a.y,
29245
- x2: b.x,
29246
- y2: b.y,
29247
- color: c.color ?? "#9ca3af",
29248
- lineWidth: 2
29249
- });
31703
+ out.push(...constraintShapes(c, a, b));
29250
31704
  }
29251
31705
  for (const b of bodies) {
29252
31706
  out.push({
@@ -29291,9 +31745,181 @@ var init_PhysicsCanvas = __esm({
29291
31745
  });
29292
31746
  }
29293
31747
  }
31748
+ for (const v of vectors) out.push(...vectorShapes(v, bodyById));
31749
+ for (const a of angles) out.push(...angleMarkerShapes(a));
31750
+ if (meters.length > 0) out.push(...meterShapes(meters, height));
29294
31751
  out.push(...shapes);
29295
31752
  return out;
29296
- }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
31753
+ }, [
31754
+ bodies,
31755
+ constraints,
31756
+ showVelocity,
31757
+ showForces,
31758
+ velocityScale,
31759
+ forceScale,
31760
+ sceneObjects,
31761
+ trails,
31762
+ vectors,
31763
+ angles,
31764
+ field,
31765
+ meters,
31766
+ shapes,
31767
+ width,
31768
+ height
31769
+ ]);
31770
+ const drawables3D = useMemo(() => {
31771
+ if (mode !== "3d") return [];
31772
+ if (shapes.length > 0) {
31773
+ physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
31774
+ }
31775
+ if (sceneObjects.length > 0) {
31776
+ physicsLog2.debug("sceneObjects ignored in 3D mode (pixel-authored 2D vocabulary)", { count: sceneObjects.length });
31777
+ }
31778
+ if (vectors.length > 0) {
31779
+ physicsLog2.debug("vectors ignored in 3D mode (pixel-authored 2D vocabulary)", { count: vectors.length });
31780
+ }
31781
+ if (angles.length > 0) {
31782
+ physicsLog2.debug("angles ignored in 3D mode (pixel-authored 2D vocabulary)", { count: angles.length });
31783
+ }
31784
+ if (field) {
31785
+ physicsLog2.debug("field ignored in 3D mode (pixel-authored 2D vocabulary)");
31786
+ }
31787
+ if (meters.length > 0) {
31788
+ physicsLog2.debug("meters ignored in 3D mode (pixel-authored 2D vocabulary)", { count: meters.length });
31789
+ }
31790
+ if (animate) {
31791
+ physicsLog2.debug("animate ignored in 3D mode (motion is entity-state driven)");
31792
+ }
31793
+ const out = [];
31794
+ const labelColor = labelColorForBackground(backgroundColor);
31795
+ const bodyById = /* @__PURE__ */ new Map();
31796
+ for (const b of bodies) {
31797
+ if (b.id) bodyById.set(b.id, b);
31798
+ }
31799
+ for (const c of constraints) {
31800
+ const a = bodyById.get(c.from);
31801
+ const b = bodyById.get(c.to);
31802
+ if (!a || !b) continue;
31803
+ const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
31804
+ const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
31805
+ if (rod) out.push(rod);
31806
+ }
31807
+ for (const b of bodies) {
31808
+ const radius = b.radius ?? 0.5;
31809
+ const bz = b.z ?? 0;
31810
+ out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
31811
+ if (b.label) {
31812
+ out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
31813
+ }
31814
+ const vx = b.vx ?? 0;
31815
+ const vy = b.vy ?? 0;
31816
+ const vz = b.vz ?? 0;
31817
+ const arrowRadius = Math.max(0.05, radius * 0.15);
31818
+ if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
31819
+ const arrow = arrowBetween(
31820
+ [b.x, b.y, bz],
31821
+ [b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
31822
+ "#16a34a",
31823
+ arrowRadius
31824
+ );
31825
+ if (arrow) out.push(arrow);
31826
+ }
31827
+ const fx = b.fx ?? 0;
31828
+ const fy = b.fy ?? 0;
31829
+ const fz = b.fz ?? 0;
31830
+ if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
31831
+ const arrow = arrowBetween(
31832
+ [b.x, b.y, bz],
31833
+ [b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
31834
+ "#dc2626",
31835
+ arrowRadius
31836
+ );
31837
+ if (arrow) out.push(arrow);
31838
+ }
31839
+ }
31840
+ for (const trail of trails) {
31841
+ if (trail.fade !== void 0) {
31842
+ physicsLog2.debug("trail.fade ignored in 3D mode (2D-only fade curve \u2014 3D draws an opaque tube)", { id: trail.id });
31843
+ }
31844
+ const points = trail.points.map((p) => [p.x, p.y, p.z ?? 0]);
31845
+ out.push(
31846
+ ...polylineTube(points, trail.width ?? 0.05, trail.color ?? "#94a3b8", {
31847
+ ...trail.opacity !== void 0 ? { opacity: trail.opacity } : {}
31848
+ })
31849
+ );
31850
+ }
31851
+ if (surface3d) {
31852
+ out.push(...heightFieldMesh(surface3d));
31853
+ }
31854
+ if (vectors3d.length > 0) {
31855
+ out.push(
31856
+ ...arrowField(
31857
+ vectors3d.map((v) => ({
31858
+ id: v.id,
31859
+ from: [v.x, v.y, v.z ?? 0],
31860
+ delta: [v.dx, v.dy, v.dz ?? 0],
31861
+ color: v.color,
31862
+ label: v.label,
31863
+ width: v.width
31864
+ })),
31865
+ { scale: vectorScale, labelColor }
31866
+ )
31867
+ );
31868
+ }
31869
+ return out;
31870
+ }, [
31871
+ mode,
31872
+ bodies,
31873
+ constraints,
31874
+ showVelocity,
31875
+ showForces,
31876
+ velocityScale,
31877
+ forceScale,
31878
+ shapes,
31879
+ sceneObjects,
31880
+ trails,
31881
+ vectors,
31882
+ surface3d,
31883
+ vectors3d,
31884
+ vectorScale,
31885
+ angles,
31886
+ field,
31887
+ meters,
31888
+ animate,
31889
+ backgroundColor
31890
+ ]);
31891
+ const bodyIndexById = useMemo(() => {
31892
+ const m = /* @__PURE__ */ new Map();
31893
+ vectors3d.forEach((v, i) => {
31894
+ if (v.id) m.set(v.id, i);
31895
+ });
31896
+ bodies.forEach((b, i) => {
31897
+ if (b.id) m.set(b.id, i);
31898
+ });
31899
+ return m;
31900
+ }, [bodies, vectors3d]);
31901
+ if (mode === "3d") {
31902
+ return /* @__PURE__ */ jsx(
31903
+ LearningScene3D,
31904
+ {
31905
+ className,
31906
+ width,
31907
+ height,
31908
+ title,
31909
+ backgroundColor,
31910
+ drawables: drawables3D,
31911
+ camera,
31912
+ lighting,
31913
+ post,
31914
+ showGrid,
31915
+ shadows,
31916
+ interactive,
31917
+ isLoading,
31918
+ error,
31919
+ onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
31920
+ }
31921
+ );
31922
+ }
29297
31923
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
29298
31924
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
29299
31925
  /* @__PURE__ */ jsx(
@@ -29303,7 +31929,9 @@ var init_PhysicsCanvas = __esm({
29303
31929
  height,
29304
31930
  backgroundColor,
29305
31931
  shapes: derivedShapes,
29306
- interactive,
31932
+ readouts,
31933
+ traces,
31934
+ interactive: interactive ?? false,
29307
31935
  animate,
29308
31936
  onShapeClick,
29309
31937
  isLoading,
@@ -29453,7 +32081,7 @@ function layoutFlow(nodeIds, adjacency, roots, width, height, margin) {
29453
32081
  }
29454
32082
  return nodeIds.map((id) => positions.get(id));
29455
32083
  }
29456
- function layoutTree(nodeIds, adjacency, roots, width, height, margin) {
32084
+ function layoutTree2(nodeIds, adjacency, roots, width, height, margin) {
29457
32085
  const effectiveRoots = roots.length > 0 ? roots : [nodeIds[0]];
29458
32086
  const layers = assignLayers(nodeIds, adjacency, effectiveRoots);
29459
32087
  const maxLayer = Math.max(...Array.from(layers.values()));
@@ -29509,7 +32137,7 @@ function computeStaticLayout(mode, input) {
29509
32137
  const adjacency = buildAdjacency(nodeIds, edges);
29510
32138
  const roots = findRoots(nodeIds, adjacency);
29511
32139
  if (mode === "flow") return layoutFlow(nodeIds, adjacency, roots, width, height, margin);
29512
- if (mode === "tree") return layoutTree(nodeIds, adjacency, roots, width, height, margin);
32140
+ if (mode === "tree") return layoutTree2(nodeIds, adjacency, roots, width, height, margin);
29513
32141
  return layoutRadial(nodeIds, adjacency, roots, width, height, margin);
29514
32142
  }
29515
32143
  var init_graphViewLayouts = __esm({
@@ -29921,12 +32549,12 @@ var init_MapView = __esm({
29921
32549
  shadowSize: [41, 41]
29922
32550
  });
29923
32551
  L.Marker.prototype.options.icon = defaultIcon;
29924
- const { useEffect: useEffect66, useRef: useRef64, useCallback: useCallback107, useState: useState104 } = React77__default;
32552
+ const { useEffect: useEffect66, useRef: useRef65, useCallback: useCallback108, useState: useState104 } = React77__default;
29925
32553
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29926
32554
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29927
32555
  function MapUpdater({ centerLat, centerLng, zoom }) {
29928
32556
  const map = useMap();
29929
- const prevRef = useRef64({ centerLat, centerLng, zoom });
32557
+ const prevRef = useRef65({ centerLat, centerLng, zoom });
29930
32558
  useEffect66(() => {
29931
32559
  const prev = prevRef.current;
29932
32560
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -29967,7 +32595,7 @@ var init_MapView = __esm({
29967
32595
  }) {
29968
32596
  const eventBus = useEventBus2();
29969
32597
  const [clickedPosition, setClickedPosition] = useState104(null);
29970
- const handleMapClick = useCallback107((lat, lng) => {
32598
+ const handleMapClick = useCallback108((lat, lng) => {
29971
32599
  if (showClickedPin) {
29972
32600
  setClickedPosition({ lat, lng });
29973
32601
  }
@@ -29976,7 +32604,7 @@ var init_MapView = __esm({
29976
32604
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
29977
32605
  }
29978
32606
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
29979
- const handleMarkerClick = useCallback107((marker) => {
32607
+ const handleMarkerClick = useCallback108((marker) => {
29980
32608
  onMarkerClick?.(marker);
29981
32609
  if (markerClickEvent) {
29982
32610
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -30752,26 +33380,6 @@ var init_Lightbox = __esm({
30752
33380
  Lightbox.displayName = "Lightbox";
30753
33381
  }
30754
33382
  });
30755
- function useMediaQuery(query) {
30756
- const subscribe = useCallback(
30757
- (onChange) => {
30758
- const mql = window.matchMedia(query);
30759
- mql.addEventListener("change", onChange);
30760
- return () => mql.removeEventListener("change", onChange);
30761
- },
30762
- [query]
30763
- );
30764
- return useSyncExternalStore(
30765
- subscribe,
30766
- () => window.matchMedia(query).matches,
30767
- () => false
30768
- );
30769
- }
30770
- var init_useMediaQuery = __esm({
30771
- "hooks/useMediaQuery.ts"() {
30772
- "use client";
30773
- }
30774
- });
30775
33383
  function renderIconInput3(icon, props) {
30776
33384
  return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
30777
33385
  }
@@ -30810,8 +33418,8 @@ function TableView({
30810
33418
  columns,
30811
33419
  fields,
30812
33420
  itemActions,
30813
- maxInlineActions,
30814
- itemClickEvent,
33421
+ maxInlineActions: _maxInlineActions,
33422
+ itemClickEvent = "",
30815
33423
  selectable = false,
30816
33424
  selectEvent,
30817
33425
  selectedIds,
@@ -30861,7 +33469,6 @@ function TableView({
30861
33469
  const hasMore = pageSize > 0 && visibleCount < ordered2.length;
30862
33470
  const hasRenderProp = typeof children === "function";
30863
33471
  const idField = dndItemIdField ?? "id";
30864
- const isCoarsePointer = useMediaQuery("(pointer: coarse)");
30865
33472
  React77__default.useEffect(() => {
30866
33473
  tableViewLog.debug("render", {
30867
33474
  rowCount: data.length,
@@ -30903,21 +33510,14 @@ function TableView({
30903
33510
  const dir = sortColumn === (col.field ?? col.key) && sortDirection === "asc" ? "desc" : "asc";
30904
33511
  eventBus.emit(`UI:${sortEvent}`, { column: col.field ?? col.key, direction: dir });
30905
33512
  };
30906
- const handleActionClick = (action, row) => (e) => {
30907
- e.stopPropagation();
30908
- const payload = {
30909
- id: row.id,
30910
- row
30911
- };
30912
- eventBus.emit(`UI:${action.event}`, payload);
30913
- };
33513
+ const rowClickEvent = itemClickEvent || actionDefs.find((a) => a.variant !== "danger")?.event;
30914
33514
  const handleRowClick = (row) => () => {
30915
- if (!itemClickEvent) return;
33515
+ if (!rowClickEvent) return;
30916
33516
  const payload = {
30917
33517
  id: row.id,
30918
33518
  row
30919
33519
  };
30920
- eventBus.emit(`UI:${itemClickEvent}`, payload);
33520
+ eventBus.emit(`UI:${rowClickEvent}`, payload);
30921
33521
  };
30922
33522
  const colFloors = React77__default.useMemo(
30923
33523
  () => colDefs.map((col) => {
@@ -30933,10 +33533,7 @@ function TableView({
30933
33533
  const statusNode = isLoading ? /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) }) : error ? /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "error", children: error.message }) }) : data.length === 0 ? /* @__PURE__ */ jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) }) : null;
30934
33534
  const lk = LOOKS[look];
30935
33535
  const hasActions = actionDefs.length > 0;
30936
- const effectiveMaxInline = isCoarsePointer ? 0 : maxInlineActions;
30937
- const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
30938
- const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
30939
- const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
33536
+ const actionsTrack = hasActions ? "3rem" : null;
30940
33537
  const gridTemplateColumns = [
30941
33538
  selectable ? "auto" : null,
30942
33539
  ...colDefs.map((c, i) => c.width ?? `minmax(${colFloors[i]}ch, 1fr)`),
@@ -30983,7 +33580,7 @@ function TableView({
30983
33580
  col.key
30984
33581
  );
30985
33582
  }),
30986
- hasActions && /* @__PURE__ */ jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)]" })
33583
+ hasActions && /* @__PURE__ */ jsx(Box, { "aria-hidden": true, className: "sticky right-0 bg-[var(--color-surface-subtle)] border-l border-[var(--color-border)] h-full" })
30987
33584
  ]
30988
33585
  }
30989
33586
  );
@@ -30995,12 +33592,12 @@ function TableView({
30995
33592
  role: "row",
30996
33593
  "data-entity-row": true,
30997
33594
  "data-entity-id": id,
30998
- onClick: itemClickEvent ? handleRowClick(row) : void 0,
33595
+ onClick: rowClickEvent ? handleRowClick(row) : void 0,
30999
33596
  style: !hasRenderProp ? { gridTemplateColumns } : void 0,
31000
33597
  className: cn(
31001
33598
  "group items-center gap-3 transition-colors duration-fast",
31002
33599
  hasRenderProp ? "flex" : "grid",
31003
- itemClickEvent && "cursor-pointer",
33600
+ rowClickEvent && "cursor-pointer",
31004
33601
  lk.rowPad,
31005
33602
  lk.divider && "border-b border-[var(--color-border)]",
31006
33603
  lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
@@ -31008,7 +33605,7 @@ function TableView({
31008
33605
  look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
31009
33606
  ),
31010
33607
  children: [
31011
- selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", onClick: itemClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsx(
33608
+ selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsx(
31012
33609
  Checkbox,
31013
33610
  {
31014
33611
  checked: selected.has(id),
@@ -31029,53 +33626,37 @@ function TableView({
31029
33626
  }
31030
33627
  return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
31031
33628
  }),
31032
- hasActions && /* @__PURE__ */ jsxs(
33629
+ hasActions && /* @__PURE__ */ jsx(
31033
33630
  HStack,
31034
33631
  {
31035
33632
  gap: "xs",
31036
- onClick: itemClickEvent ? (e) => e.stopPropagation() : void 0,
33633
+ onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0,
31037
33634
  className: cn(
31038
33635
  // Pinned: the fixed column tracks routinely overflow the caller's
31039
- // scroll container, which used to leave the actions off-screen.
31040
- // Opaque so scrolled cells pass underneath it.
33636
+ // scroll container, which would leave the kebab off-screen.
33637
+ // Opaque + hairline edge so it reads as a pinned column, not a
33638
+ // floating control, while scrolled cells pass underneath.
31041
33639
  "justify-end flex-shrink-0 sticky right-0 z-[1] transition-colors",
33640
+ "border-l border-[var(--color-border)]",
31042
33641
  lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
31043
33642
  ),
31044
- children: [
31045
- (effectiveMaxInline != null ? actionDefs.slice(0, effectiveMaxInline) : actionDefs).map((action, i) => /* @__PURE__ */ jsxs(
31046
- Button,
31047
- {
31048
- variant: action.variant === "primary" ? "primary" : "ghost",
31049
- size: "sm",
31050
- onClick: handleActionClick(action, row),
31051
- "data-testid": `action-${action.event}`,
31052
- "data-row-id": String(row.id),
31053
- className: cn(action.variant === "danger" && "text-error hover:text-error hover:bg-error/10"),
31054
- children: [
31055
- action.icon && renderIconInput3(action.icon, { size: "xs", className: "mr-1" }),
31056
- action.label
31057
- ]
31058
- },
31059
- i
31060
- )),
31061
- effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsx(
31062
- Menu,
31063
- {
31064
- position: "bottom-end",
31065
- trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
31066
- items: actionDefs.slice(effectiveMaxInline).map((action) => ({
31067
- label: action.label,
31068
- icon: action.icon,
31069
- event: action.event,
31070
- variant: action.variant === "danger" ? "danger" : "default",
31071
- onClick: () => eventBus.emit(`UI:${action.event}`, {
31072
- id: row.id,
31073
- row
31074
- })
31075
- }))
31076
- }
31077
- )
31078
- ]
33643
+ children: /* @__PURE__ */ jsx(
33644
+ Menu,
33645
+ {
33646
+ position: "bottom-end",
33647
+ trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", "data-row-id": String(row.id), children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
33648
+ items: actionDefs.map((action) => ({
33649
+ label: action.label,
33650
+ icon: action.icon,
33651
+ event: action.event,
33652
+ variant: action.variant === "danger" ? "danger" : "default",
33653
+ onClick: () => eventBus.emit(`UI:${action.event}`, {
33654
+ id: row.id,
33655
+ row
33656
+ })
33657
+ }))
33658
+ }
33659
+ )
31079
33660
  }
31080
33661
  )
31081
33662
  ]
@@ -31118,7 +33699,6 @@ var init_TableView = __esm({
31118
33699
  init_format();
31119
33700
  init_getNestedValue();
31120
33701
  init_useEventBus();
31121
- init_useMediaQuery();
31122
33702
  init_Box();
31123
33703
  init_Stack();
31124
33704
  init_Typography();
@@ -39718,6 +42298,8 @@ var init_molecules2 = __esm({
39718
42298
  init_BiologyCanvas();
39719
42299
  init_ChemistryCanvas();
39720
42300
  init_AlgorithmCanvas();
42301
+ init_AlgoGraphCanvas();
42302
+ init_learningScene3D();
39721
42303
  init_GraphView();
39722
42304
  init_MapView();
39723
42305
  init_NumberStepper();
@@ -40971,7 +43553,7 @@ function getEnumOptions(field) {
40971
43553
  }));
40972
43554
  }
40973
43555
  const validation = field.validation;
40974
- if (validation?.enum && Array.isArray(validation.enum)) {
43556
+ if (validation?.enum && validation.enum.length > 0) {
40975
43557
  return validation.enum.map((v) => ({
40976
43558
  value: v,
40977
43559
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")
@@ -46399,6 +48981,7 @@ var init_component_registry_generated = __esm({
46399
48981
  init_ActionTile();
46400
48982
  init_ActivationBlock();
46401
48983
  init_ComponentPatterns();
48984
+ init_AlgoGraphCanvas();
46402
48985
  init_AlgorithmCanvas();
46403
48986
  init_AnimatedCounter();
46404
48987
  init_AnimatedGraphic();
@@ -46662,6 +49245,7 @@ var init_component_registry_generated = __esm({
46662
49245
  "ActivationBlock": ActivationBlock,
46663
49246
  "Alert": AlertPattern,
46664
49247
  "AlertPattern": AlertPattern,
49248
+ "AlgoGraphCanvas": AlgoGraphCanvas,
46665
49249
  "AlgorithmCanvas": AlgorithmCanvas,
46666
49250
  "AnimatedCounter": AnimatedCounter,
46667
49251
  "AnimatedGraphic": AnimatedGraphic,
@@ -50134,7 +52718,23 @@ init_useAuthContext();
50134
52718
  init_useSwipeGesture();
50135
52719
  init_useLongPress();
50136
52720
  init_useDragReorder();
50137
- init_useMediaQuery();
52721
+ function useMediaQuery(query) {
52722
+ const subscribe = useCallback(
52723
+ (onChange) => {
52724
+ const mql = window.matchMedia(query);
52725
+ mql.addEventListener("change", onChange);
52726
+ return () => mql.removeEventListener("change", onChange);
52727
+ },
52728
+ [query]
52729
+ );
52730
+ return useSyncExternalStore(
52731
+ subscribe,
52732
+ () => window.matchMedia(query).matches,
52733
+ () => false
52734
+ );
52735
+ }
52736
+
52737
+ // hooks/index.ts
50138
52738
  init_useInfiniteScroll();
50139
52739
  init_usePullToRefresh();
50140
52740
  init_useCanvasGestures();
@@ -50380,4 +52980,4 @@ function useGitHubBranches(owner, repo, enabled = true) {
50380
52980
  });
50381
52981
  }
50382
52982
 
50383
- export { ALL_PRESETS, ALMADAR_DND_MIME, AR_BOOK_FIELDS, AboutPageTemplate, Accordion, Card2 as ActionCard, ActionPalette, ActionTile, ActivationBlock, Alert, AlgorithmCanvas, AnimatedCounter, AnimatedGraphic, AnimatedReveal, ArticleSection, Aside, AssetPicker, AtlasImage, AtlasPanel, AuthLayout, Avatar, Badge, BehaviorView, BiologyCanvas, BloomQuizBlock, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, Box, BranchingLogicBuilder, Breadcrumb, Button, ButtonGroup, CTABanner, CalendarGrid, Canvas, Canvas2D, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, CaseStudyCard, CaseStudyOrganism, Center, Chart, ChartLegend, ChatBar, Checkbox, ChemistryCanvas, ChoiceButton, Coachmark, CodeBlock, CodeRunnerPanel, CollapsibleSection, CommunityLinks, ConditionalWrapper, ConfettiEffect, ConfirmDialog, ConnectionBlock, Container, ContentRenderer, ContentSection, ControlButton, ControlGrid, CounterTemplate, DEFAULT_LIKERT_OPTIONS, DEFAULT_MATRIX_COLUMNS, DEFAULT_SLOTS, DIAMOND_TOP_Y, DashboardGrid, DashboardLayout, DataGrid, DataList, DataTable, DateRangePicker, DateRangeSelector, DayCell, DetailPanel, Dialog, DialogueBubble, Divider, DocBreadcrumb, DocPagination, DocSearch, DocSidebar, DocTOC, DocumentViewer, StateMachineView as DomStateMachineVisualizer, Drawer, DrawerSlot, ELEMENT_SELECTED_EVENT, EdgeDecoration, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmojiPicker, EmptyState, EntityDisplayEvents, ErrorBoundary, ErrorState, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FeatureCard, FeatureDetailPageTemplate, FeatureGrid, FeatureGridOrganism, FileTree, FilterGroup, FilterPill, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, GameAudioToggle, GameHud, GameIcon, GameMenu, GameShell, GenericAppTemplate, GeometricPattern, GradientDivider, GraphCanvas, GraphView, Grid, GridPicker, HStack, Header, HealthBar, HeroOrganism, HeroSection, I18nProvider, IDENTITY_BOOK_FIELDS, Icon, IconPicker, ImportPreviewTree, ImportProgress, ImportSourcePicker, InfiniteScrollSentinel, Input, InputGroup, InstallBox, JazariStateMachine, JsonTreeEditor, Label, LandingPageTemplate, LawReferenceTooltip, LearningCanvas, Lightbox, LikertScale, LineChart2 as LineChart, List3 as List, LoadingState, MapView, MarkdownContent, MarketingFooter, MarketingStatCard, MasterDetail, MasterDetailLayout, MathCanvas, MatrixQuestion, MediaGallery, Menu, Meter, Modal, ModalSlot, ModuleCard, Navigation, NodeSlotEditor, NotifyListener, NumberStepper, OnboardingSpotlight, OptionConstraintGroup, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, Overlay, PageHeader, PageTransition, Pagination, PatternTile, PhysicsCanvas, Popover, PositionedCanvas, Presence, PricingCard, PricingGrid, PricingOrganism, PricingPageTemplate, ProgressBar, ProgressDots, PropertyInspector, PullQuote, PullToRefresh, QrScanner, QuizBlock, Radio, RangeSlider, ReflectionBlock, RelationSelect, RepeatableFormSection, ReplyTree, RichBlockEditor, RuntimeDebugger, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScaledDiagram, ScoreDisplay, SearchInput, Section, SectionHeader, SegmentRenderer, Select, SequenceBar, ServiceCatalog, SharedEntityStoreContext, ShowcaseCard, ShowcaseOrganism, SidePanel, Sidebar, SignaturePad, SimpleGrid, Skeleton, SlotContentRenderer, SocialProof, SortableList, Spacer, Sparkline, Spinner, Split, SplitPane, SplitSection, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateGraph, StateJsonView, StateMachineView, StateNode2 as StateNode, StatsGrid, StatsOrganism, StatusBar, StatusDot, StepFlow, StepFlowOrganism, SubagentTracePanel, SvgBranch, SvgConnection, SvgFlow, SvgGrid, SvgLobe, SvgMesh, SvgMorph, SvgNode, SvgPulse, SvgRing, SvgShield, SvgStack, SwipeableRow, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, TableView, Tabs, TagCloud, TagInput, TeamCard, TeamOrganism, TerrainPalette, TextHighlight, Textarea, ThemeToggle, TimeSlotCell, Timeline, TimerDisplay, Toast, ToastSlot, Tooltip, TraitFrame, TraitSlot, TraitStateViewer, TransitionArrow, TrendIndicator, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UploadDropZone, VStack, VersionDiff, ViolationAlert, VoteStack, WizardContainer, WizardNavigation, WizardProgress, boardEntity, bool, calculateAttackTargets, calculateValidMoves, cn, createInitialGameState, createSharedEntityStore, createTranslate, createUnitAnimationState, getCurrentFrame, getTileDimensions, inferDirection, isoToScreen, makeAsset, makeAssetMap, mapBookData, num, objAvailableActions, objAvailableEvents, objCurrentState, objIcon, objId, objMaxRules, objName, objRules, objStates, parseEditFocus, parseLessonSegments, parseMarkdownWithCodeBlocks, parseQueryBinding, pendulum, projectileMotion, registerCodeLanguageLoader, resolveFieldMap, resolveFrame, resolveSheetDirection, rows, runTickFrame, screenToIso, springOscillator, str, tickAnimationState, toCodeLanguage, transitionAnimation, unitHealth, unitPosition, unitTeam, useAgentChat, useAnchorRect, useAtlasSliceDataUrl, useAuthContext, useCamera, useCanvasGestures, useCompile, useConnectGitHub, useDeepAgentGeneration, useDisconnectGitHub, useDragReorder, useDraggable, useDropZone, useEmitEvent, useEventBus, useEventListener, useExtensions, useFileEditor, useFileSystem, useGameAudio, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useImageCache, useInfiniteScroll, useLongPress, useMediaQuery, useOrbitalHistory, usePresence, usePreview, usePullToRefresh, useQuerySingleton, useRenderInterpolation, useSharedEntitySnapshot, useSharedEntityStore, useSharedEntityStoreContext, useSwipeGesture, useTapReveal, useTraitListens, useTranslate114 as useTranslate, useUIEvents, useUISlotManager, useUnitSpriteAtlas, useValidation, vec2 };
52983
+ export { ALL_PRESETS, ALMADAR_DND_MIME, AR_BOOK_FIELDS, AboutPageTemplate, Accordion, Card2 as ActionCard, ActionPalette, ActionTile, ActivationBlock, Alert, AlgoGraphCanvas, AlgorithmCanvas, AnimatedCounter, AnimatedGraphic, AnimatedReveal, ArticleSection, Aside, AssetPicker, AtlasImage, AtlasPanel, AuthLayout, Avatar, Badge, BehaviorView, BiologyCanvas, BloomQuizBlock, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, Box, BranchingLogicBuilder, Breadcrumb, Button, ButtonGroup, CTABanner, CalendarGrid, Canvas, Canvas2D, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, CaseStudyCard, CaseStudyOrganism, Center, Chart, ChartLegend, ChatBar, Checkbox, ChemistryCanvas, ChoiceButton, Coachmark, CodeBlock, CodeRunnerPanel, CollapsibleSection, CommunityLinks, ConditionalWrapper, ConfettiEffect, ConfirmDialog, ConnectionBlock, Container, ContentRenderer, ContentSection, ControlButton, ControlGrid, CounterTemplate, DEFAULT_LIKERT_OPTIONS, DEFAULT_MATRIX_COLUMNS, DEFAULT_SLOTS, DIAMOND_TOP_Y, DashboardGrid, DashboardLayout, DataGrid, DataList, DataTable, DateRangePicker, DateRangeSelector, DayCell, DetailPanel, Dialog, DialogueBubble, Divider, DocBreadcrumb, DocPagination, DocSearch, DocSidebar, DocTOC, DocumentViewer, StateMachineView as DomStateMachineVisualizer, Drawer, DrawerSlot, ELEMENT_SELECTED_EVENT, EdgeDecoration, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmojiPicker, EmptyState, EntityDisplayEvents, ErrorBoundary, ErrorState, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FeatureCard, FeatureDetailPageTemplate, FeatureGrid, FeatureGridOrganism, FileTree, FilterGroup, FilterPill, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, GameAudioToggle, GameHud, GameIcon, GameMenu, GameShell, GenericAppTemplate, GeometricPattern, GradientDivider, GraphCanvas, GraphView, Grid, GridPicker, HStack, Header, HealthBar, HeroOrganism, HeroSection, I18nProvider, IDENTITY_BOOK_FIELDS, Icon, IconPicker, ImportPreviewTree, ImportProgress, ImportSourcePicker, InfiniteScrollSentinel, Input, InputGroup, InstallBox, JazariStateMachine, JsonTreeEditor, Label, LandingPageTemplate, LawReferenceTooltip, LearningCanvas, LearningScene3D, Lightbox, LikertScale, LineChart2 as LineChart, List3 as List, LoadingState, MapView, MarkdownContent, MarketingFooter, MarketingStatCard, MasterDetail, MasterDetailLayout, MathCanvas, MatrixQuestion, MediaGallery, Menu, Meter, Modal, ModalSlot, ModuleCard, Navigation, NodeSlotEditor, NotifyListener, NumberStepper, OnboardingSpotlight, OptionConstraintGroup, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, Overlay, PageHeader, PageTransition, Pagination, PatternTile, PhysicsCanvas, Popover, PositionedCanvas, Presence, PricingCard, PricingGrid, PricingOrganism, PricingPageTemplate, ProgressBar, ProgressDots, PropertyInspector, PullQuote, PullToRefresh, QrScanner, QuizBlock, Radio, RangeSlider, ReflectionBlock, RelationSelect, RepeatableFormSection, ReplyTree, RichBlockEditor, RuntimeDebugger, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScaledDiagram, ScoreDisplay, SearchInput, Section, SectionHeader, SegmentRenderer, Select, SequenceBar, ServiceCatalog, SharedEntityStoreContext, ShowcaseCard, ShowcaseOrganism, SidePanel, Sidebar, SignaturePad, SimpleGrid, Skeleton, SlotContentRenderer, SocialProof, SortableList, Spacer, Sparkline, Spinner, Split, SplitPane, SplitSection, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateGraph, StateJsonView, StateMachineView, StateNode2 as StateNode, StatsGrid, StatsOrganism, StatusBar, StatusDot, StepFlow, StepFlowOrganism, SubagentTracePanel, SvgBranch, SvgConnection, SvgFlow, SvgGrid, SvgLobe, SvgMesh, SvgMorph, SvgNode, SvgPulse, SvgRing, SvgShield, SvgStack, SwipeableRow, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, TableView, Tabs, TagCloud, TagInput, TeamCard, TeamOrganism, TerrainPalette, TextHighlight, Textarea, ThemeToggle, TimeSlotCell, Timeline, TimerDisplay, Toast, ToastSlot, Tooltip, TraitFrame, TraitSlot, TraitStateViewer, TransitionArrow, TrendIndicator, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UploadDropZone, VStack, VersionDiff, ViolationAlert, VoteStack, WizardContainer, WizardNavigation, WizardProgress, arrowBetween, billboardLabel, boardEntity, bool, calculateAttackTargets, calculateValidMoves, cn, createInitialGameState, createSharedEntityStore, createTranslate, createUnitAnimationState, cylinderBetween, get3DClickPayload, getCurrentFrame, getTileDimensions, inferDirection, isoToScreen, makeAsset, makeAssetMap, mapBookData, meshSphere, num, objAvailableActions, objAvailableEvents, objCurrentState, objIcon, objId, objMaxRules, objName, objRules, objStates, parseEditFocus, parseLessonSegments, parseMarkdownWithCodeBlocks, parseQueryBinding, pendulum, projectileMotion, registerCodeLanguageLoader, resolveFieldMap, resolveFrame, resolveSheetDirection, rows, runTickFrame, screenToIso, springOscillator, str, tickAnimationState, toCodeLanguage, transitionAnimation, unitHealth, unitPosition, unitTeam, useAgentChat, useAnchorRect, useAtlasSliceDataUrl, useAuthContext, useCamera, useCanvasGestures, useCompile, useConnectGitHub, useDeepAgentGeneration, useDisconnectGitHub, useDragReorder, useDraggable, useDropZone, useEmitEvent, useEventBus, useEventListener, useExtensions, useFileEditor, useFileSystem, useGameAudio, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useImageCache, useInfiniteScroll, useLongPress, useMediaQuery, useOrbitalHistory, usePresence, usePreview, usePullToRefresh, useQuerySingleton, useRenderInterpolation, useSharedEntitySnapshot, useSharedEntityStore, useSharedEntityStoreContext, useSwipeGesture, useTapReveal, useTraitListens, useTranslate114 as useTranslate, useUIEvents, useUISlotManager, useUnitSpriteAtlas, useValidation, vec2 };