@almadar/ui 5.148.0 → 5.149.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 (29) 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 +438 -13
  4. package/dist/avl/index.js +438 -13
  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 +448 -16
  10. package/dist/components/index.d.cts +269 -95
  11. package/dist/components/index.d.ts +269 -95
  12. package/dist/components/index.js +444 -18
  13. package/dist/lib/drawable/three/index.cjs +56 -29
  14. package/dist/lib/drawable/three/index.d.cts +4 -3
  15. package/dist/lib/drawable/three/index.d.ts +4 -3
  16. package/dist/lib/drawable/three/index.js +56 -29
  17. package/dist/lib/index.d.cts +3 -2
  18. package/dist/lib/index.d.ts +3 -2
  19. package/dist/{paintDispatch-Cb_hQj4Y.d.ts → paintDispatch-BjZjUbcb.d.cts} +63 -69
  20. package/dist/{paintDispatch-Cb_hQj4Y.d.cts → paintDispatch-CxdLjHQq.d.ts} +63 -69
  21. package/dist/providers/index.cjs +438 -13
  22. package/dist/providers/index.d.cts +2 -1
  23. package/dist/providers/index.d.ts +2 -1
  24. package/dist/providers/index.js +438 -13
  25. package/dist/runtime/index.cjs +438 -13
  26. package/dist/runtime/index.js +438 -13
  27. package/dist/types-B3nHgnMG.d.cts +51 -0
  28. package/dist/types-B3nHgnMG.d.ts +51 -0
  29. package/package.json +3 -3
