@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
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import * as React77 from 'react';
3
- import React77__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, useState, useLayoutEffect, Suspense, useSyncExternalStore, lazy, useId } from 'react';
3
+ import React77__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, useState, useLayoutEffect, useId, Suspense, useSyncExternalStore, lazy } from 'react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { EventBusContext, useTraitScopeChain, useCurrentPagePath, useGameAudioContextOptional, useEntitySchemaOptional, useEntityBindingSnapshot, useTraitScope, TraitScopeProvider } from '@almadar/ui/providers';
@@ -9803,23 +9803,188 @@ var init_BehaviorView = __esm({
9803
9803
  BehaviorView.displayName = "BehaviorView";
9804
9804
  }
9805
9805
  });
9806
- var BiologyCanvas;
9806
+ function LearningScene3D({
9807
+ className,
9808
+ width = 600,
9809
+ height = 400,
9810
+ title,
9811
+ backgroundColor,
9812
+ drawables,
9813
+ camera,
9814
+ lighting,
9815
+ post,
9816
+ showGrid = false,
9817
+ shadows,
9818
+ interactive,
9819
+ isLoading,
9820
+ error,
9821
+ onItemClick
9822
+ }) {
9823
+ const instanceId = useId().replace(/[^a-zA-Z0-9]/g, "");
9824
+ const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
9825
+ const onItemClickRef = useRef(onItemClick);
9826
+ onItemClickRef.current = onItemClick;
9827
+ useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
9828
+ const unitId = event.payload?.unitId;
9829
+ if (typeof unitId === "string") onItemClickRef.current?.(unitId);
9830
+ });
9831
+ const props3d = {
9832
+ drawables,
9833
+ isLoading,
9834
+ error: error ? error.message : null,
9835
+ cameraMode: camera?.mode ?? "perspective",
9836
+ ...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
9837
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
9838
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
9839
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
9840
+ ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
9841
+ backgroundColor,
9842
+ showGrid,
9843
+ ...shadows !== void 0 ? { shadows } : {},
9844
+ ...interactive !== void 0 ? { controlsEnabled: interactive } : {},
9845
+ lighting,
9846
+ post,
9847
+ ...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
9848
+ };
9849
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
9850
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
9851
+ /* @__PURE__ */ jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost, { ...props3d }) }) })
9852
+ ] }) });
9853
+ }
9854
+ function meshSphere(id, x, y, z, radius, color, opts) {
9855
+ return {
9856
+ type: "draw-mesh",
9857
+ ...id !== void 0 ? { id } : {},
9858
+ shape: opts?.shape ?? "sphere",
9859
+ position: { x, y, z },
9860
+ radius,
9861
+ pivot: "center",
9862
+ segments: opts?.segments ?? 24,
9863
+ material: { color, ...opts?.material },
9864
+ ...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
9865
+ };
9866
+ }
9867
+ function axisRotation(from, to) {
9868
+ const wx = to[0] - from[0];
9869
+ const wy = to[2] - from[2];
9870
+ const wz = to[1] - from[1];
9871
+ const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
9872
+ const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
9873
+ return [0, Math.atan2(wz, -wx), tilt];
9874
+ }
9875
+ function segmentLength(from, to) {
9876
+ const dx = to[0] - from[0];
9877
+ const dy = to[1] - from[1];
9878
+ const dz = to[2] - from[2];
9879
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
9880
+ }
9881
+ function cylinderBetween(from, to, radius, color) {
9882
+ const len = segmentLength(from, to);
9883
+ if (len < 1e-6) return null;
9884
+ return {
9885
+ type: "draw-mesh",
9886
+ shape: "cylinder",
9887
+ position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
9888
+ radius,
9889
+ height: len,
9890
+ rotation: axisRotation(from, to),
9891
+ pivot: "center",
9892
+ segments: 12,
9893
+ material: { color }
9894
+ };
9895
+ }
9896
+ function arrowBetween(from, to, color, shaftRadius = 0.08) {
9897
+ const len = segmentLength(from, to);
9898
+ if (len < 1e-6) return null;
9899
+ const tipLen = Math.min(shaftRadius * 8, len * 0.35);
9900
+ const k = (len - tipLen) / len;
9901
+ const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
9902
+ const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
9903
+ const tipStart = shaftEnd;
9904
+ const tipEnd = delta;
9905
+ const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
9906
+ const tipLenActual = segmentLength(tipStart, tipEnd);
9907
+ const tip = {
9908
+ type: "draw-mesh",
9909
+ shape: "cone",
9910
+ position: {
9911
+ x: (tipStart[0] + tipEnd[0]) / 2,
9912
+ y: (tipStart[1] + tipEnd[1]) / 2,
9913
+ z: (tipStart[2] + tipEnd[2]) / 2
9914
+ },
9915
+ radius: shaftRadius * 3,
9916
+ height: tipLenActual,
9917
+ rotation: axisRotation(tipStart, tipEnd),
9918
+ pivot: "center",
9919
+ segments: 12,
9920
+ material: { color }
9921
+ };
9922
+ return {
9923
+ type: "draw-group",
9924
+ position: { x: from[0], y: from[1], z: from[2] },
9925
+ items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
9926
+ };
9927
+ }
9928
+ function billboardLabel(text, x, y, z, opts) {
9929
+ return {
9930
+ type: "draw-text",
9931
+ text,
9932
+ position: { x, y, z },
9933
+ color: opts?.color ?? "#111827"
9934
+ };
9935
+ }
9936
+ function labelColorForBackground(backgroundColor) {
9937
+ const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
9938
+ if (!m) return "#111827";
9939
+ const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
9940
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
9941
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
9942
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
9943
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
9944
+ return luminance < 0.5 ? "#e5e7eb" : "#111827";
9945
+ }
9946
+ function get3DClickPayload(onShapeClick, idToIndex) {
9947
+ if (!onShapeClick) return void 0;
9948
+ return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
9949
+ }
9950
+ var Canvas3DHost;
9951
+ var init_learningScene3D = __esm({
9952
+ "components/learning/molecules/learningScene3D.tsx"() {
9953
+ "use client";
9954
+ init_atoms();
9955
+ init_Stack();
9956
+ init_useEventBus();
9957
+ Canvas3DHost = lazy(
9958
+ () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
9959
+ );
9960
+ LearningScene3D.displayName = "LearningScene3D";
9961
+ }
9962
+ });
9963
+ var biologyLog, BiologyCanvas;
9807
9964
  var init_BiologyCanvas = __esm({
9808
9965
  "components/learning/molecules/BiologyCanvas.tsx"() {
9809
9966
  "use client";
9810
9967
  init_atoms();
9811
9968
  init_Stack();
9812
9969
  init_LearningCanvas();
9970
+ init_learningScene3D();
9971
+ biologyLog = createLogger("almadar:ui:biology-canvas");
9813
9972
  BiologyCanvas = ({
9814
9973
  className,
9815
9974
  width = 600,
9816
9975
  height = 400,
9817
9976
  title,
9818
9977
  backgroundColor,
9978
+ mode = "2d",
9979
+ camera,
9980
+ lighting,
9981
+ post,
9819
9982
  nodes = [],
9820
9983
  edges = [],
9821
9984
  shapes = [],
9822
- interactive = false,
9985
+ showGrid,
9986
+ shadows,
9987
+ interactive,
9823
9988
  animate = false,
9824
9989
  onShapeClick,
9825
9990
  isLoading,
@@ -9880,6 +10045,75 @@ var init_BiologyCanvas = __esm({
9880
10045
  out.push(...shapes);
9881
10046
  return out;
9882
10047
  }, [nodes, edges, shapes]);
10048
+ const drawables3D = useMemo(() => {
10049
+ if (mode !== "3d") return [];
10050
+ if (shapes.length > 0) {
10051
+ biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
10052
+ }
10053
+ const out = [];
10054
+ const labelColor = labelColorForBackground(backgroundColor);
10055
+ const nodeById = /* @__PURE__ */ new Map();
10056
+ for (const n of nodes) {
10057
+ if (n.id) nodeById.set(n.id, n);
10058
+ }
10059
+ for (const e of edges) {
10060
+ const a = nodeById.get(e.from);
10061
+ const b = nodeById.get(e.to);
10062
+ if (!a || !b) continue;
10063
+ const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
10064
+ const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
10065
+ if (edge) out.push(edge);
10066
+ if (e.label) {
10067
+ out.push(
10068
+ billboardLabel(
10069
+ e.label,
10070
+ (a.x + b.x) / 2,
10071
+ (a.y + b.y) / 2,
10072
+ ((a.z ?? 0) + (b.z ?? 0)) / 2,
10073
+ { color: labelColor }
10074
+ )
10075
+ );
10076
+ }
10077
+ }
10078
+ for (const n of nodes) {
10079
+ const radius = n.radius ?? 0.5;
10080
+ const nz = n.z ?? 0;
10081
+ out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
10082
+ if (n.label) {
10083
+ out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
10084
+ }
10085
+ }
10086
+ return out;
10087
+ }, [mode, nodes, edges, shapes, backgroundColor]);
10088
+ const nodeIndexById = useMemo(() => {
10089
+ const m = /* @__PURE__ */ new Map();
10090
+ nodes.forEach((n, i) => {
10091
+ if (n.id) m.set(n.id, i);
10092
+ });
10093
+ return m;
10094
+ }, [nodes]);
10095
+ if (mode === "3d") {
10096
+ return /* @__PURE__ */ jsx(
10097
+ LearningScene3D,
10098
+ {
10099
+ className,
10100
+ width,
10101
+ height,
10102
+ title,
10103
+ backgroundColor,
10104
+ drawables: drawables3D,
10105
+ camera,
10106
+ lighting,
10107
+ post,
10108
+ showGrid,
10109
+ shadows,
10110
+ interactive,
10111
+ isLoading,
10112
+ error,
10113
+ onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
10114
+ }
10115
+ );
10116
+ }
9883
10117
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
9884
10118
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
9885
10119
  /* @__PURE__ */ jsx(
@@ -9889,7 +10123,7 @@ var init_BiologyCanvas = __esm({
9889
10123
  height,
9890
10124
  backgroundColor,
9891
10125
  shapes: derivedShapes,
9892
- interactive,
10126
+ interactive: interactive ?? false,
9893
10127
  animate,
9894
10128
  onShapeClick,
9895
10129
  isLoading,
@@ -17281,9 +17515,10 @@ function Canvas({
17281
17515
  drawables: [...drawables ?? [], ...childDrawables],
17282
17516
  isLoading,
17283
17517
  cameraMode: to3DCameraMode(camera?.mode),
17284
- ...zoom !== void 0 ? { scale: zoom } : {},
17518
+ ...zoom !== void 0 ? { zoom } : {},
17285
17519
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
17286
17520
  ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
17521
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
17287
17522
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
17288
17523
  unitScale,
17289
17524
  backgroundColor,
@@ -17306,7 +17541,7 @@ function Canvas({
17306
17541
  keyUpMap
17307
17542
  };
17308
17543
  return /* @__PURE__ */ jsxs(Fragment, { children: [
17309
- /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost, { ...props3d }) }),
17544
+ /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost2, { ...props3d }) }),
17310
17545
  React77.Children.count(children) > 0 && /* @__PURE__ */ jsx(DrawableRegistryContext.Provider, { value: registerChildDrawable, children: /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children }) })
17311
17546
  ] });
17312
17547
  }
@@ -17336,13 +17571,13 @@ function Canvas({
17336
17571
  }
17337
17572
  );
17338
17573
  }
17339
- var Canvas3DHost, canvasLog;
17574
+ var Canvas3DHost2, canvasLog;
17340
17575
  var init_Canvas = __esm({
17341
17576
  "components/game/molecules/Canvas.tsx"() {
17342
17577
  "use client";
17343
17578
  init_registry();
17344
17579
  init_Canvas2D();
17345
- Canvas3DHost = lazy(
17580
+ Canvas3DHost2 = lazy(
17346
17581
  () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
17347
17582
  );
17348
17583
  canvasLog = createLogger("almadar:ui:game-canvas");
@@ -19069,24 +19304,41 @@ var init_ChatBar = __esm({
19069
19304
  ChatBar.displayName = "ChatBar";
19070
19305
  }
19071
19306
  });
19072
- var ChemistryCanvas;
19307
+ function bondPerpendicular(a, b) {
19308
+ const dx = b[0] - a[0];
19309
+ const dy = b[1] - a[1];
19310
+ const px = dy;
19311
+ const py = -dx;
19312
+ const len = Math.sqrt(px * px + py * py);
19313
+ if (len < 1e-6) return [1, 0, 0];
19314
+ return [px / len, py / len, 0];
19315
+ }
19316
+ var chemistryLog, ChemistryCanvas;
19073
19317
  var init_ChemistryCanvas = __esm({
19074
19318
  "components/learning/molecules/ChemistryCanvas.tsx"() {
19075
19319
  "use client";
19076
19320
  init_atoms();
19077
19321
  init_Stack();
19078
19322
  init_LearningCanvas();
19323
+ init_learningScene3D();
19324
+ chemistryLog = createLogger("almadar:ui:chemistry-canvas");
19079
19325
  ChemistryCanvas = ({
19080
19326
  className,
19081
19327
  width = 600,
19082
19328
  height = 400,
19083
19329
  title,
19084
19330
  backgroundColor,
19331
+ mode = "2d",
19332
+ camera,
19333
+ lighting,
19334
+ post,
19085
19335
  atoms = [],
19086
19336
  bonds = [],
19087
19337
  arrows = [],
19088
19338
  shapes = [],
19089
- interactive = false,
19339
+ showGrid,
19340
+ shadows,
19341
+ interactive,
19090
19342
  animate = false,
19091
19343
  onShapeClick,
19092
19344
  isLoading,
@@ -19165,6 +19417,88 @@ var init_ChemistryCanvas = __esm({
19165
19417
  out.push(...shapes);
19166
19418
  return out;
19167
19419
  }, [atoms, bonds, arrows, shapes]);
19420
+ const drawables3D = useMemo(() => {
19421
+ if (mode !== "3d") return [];
19422
+ if (shapes.length > 0) {
19423
+ chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
19424
+ }
19425
+ const out = [];
19426
+ const labelColor = labelColorForBackground(backgroundColor);
19427
+ const atomById = /* @__PURE__ */ new Map();
19428
+ for (const a of atoms) {
19429
+ if (a.id) atomById.set(a.id, a);
19430
+ }
19431
+ for (const b of bonds) {
19432
+ const a = atomById.get(b.from);
19433
+ const c = atomById.get(b.to);
19434
+ if (!a || !c) continue;
19435
+ const color = b.color ?? "#6b7280";
19436
+ const from = [a.x, a.y, a.z ?? 0];
19437
+ const to = [c.x, c.y, c.z ?? 0];
19438
+ const perp = bondPerpendicular(from, to);
19439
+ const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
19440
+ const step = bondRadius * 2.2;
19441
+ const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
19442
+ for (const off of offsets) {
19443
+ const bond = cylinderBetween(
19444
+ [from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
19445
+ [to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
19446
+ bondRadius,
19447
+ color
19448
+ );
19449
+ if (bond) out.push(bond);
19450
+ }
19451
+ }
19452
+ for (const a of arrows) {
19453
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
19454
+ const len = a.length ?? 60;
19455
+ const x2 = a.x + Math.cos(angle) * len;
19456
+ const y2 = a.y + Math.sin(angle) * len;
19457
+ const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
19458
+ if (arrow) out.push(arrow);
19459
+ if (a.label) {
19460
+ out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
19461
+ }
19462
+ }
19463
+ for (const a of atoms) {
19464
+ const radius = a.radius ?? 0.45;
19465
+ const az = a.z ?? 0;
19466
+ out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
19467
+ if (a.element) {
19468
+ out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
19469
+ }
19470
+ }
19471
+ return out;
19472
+ }, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
19473
+ const atomIndexById = useMemo(() => {
19474
+ const m = /* @__PURE__ */ new Map();
19475
+ atoms.forEach((a, i) => {
19476
+ if (a.id) m.set(a.id, i);
19477
+ });
19478
+ return m;
19479
+ }, [atoms]);
19480
+ if (mode === "3d") {
19481
+ return /* @__PURE__ */ jsx(
19482
+ LearningScene3D,
19483
+ {
19484
+ className,
19485
+ width,
19486
+ height,
19487
+ title,
19488
+ backgroundColor,
19489
+ drawables: drawables3D,
19490
+ camera,
19491
+ lighting,
19492
+ post,
19493
+ showGrid,
19494
+ shadows,
19495
+ interactive,
19496
+ isLoading,
19497
+ error,
19498
+ onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
19499
+ }
19500
+ );
19501
+ }
19168
19502
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
19169
19503
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
19170
19504
  /* @__PURE__ */ jsx(
@@ -19174,7 +19508,7 @@ var init_ChemistryCanvas = __esm({
19174
19508
  height,
19175
19509
  backgroundColor,
19176
19510
  shapes: derivedShapes,
19177
- interactive,
19511
+ interactive: interactive ?? false,
19178
19512
  animate,
19179
19513
  onShapeClick,
19180
19514
  isLoading,
@@ -29202,19 +29536,25 @@ var init_MathCanvas = __esm({
29202
29536
  };
29203
29537
  }
29204
29538
  });
29205
- var PhysicsCanvas;
29539
+ var physicsLog2, PhysicsCanvas;
29206
29540
  var init_PhysicsCanvas = __esm({
29207
29541
  "components/learning/molecules/PhysicsCanvas.tsx"() {
29208
29542
  "use client";
29209
29543
  init_atoms();
29210
29544
  init_Stack();
29211
29545
  init_LearningCanvas();
29546
+ init_learningScene3D();
29547
+ physicsLog2 = createLogger("almadar:ui:physics-canvas");
29212
29548
  PhysicsCanvas = ({
29213
29549
  className,
29214
29550
  width = 600,
29215
29551
  height = 400,
29216
29552
  title,
29217
29553
  backgroundColor,
29554
+ mode = "2d",
29555
+ camera,
29556
+ lighting,
29557
+ post,
29218
29558
  bodies = [],
29219
29559
  constraints = [],
29220
29560
  showVelocity = true,
@@ -29222,7 +29562,9 @@ var init_PhysicsCanvas = __esm({
29222
29562
  velocityScale = 20,
29223
29563
  forceScale = 20,
29224
29564
  shapes = [],
29225
- interactive = false,
29565
+ showGrid,
29566
+ shadows,
29567
+ interactive,
29226
29568
  animate = false,
29227
29569
  onShapeClick,
29228
29570
  isLoading,
@@ -29294,6 +29636,89 @@ var init_PhysicsCanvas = __esm({
29294
29636
  out.push(...shapes);
29295
29637
  return out;
29296
29638
  }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
29639
+ const drawables3D = useMemo(() => {
29640
+ if (mode !== "3d") return [];
29641
+ if (shapes.length > 0) {
29642
+ physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
29643
+ }
29644
+ const out = [];
29645
+ const labelColor = labelColorForBackground(backgroundColor);
29646
+ const bodyById = /* @__PURE__ */ new Map();
29647
+ for (const b of bodies) {
29648
+ if (b.id) bodyById.set(b.id, b);
29649
+ }
29650
+ for (const c of constraints) {
29651
+ const a = bodyById.get(c.from);
29652
+ const b = bodyById.get(c.to);
29653
+ if (!a || !b) continue;
29654
+ const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
29655
+ const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
29656
+ if (rod) out.push(rod);
29657
+ }
29658
+ for (const b of bodies) {
29659
+ const radius = b.radius ?? 0.5;
29660
+ const bz = b.z ?? 0;
29661
+ out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
29662
+ if (b.label) {
29663
+ out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
29664
+ }
29665
+ const vx = b.vx ?? 0;
29666
+ const vy = b.vy ?? 0;
29667
+ const vz = b.vz ?? 0;
29668
+ const arrowRadius = Math.max(0.05, radius * 0.15);
29669
+ if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
29670
+ const arrow = arrowBetween(
29671
+ [b.x, b.y, bz],
29672
+ [b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
29673
+ "#16a34a",
29674
+ arrowRadius
29675
+ );
29676
+ if (arrow) out.push(arrow);
29677
+ }
29678
+ const fx = b.fx ?? 0;
29679
+ const fy = b.fy ?? 0;
29680
+ const fz = b.fz ?? 0;
29681
+ if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
29682
+ const arrow = arrowBetween(
29683
+ [b.x, b.y, bz],
29684
+ [b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
29685
+ "#dc2626",
29686
+ arrowRadius
29687
+ );
29688
+ if (arrow) out.push(arrow);
29689
+ }
29690
+ }
29691
+ return out;
29692
+ }, [mode, bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes, backgroundColor]);
29693
+ const bodyIndexById = useMemo(() => {
29694
+ const m = /* @__PURE__ */ new Map();
29695
+ bodies.forEach((b, i) => {
29696
+ if (b.id) m.set(b.id, i);
29697
+ });
29698
+ return m;
29699
+ }, [bodies]);
29700
+ if (mode === "3d") {
29701
+ return /* @__PURE__ */ jsx(
29702
+ LearningScene3D,
29703
+ {
29704
+ className,
29705
+ width,
29706
+ height,
29707
+ title,
29708
+ backgroundColor,
29709
+ drawables: drawables3D,
29710
+ camera,
29711
+ lighting,
29712
+ post,
29713
+ showGrid,
29714
+ shadows,
29715
+ interactive,
29716
+ isLoading,
29717
+ error,
29718
+ onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
29719
+ }
29720
+ );
29721
+ }
29297
29722
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
29298
29723
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
29299
29724
  /* @__PURE__ */ jsx(
@@ -29303,7 +29728,7 @@ var init_PhysicsCanvas = __esm({
29303
29728
  height,
29304
29729
  backgroundColor,
29305
29730
  shapes: derivedShapes,
29306
- interactive,
29731
+ interactive: interactive ?? false,
29307
29732
  animate,
29308
29733
  onShapeClick,
29309
29734
  isLoading,
@@ -29921,12 +30346,12 @@ var init_MapView = __esm({
29921
30346
  shadowSize: [41, 41]
29922
30347
  });
29923
30348
  L.Marker.prototype.options.icon = defaultIcon;
29924
- const { useEffect: useEffect66, useRef: useRef64, useCallback: useCallback107, useState: useState104 } = React77__default;
30349
+ const { useEffect: useEffect66, useRef: useRef65, useCallback: useCallback107, useState: useState104 } = React77__default;
29925
30350
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29926
30351
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29927
30352
  function MapUpdater({ centerLat, centerLng, zoom }) {
29928
30353
  const map = useMap();
29929
- const prevRef = useRef64({ centerLat, centerLng, zoom });
30354
+ const prevRef = useRef65({ centerLat, centerLng, zoom });
29930
30355
  useEffect66(() => {
29931
30356
  const prev = prevRef.current;
29932
30357
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -39718,6 +40143,7 @@ var init_molecules2 = __esm({
39718
40143
  init_BiologyCanvas();
39719
40144
  init_ChemistryCanvas();
39720
40145
  init_AlgorithmCanvas();
40146
+ init_learningScene3D();
39721
40147
  init_GraphView();
39722
40148
  init_MapView();
39723
40149
  init_NumberStepper();
@@ -40971,7 +41397,7 @@ function getEnumOptions(field) {
40971
41397
  }));
40972
41398
  }
40973
41399
  const validation = field.validation;
40974
- if (validation?.enum && Array.isArray(validation.enum)) {
41400
+ if (validation?.enum && validation.enum.length > 0) {
40975
41401
  return validation.enum.map((v) => ({
40976
41402
  value: v,
40977
41403
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")
@@ -50380,4 +50806,4 @@ function useGitHubBranches(owner, repo, enabled = true) {
50380
50806
  });
50381
50807
  }
50382
50808
 
50383
- export { ALL_PRESETS, ALMADAR_DND_MIME, AR_BOOK_FIELDS, AboutPageTemplate, Accordion, Card2 as ActionCard, ActionPalette, ActionTile, ActivationBlock, Alert, AlgorithmCanvas, AnimatedCounter, AnimatedGraphic, AnimatedReveal, ArticleSection, Aside, AssetPicker, AtlasImage, AtlasPanel, AuthLayout, Avatar, Badge, BehaviorView, BiologyCanvas, BloomQuizBlock, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, Box, BranchingLogicBuilder, Breadcrumb, Button, ButtonGroup, CTABanner, CalendarGrid, Canvas, Canvas2D, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, CaseStudyCard, CaseStudyOrganism, Center, Chart, ChartLegend, ChatBar, Checkbox, ChemistryCanvas, ChoiceButton, Coachmark, CodeBlock, CodeRunnerPanel, CollapsibleSection, CommunityLinks, ConditionalWrapper, ConfettiEffect, ConfirmDialog, ConnectionBlock, Container, ContentRenderer, ContentSection, ControlButton, ControlGrid, CounterTemplate, DEFAULT_LIKERT_OPTIONS, DEFAULT_MATRIX_COLUMNS, DEFAULT_SLOTS, DIAMOND_TOP_Y, DashboardGrid, DashboardLayout, DataGrid, DataList, DataTable, DateRangePicker, DateRangeSelector, DayCell, DetailPanel, Dialog, DialogueBubble, Divider, DocBreadcrumb, DocPagination, DocSearch, DocSidebar, DocTOC, DocumentViewer, StateMachineView as DomStateMachineVisualizer, Drawer, DrawerSlot, ELEMENT_SELECTED_EVENT, EdgeDecoration, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmojiPicker, EmptyState, EntityDisplayEvents, ErrorBoundary, ErrorState, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FeatureCard, FeatureDetailPageTemplate, FeatureGrid, FeatureGridOrganism, FileTree, FilterGroup, FilterPill, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, GameAudioToggle, GameHud, GameIcon, GameMenu, GameShell, GenericAppTemplate, GeometricPattern, GradientDivider, GraphCanvas, GraphView, Grid, GridPicker, HStack, Header, HealthBar, HeroOrganism, HeroSection, I18nProvider, IDENTITY_BOOK_FIELDS, Icon, IconPicker, ImportPreviewTree, ImportProgress, ImportSourcePicker, InfiniteScrollSentinel, Input, InputGroup, InstallBox, JazariStateMachine, JsonTreeEditor, Label, LandingPageTemplate, LawReferenceTooltip, LearningCanvas, Lightbox, LikertScale, LineChart2 as LineChart, List3 as List, LoadingState, MapView, MarkdownContent, MarketingFooter, MarketingStatCard, MasterDetail, MasterDetailLayout, MathCanvas, MatrixQuestion, MediaGallery, Menu, Meter, Modal, ModalSlot, ModuleCard, Navigation, NodeSlotEditor, NotifyListener, NumberStepper, OnboardingSpotlight, OptionConstraintGroup, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, Overlay, PageHeader, PageTransition, Pagination, PatternTile, PhysicsCanvas, Popover, PositionedCanvas, Presence, PricingCard, PricingGrid, PricingOrganism, PricingPageTemplate, ProgressBar, ProgressDots, PropertyInspector, PullQuote, PullToRefresh, QrScanner, QuizBlock, Radio, RangeSlider, ReflectionBlock, RelationSelect, RepeatableFormSection, ReplyTree, RichBlockEditor, RuntimeDebugger, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScaledDiagram, ScoreDisplay, SearchInput, Section, SectionHeader, SegmentRenderer, Select, SequenceBar, ServiceCatalog, SharedEntityStoreContext, ShowcaseCard, ShowcaseOrganism, SidePanel, Sidebar, SignaturePad, SimpleGrid, Skeleton, SlotContentRenderer, SocialProof, SortableList, Spacer, Sparkline, Spinner, Split, SplitPane, SplitSection, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateGraph, StateJsonView, StateMachineView, StateNode2 as StateNode, StatsGrid, StatsOrganism, StatusBar, StatusDot, StepFlow, StepFlowOrganism, SubagentTracePanel, SvgBranch, SvgConnection, SvgFlow, SvgGrid, SvgLobe, SvgMesh, SvgMorph, SvgNode, SvgPulse, SvgRing, SvgShield, SvgStack, SwipeableRow, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, TableView, Tabs, TagCloud, TagInput, TeamCard, TeamOrganism, TerrainPalette, TextHighlight, Textarea, ThemeToggle, TimeSlotCell, Timeline, TimerDisplay, Toast, ToastSlot, Tooltip, TraitFrame, TraitSlot, TraitStateViewer, TransitionArrow, TrendIndicator, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UploadDropZone, VStack, VersionDiff, ViolationAlert, VoteStack, WizardContainer, WizardNavigation, WizardProgress, boardEntity, bool, calculateAttackTargets, calculateValidMoves, cn, createInitialGameState, createSharedEntityStore, createTranslate, createUnitAnimationState, getCurrentFrame, getTileDimensions, inferDirection, isoToScreen, makeAsset, makeAssetMap, mapBookData, num, objAvailableActions, objAvailableEvents, objCurrentState, objIcon, objId, objMaxRules, objName, objRules, objStates, parseEditFocus, parseLessonSegments, parseMarkdownWithCodeBlocks, parseQueryBinding, pendulum, projectileMotion, registerCodeLanguageLoader, resolveFieldMap, resolveFrame, resolveSheetDirection, rows, runTickFrame, screenToIso, springOscillator, str, tickAnimationState, toCodeLanguage, transitionAnimation, unitHealth, unitPosition, unitTeam, useAgentChat, useAnchorRect, useAtlasSliceDataUrl, useAuthContext, useCamera, useCanvasGestures, useCompile, useConnectGitHub, useDeepAgentGeneration, useDisconnectGitHub, useDragReorder, useDraggable, useDropZone, useEmitEvent, useEventBus, useEventListener, useExtensions, useFileEditor, useFileSystem, useGameAudio, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useImageCache, useInfiniteScroll, useLongPress, useMediaQuery, useOrbitalHistory, usePresence, usePreview, usePullToRefresh, useQuerySingleton, useRenderInterpolation, useSharedEntitySnapshot, useSharedEntityStore, useSharedEntityStoreContext, useSwipeGesture, useTapReveal, useTraitListens, useTranslate114 as useTranslate, useUIEvents, useUISlotManager, useUnitSpriteAtlas, useValidation, vec2 };
50809
+ export { ALL_PRESETS, ALMADAR_DND_MIME, AR_BOOK_FIELDS, AboutPageTemplate, Accordion, Card2 as ActionCard, ActionPalette, ActionTile, ActivationBlock, Alert, AlgorithmCanvas, AnimatedCounter, AnimatedGraphic, AnimatedReveal, ArticleSection, Aside, AssetPicker, AtlasImage, AtlasPanel, AuthLayout, Avatar, Badge, BehaviorView, BiologyCanvas, BloomQuizBlock, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, Box, BranchingLogicBuilder, Breadcrumb, Button, ButtonGroup, CTABanner, CalendarGrid, Canvas, Canvas2D, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, CaseStudyCard, CaseStudyOrganism, Center, Chart, ChartLegend, ChatBar, Checkbox, ChemistryCanvas, ChoiceButton, Coachmark, CodeBlock, CodeRunnerPanel, CollapsibleSection, CommunityLinks, ConditionalWrapper, ConfettiEffect, ConfirmDialog, ConnectionBlock, Container, ContentRenderer, ContentSection, ControlButton, ControlGrid, CounterTemplate, DEFAULT_LIKERT_OPTIONS, DEFAULT_MATRIX_COLUMNS, DEFAULT_SLOTS, DIAMOND_TOP_Y, DashboardGrid, DashboardLayout, DataGrid, DataList, DataTable, DateRangePicker, DateRangeSelector, DayCell, DetailPanel, Dialog, DialogueBubble, Divider, DocBreadcrumb, DocPagination, DocSearch, DocSidebar, DocTOC, DocumentViewer, StateMachineView as DomStateMachineVisualizer, Drawer, DrawerSlot, ELEMENT_SELECTED_EVENT, EdgeDecoration, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmojiPicker, EmptyState, EntityDisplayEvents, ErrorBoundary, ErrorState, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FeatureCard, FeatureDetailPageTemplate, FeatureGrid, FeatureGridOrganism, FileTree, FilterGroup, FilterPill, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, GameAudioToggle, GameHud, GameIcon, GameMenu, GameShell, GenericAppTemplate, GeometricPattern, GradientDivider, GraphCanvas, GraphView, Grid, GridPicker, HStack, Header, HealthBar, HeroOrganism, HeroSection, I18nProvider, IDENTITY_BOOK_FIELDS, Icon, IconPicker, ImportPreviewTree, ImportProgress, ImportSourcePicker, InfiniteScrollSentinel, Input, InputGroup, InstallBox, JazariStateMachine, JsonTreeEditor, Label, LandingPageTemplate, LawReferenceTooltip, LearningCanvas, LearningScene3D, Lightbox, LikertScale, LineChart2 as LineChart, List3 as List, LoadingState, MapView, MarkdownContent, MarketingFooter, MarketingStatCard, MasterDetail, MasterDetailLayout, MathCanvas, MatrixQuestion, MediaGallery, Menu, Meter, Modal, ModalSlot, ModuleCard, Navigation, NodeSlotEditor, NotifyListener, NumberStepper, OnboardingSpotlight, OptionConstraintGroup, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, Overlay, PageHeader, PageTransition, Pagination, PatternTile, PhysicsCanvas, Popover, PositionedCanvas, Presence, PricingCard, PricingGrid, PricingOrganism, PricingPageTemplate, ProgressBar, ProgressDots, PropertyInspector, PullQuote, PullToRefresh, QrScanner, QuizBlock, Radio, RangeSlider, ReflectionBlock, RelationSelect, RepeatableFormSection, ReplyTree, RichBlockEditor, RuntimeDebugger, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScaledDiagram, ScoreDisplay, SearchInput, Section, SectionHeader, SegmentRenderer, Select, SequenceBar, ServiceCatalog, SharedEntityStoreContext, ShowcaseCard, ShowcaseOrganism, SidePanel, Sidebar, SignaturePad, SimpleGrid, Skeleton, SlotContentRenderer, SocialProof, SortableList, Spacer, Sparkline, Spinner, Split, SplitPane, SplitSection, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateGraph, StateJsonView, StateMachineView, StateNode2 as StateNode, StatsGrid, StatsOrganism, StatusBar, StatusDot, StepFlow, StepFlowOrganism, SubagentTracePanel, SvgBranch, SvgConnection, SvgFlow, SvgGrid, SvgLobe, SvgMesh, SvgMorph, SvgNode, SvgPulse, SvgRing, SvgShield, SvgStack, SwipeableRow, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, TableView, Tabs, TagCloud, TagInput, TeamCard, TeamOrganism, TerrainPalette, TextHighlight, Textarea, ThemeToggle, TimeSlotCell, Timeline, TimerDisplay, Toast, ToastSlot, Tooltip, TraitFrame, TraitSlot, TraitStateViewer, TransitionArrow, TrendIndicator, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UploadDropZone, VStack, VersionDiff, ViolationAlert, VoteStack, WizardContainer, WizardNavigation, WizardProgress, arrowBetween, billboardLabel, boardEntity, bool, calculateAttackTargets, calculateValidMoves, cn, createInitialGameState, createSharedEntityStore, createTranslate, createUnitAnimationState, cylinderBetween, get3DClickPayload, getCurrentFrame, getTileDimensions, inferDirection, isoToScreen, makeAsset, makeAssetMap, mapBookData, meshSphere, num, objAvailableActions, objAvailableEvents, objCurrentState, objIcon, objId, objMaxRules, objName, objRules, objStates, parseEditFocus, parseLessonSegments, parseMarkdownWithCodeBlocks, parseQueryBinding, pendulum, projectileMotion, registerCodeLanguageLoader, resolveFieldMap, resolveFrame, resolveSheetDirection, rows, runTickFrame, screenToIso, springOscillator, str, tickAnimationState, toCodeLanguage, transitionAnimation, unitHealth, unitPosition, unitTeam, useAgentChat, useAnchorRect, useAtlasSliceDataUrl, useAuthContext, useCamera, useCanvasGestures, useCompile, useConnectGitHub, useDeepAgentGeneration, useDisconnectGitHub, useDragReorder, useDraggable, useDropZone, useEmitEvent, useEventBus, useEventListener, useExtensions, useFileEditor, useFileSystem, useGameAudio, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useImageCache, useInfiniteScroll, useLongPress, useMediaQuery, useOrbitalHistory, usePresence, usePreview, usePullToRefresh, useQuerySingleton, useRenderInterpolation, useSharedEntitySnapshot, useSharedEntityStore, useSharedEntityStoreContext, useSwipeGesture, useTapReveal, useTraitListens, useTranslate114 as useTranslate, useUIEvents, useUISlotManager, useUnitSpriteAtlas, useValidation, vec2 };