@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
@@ -10301,9 +10301,10 @@ function Canvas({
10301
10301
  drawables: [...drawables ?? [], ...childDrawables],
10302
10302
  isLoading,
10303
10303
  cameraMode: to3DCameraMode(camera?.mode),
10304
- ...zoom !== void 0 ? { scale: zoom } : {},
10304
+ ...zoom !== void 0 ? { zoom } : {},
10305
10305
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
10306
10306
  ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
10307
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
10307
10308
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
10308
10309
  unitScale,
10309
10310
  backgroundColor,
@@ -14832,23 +14833,188 @@ var init_BehaviorView = __esm({
14832
14833
  BehaviorView.displayName = "BehaviorView";
14833
14834
  }
14834
14835
  });
14835
- var BiologyCanvas;
14836
+ function LearningScene3D({
14837
+ className,
14838
+ width = 600,
14839
+ height = 400,
14840
+ title,
14841
+ backgroundColor,
14842
+ drawables,
14843
+ camera,
14844
+ lighting,
14845
+ post,
14846
+ showGrid = false,
14847
+ shadows,
14848
+ interactive,
14849
+ isLoading,
14850
+ error,
14851
+ onItemClick
14852
+ }) {
14853
+ const instanceId = useId().replace(/[^a-zA-Z0-9]/g, "");
14854
+ const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
14855
+ const onItemClickRef = useRef(onItemClick);
14856
+ onItemClickRef.current = onItemClick;
14857
+ useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
14858
+ const unitId = event.payload?.unitId;
14859
+ if (typeof unitId === "string") onItemClickRef.current?.(unitId);
14860
+ });
14861
+ const props3d = {
14862
+ drawables,
14863
+ isLoading,
14864
+ error: error ? error.message : null,
14865
+ cameraMode: camera?.mode ?? "perspective",
14866
+ ...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
14867
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
14868
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
14869
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
14870
+ ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
14871
+ backgroundColor,
14872
+ showGrid,
14873
+ ...shadows !== void 0 ? { shadows } : {},
14874
+ ...interactive !== void 0 ? { controlsEnabled: interactive } : {},
14875
+ lighting,
14876
+ post,
14877
+ ...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
14878
+ };
14879
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
14880
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
14881
+ /* @__PURE__ */ jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost2, { ...props3d }) }) })
14882
+ ] }) });
14883
+ }
14884
+ function meshSphere(id, x, y, z, radius, color, opts) {
14885
+ return {
14886
+ type: "draw-mesh",
14887
+ ...id !== void 0 ? { id } : {},
14888
+ shape: opts?.shape ?? "sphere",
14889
+ position: { x, y, z },
14890
+ radius,
14891
+ pivot: "center",
14892
+ segments: opts?.segments ?? 24,
14893
+ material: { color, ...opts?.material },
14894
+ ...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
14895
+ };
14896
+ }
14897
+ function axisRotation(from, to) {
14898
+ const wx = to[0] - from[0];
14899
+ const wy = to[2] - from[2];
14900
+ const wz = to[1] - from[1];
14901
+ const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
14902
+ const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
14903
+ return [0, Math.atan2(wz, -wx), tilt];
14904
+ }
14905
+ function segmentLength(from, to) {
14906
+ const dx = to[0] - from[0];
14907
+ const dy = to[1] - from[1];
14908
+ const dz = to[2] - from[2];
14909
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
14910
+ }
14911
+ function cylinderBetween(from, to, radius, color) {
14912
+ const len = segmentLength(from, to);
14913
+ if (len < 1e-6) return null;
14914
+ return {
14915
+ type: "draw-mesh",
14916
+ shape: "cylinder",
14917
+ position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
14918
+ radius,
14919
+ height: len,
14920
+ rotation: axisRotation(from, to),
14921
+ pivot: "center",
14922
+ segments: 12,
14923
+ material: { color }
14924
+ };
14925
+ }
14926
+ function arrowBetween(from, to, color, shaftRadius = 0.08) {
14927
+ const len = segmentLength(from, to);
14928
+ if (len < 1e-6) return null;
14929
+ const tipLen = Math.min(shaftRadius * 8, len * 0.35);
14930
+ const k = (len - tipLen) / len;
14931
+ const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
14932
+ const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
14933
+ const tipStart = shaftEnd;
14934
+ const tipEnd = delta;
14935
+ const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
14936
+ const tipLenActual = segmentLength(tipStart, tipEnd);
14937
+ const tip = {
14938
+ type: "draw-mesh",
14939
+ shape: "cone",
14940
+ position: {
14941
+ x: (tipStart[0] + tipEnd[0]) / 2,
14942
+ y: (tipStart[1] + tipEnd[1]) / 2,
14943
+ z: (tipStart[2] + tipEnd[2]) / 2
14944
+ },
14945
+ radius: shaftRadius * 3,
14946
+ height: tipLenActual,
14947
+ rotation: axisRotation(tipStart, tipEnd),
14948
+ pivot: "center",
14949
+ segments: 12,
14950
+ material: { color }
14951
+ };
14952
+ return {
14953
+ type: "draw-group",
14954
+ position: { x: from[0], y: from[1], z: from[2] },
14955
+ items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
14956
+ };
14957
+ }
14958
+ function billboardLabel(text, x, y, z, opts) {
14959
+ return {
14960
+ type: "draw-text",
14961
+ text,
14962
+ position: { x, y, z },
14963
+ color: opts?.color ?? "#111827"
14964
+ };
14965
+ }
14966
+ function labelColorForBackground(backgroundColor) {
14967
+ const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
14968
+ if (!m) return "#111827";
14969
+ const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
14970
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
14971
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
14972
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
14973
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
14974
+ return luminance < 0.5 ? "#e5e7eb" : "#111827";
14975
+ }
14976
+ function get3DClickPayload(onShapeClick, idToIndex) {
14977
+ if (!onShapeClick) return void 0;
14978
+ return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
14979
+ }
14980
+ var Canvas3DHost2;
14981
+ var init_learningScene3D = __esm({
14982
+ "components/learning/molecules/learningScene3D.tsx"() {
14983
+ "use client";
14984
+ init_atoms();
14985
+ init_Stack();
14986
+ init_useEventBus();
14987
+ Canvas3DHost2 = lazy(
14988
+ () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
14989
+ );
14990
+ LearningScene3D.displayName = "LearningScene3D";
14991
+ }
14992
+ });
14993
+ var biologyLog, BiologyCanvas;
14836
14994
  var init_BiologyCanvas = __esm({
14837
14995
  "components/learning/molecules/BiologyCanvas.tsx"() {
14838
14996
  "use client";
14839
14997
  init_atoms();
14840
14998
  init_Stack();
14841
14999
  init_LearningCanvas();
15000
+ init_learningScene3D();
15001
+ biologyLog = createLogger("almadar:ui:biology-canvas");
14842
15002
  BiologyCanvas = ({
14843
15003
  className,
14844
15004
  width = 600,
14845
15005
  height = 400,
14846
15006
  title,
14847
15007
  backgroundColor,
15008
+ mode = "2d",
15009
+ camera,
15010
+ lighting,
15011
+ post,
14848
15012
  nodes = [],
14849
15013
  edges = [],
14850
15014
  shapes = [],
14851
- interactive = false,
15015
+ showGrid,
15016
+ shadows,
15017
+ interactive,
14852
15018
  animate = false,
14853
15019
  onShapeClick,
14854
15020
  isLoading,
@@ -14909,6 +15075,75 @@ var init_BiologyCanvas = __esm({
14909
15075
  out.push(...shapes);
14910
15076
  return out;
14911
15077
  }, [nodes, edges, shapes]);
15078
+ const drawables3D = useMemo(() => {
15079
+ if (mode !== "3d") return [];
15080
+ if (shapes.length > 0) {
15081
+ biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
15082
+ }
15083
+ const out = [];
15084
+ const labelColor = labelColorForBackground(backgroundColor);
15085
+ const nodeById = /* @__PURE__ */ new Map();
15086
+ for (const n of nodes) {
15087
+ if (n.id) nodeById.set(n.id, n);
15088
+ }
15089
+ for (const e of edges) {
15090
+ const a = nodeById.get(e.from);
15091
+ const b = nodeById.get(e.to);
15092
+ if (!a || !b) continue;
15093
+ const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
15094
+ const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
15095
+ if (edge) out.push(edge);
15096
+ if (e.label) {
15097
+ out.push(
15098
+ billboardLabel(
15099
+ e.label,
15100
+ (a.x + b.x) / 2,
15101
+ (a.y + b.y) / 2,
15102
+ ((a.z ?? 0) + (b.z ?? 0)) / 2,
15103
+ { color: labelColor }
15104
+ )
15105
+ );
15106
+ }
15107
+ }
15108
+ for (const n of nodes) {
15109
+ const radius = n.radius ?? 0.5;
15110
+ const nz = n.z ?? 0;
15111
+ out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
15112
+ if (n.label) {
15113
+ out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
15114
+ }
15115
+ }
15116
+ return out;
15117
+ }, [mode, nodes, edges, shapes, backgroundColor]);
15118
+ const nodeIndexById = useMemo(() => {
15119
+ const m = /* @__PURE__ */ new Map();
15120
+ nodes.forEach((n, i) => {
15121
+ if (n.id) m.set(n.id, i);
15122
+ });
15123
+ return m;
15124
+ }, [nodes]);
15125
+ if (mode === "3d") {
15126
+ return /* @__PURE__ */ jsx(
15127
+ LearningScene3D,
15128
+ {
15129
+ className,
15130
+ width,
15131
+ height,
15132
+ title,
15133
+ backgroundColor,
15134
+ drawables: drawables3D,
15135
+ camera,
15136
+ lighting,
15137
+ post,
15138
+ showGrid,
15139
+ shadows,
15140
+ interactive,
15141
+ isLoading,
15142
+ error,
15143
+ onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
15144
+ }
15145
+ );
15146
+ }
14912
15147
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
14913
15148
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
14914
15149
  /* @__PURE__ */ jsx(
@@ -14918,7 +15153,7 @@ var init_BiologyCanvas = __esm({
14918
15153
  height,
14919
15154
  backgroundColor,
14920
15155
  shapes: derivedShapes,
14921
- interactive,
15156
+ interactive: interactive ?? false,
14922
15157
  animate,
14923
15158
  onShapeClick,
14924
15159
  isLoading,
@@ -21758,24 +21993,41 @@ var init_ChatBar = __esm({
21758
21993
  ChatBar.displayName = "ChatBar";
21759
21994
  }
21760
21995
  });
21761
- var ChemistryCanvas;
21996
+ function bondPerpendicular(a, b) {
21997
+ const dx = b[0] - a[0];
21998
+ const dy = b[1] - a[1];
21999
+ const px = dy;
22000
+ const py = -dx;
22001
+ const len = Math.sqrt(px * px + py * py);
22002
+ if (len < 1e-6) return [1, 0, 0];
22003
+ return [px / len, py / len, 0];
22004
+ }
22005
+ var chemistryLog, ChemistryCanvas;
21762
22006
  var init_ChemistryCanvas = __esm({
21763
22007
  "components/learning/molecules/ChemistryCanvas.tsx"() {
21764
22008
  "use client";
21765
22009
  init_atoms();
21766
22010
  init_Stack();
21767
22011
  init_LearningCanvas();
22012
+ init_learningScene3D();
22013
+ chemistryLog = createLogger("almadar:ui:chemistry-canvas");
21768
22014
  ChemistryCanvas = ({
21769
22015
  className,
21770
22016
  width = 600,
21771
22017
  height = 400,
21772
22018
  title,
21773
22019
  backgroundColor,
22020
+ mode = "2d",
22021
+ camera,
22022
+ lighting,
22023
+ post,
21774
22024
  atoms = [],
21775
22025
  bonds = [],
21776
22026
  arrows = [],
21777
22027
  shapes = [],
21778
- interactive = false,
22028
+ showGrid,
22029
+ shadows,
22030
+ interactive,
21779
22031
  animate = false,
21780
22032
  onShapeClick,
21781
22033
  isLoading,
@@ -21854,6 +22106,88 @@ var init_ChemistryCanvas = __esm({
21854
22106
  out.push(...shapes);
21855
22107
  return out;
21856
22108
  }, [atoms, bonds, arrows, shapes]);
22109
+ const drawables3D = useMemo(() => {
22110
+ if (mode !== "3d") return [];
22111
+ if (shapes.length > 0) {
22112
+ chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
22113
+ }
22114
+ const out = [];
22115
+ const labelColor = labelColorForBackground(backgroundColor);
22116
+ const atomById = /* @__PURE__ */ new Map();
22117
+ for (const a of atoms) {
22118
+ if (a.id) atomById.set(a.id, a);
22119
+ }
22120
+ for (const b of bonds) {
22121
+ const a = atomById.get(b.from);
22122
+ const c = atomById.get(b.to);
22123
+ if (!a || !c) continue;
22124
+ const color = b.color ?? "#6b7280";
22125
+ const from = [a.x, a.y, a.z ?? 0];
22126
+ const to = [c.x, c.y, c.z ?? 0];
22127
+ const perp = bondPerpendicular(from, to);
22128
+ const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
22129
+ const step = bondRadius * 2.2;
22130
+ const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
22131
+ for (const off of offsets) {
22132
+ const bond = cylinderBetween(
22133
+ [from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
22134
+ [to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
22135
+ bondRadius,
22136
+ color
22137
+ );
22138
+ if (bond) out.push(bond);
22139
+ }
22140
+ }
22141
+ for (const a of arrows) {
22142
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
22143
+ const len = a.length ?? 60;
22144
+ const x2 = a.x + Math.cos(angle) * len;
22145
+ const y2 = a.y + Math.sin(angle) * len;
22146
+ const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
22147
+ if (arrow) out.push(arrow);
22148
+ if (a.label) {
22149
+ out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
22150
+ }
22151
+ }
22152
+ for (const a of atoms) {
22153
+ const radius = a.radius ?? 0.45;
22154
+ const az = a.z ?? 0;
22155
+ out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
22156
+ if (a.element) {
22157
+ out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
22158
+ }
22159
+ }
22160
+ return out;
22161
+ }, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
22162
+ const atomIndexById = useMemo(() => {
22163
+ const m = /* @__PURE__ */ new Map();
22164
+ atoms.forEach((a, i) => {
22165
+ if (a.id) m.set(a.id, i);
22166
+ });
22167
+ return m;
22168
+ }, [atoms]);
22169
+ if (mode === "3d") {
22170
+ return /* @__PURE__ */ jsx(
22171
+ LearningScene3D,
22172
+ {
22173
+ className,
22174
+ width,
22175
+ height,
22176
+ title,
22177
+ backgroundColor,
22178
+ drawables: drawables3D,
22179
+ camera,
22180
+ lighting,
22181
+ post,
22182
+ showGrid,
22183
+ shadows,
22184
+ interactive,
22185
+ isLoading,
22186
+ error,
22187
+ onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
22188
+ }
22189
+ );
22190
+ }
21857
22191
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
21858
22192
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
21859
22193
  /* @__PURE__ */ jsx(
@@ -21863,7 +22197,7 @@ var init_ChemistryCanvas = __esm({
21863
22197
  height,
21864
22198
  backgroundColor,
21865
22199
  shapes: derivedShapes,
21866
- interactive,
22200
+ interactive: interactive ?? false,
21867
22201
  animate,
21868
22202
  onShapeClick,
21869
22203
  isLoading,
@@ -28168,19 +28502,25 @@ var init_MathCanvas = __esm({
28168
28502
  };
28169
28503
  }
28170
28504
  });
28171
- var PhysicsCanvas;
28505
+ var physicsLog2, PhysicsCanvas;
28172
28506
  var init_PhysicsCanvas = __esm({
28173
28507
  "components/learning/molecules/PhysicsCanvas.tsx"() {
28174
28508
  "use client";
28175
28509
  init_atoms();
28176
28510
  init_Stack();
28177
28511
  init_LearningCanvas();
28512
+ init_learningScene3D();
28513
+ physicsLog2 = createLogger("almadar:ui:physics-canvas");
28178
28514
  PhysicsCanvas = ({
28179
28515
  className,
28180
28516
  width = 600,
28181
28517
  height = 400,
28182
28518
  title,
28183
28519
  backgroundColor,
28520
+ mode = "2d",
28521
+ camera,
28522
+ lighting,
28523
+ post,
28184
28524
  bodies = [],
28185
28525
  constraints = [],
28186
28526
  showVelocity = true,
@@ -28188,7 +28528,9 @@ var init_PhysicsCanvas = __esm({
28188
28528
  velocityScale = 20,
28189
28529
  forceScale = 20,
28190
28530
  shapes = [],
28191
- interactive = false,
28531
+ showGrid,
28532
+ shadows,
28533
+ interactive,
28192
28534
  animate = false,
28193
28535
  onShapeClick,
28194
28536
  isLoading,
@@ -28260,6 +28602,89 @@ var init_PhysicsCanvas = __esm({
28260
28602
  out.push(...shapes);
28261
28603
  return out;
28262
28604
  }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
28605
+ const drawables3D = useMemo(() => {
28606
+ if (mode !== "3d") return [];
28607
+ if (shapes.length > 0) {
28608
+ physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
28609
+ }
28610
+ const out = [];
28611
+ const labelColor = labelColorForBackground(backgroundColor);
28612
+ const bodyById = /* @__PURE__ */ new Map();
28613
+ for (const b of bodies) {
28614
+ if (b.id) bodyById.set(b.id, b);
28615
+ }
28616
+ for (const c of constraints) {
28617
+ const a = bodyById.get(c.from);
28618
+ const b = bodyById.get(c.to);
28619
+ if (!a || !b) continue;
28620
+ const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
28621
+ const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
28622
+ if (rod) out.push(rod);
28623
+ }
28624
+ for (const b of bodies) {
28625
+ const radius = b.radius ?? 0.5;
28626
+ const bz = b.z ?? 0;
28627
+ out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
28628
+ if (b.label) {
28629
+ out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
28630
+ }
28631
+ const vx = b.vx ?? 0;
28632
+ const vy = b.vy ?? 0;
28633
+ const vz = b.vz ?? 0;
28634
+ const arrowRadius = Math.max(0.05, radius * 0.15);
28635
+ if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
28636
+ const arrow = arrowBetween(
28637
+ [b.x, b.y, bz],
28638
+ [b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
28639
+ "#16a34a",
28640
+ arrowRadius
28641
+ );
28642
+ if (arrow) out.push(arrow);
28643
+ }
28644
+ const fx = b.fx ?? 0;
28645
+ const fy = b.fy ?? 0;
28646
+ const fz = b.fz ?? 0;
28647
+ if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
28648
+ const arrow = arrowBetween(
28649
+ [b.x, b.y, bz],
28650
+ [b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
28651
+ "#dc2626",
28652
+ arrowRadius
28653
+ );
28654
+ if (arrow) out.push(arrow);
28655
+ }
28656
+ }
28657
+ return out;
28658
+ }, [mode, bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes, backgroundColor]);
28659
+ const bodyIndexById = useMemo(() => {
28660
+ const m = /* @__PURE__ */ new Map();
28661
+ bodies.forEach((b, i) => {
28662
+ if (b.id) m.set(b.id, i);
28663
+ });
28664
+ return m;
28665
+ }, [bodies]);
28666
+ if (mode === "3d") {
28667
+ return /* @__PURE__ */ jsx(
28668
+ LearningScene3D,
28669
+ {
28670
+ className,
28671
+ width,
28672
+ height,
28673
+ title,
28674
+ backgroundColor,
28675
+ drawables: drawables3D,
28676
+ camera,
28677
+ lighting,
28678
+ post,
28679
+ showGrid,
28680
+ shadows,
28681
+ interactive,
28682
+ isLoading,
28683
+ error,
28684
+ onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
28685
+ }
28686
+ );
28687
+ }
28263
28688
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
28264
28689
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
28265
28690
  /* @__PURE__ */ jsx(
@@ -28269,7 +28694,7 @@ var init_PhysicsCanvas = __esm({
28269
28694
  height,
28270
28695
  backgroundColor,
28271
28696
  shapes: derivedShapes,
28272
- interactive,
28697
+ interactive: interactive ?? false,
28273
28698
  animate,
28274
28699
  onShapeClick,
28275
28700
  isLoading,
@@ -28887,12 +29312,12 @@ var init_MapView = __esm({
28887
29312
  shadowSize: [41, 41]
28888
29313
  });
28889
29314
  L.Marker.prototype.options.icon = defaultIcon;
28890
- const { useEffect: useEffect63, useRef: useRef62, useCallback: useCallback96, useState: useState94 } = React87__default;
29315
+ const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback96, useState: useState94 } = React87__default;
28891
29316
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
28892
29317
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
28893
29318
  function MapUpdater({ centerLat, centerLng, zoom }) {
28894
29319
  const map = useMap();
28895
- const prevRef = useRef62({ centerLat, centerLng, zoom });
29320
+ const prevRef = useRef63({ centerLat, centerLng, zoom });
28896
29321
  useEffect63(() => {
28897
29322
  const prev = prevRef.current;
28898
29323
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -39715,7 +40140,7 @@ function getEnumOptions(field) {
39715
40140
  }));
39716
40141
  }
39717
40142
  const validation = field.validation;
39718
- if (validation?.enum && Array.isArray(validation.enum)) {
40143
+ if (validation?.enum && validation.enum.length > 0) {
39719
40144
  return validation.enum.map((v) => ({
39720
40145
  value: v,
39721
40146
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")