@@ -10729,9 +10729,10 @@ function Canvas({
10729
10729
  drawables: [...drawables ?? [], ...childDrawables],
10730
10730
  isLoading,
10731
10731
  cameraMode: to3DCameraMode(camera?.mode),
10732
- ...zoom !== void 0 ? { scale: zoom } : {},
10732
+ ...zoom !== void 0 ? { zoom } : {},
10733
10733
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
10734
10734
  ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
10735
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
10735
10736
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
10736
10737
  unitScale,
10737
10738
  backgroundColor,
@@ -14610,23 +14611,188 @@ var init_BehaviorView = __esm({
14610
14611
  BehaviorView.displayName = "BehaviorView";
14611
14612
  }
14612
14613
  });
14613
- var BiologyCanvas;
14614
+ function LearningScene3D({
14615
+ className,
14616
+ width = 600,
14617
+ height = 400,
14618
+ title,
14619
+ backgroundColor,
14620
+ drawables,
14621
+ camera,
14622
+ lighting,
14623
+ post,
14624
+ showGrid = false,
14625
+ shadows,
14626
+ interactive,
14627
+ isLoading,
14628
+ error,
14629
+ onItemClick
14630
+ }) {
14631
+ const instanceId = React85.useId().replace(/[^a-zA-Z0-9]/g, "");
14632
+ const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
14633
+ const onItemClickRef = React85.useRef(onItemClick);
14634
+ onItemClickRef.current = onItemClick;
14635
+ useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
14636
+ const unitId = event.payload?.unitId;
14637
+ if (typeof unitId === "string") onItemClickRef.current?.(unitId);
14638
+ });
14639
+ const props3d = {
14640
+ drawables,
14641
+ isLoading,
14642
+ error: error ? error.message : null,
14643
+ cameraMode: camera?.mode ?? "perspective",
14644
+ ...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
14645
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
14646
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
14647
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
14648
+ ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
14649
+ backgroundColor,
14650
+ showGrid,
14651
+ ...shadows !== void 0 ? { shadows } : {},
14652
+ ...interactive !== void 0 ? { controlsEnabled: interactive } : {},
14653
+ lighting,
14654
+ post,
14655
+ ...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
14656
+ };
14657
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
14658
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
14659
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsxRuntime.jsx(React85.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(Canvas3DHost2, { ...props3d }) }) })
14660
+ ] }) });
14661
+ }
14662
+ function meshSphere(id, x, y, z, radius, color, opts) {
14663
+ return {
14664
+ type: "draw-mesh",
14665
+ ...id !== void 0 ? { id } : {},
14666
+ shape: opts?.shape ?? "sphere",
14667
+ position: { x, y, z },
14668
+ radius,
14669
+ pivot: "center",
14670
+ segments: opts?.segments ?? 24,
14671
+ material: { color, ...opts?.material },
14672
+ ...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
14673
+ };
14674
+ }
14675
+ function axisRotation(from, to) {
14676
+ const wx = to[0] - from[0];
14677
+ const wy = to[2] - from[2];
14678
+ const wz = to[1] - from[1];
14679
+ const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
14680
+ const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
14681
+ return [0, Math.atan2(wz, -wx), tilt];
14682
+ }
14683
+ function segmentLength(from, to) {
14684
+ const dx = to[0] - from[0];
14685
+ const dy = to[1] - from[1];
14686
+ const dz = to[2] - from[2];
14687
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
14688
+ }
14689
+ function cylinderBetween(from, to, radius, color) {
14690
+ const len = segmentLength(from, to);
14691
+ if (len < 1e-6) return null;
14692
+ return {
14693
+ type: "draw-mesh",
14694
+ shape: "cylinder",
14695
+ position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
14696
+ radius,
14697
+ height: len,
14698
+ rotation: axisRotation(from, to),
14699
+ pivot: "center",
14700
+ segments: 12,
14701
+ material: { color }
14702
+ };
14703
+ }
14704
+ function arrowBetween(from, to, color, shaftRadius = 0.08) {
14705
+ const len = segmentLength(from, to);
14706
+ if (len < 1e-6) return null;
14707
+ const tipLen = Math.min(shaftRadius * 8, len * 0.35);
14708
+ const k = (len - tipLen) / len;
14709
+ const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
14710
+ const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
14711
+ const tipStart = shaftEnd;
14712
+ const tipEnd = delta;
14713
+ const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
14714
+ const tipLenActual = segmentLength(tipStart, tipEnd);
14715
+ const tip = {
14716
+ type: "draw-mesh",
14717
+ shape: "cone",
14718
+ position: {
14719
+ x: (tipStart[0] + tipEnd[0]) / 2,
14720
+ y: (tipStart[1] + tipEnd[1]) / 2,
14721
+ z: (tipStart[2] + tipEnd[2]) / 2
14722
+ },
14723
+ radius: shaftRadius * 3,
14724
+ height: tipLenActual,
14725
+ rotation: axisRotation(tipStart, tipEnd),
14726
+ pivot: "center",
14727
+ segments: 12,
14728
+ material: { color }
14729
+ };
14730
+ return {
14731
+ type: "draw-group",
14732
+ position: { x: from[0], y: from[1], z: from[2] },
14733
+ items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
14734
+ };
14735
+ }
14736
+ function billboardLabel(text, x, y, z, opts) {
14737
+ return {
14738
+ type: "draw-text",
14739
+ text,
14740
+ position: { x, y, z },
14741
+ color: opts?.color ?? "#111827"
14742
+ };
14743
+ }
14744
+ function labelColorForBackground(backgroundColor) {
14745
+ const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
14746
+ if (!m) return "#111827";
14747
+ const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
14748
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
14749
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
14750
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
14751
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
14752
+ return luminance < 0.5 ? "#e5e7eb" : "#111827";
14753
+ }
14754
+ function get3DClickPayload(onShapeClick, idToIndex) {
14755
+ if (!onShapeClick) return void 0;
14756
+ return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
14757
+ }
14758
+ var Canvas3DHost2;
14759
+ var init_learningScene3D = __esm({
14760
+ "components/learning/molecules/learningScene3D.tsx"() {
14761
+ "use client";
14762
+ init_atoms();
14763
+ init_Stack();
14764
+ init_useEventBus();
14765
+ Canvas3DHost2 = React85.lazy(
14766
+ () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
14767
+ );
14768
+ LearningScene3D.displayName = "LearningScene3D";
14769
+ }
14770
+ });
14771
+ var biologyLog, BiologyCanvas;
14614
14772
  var init_BiologyCanvas = __esm({
14615
14773
  "components/learning/molecules/BiologyCanvas.tsx"() {
14616
14774
  "use client";
14617
14775
  init_atoms();
14618
14776
  init_Stack();
14619
14777
  init_LearningCanvas();
14778
+ init_learningScene3D();
14779
+ biologyLog = logger.createLogger("almadar:ui:biology-canvas");
14620
14780
  BiologyCanvas = ({
14621
14781
  className,
14622
14782
  width = 600,
14623
14783
  height = 400,
14624
14784
  title,
14625
14785
  backgroundColor,
14786
+ mode = "2d",
14787
+ camera,
14788
+ lighting,
14789
+ post,
14626
14790
  nodes = [],
14627
14791
  edges = [],
14628
14792
  shapes = [],
14629
- interactive = false,
14793
+ showGrid,
14794
+ shadows,
14795
+ interactive,
14630
14796
  animate = false,
14631
14797
  onShapeClick,
14632
14798
  isLoading,
@@ -14687,6 +14853,75 @@ var init_BiologyCanvas = __esm({
14687
14853
  out.push(...shapes);
14688
14854
  return out;
14689
14855
  }, [nodes, edges, shapes]);
14856
+ const drawables3D = React85.useMemo(() => {
14857
+ if (mode !== "3d") return [];
14858
+ if (shapes.length > 0) {
14859
+ biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
14860
+ }
14861
+ const out = [];
14862
+ const labelColor = labelColorForBackground(backgroundColor);
14863
+ const nodeById = /* @__PURE__ */ new Map();
14864
+ for (const n of nodes) {
14865
+ if (n.id) nodeById.set(n.id, n);
14866
+ }
14867
+ for (const e of edges) {
14868
+ const a = nodeById.get(e.from);
14869
+ const b = nodeById.get(e.to);
14870
+ if (!a || !b) continue;
14871
+ const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
14872
+ const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
14873
+ if (edge) out.push(edge);
14874
+ if (e.label) {
14875
+ out.push(
14876
+ billboardLabel(
14877
+ e.label,
14878
+ (a.x + b.x) / 2,
14879
+ (a.y + b.y) / 2,
14880
+ ((a.z ?? 0) + (b.z ?? 0)) / 2,
14881
+ { color: labelColor }
14882
+ )
14883
+ );
14884
+ }
14885
+ }
14886
+ for (const n of nodes) {
14887
+ const radius = n.radius ?? 0.5;
14888
+ const nz = n.z ?? 0;
14889
+ out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
14890
+ if (n.label) {
14891
+ out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
14892
+ }
14893
+ }
14894
+ return out;
14895
+ }, [mode, nodes, edges, shapes, backgroundColor]);
14896
+ const nodeIndexById = React85.useMemo(() => {
14897
+ const m = /* @__PURE__ */ new Map();
14898
+ nodes.forEach((n, i) => {
14899
+ if (n.id) m.set(n.id, i);
14900
+ });
14901
+ return m;
14902
+ }, [nodes]);
14903
+ if (mode === "3d") {
14904
+ return /* @__PURE__ */ jsxRuntime.jsx(
14905
+ LearningScene3D,
14906
+ {
14907
+ className,
14908
+ width,
14909
+ height,
14910
+ title,
14911
+ backgroundColor,
14912
+ drawables: drawables3D,
14913
+ camera,
14914
+ lighting,
14915
+ post,
14916
+ showGrid,
14917
+ shadows,
14918
+ interactive,
14919
+ isLoading,
14920
+ error,
14921
+ onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
14922
+ }
14923
+ );
14924
+ }
14690
14925
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
14691
14926
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
14692
14927
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -14696,7 +14931,7 @@ var init_BiologyCanvas = __esm({
14696
14931
  height,
14697
14932
  backgroundColor,
14698
14933
  shapes: derivedShapes,
14699
- interactive,
14934
+ interactive: interactive ?? false,
14700
14935
  animate,
14701
14936
  onShapeClick,
14702
14937
  isLoading,
@@ -21419,24 +21654,41 @@ var init_ChatBar = __esm({
21419
21654
  ChatBar.displayName = "ChatBar";
21420
21655
  }
21421
21656
  });
21422
- var ChemistryCanvas;
21657
+ function bondPerpendicular(a, b) {
21658
+ const dx = b[0] - a[0];
21659
+ const dy = b[1] - a[1];
21660
+ const px = dy;
21661
+ const py = -dx;
21662
+ const len = Math.sqrt(px * px + py * py);
21663
+ if (len < 1e-6) return [1, 0, 0];
21664
+ return [px / len, py / len, 0];
21665
+ }
21666
+ var chemistryLog, ChemistryCanvas;
21423
21667
  var init_ChemistryCanvas = __esm({
21424
21668
  "components/learning/molecules/ChemistryCanvas.tsx"() {
21425
21669
  "use client";
21426
21670
  init_atoms();
21427
21671
  init_Stack();
21428
21672
  init_LearningCanvas();
21673
+ init_learningScene3D();
21674
+ chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
21429
21675
  ChemistryCanvas = ({
21430
21676
  className,
21431
21677
  width = 600,
21432
21678
  height = 400,
21433
21679
  title,
21434
21680
  backgroundColor,
21681
+ mode = "2d",
21682
+ camera,
21683
+ lighting,
21684
+ post,
21435
21685
  atoms = [],
21436
21686
  bonds = [],
21437
21687
  arrows = [],
21438
21688
  shapes = [],
21439
- interactive = false,
21689
+ showGrid,
21690
+ shadows,
21691
+ interactive,
21440
21692
  animate = false,
21441
21693
  onShapeClick,
21442
21694
  isLoading,
@@ -21515,6 +21767,88 @@ var init_ChemistryCanvas = __esm({
21515
21767
  out.push(...shapes);
21516
21768
  return out;
21517
21769
  }, [atoms, bonds, arrows, shapes]);
21770
+ const drawables3D = React85.useMemo(() => {
21771
+ if (mode !== "3d") return [];
21772
+ if (shapes.length > 0) {
21773
+ chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
21774
+ }
21775
+ const out = [];
21776
+ const labelColor = labelColorForBackground(backgroundColor);
21777
+ const atomById = /* @__PURE__ */ new Map();
21778
+ for (const a of atoms) {
21779
+ if (a.id) atomById.set(a.id, a);
21780
+ }
21781
+ for (const b of bonds) {
21782
+ const a = atomById.get(b.from);
21783
+ const c = atomById.get(b.to);
21784
+ if (!a || !c) continue;
21785
+ const color = b.color ?? "#6b7280";
21786
+ const from = [a.x, a.y, a.z ?? 0];
21787
+ const to = [c.x, c.y, c.z ?? 0];
21788
+ const perp = bondPerpendicular(from, to);
21789
+ const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
21790
+ const step = bondRadius * 2.2;
21791
+ const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
21792
+ for (const off of offsets) {
21793
+ const bond = cylinderBetween(
21794
+ [from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
21795
+ [to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
21796
+ bondRadius,
21797
+ color
21798
+ );
21799
+ if (bond) out.push(bond);
21800
+ }
21801
+ }
21802
+ for (const a of arrows) {
21803
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
21804
+ const len = a.length ?? 60;
21805
+ const x2 = a.x + Math.cos(angle) * len;
21806
+ const y2 = a.y + Math.sin(angle) * len;
21807
+ const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
21808
+ if (arrow) out.push(arrow);
21809
+ if (a.label) {
21810
+ out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
21811
+ }
21812
+ }
21813
+ for (const a of atoms) {
21814
+ const radius = a.radius ?? 0.45;
21815
+ const az = a.z ?? 0;
21816
+ out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
21817
+ if (a.element) {
21818
+ out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
21819
+ }
21820
+ }
21821
+ return out;
21822
+ }, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
21823
+ const atomIndexById = React85.useMemo(() => {
21824
+ const m = /* @__PURE__ */ new Map();
21825
+ atoms.forEach((a, i) => {
21826
+ if (a.id) m.set(a.id, i);
21827
+ });
21828
+ return m;
21829
+ }, [atoms]);
21830
+ if (mode === "3d") {
21831
+ return /* @__PURE__ */ jsxRuntime.jsx(
21832
+ LearningScene3D,
21833
+ {
21834
+ className,
21835
+ width,
21836
+ height,
21837
+ title,
21838
+ backgroundColor,
21839
+ drawables: drawables3D,
21840
+ camera,
21841
+ lighting,
21842
+ post,
21843
+ showGrid,
21844
+ shadows,
21845
+ interactive,
21846
+ isLoading,
21847
+ error,
21848
+ onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
21849
+ }
21850
+ );
21851
+ }
21518
21852
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
21519
21853
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
21520
21854
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -21524,7 +21858,7 @@ var init_ChemistryCanvas = __esm({
21524
21858
  height,
21525
21859
  backgroundColor,
21526
21860
  shapes: derivedShapes,
21527
- interactive,
21861
+ interactive: interactive ?? false,
21528
21862
  animate,
21529
21863
  onShapeClick,
21530
21864
  isLoading,
@@ -27751,19 +28085,25 @@ var init_MathCanvas = __esm({
27751
28085
  };
27752
28086
  }
27753
28087
  });
27754
- var PhysicsCanvas;
28088
+ var physicsLog2, PhysicsCanvas;
27755
28089
  var init_PhysicsCanvas = __esm({
27756
28090
  "components/learning/molecules/PhysicsCanvas.tsx"() {
27757
28091
  "use client";
27758
28092
  init_atoms();
27759
28093
  init_Stack();
27760
28094
  init_LearningCanvas();
28095
+ init_learningScene3D();
28096
+ physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
27761
28097
  PhysicsCanvas = ({
27762
28098
  className,
27763
28099
  width = 600,
27764
28100
  height = 400,
27765
28101
  title,
27766
28102
  backgroundColor,
28103
+ mode = "2d",
28104
+ camera,
28105
+ lighting,
28106
+ post,
27767
28107
  bodies = [],
27768
28108
  constraints = [],
27769
28109
  showVelocity = true,
@@ -27771,7 +28111,9 @@ var init_PhysicsCanvas = __esm({
27771
28111
  velocityScale = 20,
27772
28112
  forceScale = 20,
27773
28113
  shapes = [],
27774
- interactive = false,
28114
+ showGrid,
28115
+ shadows,
28116
+ interactive,
27775
28117
  animate = false,
27776
28118
  onShapeClick,
27777
28119
  isLoading,
@@ -27843,6 +28185,89 @@ var init_PhysicsCanvas = __esm({
27843
28185
  out.push(...shapes);
27844
28186
  return out;
27845
28187
  }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
28188
+ const drawables3D = React85.useMemo(() => {
28189
+ if (mode !== "3d") return [];
28190
+ if (shapes.length > 0) {
28191
+ physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
28192
+ }
28193
+ const out = [];
28194
+ const labelColor = labelColorForBackground(backgroundColor);
28195
+ const bodyById = /* @__PURE__ */ new Map();
28196
+ for (const b of bodies) {
28197
+ if (b.id) bodyById.set(b.id, b);
28198
+ }
28199
+ for (const c of constraints) {
28200
+ const a = bodyById.get(c.from);
28201
+ const b = bodyById.get(c.to);
28202
+ if (!a || !b) continue;
28203
+ const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
28204
+ const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
28205
+ if (rod) out.push(rod);
28206
+ }
28207
+ for (const b of bodies) {
28208
+ const radius = b.radius ?? 0.5;
28209
+ const bz = b.z ?? 0;
28210
+ out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
28211
+ if (b.label) {
28212
+ out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
28213
+ }
28214
+ const vx = b.vx ?? 0;
28215
+ const vy = b.vy ?? 0;
28216
+ const vz = b.vz ?? 0;
28217
+ const arrowRadius = Math.max(0.05, radius * 0.15);
28218
+ if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
28219
+ const arrow = arrowBetween(
28220
+ [b.x, b.y, bz],
28221
+ [b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
28222
+ "#16a34a",
28223
+ arrowRadius
28224
+ );
28225
+ if (arrow) out.push(arrow);
28226
+ }
28227
+ const fx = b.fx ?? 0;
28228
+ const fy = b.fy ?? 0;
28229
+ const fz = b.fz ?? 0;
28230
+ if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
28231
+ const arrow = arrowBetween(
28232
+ [b.x, b.y, bz],
28233
+ [b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
28234
+ "#dc2626",
28235
+ arrowRadius
28236
+ );
28237
+ if (arrow) out.push(arrow);
28238
+ }
28239
+ }
28240
+ return out;
28241
+ }, [mode, bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes, backgroundColor]);
28242
+ const bodyIndexById = React85.useMemo(() => {
28243
+ const m = /* @__PURE__ */ new Map();
28244
+ bodies.forEach((b, i) => {
28245
+ if (b.id) m.set(b.id, i);
28246
+ });
28247
+ return m;
28248
+ }, [bodies]);
28249
+ if (mode === "3d") {
28250
+ return /* @__PURE__ */ jsxRuntime.jsx(
28251
+ LearningScene3D,
28252
+ {
28253
+ className,
28254
+ width,
28255
+ height,
28256
+ title,
28257
+ backgroundColor,
28258
+ drawables: drawables3D,
28259
+ camera,
28260
+ lighting,
28261
+ post,
28262
+ showGrid,
28263
+ shadows,
28264
+ interactive,
28265
+ isLoading,
28266
+ error,
28267
+ onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
28268
+ }
28269
+ );
28270
+ }
27846
28271
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
27847
28272
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
27848
28273
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -27852,7 +28277,7 @@ var init_PhysicsCanvas = __esm({
27852
28277
  height,
27853
28278
  backgroundColor,
27854
28279
  shapes: derivedShapes,
27855
- interactive,
28280
+ interactive: interactive ?? false,
27856
28281
  animate,
27857
28282
  onShapeClick,
27858
28283
  isLoading,
@@ -28470,12 +28895,12 @@ var init_MapView = __esm({
28470
28895
  shadowSize: [41, 41]
28471
28896
  });
28472
28897
  L.Marker.prototype.options.icon = defaultIcon;
28473
- const { useEffect: useEffect60, useRef: useRef62, useCallback: useCallback90, useState: useState92 } = React85__namespace.default;
28898
+ const { useEffect: useEffect60, useRef: useRef63, useCallback: useCallback90, useState: useState92 } = React85__namespace.default;
28474
28899
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
28475
28900
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
28476
28901
  function MapUpdater({ centerLat, centerLng, zoom }) {
28477
28902
  const map = useMap();
28478
- const prevRef = useRef62({ centerLat, centerLng, zoom });
28903
+ const prevRef = useRef63({ centerLat, centerLng, zoom });
28479
28904
  useEffect60(() => {
28480
28905
  const prev = prevRef.current;
28481
28906
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -39140,7 +39565,7 @@ function getEnumOptions(field) {
39140
39565
  }));
39141
39566
  }
39142
39567
  const validation = field.validation;
39143
- if (validation?.enum && Array.isArray(validation.enum)) {
39568
+ if (validation?.enum && validation.enum.length > 0) {
39144
39569
  return validation.enum.map((v) => ({
39145
39570
  value: v,
39146
39571
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")