@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
@@ -10655,9 +10655,10 @@ function Canvas({
10655
10655
  drawables: [...drawables ?? [], ...childDrawables],
10656
10656
  isLoading,
10657
10657
  cameraMode: to3DCameraMode(camera?.mode),
10658
- ...zoom !== void 0 ? { scale: zoom } : {},
10658
+ ...zoom !== void 0 ? { zoom } : {},
10659
10659
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
10660
10660
  ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
10661
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
10661
10662
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
10662
10663
  unitScale,
10663
10664
  backgroundColor,
@@ -14536,23 +14537,188 @@ var init_BehaviorView = __esm({
14536
14537
  BehaviorView.displayName = "BehaviorView";
14537
14538
  }
14538
14539
  });
14539
- var BiologyCanvas;
14540
+ function LearningScene3D({
14541
+ className,
14542
+ width = 600,
14543
+ height = 400,
14544
+ title,
14545
+ backgroundColor,
14546
+ drawables,
14547
+ camera,
14548
+ lighting,
14549
+ post,
14550
+ showGrid = false,
14551
+ shadows,
14552
+ interactive,
14553
+ isLoading,
14554
+ error,
14555
+ onItemClick
14556
+ }) {
14557
+ const instanceId = useId().replace(/[^a-zA-Z0-9]/g, "");
14558
+ const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
14559
+ const onItemClickRef = useRef(onItemClick);
14560
+ onItemClickRef.current = onItemClick;
14561
+ useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
14562
+ const unitId = event.payload?.unitId;
14563
+ if (typeof unitId === "string") onItemClickRef.current?.(unitId);
14564
+ });
14565
+ const props3d = {
14566
+ drawables,
14567
+ isLoading,
14568
+ error: error ? error.message : null,
14569
+ cameraMode: camera?.mode ?? "perspective",
14570
+ ...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
14571
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
14572
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
14573
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
14574
+ ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
14575
+ backgroundColor,
14576
+ showGrid,
14577
+ ...shadows !== void 0 ? { shadows } : {},
14578
+ ...interactive !== void 0 ? { controlsEnabled: interactive } : {},
14579
+ lighting,
14580
+ post,
14581
+ ...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
14582
+ };
14583
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
14584
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
14585
+ /* @__PURE__ */ jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost2, { ...props3d }) }) })
14586
+ ] }) });
14587
+ }
14588
+ function meshSphere(id, x, y, z, radius, color, opts) {
14589
+ return {
14590
+ type: "draw-mesh",
14591
+ ...id !== void 0 ? { id } : {},
14592
+ shape: opts?.shape ?? "sphere",
14593
+ position: { x, y, z },
14594
+ radius,
14595
+ pivot: "center",
14596
+ segments: opts?.segments ?? 24,
14597
+ material: { color, ...opts?.material },
14598
+ ...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
14599
+ };
14600
+ }
14601
+ function axisRotation(from, to) {
14602
+ const wx = to[0] - from[0];
14603
+ const wy = to[2] - from[2];
14604
+ const wz = to[1] - from[1];
14605
+ const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
14606
+ const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
14607
+ return [0, Math.atan2(wz, -wx), tilt];
14608
+ }
14609
+ function segmentLength(from, to) {
14610
+ const dx = to[0] - from[0];
14611
+ const dy = to[1] - from[1];
14612
+ const dz = to[2] - from[2];
14613
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
14614
+ }
14615
+ function cylinderBetween(from, to, radius, color) {
14616
+ const len = segmentLength(from, to);
14617
+ if (len < 1e-6) return null;
14618
+ return {
14619
+ type: "draw-mesh",
14620
+ shape: "cylinder",
14621
+ position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
14622
+ radius,
14623
+ height: len,
14624
+ rotation: axisRotation(from, to),
14625
+ pivot: "center",
14626
+ segments: 12,
14627
+ material: { color }
14628
+ };
14629
+ }
14630
+ function arrowBetween(from, to, color, shaftRadius = 0.08) {
14631
+ const len = segmentLength(from, to);
14632
+ if (len < 1e-6) return null;
14633
+ const tipLen = Math.min(shaftRadius * 8, len * 0.35);
14634
+ const k = (len - tipLen) / len;
14635
+ const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
14636
+ const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
14637
+ const tipStart = shaftEnd;
14638
+ const tipEnd = delta;
14639
+ const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
14640
+ const tipLenActual = segmentLength(tipStart, tipEnd);
14641
+ const tip = {
14642
+ type: "draw-mesh",
14643
+ shape: "cone",
14644
+ position: {
14645
+ x: (tipStart[0] + tipEnd[0]) / 2,
14646
+ y: (tipStart[1] + tipEnd[1]) / 2,
14647
+ z: (tipStart[2] + tipEnd[2]) / 2
14648
+ },
14649
+ radius: shaftRadius * 3,
14650
+ height: tipLenActual,
14651
+ rotation: axisRotation(tipStart, tipEnd),
14652
+ pivot: "center",
14653
+ segments: 12,
14654
+ material: { color }
14655
+ };
14656
+ return {
14657
+ type: "draw-group",
14658
+ position: { x: from[0], y: from[1], z: from[2] },
14659
+ items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
14660
+ };
14661
+ }
14662
+ function billboardLabel(text, x, y, z, opts) {
14663
+ return {
14664
+ type: "draw-text",
14665
+ text,
14666
+ position: { x, y, z },
14667
+ color: opts?.color ?? "#111827"
14668
+ };
14669
+ }
14670
+ function labelColorForBackground(backgroundColor) {
14671
+ const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
14672
+ if (!m) return "#111827";
14673
+ const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
14674
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
14675
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
14676
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
14677
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
14678
+ return luminance < 0.5 ? "#e5e7eb" : "#111827";
14679
+ }
14680
+ function get3DClickPayload(onShapeClick, idToIndex) {
14681
+ if (!onShapeClick) return void 0;
14682
+ return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
14683
+ }
14684
+ var Canvas3DHost2;
14685
+ var init_learningScene3D = __esm({
14686
+ "components/learning/molecules/learningScene3D.tsx"() {
14687
+ "use client";
14688
+ init_atoms();
14689
+ init_Stack();
14690
+ init_useEventBus();
14691
+ Canvas3DHost2 = lazy(
14692
+ () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
14693
+ );
14694
+ LearningScene3D.displayName = "LearningScene3D";
14695
+ }
14696
+ });
14697
+ var biologyLog, BiologyCanvas;
14540
14698
  var init_BiologyCanvas = __esm({
14541
14699
  "components/learning/molecules/BiologyCanvas.tsx"() {
14542
14700
  "use client";
14543
14701
  init_atoms();
14544
14702
  init_Stack();
14545
14703
  init_LearningCanvas();
14704
+ init_learningScene3D();
14705
+ biologyLog = createLogger("almadar:ui:biology-canvas");
14546
14706
  BiologyCanvas = ({
14547
14707
  className,
14548
14708
  width = 600,
14549
14709
  height = 400,
14550
14710
  title,
14551
14711
  backgroundColor,
14712
+ mode = "2d",
14713
+ camera,
14714
+ lighting,
14715
+ post,
14552
14716
  nodes = [],
14553
14717
  edges = [],
14554
14718
  shapes = [],
14555
- interactive = false,
14719
+ showGrid,
14720
+ shadows,
14721
+ interactive,
14556
14722
  animate = false,
14557
14723
  onShapeClick,
14558
14724
  isLoading,
@@ -14613,6 +14779,75 @@ var init_BiologyCanvas = __esm({
14613
14779
  out.push(...shapes);
14614
14780
  return out;
14615
14781
  }, [nodes, edges, shapes]);
14782
+ const drawables3D = useMemo(() => {
14783
+ if (mode !== "3d") return [];
14784
+ if (shapes.length > 0) {
14785
+ biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
14786
+ }
14787
+ const out = [];
14788
+ const labelColor = labelColorForBackground(backgroundColor);
14789
+ const nodeById = /* @__PURE__ */ new Map();
14790
+ for (const n of nodes) {
14791
+ if (n.id) nodeById.set(n.id, n);
14792
+ }
14793
+ for (const e of edges) {
14794
+ const a = nodeById.get(e.from);
14795
+ const b = nodeById.get(e.to);
14796
+ if (!a || !b) continue;
14797
+ const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
14798
+ const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
14799
+ if (edge) out.push(edge);
14800
+ if (e.label) {
14801
+ out.push(
14802
+ billboardLabel(
14803
+ e.label,
14804
+ (a.x + b.x) / 2,
14805
+ (a.y + b.y) / 2,
14806
+ ((a.z ?? 0) + (b.z ?? 0)) / 2,
14807
+ { color: labelColor }
14808
+ )
14809
+ );
14810
+ }
14811
+ }
14812
+ for (const n of nodes) {
14813
+ const radius = n.radius ?? 0.5;
14814
+ const nz = n.z ?? 0;
14815
+ out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
14816
+ if (n.label) {
14817
+ out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
14818
+ }
14819
+ }
14820
+ return out;
14821
+ }, [mode, nodes, edges, shapes, backgroundColor]);
14822
+ const nodeIndexById = useMemo(() => {
14823
+ const m = /* @__PURE__ */ new Map();
14824
+ nodes.forEach((n, i) => {
14825
+ if (n.id) m.set(n.id, i);
14826
+ });
14827
+ return m;
14828
+ }, [nodes]);
14829
+ if (mode === "3d") {
14830
+ return /* @__PURE__ */ jsx(
14831
+ LearningScene3D,
14832
+ {
14833
+ className,
14834
+ width,
14835
+ height,
14836
+ title,
14837
+ backgroundColor,
14838
+ drawables: drawables3D,
14839
+ camera,
14840
+ lighting,
14841
+ post,
14842
+ showGrid,
14843
+ shadows,
14844
+ interactive,
14845
+ isLoading,
14846
+ error,
14847
+ onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
14848
+ }
14849
+ );
14850
+ }
14616
14851
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
14617
14852
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
14618
14853
  /* @__PURE__ */ jsx(
@@ -14622,7 +14857,7 @@ var init_BiologyCanvas = __esm({
14622
14857
  height,
14623
14858
  backgroundColor,
14624
14859
  shapes: derivedShapes,
14625
- interactive,
14860
+ interactive: interactive ?? false,
14626
14861
  animate,
14627
14862
  onShapeClick,
14628
14863
  isLoading,
@@ -21345,24 +21580,41 @@ var init_ChatBar = __esm({
21345
21580
  ChatBar.displayName = "ChatBar";
21346
21581
  }
21347
21582
  });
21348
- var ChemistryCanvas;
21583
+ function bondPerpendicular(a, b) {
21584
+ const dx = b[0] - a[0];
21585
+ const dy = b[1] - a[1];
21586
+ const px = dy;
21587
+ const py = -dx;
21588
+ const len = Math.sqrt(px * px + py * py);
21589
+ if (len < 1e-6) return [1, 0, 0];
21590
+ return [px / len, py / len, 0];
21591
+ }
21592
+ var chemistryLog, ChemistryCanvas;
21349
21593
  var init_ChemistryCanvas = __esm({
21350
21594
  "components/learning/molecules/ChemistryCanvas.tsx"() {
21351
21595
  "use client";
21352
21596
  init_atoms();
21353
21597
  init_Stack();
21354
21598
  init_LearningCanvas();
21599
+ init_learningScene3D();
21600
+ chemistryLog = createLogger("almadar:ui:chemistry-canvas");
21355
21601
  ChemistryCanvas = ({
21356
21602
  className,
21357
21603
  width = 600,
21358
21604
  height = 400,
21359
21605
  title,
21360
21606
  backgroundColor,
21607
+ mode = "2d",
21608
+ camera,
21609
+ lighting,
21610
+ post,
21361
21611
  atoms = [],
21362
21612
  bonds = [],
21363
21613
  arrows = [],
21364
21614
  shapes = [],
21365
- interactive = false,
21615
+ showGrid,
21616
+ shadows,
21617
+ interactive,
21366
21618
  animate = false,
21367
21619
  onShapeClick,
21368
21620
  isLoading,
@@ -21441,6 +21693,88 @@ var init_ChemistryCanvas = __esm({
21441
21693
  out.push(...shapes);
21442
21694
  return out;
21443
21695
  }, [atoms, bonds, arrows, shapes]);
21696
+ const drawables3D = useMemo(() => {
21697
+ if (mode !== "3d") return [];
21698
+ if (shapes.length > 0) {
21699
+ chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
21700
+ }
21701
+ const out = [];
21702
+ const labelColor = labelColorForBackground(backgroundColor);
21703
+ const atomById = /* @__PURE__ */ new Map();
21704
+ for (const a of atoms) {
21705
+ if (a.id) atomById.set(a.id, a);
21706
+ }
21707
+ for (const b of bonds) {
21708
+ const a = atomById.get(b.from);
21709
+ const c = atomById.get(b.to);
21710
+ if (!a || !c) continue;
21711
+ const color = b.color ?? "#6b7280";
21712
+ const from = [a.x, a.y, a.z ?? 0];
21713
+ const to = [c.x, c.y, c.z ?? 0];
21714
+ const perp = bondPerpendicular(from, to);
21715
+ const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
21716
+ const step = bondRadius * 2.2;
21717
+ const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
21718
+ for (const off of offsets) {
21719
+ const bond = cylinderBetween(
21720
+ [from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
21721
+ [to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
21722
+ bondRadius,
21723
+ color
21724
+ );
21725
+ if (bond) out.push(bond);
21726
+ }
21727
+ }
21728
+ for (const a of arrows) {
21729
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
21730
+ const len = a.length ?? 60;
21731
+ const x2 = a.x + Math.cos(angle) * len;
21732
+ const y2 = a.y + Math.sin(angle) * len;
21733
+ const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
21734
+ if (arrow) out.push(arrow);
21735
+ if (a.label) {
21736
+ out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
21737
+ }
21738
+ }
21739
+ for (const a of atoms) {
21740
+ const radius = a.radius ?? 0.45;
21741
+ const az = a.z ?? 0;
21742
+ out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
21743
+ if (a.element) {
21744
+ out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
21745
+ }
21746
+ }
21747
+ return out;
21748
+ }, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
21749
+ const atomIndexById = useMemo(() => {
21750
+ const m = /* @__PURE__ */ new Map();
21751
+ atoms.forEach((a, i) => {
21752
+ if (a.id) m.set(a.id, i);
21753
+ });
21754
+ return m;
21755
+ }, [atoms]);
21756
+ if (mode === "3d") {
21757
+ return /* @__PURE__ */ jsx(
21758
+ LearningScene3D,
21759
+ {
21760
+ className,
21761
+ width,
21762
+ height,
21763
+ title,
21764
+ backgroundColor,
21765
+ drawables: drawables3D,
21766
+ camera,
21767
+ lighting,
21768
+ post,
21769
+ showGrid,
21770
+ shadows,
21771
+ interactive,
21772
+ isLoading,
21773
+ error,
21774
+ onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
21775
+ }
21776
+ );
21777
+ }
21444
21778
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
21445
21779
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
21446
21780
  /* @__PURE__ */ jsx(
@@ -21450,7 +21784,7 @@ var init_ChemistryCanvas = __esm({
21450
21784
  height,
21451
21785
  backgroundColor,
21452
21786
  shapes: derivedShapes,
21453
- interactive,
21787
+ interactive: interactive ?? false,
21454
21788
  animate,
21455
21789
  onShapeClick,
21456
21790
  isLoading,
@@ -27677,19 +28011,25 @@ var init_MathCanvas = __esm({
27677
28011
  };
27678
28012
  }
27679
28013
  });
27680
- var PhysicsCanvas;
28014
+ var physicsLog2, PhysicsCanvas;
27681
28015
  var init_PhysicsCanvas = __esm({
27682
28016
  "components/learning/molecules/PhysicsCanvas.tsx"() {
27683
28017
  "use client";
27684
28018
  init_atoms();
27685
28019
  init_Stack();
27686
28020
  init_LearningCanvas();
28021
+ init_learningScene3D();
28022
+ physicsLog2 = createLogger("almadar:ui:physics-canvas");
27687
28023
  PhysicsCanvas = ({
27688
28024
  className,
27689
28025
  width = 600,
27690
28026
  height = 400,
27691
28027
  title,
27692
28028
  backgroundColor,
28029
+ mode = "2d",
28030
+ camera,
28031
+ lighting,
28032
+ post,
27693
28033
  bodies = [],
27694
28034
  constraints = [],
27695
28035
  showVelocity = true,
@@ -27697,7 +28037,9 @@ var init_PhysicsCanvas = __esm({
27697
28037
  velocityScale = 20,
27698
28038
  forceScale = 20,
27699
28039
  shapes = [],
27700
- interactive = false,
28040
+ showGrid,
28041
+ shadows,
28042
+ interactive,
27701
28043
  animate = false,
27702
28044
  onShapeClick,
27703
28045
  isLoading,
@@ -27769,6 +28111,89 @@ var init_PhysicsCanvas = __esm({
27769
28111
  out.push(...shapes);
27770
28112
  return out;
27771
28113
  }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
28114
+ const drawables3D = useMemo(() => {
28115
+ if (mode !== "3d") return [];
28116
+ if (shapes.length > 0) {
28117
+ physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
28118
+ }
28119
+ const out = [];
28120
+ const labelColor = labelColorForBackground(backgroundColor);
28121
+ const bodyById = /* @__PURE__ */ new Map();
28122
+ for (const b of bodies) {
28123
+ if (b.id) bodyById.set(b.id, b);
28124
+ }
28125
+ for (const c of constraints) {
28126
+ const a = bodyById.get(c.from);
28127
+ const b = bodyById.get(c.to);
28128
+ if (!a || !b) continue;
28129
+ const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
28130
+ const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
28131
+ if (rod) out.push(rod);
28132
+ }
28133
+ for (const b of bodies) {
28134
+ const radius = b.radius ?? 0.5;
28135
+ const bz = b.z ?? 0;
28136
+ out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
28137
+ if (b.label) {
28138
+ out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
28139
+ }
28140
+ const vx = b.vx ?? 0;
28141
+ const vy = b.vy ?? 0;
28142
+ const vz = b.vz ?? 0;
28143
+ const arrowRadius = Math.max(0.05, radius * 0.15);
28144
+ if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
28145
+ const arrow = arrowBetween(
28146
+ [b.x, b.y, bz],
28147
+ [b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
28148
+ "#16a34a",
28149
+ arrowRadius
28150
+ );
28151
+ if (arrow) out.push(arrow);
28152
+ }
28153
+ const fx = b.fx ?? 0;
28154
+ const fy = b.fy ?? 0;
28155
+ const fz = b.fz ?? 0;
28156
+ if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
28157
+ const arrow = arrowBetween(
28158
+ [b.x, b.y, bz],
28159
+ [b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
28160
+ "#dc2626",
28161
+ arrowRadius
28162
+ );
28163
+ if (arrow) out.push(arrow);
28164
+ }
28165
+ }
28166
+ return out;
28167
+ }, [mode, bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes, backgroundColor]);
28168
+ const bodyIndexById = useMemo(() => {
28169
+ const m = /* @__PURE__ */ new Map();
28170
+ bodies.forEach((b, i) => {
28171
+ if (b.id) m.set(b.id, i);
28172
+ });
28173
+ return m;
28174
+ }, [bodies]);
28175
+ if (mode === "3d") {
28176
+ return /* @__PURE__ */ jsx(
28177
+ LearningScene3D,
28178
+ {
28179
+ className,
28180
+ width,
28181
+ height,
28182
+ title,
28183
+ backgroundColor,
28184
+ drawables: drawables3D,
28185
+ camera,
28186
+ lighting,
28187
+ post,
28188
+ showGrid,
28189
+ shadows,
28190
+ interactive,
28191
+ isLoading,
28192
+ error,
28193
+ onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
28194
+ }
28195
+ );
28196
+ }
27772
28197
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
27773
28198
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
27774
28199
  /* @__PURE__ */ jsx(
@@ -27778,7 +28203,7 @@ var init_PhysicsCanvas = __esm({
27778
28203
  height,
27779
28204
  backgroundColor,
27780
28205
  shapes: derivedShapes,
27781
- interactive,
28206
+ interactive: interactive ?? false,
27782
28207
  animate,
27783
28208
  onShapeClick,
27784
28209
  isLoading,
@@ -28396,12 +28821,12 @@ var init_MapView = __esm({
28396
28821
  shadowSize: [41, 41]
28397
28822
  });
28398
28823
  L.Marker.prototype.options.icon = defaultIcon;
28399
- const { useEffect: useEffect60, useRef: useRef62, useCallback: useCallback90, useState: useState92 } = React85__default;
28824
+ const { useEffect: useEffect60, useRef: useRef63, useCallback: useCallback90, useState: useState92 } = React85__default;
28400
28825
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
28401
28826
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
28402
28827
  function MapUpdater({ centerLat, centerLng, zoom }) {
28403
28828
  const map = useMap();
28404
- const prevRef = useRef62({ centerLat, centerLng, zoom });
28829
+ const prevRef = useRef63({ centerLat, centerLng, zoom });
28405
28830
  useEffect60(() => {
28406
28831
  const prev = prevRef.current;
28407
28832
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -39066,7 +39491,7 @@ function getEnumOptions(field) {
39066
39491
  }));
39067
39492
  }
39068
39493
  const validation = field.validation;
39069
- if (validation?.enum && Array.isArray(validation.enum)) {
39494
+ if (validation?.enum && validation.enum.length > 0) {
39070
39495
  return validation.enum.map((v) => ({
39071
39496
  value: v,
39072
39497
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")
@@ -0,0 +1,51 @@
1
+ import { AssetUrl } from '@almadar/core';
2
+
3
+ /**
4
+ * Cross-cutting atom-level prop shapes shared across the design system.
5
+ */
6
+
7
+ /**
8
+ * Canonical semantic color palette. Values are the Tailwind / CSS-var token
9
+ * names that every component in the design system understands. Prefer this
10
+ * over a bare `string` for any `color` or `variant` prop.
11
+ */
12
+ type ColorToken = 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'muted';
13
+ /**
14
+ * Concrete error-state shape read by display components (`error.message`,
15
+ * occasionally `error.stack`). Structurally assignable from the global `Error`,
16
+ * so existing call sites passing an `Error` keep working — this just gives the
17
+ * pattern extractor a readable field schema instead of the opaque `Error` global.
18
+ */
19
+ type UiError = {
20
+ message: string;
21
+ name?: string;
22
+ code?: string;
23
+ stack?: string;
24
+ };
25
+ /**
26
+ * A labelled link/CTA used by marketing molecules (HeroSection, CTABanner,
27
+ * PricingCard) for their primary/secondary call-to-action props.
28
+ */
29
+ type LinkAction = {
30
+ label: string;
31
+ href: string;
32
+ };
33
+ /** An image with its required alt text. */
34
+ type ImageSource = {
35
+ src: AssetUrl;
36
+ alt: string;
37
+ };
38
+ /** A 2D point in canvas/layout coordinates. */
39
+ type Point = {
40
+ x: number;
41
+ y: number;
42
+ };
43
+ /** A 2D rectangle in canvas coordinates (`w`/`h` = width/height). */
44
+ type Rect = {
45
+ x: number;
46
+ y: number;
47
+ w: number;
48
+ h: number;
49
+ };
50
+
51
+ export type { ColorToken as C, ImageSource as I, LinkAction as L, Point as P, Rect as R, UiError as U };
@@ -0,0 +1,51 @@
1
+ import { AssetUrl } from '@almadar/core';
2
+
3
+ /**
4
+ * Cross-cutting atom-level prop shapes shared across the design system.
5
+ */
6
+
7
+ /**
8
+ * Canonical semantic color palette. Values are the Tailwind / CSS-var token
9
+ * names that every component in the design system understands. Prefer this
10
+ * over a bare `string` for any `color` or `variant` prop.
11
+ */
12
+ type ColorToken = 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'muted';
13
+ /**
14
+ * Concrete error-state shape read by display components (`error.message`,
15
+ * occasionally `error.stack`). Structurally assignable from the global `Error`,
16
+ * so existing call sites passing an `Error` keep working — this just gives the
17
+ * pattern extractor a readable field schema instead of the opaque `Error` global.
18
+ */
19
+ type UiError = {
20
+ message: string;
21
+ name?: string;
22
+ code?: string;
23
+ stack?: string;
24
+ };
25
+ /**
26
+ * A labelled link/CTA used by marketing molecules (HeroSection, CTABanner,
27
+ * PricingCard) for their primary/secondary call-to-action props.
28
+ */
29
+ type LinkAction = {
30
+ label: string;
31
+ href: string;
32
+ };
33
+ /** An image with its required alt text. */
34
+ type ImageSource = {
35
+ src: AssetUrl;
36
+ alt: string;
37
+ };
38
+ /** A 2D point in canvas/layout coordinates. */
39
+ type Point = {
40
+ x: number;
41
+ y: number;
42
+ };
43
+ /** A 2D rectangle in canvas coordinates (`w`/`h` = width/height). */
44
+ type Rect = {
45
+ x: number;
46
+ y: number;
47
+ w: number;
48
+ h: number;
49
+ };
50
+
51
+ export type { ColorToken as C, ImageSource as I, LinkAction as L, Point as P, Rect as R, UiError as U };