@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
@@ -14117,9 +14117,10 @@ function Canvas({
14117
14117
  drawables: [...drawables ?? [], ...childDrawables],
14118
14118
  isLoading,
14119
14119
  cameraMode: to3DCameraMode(camera?.mode),
14120
- ...zoom !== void 0 ? { scale: zoom } : {},
14120
+ ...zoom !== void 0 ? { zoom } : {},
14121
14121
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
14122
14122
  ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
14123
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
14123
14124
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
14124
14125
  unitScale,
14125
14126
  backgroundColor,
@@ -17171,23 +17172,188 @@ var init_AuthLayout = __esm({
17171
17172
  AuthLayout.displayName = "AuthLayout";
17172
17173
  }
17173
17174
  });
17174
- var BiologyCanvas;
17175
+ function LearningScene3D({
17176
+ className,
17177
+ width = 600,
17178
+ height = 400,
17179
+ title,
17180
+ backgroundColor,
17181
+ drawables,
17182
+ camera,
17183
+ lighting,
17184
+ post,
17185
+ showGrid = false,
17186
+ shadows,
17187
+ interactive,
17188
+ isLoading,
17189
+ error,
17190
+ onItemClick
17191
+ }) {
17192
+ const instanceId = React94.useId().replace(/[^a-zA-Z0-9]/g, "");
17193
+ const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
17194
+ const onItemClickRef = React94.useRef(onItemClick);
17195
+ onItemClickRef.current = onItemClick;
17196
+ useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
17197
+ const unitId = event.payload?.unitId;
17198
+ if (typeof unitId === "string") onItemClickRef.current?.(unitId);
17199
+ });
17200
+ const props3d = {
17201
+ drawables,
17202
+ isLoading,
17203
+ error: error ? error.message : null,
17204
+ cameraMode: camera?.mode ?? "perspective",
17205
+ ...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
17206
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
17207
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
17208
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
17209
+ ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
17210
+ backgroundColor,
17211
+ showGrid,
17212
+ ...shadows !== void 0 ? { shadows } : {},
17213
+ ...interactive !== void 0 ? { controlsEnabled: interactive } : {},
17214
+ lighting,
17215
+ post,
17216
+ ...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
17217
+ };
17218
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
17219
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
17220
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsxRuntime.jsx(React94.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(Canvas3DHost2, { ...props3d }) }) })
17221
+ ] }) });
17222
+ }
17223
+ function meshSphere(id, x, y, z, radius, color, opts) {
17224
+ return {
17225
+ type: "draw-mesh",
17226
+ ...id !== void 0 ? { id } : {},
17227
+ shape: opts?.shape ?? "sphere",
17228
+ position: { x, y, z },
17229
+ radius,
17230
+ pivot: "center",
17231
+ segments: opts?.segments ?? 24,
17232
+ material: { color, ...opts?.material },
17233
+ ...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
17234
+ };
17235
+ }
17236
+ function axisRotation(from, to) {
17237
+ const wx = to[0] - from[0];
17238
+ const wy = to[2] - from[2];
17239
+ const wz = to[1] - from[1];
17240
+ const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
17241
+ const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
17242
+ return [0, Math.atan2(wz, -wx), tilt];
17243
+ }
17244
+ function segmentLength(from, to) {
17245
+ const dx = to[0] - from[0];
17246
+ const dy = to[1] - from[1];
17247
+ const dz = to[2] - from[2];
17248
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
17249
+ }
17250
+ function cylinderBetween(from, to, radius, color) {
17251
+ const len = segmentLength(from, to);
17252
+ if (len < 1e-6) return null;
17253
+ return {
17254
+ type: "draw-mesh",
17255
+ shape: "cylinder",
17256
+ position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
17257
+ radius,
17258
+ height: len,
17259
+ rotation: axisRotation(from, to),
17260
+ pivot: "center",
17261
+ segments: 12,
17262
+ material: { color }
17263
+ };
17264
+ }
17265
+ function arrowBetween(from, to, color, shaftRadius = 0.08) {
17266
+ const len = segmentLength(from, to);
17267
+ if (len < 1e-6) return null;
17268
+ const tipLen = Math.min(shaftRadius * 8, len * 0.35);
17269
+ const k = (len - tipLen) / len;
17270
+ const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
17271
+ const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
17272
+ const tipStart = shaftEnd;
17273
+ const tipEnd = delta;
17274
+ const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
17275
+ const tipLenActual = segmentLength(tipStart, tipEnd);
17276
+ const tip = {
17277
+ type: "draw-mesh",
17278
+ shape: "cone",
17279
+ position: {
17280
+ x: (tipStart[0] + tipEnd[0]) / 2,
17281
+ y: (tipStart[1] + tipEnd[1]) / 2,
17282
+ z: (tipStart[2] + tipEnd[2]) / 2
17283
+ },
17284
+ radius: shaftRadius * 3,
17285
+ height: tipLenActual,
17286
+ rotation: axisRotation(tipStart, tipEnd),
17287
+ pivot: "center",
17288
+ segments: 12,
17289
+ material: { color }
17290
+ };
17291
+ return {
17292
+ type: "draw-group",
17293
+ position: { x: from[0], y: from[1], z: from[2] },
17294
+ items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
17295
+ };
17296
+ }
17297
+ function billboardLabel(text, x, y, z, opts) {
17298
+ return {
17299
+ type: "draw-text",
17300
+ text,
17301
+ position: { x, y, z },
17302
+ color: opts?.color ?? "#111827"
17303
+ };
17304
+ }
17305
+ function labelColorForBackground(backgroundColor) {
17306
+ const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
17307
+ if (!m) return "#111827";
17308
+ const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
17309
+ const r2 = parseInt(hex.slice(0, 2), 16) / 255;
17310
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
17311
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
17312
+ const luminance = 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
17313
+ return luminance < 0.5 ? "#e5e7eb" : "#111827";
17314
+ }
17315
+ function get3DClickPayload(onShapeClick, idToIndex) {
17316
+ if (!onShapeClick) return void 0;
17317
+ return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
17318
+ }
17319
+ var Canvas3DHost2;
17320
+ var init_learningScene3D = __esm({
17321
+ "components/learning/molecules/learningScene3D.tsx"() {
17322
+ "use client";
17323
+ init_atoms();
17324
+ init_Stack();
17325
+ init_useEventBus();
17326
+ Canvas3DHost2 = React94.lazy(
17327
+ () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
17328
+ );
17329
+ LearningScene3D.displayName = "LearningScene3D";
17330
+ }
17331
+ });
17332
+ var biologyLog, BiologyCanvas;
17175
17333
  var init_BiologyCanvas = __esm({
17176
17334
  "components/learning/molecules/BiologyCanvas.tsx"() {
17177
17335
  "use client";
17178
17336
  init_atoms();
17179
17337
  init_Stack();
17180
17338
  init_LearningCanvas();
17339
+ init_learningScene3D();
17340
+ biologyLog = logger.createLogger("almadar:ui:biology-canvas");
17181
17341
  BiologyCanvas = ({
17182
17342
  className,
17183
17343
  width = 600,
17184
17344
  height = 400,
17185
17345
  title,
17186
17346
  backgroundColor,
17347
+ mode = "2d",
17348
+ camera,
17349
+ lighting,
17350
+ post,
17187
17351
  nodes = [],
17188
17352
  edges = [],
17189
17353
  shapes = [],
17190
- interactive = false,
17354
+ showGrid,
17355
+ shadows,
17356
+ interactive,
17191
17357
  animate = false,
17192
17358
  onShapeClick,
17193
17359
  isLoading,
@@ -17248,6 +17414,75 @@ var init_BiologyCanvas = __esm({
17248
17414
  out.push(...shapes);
17249
17415
  return out;
17250
17416
  }, [nodes, edges, shapes]);
17417
+ const drawables3D = React94.useMemo(() => {
17418
+ if (mode !== "3d") return [];
17419
+ if (shapes.length > 0) {
17420
+ biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
17421
+ }
17422
+ const out = [];
17423
+ const labelColor = labelColorForBackground(backgroundColor);
17424
+ const nodeById = /* @__PURE__ */ new Map();
17425
+ for (const n of nodes) {
17426
+ if (n.id) nodeById.set(n.id, n);
17427
+ }
17428
+ for (const e of edges) {
17429
+ const a = nodeById.get(e.from);
17430
+ const b = nodeById.get(e.to);
17431
+ if (!a || !b) continue;
17432
+ const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
17433
+ const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
17434
+ if (edge) out.push(edge);
17435
+ if (e.label) {
17436
+ out.push(
17437
+ billboardLabel(
17438
+ e.label,
17439
+ (a.x + b.x) / 2,
17440
+ (a.y + b.y) / 2,
17441
+ ((a.z ?? 0) + (b.z ?? 0)) / 2,
17442
+ { color: labelColor }
17443
+ )
17444
+ );
17445
+ }
17446
+ }
17447
+ for (const n of nodes) {
17448
+ const radius = n.radius ?? 0.5;
17449
+ const nz = n.z ?? 0;
17450
+ out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
17451
+ if (n.label) {
17452
+ out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
17453
+ }
17454
+ }
17455
+ return out;
17456
+ }, [mode, nodes, edges, shapes, backgroundColor]);
17457
+ const nodeIndexById = React94.useMemo(() => {
17458
+ const m = /* @__PURE__ */ new Map();
17459
+ nodes.forEach((n, i) => {
17460
+ if (n.id) m.set(n.id, i);
17461
+ });
17462
+ return m;
17463
+ }, [nodes]);
17464
+ if (mode === "3d") {
17465
+ return /* @__PURE__ */ jsxRuntime.jsx(
17466
+ LearningScene3D,
17467
+ {
17468
+ className,
17469
+ width,
17470
+ height,
17471
+ title,
17472
+ backgroundColor,
17473
+ drawables: drawables3D,
17474
+ camera,
17475
+ lighting,
17476
+ post,
17477
+ showGrid,
17478
+ shadows,
17479
+ interactive,
17480
+ isLoading,
17481
+ error,
17482
+ onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
17483
+ }
17484
+ );
17485
+ }
17251
17486
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
17252
17487
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
17253
17488
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -17257,7 +17492,7 @@ var init_BiologyCanvas = __esm({
17257
17492
  height,
17258
17493
  backgroundColor,
17259
17494
  shapes: derivedShapes,
17260
- interactive,
17495
+ interactive: interactive ?? false,
17261
17496
  animate,
17262
17497
  onShapeClick,
17263
17498
  isLoading,
@@ -24097,24 +24332,41 @@ var init_ChatBar = __esm({
24097
24332
  ChatBar.displayName = "ChatBar";
24098
24333
  }
24099
24334
  });
24100
- var ChemistryCanvas;
24335
+ function bondPerpendicular(a, b) {
24336
+ const dx = b[0] - a[0];
24337
+ const dy = b[1] - a[1];
24338
+ const px = dy;
24339
+ const py = -dx;
24340
+ const len = Math.sqrt(px * px + py * py);
24341
+ if (len < 1e-6) return [1, 0, 0];
24342
+ return [px / len, py / len, 0];
24343
+ }
24344
+ var chemistryLog, ChemistryCanvas;
24101
24345
  var init_ChemistryCanvas = __esm({
24102
24346
  "components/learning/molecules/ChemistryCanvas.tsx"() {
24103
24347
  "use client";
24104
24348
  init_atoms();
24105
24349
  init_Stack();
24106
24350
  init_LearningCanvas();
24351
+ init_learningScene3D();
24352
+ chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
24107
24353
  ChemistryCanvas = ({
24108
24354
  className,
24109
24355
  width = 600,
24110
24356
  height = 400,
24111
24357
  title,
24112
24358
  backgroundColor,
24359
+ mode = "2d",
24360
+ camera,
24361
+ lighting,
24362
+ post,
24113
24363
  atoms = [],
24114
24364
  bonds = [],
24115
24365
  arrows = [],
24116
24366
  shapes = [],
24117
- interactive = false,
24367
+ showGrid,
24368
+ shadows,
24369
+ interactive,
24118
24370
  animate = false,
24119
24371
  onShapeClick,
24120
24372
  isLoading,
@@ -24193,6 +24445,88 @@ var init_ChemistryCanvas = __esm({
24193
24445
  out.push(...shapes);
24194
24446
  return out;
24195
24447
  }, [atoms, bonds, arrows, shapes]);
24448
+ const drawables3D = React94.useMemo(() => {
24449
+ if (mode !== "3d") return [];
24450
+ if (shapes.length > 0) {
24451
+ chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
24452
+ }
24453
+ const out = [];
24454
+ const labelColor = labelColorForBackground(backgroundColor);
24455
+ const atomById = /* @__PURE__ */ new Map();
24456
+ for (const a of atoms) {
24457
+ if (a.id) atomById.set(a.id, a);
24458
+ }
24459
+ for (const b of bonds) {
24460
+ const a = atomById.get(b.from);
24461
+ const c = atomById.get(b.to);
24462
+ if (!a || !c) continue;
24463
+ const color = b.color ?? "#6b7280";
24464
+ const from = [a.x, a.y, a.z ?? 0];
24465
+ const to = [c.x, c.y, c.z ?? 0];
24466
+ const perp = bondPerpendicular(from, to);
24467
+ const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
24468
+ const step = bondRadius * 2.2;
24469
+ const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
24470
+ for (const off of offsets) {
24471
+ const bond = cylinderBetween(
24472
+ [from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
24473
+ [to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
24474
+ bondRadius,
24475
+ color
24476
+ );
24477
+ if (bond) out.push(bond);
24478
+ }
24479
+ }
24480
+ for (const a of arrows) {
24481
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
24482
+ const len = a.length ?? 60;
24483
+ const x2 = a.x + Math.cos(angle) * len;
24484
+ const y2 = a.y + Math.sin(angle) * len;
24485
+ const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
24486
+ if (arrow) out.push(arrow);
24487
+ if (a.label) {
24488
+ out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
24489
+ }
24490
+ }
24491
+ for (const a of atoms) {
24492
+ const radius = a.radius ?? 0.45;
24493
+ const az = a.z ?? 0;
24494
+ out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
24495
+ if (a.element) {
24496
+ out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
24497
+ }
24498
+ }
24499
+ return out;
24500
+ }, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
24501
+ const atomIndexById = React94.useMemo(() => {
24502
+ const m = /* @__PURE__ */ new Map();
24503
+ atoms.forEach((a, i) => {
24504
+ if (a.id) m.set(a.id, i);
24505
+ });
24506
+ return m;
24507
+ }, [atoms]);
24508
+ if (mode === "3d") {
24509
+ return /* @__PURE__ */ jsxRuntime.jsx(
24510
+ LearningScene3D,
24511
+ {
24512
+ className,
24513
+ width,
24514
+ height,
24515
+ title,
24516
+ backgroundColor,
24517
+ drawables: drawables3D,
24518
+ camera,
24519
+ lighting,
24520
+ post,
24521
+ showGrid,
24522
+ shadows,
24523
+ interactive,
24524
+ isLoading,
24525
+ error,
24526
+ onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
24527
+ }
24528
+ );
24529
+ }
24196
24530
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
24197
24531
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
24198
24532
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -24202,7 +24536,7 @@ var init_ChemistryCanvas = __esm({
24202
24536
  height,
24203
24537
  backgroundColor,
24204
24538
  shapes: derivedShapes,
24205
- interactive,
24539
+ interactive: interactive ?? false,
24206
24540
  animate,
24207
24541
  onShapeClick,
24208
24542
  isLoading,
@@ -30507,19 +30841,25 @@ var init_MathCanvas = __esm({
30507
30841
  };
30508
30842
  }
30509
30843
  });
30510
- var PhysicsCanvas;
30844
+ var physicsLog2, PhysicsCanvas;
30511
30845
  var init_PhysicsCanvas = __esm({
30512
30846
  "components/learning/molecules/PhysicsCanvas.tsx"() {
30513
30847
  "use client";
30514
30848
  init_atoms();
30515
30849
  init_Stack();
30516
30850
  init_LearningCanvas();
30851
+ init_learningScene3D();
30852
+ physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
30517
30853
  PhysicsCanvas = ({
30518
30854
  className,
30519
30855
  width = 600,
30520
30856
  height = 400,
30521
30857
  title,
30522
30858
  backgroundColor,
30859
+ mode = "2d",
30860
+ camera,
30861
+ lighting,
30862
+ post,
30523
30863
  bodies = [],
30524
30864
  constraints = [],
30525
30865
  showVelocity = true,
@@ -30527,7 +30867,9 @@ var init_PhysicsCanvas = __esm({
30527
30867
  velocityScale = 20,
30528
30868
  forceScale = 20,
30529
30869
  shapes = [],
30530
- interactive = false,
30870
+ showGrid,
30871
+ shadows,
30872
+ interactive,
30531
30873
  animate = false,
30532
30874
  onShapeClick,
30533
30875
  isLoading,
@@ -30599,6 +30941,89 @@ var init_PhysicsCanvas = __esm({
30599
30941
  out.push(...shapes);
30600
30942
  return out;
30601
30943
  }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
30944
+ const drawables3D = React94.useMemo(() => {
30945
+ if (mode !== "3d") return [];
30946
+ if (shapes.length > 0) {
30947
+ physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
30948
+ }
30949
+ const out = [];
30950
+ const labelColor = labelColorForBackground(backgroundColor);
30951
+ const bodyById = /* @__PURE__ */ new Map();
30952
+ for (const b of bodies) {
30953
+ if (b.id) bodyById.set(b.id, b);
30954
+ }
30955
+ for (const c of constraints) {
30956
+ const a = bodyById.get(c.from);
30957
+ const b = bodyById.get(c.to);
30958
+ if (!a || !b) continue;
30959
+ const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
30960
+ const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
30961
+ if (rod) out.push(rod);
30962
+ }
30963
+ for (const b of bodies) {
30964
+ const radius = b.radius ?? 0.5;
30965
+ const bz = b.z ?? 0;
30966
+ out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
30967
+ if (b.label) {
30968
+ out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
30969
+ }
30970
+ const vx = b.vx ?? 0;
30971
+ const vy = b.vy ?? 0;
30972
+ const vz = b.vz ?? 0;
30973
+ const arrowRadius = Math.max(0.05, radius * 0.15);
30974
+ if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
30975
+ const arrow = arrowBetween(
30976
+ [b.x, b.y, bz],
30977
+ [b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
30978
+ "#16a34a",
30979
+ arrowRadius
30980
+ );
30981
+ if (arrow) out.push(arrow);
30982
+ }
30983
+ const fx = b.fx ?? 0;
30984
+ const fy = b.fy ?? 0;
30985
+ const fz = b.fz ?? 0;
30986
+ if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
30987
+ const arrow = arrowBetween(
30988
+ [b.x, b.y, bz],
30989
+ [b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
30990
+ "#dc2626",
30991
+ arrowRadius
30992
+ );
30993
+ if (arrow) out.push(arrow);
30994
+ }
30995
+ }
30996
+ return out;
30997
+ }, [mode, bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes, backgroundColor]);
30998
+ const bodyIndexById = React94.useMemo(() => {
30999
+ const m = /* @__PURE__ */ new Map();
31000
+ bodies.forEach((b, i) => {
31001
+ if (b.id) m.set(b.id, i);
31002
+ });
31003
+ return m;
31004
+ }, [bodies]);
31005
+ if (mode === "3d") {
31006
+ return /* @__PURE__ */ jsxRuntime.jsx(
31007
+ LearningScene3D,
31008
+ {
31009
+ className,
31010
+ width,
31011
+ height,
31012
+ title,
31013
+ backgroundColor,
31014
+ drawables: drawables3D,
31015
+ camera,
31016
+ lighting,
31017
+ post,
31018
+ showGrid,
31019
+ shadows,
31020
+ interactive,
31021
+ isLoading,
31022
+ error,
31023
+ onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
31024
+ }
31025
+ );
31026
+ }
30602
31027
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
30603
31028
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
30604
31029
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -30608,7 +31033,7 @@ var init_PhysicsCanvas = __esm({
30608
31033
  height,
30609
31034
  backgroundColor,
30610
31035
  shapes: derivedShapes,
30611
- interactive,
31036
+ interactive: interactive ?? false,
30612
31037
  animate,
30613
31038
  onShapeClick,
30614
31039
  isLoading,
@@ -31226,12 +31651,12 @@ var init_MapView = __esm({
31226
31651
  shadowSize: [41, 41]
31227
31652
  });
31228
31653
  L.Marker.prototype.options.icon = defaultIcon;
31229
- const { useEffect: useEffect64, useRef: useRef64, useCallback: useCallback96, useState: useState100 } = React94__namespace.default;
31654
+ const { useEffect: useEffect64, useRef: useRef65, useCallback: useCallback96, useState: useState100 } = React94__namespace.default;
31230
31655
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31231
31656
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31232
31657
  function MapUpdater({ centerLat, centerLng, zoom }) {
31233
31658
  const map = useMap();
31234
- const prevRef = useRef64({ centerLat, centerLng, zoom });
31659
+ const prevRef = useRef65({ centerLat, centerLng, zoom });
31235
31660
  useEffect64(() => {
31236
31661
  const prev = prevRef.current;
31237
31662
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -41645,7 +42070,7 @@ function getEnumOptions(field) {
41645
42070
  }));
41646
42071
  }
41647
42072
  const validation = field.validation;
41648
- if (validation?.enum && Array.isArray(validation.enum)) {
42073
+ if (validation?.enum && validation.enum.length > 0) {
41649
42074
  return validation.enum.map((v) => ({
41650
42075
  value: v,
41651
42076
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")