@almadar/ui 5.147.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 +535 -15
  4. package/dist/avl/index.js +535 -15
  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 +545 -18
  10. package/dist/components/index.d.cts +278 -98
  11. package/dist/components/index.d.ts +278 -98
  12. package/dist/components/index.js +541 -20
  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 +535 -15
  22. package/dist/providers/index.d.cts +2 -1
  23. package/dist/providers/index.d.ts +2 -1
  24. package/dist/providers/index.js +535 -15
  25. package/dist/runtime/index.cjs +535 -15
  26. package/dist/runtime/index.js +535 -15
  27. package/dist/types-B3nHgnMG.d.cts +51 -0
  28. package/dist/types-B3nHgnMG.d.ts +51 -0
  29. package/package.json +4 -4
@@ -8690,7 +8690,10 @@ var init_LearningCanvas = __esm({
8690
8690
  ctx.fillRect(0, 0, width, height);
8691
8691
  }
8692
8692
  for (const shape of shapes) {
8693
- drawShape(ctx, shape, width, height);
8693
+ if (shape.type !== "text") drawShape(ctx, shape, width, height);
8694
+ }
8695
+ for (const shape of shapes) {
8696
+ if (shape.type === "text") drawShape(ctx, shape, width, height);
8694
8697
  }
8695
8698
  }, [width, height, backgroundColor, shapes]);
