@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
@@ -9878,23 +9878,188 @@ var init_BehaviorView = __esm({
9878
9878
  exports.BehaviorView.displayName = "BehaviorView";
9879
9879
  }
9880
9880
  });
9881
- exports.BiologyCanvas = void 0;
9881
+ function LearningScene3D({
9882
+ className,
9883
+ width = 600,
9884
+ height = 400,
9885
+ title,
9886
+ backgroundColor,
9887
+ drawables,
9888
+ camera,
9889
+ lighting,
9890
+ post,
9891
+ showGrid = false,
9892
+ shadows,
9893
+ interactive,
9894
+ isLoading,
9895
+ error,
9896
+ onItemClick
9897
+ }) {
9898
+ const instanceId = React77.useId().replace(/[^a-zA-Z0-9]/g, "");
9899
+ const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
9900
+ const onItemClickRef = React77.useRef(onItemClick);
9901
+ onItemClickRef.current = onItemClick;
9902
+ useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
9903
+ const unitId = event.payload?.unitId;
9904
+ if (typeof unitId === "string") onItemClickRef.current?.(unitId);
9905
+ });
9906
+ const props3d = {
9907
+ drawables,
9908
+ isLoading,
9909
+ error: error ? error.message : null,
9910
+ cameraMode: camera?.mode ?? "perspective",
9911
+ ...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
9912
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
9913
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
9914
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
9915
+ ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
9916
+ backgroundColor,
9917
+ showGrid,
9918
+ ...shadows !== void 0 ? { shadows } : {},
9919
+ ...interactive !== void 0 ? { controlsEnabled: interactive } : {},
9920
+ lighting,
9921
+ post,
9922
+ ...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
9923
+ };
9924
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
9925
+ title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
9926
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsxRuntime.jsx(React77.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(Canvas3DHost, { ...props3d }) }) })
9927
+ ] }) });
9928
+ }
9929
+ function meshSphere(id, x, y, z, radius, color, opts) {
9930
+ return {
9931
+ type: "draw-mesh",
9932
+ ...id !== void 0 ? { id } : {},
9933
+ shape: opts?.shape ?? "sphere",
9934
+ position: { x, y, z },
9935
+ radius,
9936
+ pivot: "center",
9937
+ segments: opts?.segments ?? 24,
9938
+ material: { color, ...opts?.material },
9939
+ ...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
9940
+ };
9941
+ }
9942
+ function axisRotation(from, to) {
9943
+ const wx = to[0] - from[0];
9944
+ const wy = to[2] - from[2];
9945
+ const wz = to[1] - from[1];
9946
+ const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
9947
+ const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
9948
+ return [0, Math.atan2(wz, -wx), tilt];
9949
+ }
9950
+ function segmentLength(from, to) {
9951
+ const dx = to[0] - from[0];
9952
+ const dy = to[1] - from[1];
9953
+ const dz = to[2] - from[2];
9954
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
9955
+ }
9956
+ function cylinderBetween(from, to, radius, color) {
9957
+ const len = segmentLength(from, to);
9958
+ if (len < 1e-6) return null;
9959
+ return {
9960
+ type: "draw-mesh",
9961
+ shape: "cylinder",
9962
+ position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
9963
+ radius,
9964
+ height: len,
9965
+ rotation: axisRotation(from, to),
9966
+ pivot: "center",
9967
+ segments: 12,
9968
+ material: { color }
9969
+ };
9970
+ }
9971
+ function arrowBetween(from, to, color, shaftRadius = 0.08) {
9972
+ const len = segmentLength(from, to);
9973
+ if (len < 1e-6) return null;
9974
+ const tipLen = Math.min(shaftRadius * 8, len * 0.35);
9975
+ const k = (len - tipLen) / len;
9976
+ const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
9977
+ const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
9978
+ const tipStart = shaftEnd;
9979
+ const tipEnd = delta;
9980
+ const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
9981
+ const tipLenActual = segmentLength(tipStart, tipEnd);
9982
+ const tip = {
9983
+ type: "draw-mesh",
9984
+ shape: "cone",
9985
+ position: {
9986
+ x: (tipStart[0] + tipEnd[0]) / 2,
9987
+ y: (tipStart[1] + tipEnd[1]) / 2,
9988
+ z: (tipStart[2] + tipEnd[2]) / 2
9989
+ },
9990
+ radius: shaftRadius * 3,
9991
+ height: tipLenActual,
9992
+ rotation: axisRotation(tipStart, tipEnd),
9993
+ pivot: "center",
9994
+ segments: 12,
9995
+ material: { color }
9996
+ };
9997
+ return {
9998
+ type: "draw-group",
9999
+ position: { x: from[0], y: from[1], z: from[2] },
10000
+ items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
10001
+ };
10002
+ }
10003
+ function billboardLabel(text, x, y, z, opts) {
10004
+ return {
10005
+ type: "draw-text",
10006
+ text,
10007
+ position: { x, y, z },
10008
+ color: opts?.color ?? "#111827"
10009
+ };
10010
+ }
10011
+ function labelColorForBackground(backgroundColor) {
10012
+ const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
10013
+ if (!m) return "#111827";
10014
+ const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
10015
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
10016
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
10017
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
10018
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
10019
+ return luminance < 0.5 ? "#e5e7eb" : "#111827";
10020
+ }
10021
+ function get3DClickPayload(onShapeClick, idToIndex) {
10022
+ if (!onShapeClick) return void 0;
10023
+ return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
10024
+ }
10025
+ var Canvas3DHost;
10026
+ var init_learningScene3D = __esm({
10027
+ "components/learning/molecules/learningScene3D.tsx"() {
10028
+ "use client";
10029
+ init_atoms();
10030
+ init_Stack();
10031
+ init_useEventBus();
10032
+ Canvas3DHost = React77.lazy(
10033
+ () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
10034
+ );
10035
+ LearningScene3D.displayName = "LearningScene3D";
10036
+ }
10037
+ });
10038
+ var biologyLog; exports.BiologyCanvas = void 0;
9882
10039
  var init_BiologyCanvas = __esm({
9883
10040
  "components/learning/molecules/BiologyCanvas.tsx"() {
9884
10041
  "use client";
9885
10042
  init_atoms();
9886
10043
  init_Stack();
9887
10044
  init_LearningCanvas();
10045
+ init_learningScene3D();
10046
+ biologyLog = logger.createLogger("almadar:ui:biology-canvas");
9888
10047
  exports.BiologyCanvas = ({
9889
10048
  className,
9890
10049
  width = 600,
9891
10050
  height = 400,
9892
10051
  title,
9893
10052
  backgroundColor,
10053
+ mode = "2d",
10054
+ camera,
10055
+ lighting,
10056
+ post,
9894
10057
  nodes = [],
9895
10058
  edges = [],
9896
10059
  shapes = [],
9897
- interactive = false,
10060
+ showGrid,
10061
+ shadows,
10062
+ interactive,
9898
10063
  animate = false,
9899
10064
  onShapeClick,
9900
10065
  isLoading,
@@ -9955,6 +10120,75 @@ var init_BiologyCanvas = __esm({
9955
10120
  out.push(...shapes);
9956
10121
  return out;
9957
10122
  }, [nodes, edges, shapes]);
10123
+ const drawables3D = React77.useMemo(() => {
10124
+ if (mode !== "3d") return [];
10125
+ if (shapes.length > 0) {
10126
+ biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
10127
+ }
10128
+ const out = [];
10129
+ const labelColor = labelColorForBackground(backgroundColor);
10130
+ const nodeById = /* @__PURE__ */ new Map();
10131
+ for (const n of nodes) {
10132
+ if (n.id) nodeById.set(n.id, n);
10133
+ }
10134
+ for (const e of edges) {
10135
+ const a = nodeById.get(e.from);
10136
+ const b = nodeById.get(e.to);
10137
+ if (!a || !b) continue;
10138
+ const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
10139
+ const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
10140
+ if (edge) out.push(edge);
10141
+ if (e.label) {
10142
+ out.push(
10143
+ billboardLabel(
10144
+ e.label,
10145
+ (a.x + b.x) / 2,
10146
+ (a.y + b.y) / 2,
10147
+ ((a.z ?? 0) + (b.z ?? 0)) / 2,
10148
+ { color: labelColor }
10149
+ )
10150
+ );
10151
+ }
10152
+ }
10153
+ for (const n of nodes) {
10154
+ const radius = n.radius ?? 0.5;
10155
+ const nz = n.z ?? 0;
10156
+ out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
10157
+ if (n.label) {
10158
+ out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
10159
+ }
10160
+ }
10161
+ return out;
10162
+ }, [mode, nodes, edges, shapes, backgroundColor]);
10163
+ const nodeIndexById = React77.useMemo(() => {
10164
+ const m = /* @__PURE__ */ new Map();
10165
+ nodes.forEach((n, i) => {
10166
+ if (n.id) m.set(n.id, i);
10167
+ });
10168
+ return m;
10169
+ }, [nodes]);
10170
+ if (mode === "3d") {
10171
+ return /* @__PURE__ */ jsxRuntime.jsx(
10172
+ LearningScene3D,
10173
+ {
10174
+ className,
10175
+ width,
10176
+ height,
10177
+ title,
10178
+ backgroundColor,
10179
+ drawables: drawables3D,
10180
+ camera,
10181
+ lighting,
10182
+ post,
10183
+ showGrid,
10184
+ shadows,
10185
+ interactive,
10186
+ isLoading,
10187
+ error,
10188
+ onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
10189
+ }
10190
+ );
10191
+ }
9958
10192
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
9959
10193
  title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
9960
10194
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -9964,7 +10198,7 @@ var init_BiologyCanvas = __esm({
9964
10198
  height,
9965
10199
  backgroundColor,
9966
10200
  shapes: derivedShapes,
9967
- interactive,
10201
+ interactive: interactive ?? false,
9968
10202
  animate,
9969
10203
  onShapeClick,
9970
10204
  isLoading,
@@ -17356,9 +17590,10 @@ function Canvas({
17356
17590
  drawables: [...drawables ?? [], ...childDrawables],
17357
17591
  isLoading,
17358
17592
  cameraMode: to3DCameraMode(camera?.mode),
17359
- ...zoom !== void 0 ? { scale: zoom } : {},
17593
+ ...zoom !== void 0 ? { zoom } : {},
17360
17594
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
17361
17595
  ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
17596
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
17362
17597
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
17363
17598
  unitScale,
17364
17599
  backgroundColor,
@@ -17381,7 +17616,7 @@ function Canvas({
17381
17616
  keyUpMap
17382
17617
  };
17383
17618
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17384
- /* @__PURE__ */ jsxRuntime.jsx(React77.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(Canvas3DHost, { ...props3d }) }),
17619
+ /* @__PURE__ */ jsxRuntime.jsx(React77.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(Canvas3DHost2, { ...props3d }) }),
17385
17620
  React77__namespace.Children.count(children) > 0 && /* @__PURE__ */ jsxRuntime.jsx(DrawableRegistryContext.Provider, { value: registerChildDrawable, children: /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children }) })
17386
17621
  ] });
17387
17622
  }
@@ -17411,13 +17646,13 @@ function Canvas({
17411
17646
  }
17412
17647
  );
17413
17648
  }
17414
- var Canvas3DHost, canvasLog;
17649
+ var Canvas3DHost2, canvasLog;
17415
17650
  var init_Canvas = __esm({
17416
17651
  "components/game/molecules/Canvas.tsx"() {
17417
17652
  "use client";
17418
17653
  init_registry();
17419
17654
  init_Canvas2D();
17420
- Canvas3DHost = React77.lazy(
17655
+ Canvas3DHost2 = React77.lazy(
17421
17656
  () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
17422
17657
  );
17423
17658
  canvasLog = logger.createLogger("almadar:ui:game-canvas");
@@ -19144,24 +19379,41 @@ var init_ChatBar = __esm({
19144
19379
  ChatBar.displayName = "ChatBar";
19145
19380
  }
19146
19381
  });
19147
- exports.ChemistryCanvas = void 0;
19382
+ function bondPerpendicular(a, b) {
19383
+ const dx = b[0] - a[0];
19384
+ const dy = b[1] - a[1];
19385
+ const px = dy;
19386
+ const py = -dx;
19387
+ const len = Math.sqrt(px * px + py * py);
19388
+ if (len < 1e-6) return [1, 0, 0];
19389
+ return [px / len, py / len, 0];
19390
+ }
19391
+ var chemistryLog; exports.ChemistryCanvas = void 0;
19148
19392
  var init_ChemistryCanvas = __esm({
19149
19393
  "components/learning/molecules/ChemistryCanvas.tsx"() {
19150
19394
  "use client";
19151
19395
  init_atoms();
19152
19396
  init_Stack();
19153
19397
  init_LearningCanvas();
19398
+ init_learningScene3D();
19399
+ chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
19154
19400
  exports.ChemistryCanvas = ({
19155
19401
  className,
19156
19402
  width = 600,
19157
19403
  height = 400,
19158
19404
  title,
19159
19405
  backgroundColor,
19406
+ mode = "2d",
19407
+ camera,
19408
+ lighting,
19409
+ post,
19160
19410
  atoms = [],
19161
19411
  bonds = [],
19162
19412
  arrows = [],
19163
19413
  shapes = [],
19164
- interactive = false,
19414
+ showGrid,
19415
+ shadows,
19416
+ interactive,
19165
19417
  animate = false,
19166
19418
  onShapeClick,
19167
19419
  isLoading,
@@ -19240,6 +19492,88 @@ var init_ChemistryCanvas = __esm({
19240
19492
  out.push(...shapes);
19241
19493
  return out;
19242
19494
  }, [atoms, bonds, arrows, shapes]);
19495
+ const drawables3D = React77.useMemo(() => {
19496
+ if (mode !== "3d") return [];
19497
+ if (shapes.length > 0) {
19498
+ chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
19499
+ }
19500
+ const out = [];
19501
+ const labelColor = labelColorForBackground(backgroundColor);
19502
+ const atomById = /* @__PURE__ */ new Map();
19503
+ for (const a of atoms) {
19504
+ if (a.id) atomById.set(a.id, a);
19505
+ }
19506
+ for (const b of bonds) {
19507
+ const a = atomById.get(b.from);
19508
+ const c = atomById.get(b.to);
19509
+ if (!a || !c) continue;
19510
+ const color = b.color ?? "#6b7280";
19511
+ const from = [a.x, a.y, a.z ?? 0];
19512
+ const to = [c.x, c.y, c.z ?? 0];
19513
+ const perp = bondPerpendicular(from, to);
19514
+ const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
19515
+ const step = bondRadius * 2.2;
19516
+ const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
19517
+ for (const off of offsets) {
19518
+ const bond = cylinderBetween(
19519
+ [from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
19520
+ [to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
19521
+ bondRadius,
19522
+ color
19523
+ );
19524
+ if (bond) out.push(bond);
19525
+ }
19526
+ }
19527
+ for (const a of arrows) {
19528
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
19529
+ const len = a.length ?? 60;
19530
+ const x2 = a.x + Math.cos(angle) * len;
19531
+ const y2 = a.y + Math.sin(angle) * len;
19532
+ const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
19533
+ if (arrow) out.push(arrow);
19534
+ if (a.label) {
19535
+ out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
19536
+ }
19537
+ }
19538
+ for (const a of atoms) {
19539
+ const radius = a.radius ?? 0.45;
19540
+ const az = a.z ?? 0;
19541
+ out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
19542
+ if (a.element) {
19543
+ out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
19544
+ }
19545
+ }
19546
+ return out;
19547
+ }, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
19548
+ const atomIndexById = React77.useMemo(() => {
19549
+ const m = /* @__PURE__ */ new Map();
19550
+ atoms.forEach((a, i) => {
19551
+ if (a.id) m.set(a.id, i);
19552
+ });
19553
+ return m;
19554
+ }, [atoms]);
19555
+ if (mode === "3d") {
19556
+ return /* @__PURE__ */ jsxRuntime.jsx(
19557
+ LearningScene3D,
19558
+ {
19559
+ className,
19560
+ width,
19561
+ height,
19562
+ title,
19563
+ backgroundColor,
19564
+ drawables: drawables3D,
19565
+ camera,
19566
+ lighting,
19567
+ post,
19568
+ showGrid,
19569
+ shadows,
19570
+ interactive,
19571
+ isLoading,
19572
+ error,
19573
+ onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
19574
+ }
19575
+ );
19576
+ }
19243
19577
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
19244
19578
  title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
19245
19579
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -19249,7 +19583,7 @@ var init_ChemistryCanvas = __esm({
19249
19583
  height,
19250
19584
  backgroundColor,
19251
19585
  shapes: derivedShapes,
19252
- interactive,
19586
+ interactive: interactive ?? false,
19253
19587
  animate,
19254
19588
  onShapeClick,
19255
19589
  isLoading,
@@ -29277,19 +29611,25 @@ var init_MathCanvas = __esm({
29277
29611
  };
29278
29612
  }
29279
29613
  });
29280
- exports.PhysicsCanvas = void 0;
29614
+ var physicsLog2; exports.PhysicsCanvas = void 0;
29281
29615
  var init_PhysicsCanvas = __esm({
29282
29616
  "components/learning/molecules/PhysicsCanvas.tsx"() {
29283
29617
  "use client";
29284
29618
  init_atoms();
29285
29619
  init_Stack();
29286
29620
  init_LearningCanvas();
29621
+ init_learningScene3D();
29622
+ physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
29287
29623
  exports.PhysicsCanvas = ({
29288
29624
  className,
29289
29625
  width = 600,
29290
29626
  height = 400,
29291
29627
  title,
29292
29628
  backgroundColor,
29629
+ mode = "2d",
29630
+ camera,
29631
+ lighting,
29632
+ post,
29293
29633
  bodies = [],
29294
29634
  constraints = [],
29295
29635
  showVelocity = true,
@@ -29297,7 +29637,9 @@ var init_PhysicsCanvas = __esm({
29297
29637
  velocityScale = 20,
29298
29638
  forceScale = 20,
29299
29639
  shapes = [],
29300
- interactive = false,
29640
+ showGrid,
29641
+ shadows,
29642
+ interactive,
29301
29643
  animate = false,
29302
29644
  onShapeClick,
29303
29645
  isLoading,
@@ -29369,6 +29711,89 @@ var init_PhysicsCanvas = __esm({
29369
29711
  out.push(...shapes);
29370
29712
  return out;
29371
29713
  }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
29714
+ const drawables3D = React77.useMemo(() => {
29715
+ if (mode !== "3d") return [];
29716
+ if (shapes.length > 0) {
29717
+ physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
29718
+ }
29719
+ const out = [];
29720
+ const labelColor = labelColorForBackground(backgroundColor);
29721
+ const bodyById = /* @__PURE__ */ new Map();
29722
+ for (const b of bodies) {
29723
+ if (b.id) bodyById.set(b.id, b);
29724
+ }
29725
+ for (const c of constraints) {
29726
+ const a = bodyById.get(c.from);
29727
+ const b = bodyById.get(c.to);
29728
+ if (!a || !b) continue;
29729
+ const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
29730
+ const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
29731
+ if (rod) out.push(rod);
29732
+ }
29733
+ for (const b of bodies) {
29734
+ const radius = b.radius ?? 0.5;
29735
+ const bz = b.z ?? 0;
29736
+ out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
29737
+ if (b.label) {
29738
+ out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
29739
+ }
29740
+ const vx = b.vx ?? 0;
29741
+ const vy = b.vy ?? 0;
29742
+ const vz = b.vz ?? 0;
29743
+ const arrowRadius = Math.max(0.05, radius * 0.15);
29744
+ if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
29745
+ const arrow = arrowBetween(
29746
+ [b.x, b.y, bz],
29747
+ [b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
29748
+ "#16a34a",
29749
+ arrowRadius
29750
+ );
29751
+ if (arrow) out.push(arrow);
29752
+ }
29753
+ const fx = b.fx ?? 0;
29754
+ const fy = b.fy ?? 0;
29755
+ const fz = b.fz ?? 0;
29756
+ if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
29757
+ const arrow = arrowBetween(
29758
+ [b.x, b.y, bz],
29759
+ [b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
29760
+ "#dc2626",
29761
+ arrowRadius
29762
+ );
29763
+ if (arrow) out.push(arrow);
29764
+ }
29765
+ }
29766
+ return out;
29767
+ }, [mode, bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes, backgroundColor]);
29768
+ const bodyIndexById = React77.useMemo(() => {
29769
+ const m = /* @__PURE__ */ new Map();
29770
+ bodies.forEach((b, i) => {
29771
+ if (b.id) m.set(b.id, i);
29772
+ });
29773
+ return m;
29774
+ }, [bodies]);
29775
+ if (mode === "3d") {
29776
+ return /* @__PURE__ */ jsxRuntime.jsx(
29777
+ LearningScene3D,
29778
+ {
29779
+ className,
29780
+ width,
29781
+ height,
29782
+ title,
29783
+ backgroundColor,
29784
+ drawables: drawables3D,
29785
+ camera,
29786
+ lighting,
29787
+ post,
29788
+ showGrid,
29789
+ shadows,
29790
+ interactive,
29791
+ isLoading,
29792
+ error,
29793
+ onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
29794
+ }
29795
+ );
29796
+ }
29372
29797
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
29373
29798
  title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
29374
29799
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -29378,7 +29803,7 @@ var init_PhysicsCanvas = __esm({
29378
29803
  height,
29379
29804
  backgroundColor,
29380
29805
  shapes: derivedShapes,
29381
- interactive,
29806
+ interactive: interactive ?? false,
29382
29807
  animate,
29383
29808
  onShapeClick,
29384
29809
  isLoading,
@@ -29996,12 +30421,12 @@ var init_MapView = __esm({
29996
30421
  shadowSize: [41, 41]
29997
30422
  });
29998
30423
  L.Marker.prototype.options.icon = defaultIcon;
29999
- const { useEffect: useEffect66, useRef: useRef64, useCallback: useCallback107, useState: useState104 } = React77__namespace.default;
30424
+ const { useEffect: useEffect66, useRef: useRef65, useCallback: useCallback107, useState: useState104 } = React77__namespace.default;
30000
30425
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
30001
30426
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
30002
30427
  function MapUpdater({ centerLat, centerLng, zoom }) {
30003
30428
  const map = useMap();
30004
- const prevRef = useRef64({ centerLat, centerLng, zoom });
30429
+ const prevRef = useRef65({ centerLat, centerLng, zoom });
30005
30430
  useEffect66(() => {
30006
30431
  const prev = prevRef.current;
30007
30432
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -39793,6 +40218,7 @@ var init_molecules2 = __esm({
39793
40218
  init_BiologyCanvas();
39794
40219
  init_ChemistryCanvas();
39795
40220
  init_AlgorithmCanvas();
40221
+ init_learningScene3D();
39796
40222
  init_GraphView();
39797
40223
  init_MapView();
39798
40224
  init_NumberStepper();
@@ -41046,7 +41472,7 @@ function getEnumOptions(field) {
41046
41472
  }));
41047
41473
  }
41048
41474
  const validation = field.validation;
41049
- if (validation?.enum && Array.isArray(validation.enum)) {
41475
+ if (validation?.enum && validation.enum.length > 0) {
41050
41476
  return validation.enum.map((v) => ({
41051
41477
  value: v,
41052
41478
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")
@@ -50501,6 +50927,7 @@ exports.GameIcon = GameIcon;
50501
50927
  exports.GameMenu = GameMenu;
50502
50928
  exports.HealthBar = HealthBar;
50503
50929
  exports.I18nProvider = I18nProvider;
50930
+ exports.LearningScene3D = LearningScene3D;
50504
50931
  exports.MapView = MapView;
50505
50932
  exports.MasterDetail = MasterDetail;
50506
50933
  exports.NotifyListener = NotifyListener;
@@ -50526,6 +50953,8 @@ exports.TraitStateViewer = TraitStateViewer;
50526
50953
  exports.TransitionArrow = TransitionArrow;
50527
50954
  exports.UISlotComponent = UISlotComponent;
50528
50955
  exports.UISlotRenderer = UISlotRenderer;
50956
+ exports.arrowBetween = arrowBetween;
50957
+ exports.billboardLabel = billboardLabel;
50529
50958
  exports.boardEntity = boardEntity;
50530
50959
  exports.bool = bool;
50531
50960
  exports.calculateAttackTargets = calculateAttackTargets;
@@ -50535,6 +50964,8 @@ exports.createInitialGameState = createInitialGameState;
50535
50964
  exports.createSharedEntityStore = createSharedEntityStore;
50536
50965
  exports.createTranslate = createTranslate;
50537
50966
  exports.createUnitAnimationState = createUnitAnimationState;
50967
+ exports.cylinderBetween = cylinderBetween;
50968
+ exports.get3DClickPayload = get3DClickPayload;
50538
50969
  exports.getCurrentFrame = getCurrentFrame;
50539
50970
  exports.getTileDimensions = getTileDimensions;
50540
50971
  exports.inferDirection = inferDirection;
@@ -50542,6 +50973,7 @@ exports.isoToScreen = isoToScreen;
50542
50973
  exports.makeAsset = makeAsset;
50543
50974
  exports.makeAssetMap = makeAssetMap;
50544
50975
  exports.mapBookData = mapBookData;
50976
+ exports.meshSphere = meshSphere;
50545
50977
  exports.num = num;
50546
50978
  exports.objAvailableActions = objAvailableActions;
50547
50979
  exports.objAvailableEvents = objAvailableEvents;