@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
@@ -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,
@@ -11866,7 +11867,10 @@ var init_LearningCanvas = __esm({
11866
11867
  ctx.fillRect(0, 0, width, height);
11867
11868
  }
11868
11869
  for (const shape of shapes) {
11869
- drawShape(ctx, shape, width, height);
11870
+ if (shape.type !== "text") drawShape(ctx, shape, width, height);
11871
+ }
11872
+ for (const shape of shapes) {
11873
+ if (shape.type === "text") drawShape(ctx, shape, width, height);
11870
11874
  }
11871
11875
  }, [width, height, backgroundColor, shapes]);
11872
11876
  useEffect(() => {
@@ -13429,7 +13433,7 @@ var init_AlgorithmCanvas = __esm({
13429
13433
  DEFAULT_CELL_COLOR = "#e5e7eb";
13430
13434
  DEFAULT_POINTER_COLOR = "#dc2626";
13431
13435
  POINTER_BAND = 34;
13432
- TOP_PAD = 12;
13436
+ TOP_PAD = 26;
13433
13437
  AlgorithmCanvas = ({
13434
13438
  className,
13435
13439
  width = 600,
@@ -14533,23 +14537,188 @@ var init_BehaviorView = __esm({
14533
14537
  BehaviorView.displayName = "BehaviorView";
14534
14538
  }
14535
14539
  });
14536
- 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;
14537
14698
  var init_BiologyCanvas = __esm({
14538
14699
  "components/learning/molecules/BiologyCanvas.tsx"() {
14539
14700
  "use client";
14540
14701
  init_atoms();
14541
14702
  init_Stack();
14542
14703
  init_LearningCanvas();
14704
+ init_learningScene3D();
14705
+ biologyLog = createLogger("almadar:ui:biology-canvas");
14543
14706
  BiologyCanvas = ({
14544
14707
  className,
14545
14708
  width = 600,
14546
14709
  height = 400,
14547
14710
  title,
14548
14711
  backgroundColor,
14712
+ mode = "2d",
14713
+ camera,
14714
+ lighting,
14715
+ post,
14549
14716
  nodes = [],
14550
14717
  edges = [],
14551
14718
  shapes = [],
14552
- interactive = false,
14719
+ showGrid,
14720
+ shadows,
14721
+ interactive,
14553
14722
  animate = false,
14554
14723
  onShapeClick,
14555
14724
  isLoading,
@@ -14610,6 +14779,75 @@ var init_BiologyCanvas = __esm({
14610
14779
  out.push(...shapes);
14611
14780
  return out;
14612
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
+ }
14613
14851
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
14614
14852
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
14615
14853
  /* @__PURE__ */ jsx(
@@ -14619,7 +14857,7 @@ var init_BiologyCanvas = __esm({
14619
14857
  height,
14620
14858
  backgroundColor,
14621
14859
  shapes: derivedShapes,
14622
- interactive,
14860
+ interactive: interactive ?? false,
14623
14861
  animate,
14624
14862
  onShapeClick,
14625
14863
  isLoading,
@@ -21342,24 +21580,41 @@ var init_ChatBar = __esm({
21342
21580
  ChatBar.displayName = "ChatBar";
21343
21581
  }
21344
21582
  });
21345
- 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;
21346
21593
  var init_ChemistryCanvas = __esm({
21347
21594
  "components/learning/molecules/ChemistryCanvas.tsx"() {
21348
21595
  "use client";
21349
21596
  init_atoms();
21350
21597
  init_Stack();
21351
21598
  init_LearningCanvas();
21599
+ init_learningScene3D();
21600
+ chemistryLog = createLogger("almadar:ui:chemistry-canvas");
21352
21601
  ChemistryCanvas = ({
21353
21602
  className,
21354
21603
  width = 600,
21355
21604
  height = 400,
21356
21605
  title,
21357
21606
  backgroundColor,
21607
+ mode = "2d",
21608
+ camera,
21609
+ lighting,
21610
+ post,
21358
21611
  atoms = [],
21359
21612
  bonds = [],
21360
21613
  arrows = [],
21361
21614
  shapes = [],
21362
- interactive = false,
21615
+ showGrid,
21616
+ shadows,
21617
+ interactive,
21363
21618
  animate = false,
21364
21619
  onShapeClick,
21365
21620
  isLoading,
@@ -21438,6 +21693,88 @@ var init_ChemistryCanvas = __esm({
21438
21693
  out.push(...shapes);
21439
21694
  return out;
21440
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
+ }
21441
21778
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
21442
21779
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
21443
21780
  /* @__PURE__ */ jsx(
@@ -21447,7 +21784,7 @@ var init_ChemistryCanvas = __esm({
21447
21784
  height,
21448
21785
  backgroundColor,
21449
21786
  shapes: derivedShapes,
21450
- interactive,
21787
+ interactive: interactive ?? false,
21451
21788
  animate,
21452
21789
  onShapeClick,
21453
21790
  isLoading,
@@ -24840,6 +25177,7 @@ var init_FilterGroup = __esm({
24840
25177
  init_Badge();
24841
25178
  init_Stack();
24842
25179
  init_Icon();
25180
+ init_RangeSlider();
24843
25181
  init_useEventBus();
24844
25182
  init_useQuerySingleton();
24845
25183
  resolveFilterType = (filter) => filter.filterType ?? filter.type;
@@ -25034,6 +25372,35 @@ var init_FilterGroup = __esm({
25034
25372
  onClear: () => handleFilterSelect(`${filter.field}_to`, null)
25035
25373
  }
25036
25374
  )
25375
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
25376
+ /* @__PURE__ */ jsx(
25377
+ RangeSlider,
25378
+ {
25379
+ min: filter.min ?? 0,
25380
+ max: filter.max ?? 100,
25381
+ step: filter.step ?? 1,
25382
+ value: Number(
25383
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
25384
+ ),
25385
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
25386
+ showTooltip: true,
25387
+ "aria-label": t("filterGroup.from")
25388
+ }
25389
+ ),
25390
+ /* @__PURE__ */ jsx(
25391
+ RangeSlider,
25392
+ {
25393
+ min: filter.min ?? 0,
25394
+ max: filter.max ?? 100,
25395
+ step: filter.step ?? 1,
25396
+ value: Number(
25397
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
25398
+ ),
25399
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
25400
+ showTooltip: true,
25401
+ "aria-label": t("filterGroup.to")
25402
+ }
25403
+ )
25037
25404
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsx(
25038
25405
  Input,
25039
25406
  {
@@ -25116,6 +25483,36 @@ var init_FilterGroup = __esm({
25116
25483
  className: "text-sm min-w-[100px]"
25117
25484
  }
25118
25485
  )
25486
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
25487
+ /* @__PURE__ */ jsx(
25488
+ RangeSlider,
25489
+ {
25490
+ min: filter.min ?? 0,
25491
+ max: filter.max ?? 100,
25492
+ step: filter.step ?? 1,
25493
+ value: Number(
25494
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
25495
+ ),
25496
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
25497
+ className: "min-w-[100px]",
25498
+ "aria-label": t("filterGroup.from")
25499
+ }
25500
+ ),
25501
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "-" }),
25502
+ /* @__PURE__ */ jsx(
25503
+ RangeSlider,
25504
+ {
25505
+ min: filter.min ?? 0,
25506
+ max: filter.max ?? 100,
25507
+ step: filter.step ?? 1,
25508
+ value: Number(
25509
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
25510
+ ),
25511
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
25512
+ className: "min-w-[100px]",
25513
+ "aria-label": t("filterGroup.to")
25514
+ }
25515
+ )
25119
25516
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsx(
25120
25517
  Input,
25121
25518
  {
@@ -25234,6 +25631,38 @@ var init_FilterGroup = __esm({
25234
25631
  className: "min-w-[130px]"
25235
25632
  }
25236
25633
  )
25634
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
25635
+ /* @__PURE__ */ jsx(
25636
+ RangeSlider,
25637
+ {
25638
+ min: filter.min ?? 0,
25639
+ max: filter.max ?? 100,
25640
+ step: filter.step ?? 1,
25641
+ value: Number(
25642
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
25643
+ ),
25644
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
25645
+ showTooltip: true,
25646
+ className: "min-w-[130px]",
25647
+ "aria-label": t("filterGroup.from")
25648
+ }
25649
+ ),
25650
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "-" }),
25651
+ /* @__PURE__ */ jsx(
25652
+ RangeSlider,
25653
+ {
25654
+ min: filter.min ?? 0,
25655
+ max: filter.max ?? 100,
25656
+ step: filter.step ?? 1,
25657
+ value: Number(
25658
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
25659
+ ),
25660
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
25661
+ showTooltip: true,
25662
+ className: "min-w-[130px]",
25663
+ "aria-label": t("filterGroup.to")
25664
+ }
25665
+ )
25237
25666
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsx(
25238
25667
  Input,
25239
25668
  {
@@ -27582,19 +28011,25 @@ var init_MathCanvas = __esm({
27582
28011
  };
27583
28012
  }
27584
28013
  });
27585
- var PhysicsCanvas;
28014
+ var physicsLog2, PhysicsCanvas;
27586
28015
  var init_PhysicsCanvas = __esm({
27587
28016
  "components/learning/molecules/PhysicsCanvas.tsx"() {
27588
28017
  "use client";
27589
28018
  init_atoms();
27590
28019
  init_Stack();
27591
28020
  init_LearningCanvas();
28021
+ init_learningScene3D();
28022
+ physicsLog2 = createLogger("almadar:ui:physics-canvas");
27592
28023
  PhysicsCanvas = ({
27593
28024
  className,
27594
28025
  width = 600,
27595
28026
  height = 400,
27596
28027
  title,
27597
28028
  backgroundColor,
28029
+ mode = "2d",
28030
+ camera,
28031
+ lighting,
28032
+ post,
27598
28033
  bodies = [],
27599
28034
  constraints = [],
27600
28035
  showVelocity = true,
@@ -27602,7 +28037,9 @@ var init_PhysicsCanvas = __esm({
27602
28037
  velocityScale = 20,
27603
28038
  forceScale = 20,
27604
28039
  shapes = [],
27605
- interactive = false,
28040
+ showGrid,
28041
+ shadows,
28042
+ interactive,
27606
28043
  animate = false,
27607
28044
  onShapeClick,
27608
28045
  isLoading,
@@ -27674,6 +28111,89 @@ var init_PhysicsCanvas = __esm({
27674
28111
  out.push(...shapes);
27675
28112
  return out;
27676
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
+ }
27677
28197
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
27678
28198
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
27679
28199
  /* @__PURE__ */ jsx(
@@ -27683,7 +28203,7 @@ var init_PhysicsCanvas = __esm({
27683
28203
  height,
27684
28204
  backgroundColor,
27685
28205
  shapes: derivedShapes,
27686
- interactive,
28206
+ interactive: interactive ?? false,
27687
28207
  animate,
27688
28208
  onShapeClick,
27689
28209
  isLoading,
@@ -28301,12 +28821,12 @@ var init_MapView = __esm({
28301
28821
  shadowSize: [41, 41]
28302
28822
  });
28303
28823
  L.Marker.prototype.options.icon = defaultIcon;
28304
- const { useEffect: useEffect60, useRef: useRef62, useCallback: useCallback90, useState: useState92 } = React85__default;
28824
+ const { useEffect: useEffect60, useRef: useRef63, useCallback: useCallback90, useState: useState92 } = React85__default;
28305
28825
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
28306
28826
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
28307
28827
  function MapUpdater({ centerLat, centerLng, zoom }) {
28308
28828
  const map = useMap();
28309
- const prevRef = useRef62({ centerLat, centerLng, zoom });
28829
+ const prevRef = useRef63({ centerLat, centerLng, zoom });
28310
28830
  useEffect60(() => {
28311
28831
  const prev = prevRef.current;
28312
28832
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -38971,7 +39491,7 @@ function getEnumOptions(field) {
38971
39491
  }));
38972
39492
  }
38973
39493
  const validation = field.validation;
38974
- if (validation?.enum && Array.isArray(validation.enum)) {
39494
+ if (validation?.enum && validation.enum.length > 0) {
38975
39495
  return validation.enum.map((v) => ({
38976
39496
  value: v,
38977
39497
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")