8696
8699
  React77.useEffect(() => {
@@ -8771,7 +8774,7 @@ var init_AlgorithmCanvas = __esm({
8771
8774
  DEFAULT_CELL_COLOR = "#e5e7eb";
8772
8775
  DEFAULT_POINTER_COLOR = "#dc2626";
8773
8776
  POINTER_BAND = 34;
8774
- TOP_PAD = 12;
8777
+ TOP_PAD = 26;
8775
8778
  exports.AlgorithmCanvas = ({
8776
8779
  className,
8777
8780
  width = 600,
@@ -9875,23 +9878,188 @@ var init_BehaviorView = __esm({
9875
9878
  exports.BehaviorView.displayName = "BehaviorView";
9876
9879
  }
9877
9880
  });
9878
- 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;
9879
10039
  var init_BiologyCanvas = __esm({
9880
10040
  "components/learning/molecules/BiologyCanvas.tsx"() {
9881
10041
  "use client";
9882
10042
  init_atoms();
9883
10043
  init_Stack();
9884
10044
  init_LearningCanvas();
10045
+ init_learningScene3D();
10046
+ biologyLog = logger.createLogger("almadar:ui:biology-canvas");
9885
10047
  exports.BiologyCanvas = ({
9886
10048
  className,
9887
10049
  width = 600,
9888
10050
  height = 400,
9889
10051
  title,
9890
10052
  backgroundColor,
10053
+ mode = "2d",
10054
+ camera,
10055
+ lighting,
10056
+ post,
9891
10057
  nodes = [],
9892
10058
  edges = [],
9893
10059
  shapes = [],
9894
- interactive = false,
10060
+ showGrid,
10061
+ shadows,
10062
+ interactive,
9895
10063
  animate = false,
9896
10064
  onShapeClick,
9897
10065
  isLoading,
@@ -9952,6 +10120,75 @@ var init_BiologyCanvas = __esm({
9952
10120
  out.push(...shapes);
9953
10121
  return out;
9954
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
+ }
9955
10192
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
9956
10193
  title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
9957
10194
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -9961,7 +10198,7 @@ var init_BiologyCanvas = __esm({
9961
10198
  height,
9962
10199
  backgroundColor,
9963
10200
  shapes: derivedShapes,
9964
- interactive,
10201
+ interactive: interactive ?? false,
9965
10202
  animate,
9966
10203
  onShapeClick,
9967
10204
  isLoading,
@@ -17353,9 +17590,10 @@ function Canvas({
17353
17590
  drawables: [...drawables ?? [], ...childDrawables],
17354
17591
  isLoading,
17355
17592
  cameraMode: to3DCameraMode(camera?.mode),
17356
- ...zoom !== void 0 ? { scale: zoom } : {},
17593
+ ...zoom !== void 0 ? { zoom } : {},
17357
17594
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
17358
17595
  ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
17596
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
17359
17597
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
17360
17598
  unitScale,
17361
17599
  backgroundColor,
@@ -17378,7 +17616,7 @@ function Canvas({
17378
17616
  keyUpMap
17379
17617
  };
17380
17618
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17381
- /* @__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 }) }),
17382
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 }) })
17383
17621
  ] });
17384
17622
  }
@@ -17408,13 +17646,13 @@ function Canvas({
17408
17646
  }
17409
17647
  );
17410
17648
  }
17411
- var Canvas3DHost, canvasLog;
17649
+ var Canvas3DHost2, canvasLog;
17412
17650
  var init_Canvas = __esm({
17413
17651
  "components/game/molecules/Canvas.tsx"() {
17414
17652
  "use client";
17415
17653
  init_registry();
17416
17654
  init_Canvas2D();
17417
- Canvas3DHost = React77.lazy(
17655
+ Canvas3DHost2 = React77.lazy(
17418
17656
  () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
17419
17657
  );
17420
17658
  canvasLog = logger.createLogger("almadar:ui:game-canvas");
@@ -19141,24 +19379,41 @@ var init_ChatBar = __esm({
19141
19379
  ChatBar.displayName = "ChatBar";
19142
19380
  }
19143
19381
  });
19144
- 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;
19145
19392
  var init_ChemistryCanvas = __esm({
19146
19393
  "components/learning/molecules/ChemistryCanvas.tsx"() {
19147
19394
  "use client";
19148
19395
  init_atoms();
19149
19396
  init_Stack();
19150
19397
  init_LearningCanvas();
19398
+ init_learningScene3D();
19399
+ chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
19151
19400
  exports.ChemistryCanvas = ({
19152
19401
  className,
19153
19402
  width = 600,
19154
19403
  height = 400,
19155
19404
  title,
19156
19405
  backgroundColor,
19406
+ mode = "2d",
19407
+ camera,
19408
+ lighting,
19409
+ post,
19157
19410
  atoms = [],
19158
19411
  bonds = [],
19159
19412
  arrows = [],
19160
19413
  shapes = [],
19161
- interactive = false,
19414
+ showGrid,
19415
+ shadows,
19416
+ interactive,
19162
19417
  animate = false,
19163
19418
  onShapeClick,
19164
19419
  isLoading,
@@ -19237,6 +19492,88 @@ var init_ChemistryCanvas = __esm({
19237
19492
  out.push(...shapes);
19238
19493
  return out;
19239
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
+ }
19240
19577
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
19241
19578
  title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
19242
19579
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -19246,7 +19583,7 @@ var init_ChemistryCanvas = __esm({
19246
19583
  height,
19247
19584
  backgroundColor,
19248
19585
  shapes: derivedShapes,
19249
- interactive,
19586
+ interactive: interactive ?? false,
19250
19587
  animate,
19251
19588
  onShapeClick,
19252
19589
  isLoading,
@@ -23756,6 +24093,7 @@ var init_FilterGroup = __esm({
23756
24093
  init_Badge();
23757
24094
  init_Stack();
23758
24095
  init_Icon();
24096
+ init_RangeSlider();
23759
24097
  init_useEventBus();
23760
24098
  init_useQuerySingleton();
23761
24099
  resolveFilterType = (filter) => filter.filterType ?? filter.type;
@@ -23950,6 +24288,35 @@ var init_FilterGroup = __esm({
23950
24288
  onClear: () => handleFilterSelect(`${filter.field}_to`, null)
23951
24289
  }
23952
24290
  )
24291
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
24292
+ /* @__PURE__ */ jsxRuntime.jsx(
24293
+ exports.RangeSlider,
24294
+ {
24295
+ min: filter.min ?? 0,
24296
+ max: filter.max ?? 100,
24297
+ step: filter.step ?? 1,
24298
+ value: Number(
24299
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
24300
+ ),
24301
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
24302
+ showTooltip: true,
24303
+ "aria-label": t("filterGroup.from")
24304
+ }
24305
+ ),
24306
+ /* @__PURE__ */ jsxRuntime.jsx(
24307
+ exports.RangeSlider,
24308
+ {
24309
+ min: filter.min ?? 0,
24310
+ max: filter.max ?? 100,
24311
+ step: filter.step ?? 1,
24312
+ value: Number(
24313
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
24314
+ ),
24315
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
24316
+ showTooltip: true,
24317
+ "aria-label": t("filterGroup.to")
24318
+ }
24319
+ )
23953
24320
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsxRuntime.jsx(
23954
24321
  exports.Input,
23955
24322
  {
@@ -24032,6 +24399,36 @@ var init_FilterGroup = __esm({
24032
24399
  className: "text-sm min-w-[100px]"
24033
24400
  }
24034
24401
  )
24402
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", align: "center", children: [
24403
+ /* @__PURE__ */ jsxRuntime.jsx(
24404
+ exports.RangeSlider,
24405
+ {
24406
+ min: filter.min ?? 0,
24407
+ max: filter.max ?? 100,
24408
+ step: filter.step ?? 1,
24409
+ value: Number(
24410
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
24411
+ ),
24412
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
24413
+ className: "min-w-[100px]",
24414
+ "aria-label": t("filterGroup.from")
24415
+ }
24416
+ ),
24417
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "-" }),
24418
+ /* @__PURE__ */ jsxRuntime.jsx(
24419
+ exports.RangeSlider,
24420
+ {
24421
+ min: filter.min ?? 0,
24422
+ max: filter.max ?? 100,
24423
+ step: filter.step ?? 1,
24424
+ value: Number(
24425
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
24426
+ ),
24427
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
24428
+ className: "min-w-[100px]",
24429
+ "aria-label": t("filterGroup.to")
24430
+ }
24431
+ )
24035
24432
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsxRuntime.jsx(
24036
24433
  exports.Input,
24037
24434
  {
@@ -24150,6 +24547,38 @@ var init_FilterGroup = __esm({
24150
24547
  className: "min-w-[130px]"
24151
24548
  }
24152
24549
  )
24550
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", align: "center", children: [
24551
+ /* @__PURE__ */ jsxRuntime.jsx(
24552
+ exports.RangeSlider,
24553
+ {
24554
+ min: filter.min ?? 0,
24555
+ max: filter.max ?? 100,
24556
+ step: filter.step ?? 1,
24557
+ value: Number(
24558
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
24559
+ ),
24560
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
24561
+ showTooltip: true,
24562
+ className: "min-w-[130px]",
24563
+ "aria-label": t("filterGroup.from")
24564
+ }
24565
+ ),
24566
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "-" }),
24567
+ /* @__PURE__ */ jsxRuntime.jsx(
24568
+ exports.RangeSlider,
24569
+ {
24570
+ min: filter.min ?? 0,
24571
+ max: filter.max ?? 100,
24572
+ step: filter.step ?? 1,
24573
+ value: Number(
24574
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
24575
+ ),
24576
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
24577
+ showTooltip: true,
24578
+ className: "min-w-[130px]",
24579
+ "aria-label": t("filterGroup.to")
24580
+ }
24581
+ )
24153
24582
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsxRuntime.jsx(
24154
24583
  exports.Input,
24155
24584
  {
@@ -29182,19 +29611,25 @@ var init_MathCanvas = __esm({
29182
29611
  };
29183
29612
  }
29184
29613
  });
29185
- exports.PhysicsCanvas = void 0;
29614
+ var physicsLog2; exports.PhysicsCanvas = void 0;
29186
29615
  var init_PhysicsCanvas = __esm({
29187
29616
  "components/learning/molecules/PhysicsCanvas.tsx"() {
29188
29617
  "use client";
29189
29618
  init_atoms();
29190
29619
  init_Stack();
29191
29620
  init_LearningCanvas();
29621
+ init_learningScene3D();
29622
+ physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
29192
29623
  exports.PhysicsCanvas = ({
29193
29624
  className,
29194
29625
  width = 600,
29195
29626
  height = 400,
29196
29627
  title,
29197
29628
  backgroundColor,
29629
+ mode = "2d",
29630
+ camera,
29631
+ lighting,
29632
+ post,
29198
29633
  bodies = [],
29199
29634
  constraints = [],
29200
29635
  showVelocity = true,
@@ -29202,7 +29637,9 @@ var init_PhysicsCanvas = __esm({
29202
29637
  velocityScale = 20,
29203
29638
  forceScale = 20,
29204
29639
  shapes = [],
29205
- interactive = false,
29640
+ showGrid,
29641
+ shadows,
29642
+ interactive,
29206
29643
  animate = false,
29207
29644
  onShapeClick,
29208
29645
  isLoading,
@@ -29274,6 +29711,89 @@ var init_PhysicsCanvas = __esm({
29274
29711
  out.push(...shapes);
29275
29712
  return out;
29276
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
+ }
29277
29797
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
29278
29798
  title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
29279
29799
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -29283,7 +29803,7 @@ var init_PhysicsCanvas = __esm({
29283
29803
  height,
29284
29804
  backgroundColor,
29285
29805
  shapes: derivedShapes,
29286
- interactive,
29806
+ interactive: interactive ?? false,
29287
29807
  animate,
29288
29808
  onShapeClick,
29289
29809
  isLoading,
@@ -29901,12 +30421,12 @@ var init_MapView = __esm({
29901
30421
  shadowSize: [41, 41]
29902
30422
  });
29903
30423
  L.Marker.prototype.options.icon = defaultIcon;
29904
- 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;
29905
30425
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29906
30426
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29907
30427
  function MapUpdater({ centerLat, centerLng, zoom }) {
29908
30428
  const map = useMap();
29909
- const prevRef = useRef64({ centerLat, centerLng, zoom });
30429
+ const prevRef = useRef65({ centerLat, centerLng, zoom });
29910
30430
  useEffect66(() => {
29911
30431
  const prev = prevRef.current;
29912
30432
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -39698,6 +40218,7 @@ var init_molecules2 = __esm({
39698
40218
  init_BiologyCanvas();
39699
40219
  init_ChemistryCanvas();
39700
40220
  init_AlgorithmCanvas();
40221
+ init_learningScene3D();
39701
40222
  init_GraphView();
39702
40223
  init_MapView();
39703
40224
  init_NumberStepper();
@@ -40951,7 +41472,7 @@ function getEnumOptions(field) {
40951
41472
  }));
40952
41473
  }
40953
41474
  const validation = field.validation;
40954
- if (validation?.enum && Array.isArray(validation.enum)) {
41475
+ if (validation?.enum && validation.enum.length > 0) {
40955
41476
  return validation.enum.map((v) => ({
40956
41477
  value: v,
40957
41478
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")
@@ -50406,6 +50927,7 @@ exports.GameIcon = GameIcon;
50406
50927
  exports.GameMenu = GameMenu;
50407
50928
  exports.HealthBar = HealthBar;
50408
50929
  exports.I18nProvider = I18nProvider;
50930
+ exports.LearningScene3D = LearningScene3D;
50409
50931
  exports.MapView = MapView;
50410
50932
  exports.MasterDetail = MasterDetail;
50411
50933
  exports.NotifyListener = NotifyListener;
@@ -50431,6 +50953,8 @@ exports.TraitStateViewer = TraitStateViewer;
50431
50953
  exports.TransitionArrow = TransitionArrow;
50432
50954
  exports.UISlotComponent = UISlotComponent;
50433
50955
  exports.UISlotRenderer = UISlotRenderer;
50956
+ exports.arrowBetween = arrowBetween;
50957
+ exports.billboardLabel = billboardLabel;
50434
50958
  exports.boardEntity = boardEntity;
50435
50959
  exports.bool = bool;
50436
50960
  exports.calculateAttackTargets = calculateAttackTargets;
@@ -50440,6 +50964,8 @@ exports.createInitialGameState = createInitialGameState;
50440
50964
  exports.createSharedEntityStore = createSharedEntityStore;
50441
50965
  exports.createTranslate = createTranslate;
50442
50966
  exports.createUnitAnimationState = createUnitAnimationState;
50967
+ exports.cylinderBetween = cylinderBetween;
50968
+ exports.get3DClickPayload = get3DClickPayload;
50443
50969
  exports.getCurrentFrame = getCurrentFrame;
50444
50970
  exports.getTileDimensions = getTileDimensions;
50445
50971
  exports.inferDirection = inferDirection;
@@ -50447,6 +50973,7 @@ exports.isoToScreen = isoToScreen;
50447
50973
  exports.makeAsset = makeAsset;
50448
50974
  exports.makeAssetMap = makeAssetMap;
50449
50975
  exports.mapBookData = mapBookData;
50976
+ exports.meshSphere = meshSphere;
50450
50977
  exports.num = num;
50451
50978
  exports.objAvailableActions = objAvailableActions;
50452
50979
  exports.objAvailableEvents = objAvailableEvents;