@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
@@ -10301,9 +10301,10 @@ function Canvas({
10301
10301
  drawables: [...drawables ?? [], ...childDrawables],
10302
10302
  isLoading,
10303
10303
  cameraMode: to3DCameraMode(camera?.mode),
10304
- ...zoom !== void 0 ? { scale: zoom } : {},
10304
+ ...zoom !== void 0 ? { zoom } : {},
10305
10305
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
10306
10306
  ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
10307
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
10307
10308
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
10308
10309
  unitScale,
10309
10310
  backgroundColor,
@@ -11779,7 +11780,10 @@ var init_LearningCanvas = __esm({
11779
11780
  ctx.fillRect(0, 0, width, height);
11780
11781
  }
11781
11782
  for (const shape of shapes) {
11782
- drawShape(ctx, shape, width, height);
11783
+ if (shape.type !== "text") drawShape(ctx, shape, width, height);
11784
+ }
11785
+ for (const shape of shapes) {
11786
+ if (shape.type === "text") drawShape(ctx, shape, width, height);
11783
11787
  }
11784
11788
  }, [width, height, backgroundColor, shapes]);
11785
11789
  useEffect(() => {
@@ -13725,7 +13729,7 @@ var init_AlgorithmCanvas = __esm({
13725
13729
  DEFAULT_CELL_COLOR = "#e5e7eb";
13726
13730
  DEFAULT_POINTER_COLOR = "#dc2626";
13727
13731
  POINTER_BAND = 34;
13728
- TOP_PAD = 12;
13732
+ TOP_PAD = 26;
13729
13733
  AlgorithmCanvas = ({
13730
13734
  className,
13731
13735
  width = 600,
@@ -14829,23 +14833,188 @@ var init_BehaviorView = __esm({
14829
14833
  BehaviorView.displayName = "BehaviorView";
14830
14834
  }
14831
14835
  });
14832
- var BiologyCanvas;
14836
+ function LearningScene3D({
14837
+ className,
14838
+ width = 600,
14839
+ height = 400,
14840
+ title,
14841
+ backgroundColor,
14842
+ drawables,
14843
+ camera,
14844
+ lighting,
14845
+ post,
14846
+ showGrid = false,
14847
+ shadows,
14848
+ interactive,
14849
+ isLoading,
14850
+ error,
14851
+ onItemClick
14852
+ }) {
14853
+ const instanceId = useId().replace(/[^a-zA-Z0-9]/g, "");
14854
+ const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
14855
+ const onItemClickRef = useRef(onItemClick);
14856
+ onItemClickRef.current = onItemClick;
14857
+ useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
14858
+ const unitId = event.payload?.unitId;
14859
+ if (typeof unitId === "string") onItemClickRef.current?.(unitId);
14860
+ });
14861
+ const props3d = {
14862
+ drawables,
14863
+ isLoading,
14864
+ error: error ? error.message : null,
14865
+ cameraMode: camera?.mode ?? "perspective",
14866
+ ...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
14867
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
14868
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
14869
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
14870
+ ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
14871
+ backgroundColor,
14872
+ showGrid,
14873
+ ...shadows !== void 0 ? { shadows } : {},
14874
+ ...interactive !== void 0 ? { controlsEnabled: interactive } : {},
14875
+ lighting,
14876
+ post,
14877
+ ...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
14878
+ };
14879
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
14880
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
14881
+ /* @__PURE__ */ jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost2, { ...props3d }) }) })
14882
+ ] }) });
14883
+ }
14884
+ function meshSphere(id, x, y, z, radius, color, opts) {
14885
+ return {
14886
+ type: "draw-mesh",
14887
+ ...id !== void 0 ? { id } : {},
14888
+ shape: opts?.shape ?? "sphere",
14889
+ position: { x, y, z },
14890
+ radius,
14891
+ pivot: "center",
14892
+ segments: opts?.segments ?? 24,
14893
+ material: { color, ...opts?.material },
14894
+ ...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
14895
+ };
14896
+ }
14897
+ function axisRotation(from, to) {
14898
+ const wx = to[0] - from[0];
14899
+ const wy = to[2] - from[2];
14900
+ const wz = to[1] - from[1];
14901
+ const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
14902
+ const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
14903
+ return [0, Math.atan2(wz, -wx), tilt];
14904
+ }
14905
+ function segmentLength(from, to) {
14906
+ const dx = to[0] - from[0];
14907
+ const dy = to[1] - from[1];
14908
+ const dz = to[2] - from[2];
14909
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
14910
+ }
14911
+ function cylinderBetween(from, to, radius, color) {
14912
+ const len = segmentLength(from, to);
14913
+ if (len < 1e-6) return null;
14914
+ return {
14915
+ type: "draw-mesh",
14916
+ shape: "cylinder",
14917
+ position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
14918
+ radius,
14919
+ height: len,
14920
+ rotation: axisRotation(from, to),
14921
+ pivot: "center",
14922
+ segments: 12,
14923
+ material: { color }
14924
+ };
14925
+ }
14926
+ function arrowBetween(from, to, color, shaftRadius = 0.08) {
14927
+ const len = segmentLength(from, to);
14928
+ if (len < 1e-6) return null;
14929
+ const tipLen = Math.min(shaftRadius * 8, len * 0.35);
14930
+ const k = (len - tipLen) / len;
14931
+ const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
14932
+ const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
14933
+ const tipStart = shaftEnd;
14934
+ const tipEnd = delta;
14935
+ const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
14936
+ const tipLenActual = segmentLength(tipStart, tipEnd);
14937
+ const tip = {
14938
+ type: "draw-mesh",
14939
+ shape: "cone",
14940
+ position: {
14941
+ x: (tipStart[0] + tipEnd[0]) / 2,
14942
+ y: (tipStart[1] + tipEnd[1]) / 2,
14943
+ z: (tipStart[2] + tipEnd[2]) / 2
14944
+ },
14945
+ radius: shaftRadius * 3,
14946
+ height: tipLenActual,
14947
+ rotation: axisRotation(tipStart, tipEnd),
14948
+ pivot: "center",
14949
+ segments: 12,
14950
+ material: { color }
14951
+ };
14952
+ return {
14953
+ type: "draw-group",
14954
+ position: { x: from[0], y: from[1], z: from[2] },
14955
+ items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
14956
+ };
14957
+ }
14958
+ function billboardLabel(text, x, y, z, opts) {
14959
+ return {
14960
+ type: "draw-text",
14961
+ text,
14962
+ position: { x, y, z },
14963
+ color: opts?.color ?? "#111827"
14964
+ };
14965
+ }
14966
+ function labelColorForBackground(backgroundColor) {
14967
+ const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
14968
+ if (!m) return "#111827";
14969
+ const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
14970
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
14971
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
14972
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
14973
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
14974
+ return luminance < 0.5 ? "#e5e7eb" : "#111827";
14975
+ }
14976
+ function get3DClickPayload(onShapeClick, idToIndex) {
14977
+ if (!onShapeClick) return void 0;
14978
+ return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
14979
+ }
14980
+ var Canvas3DHost2;
14981
+ var init_learningScene3D = __esm({
14982
+ "components/learning/molecules/learningScene3D.tsx"() {
14983
+ "use client";
14984
+ init_atoms();
14985
+ init_Stack();
14986
+ init_useEventBus();
14987
+ Canvas3DHost2 = lazy(
14988
+ () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
14989
+ );
14990
+ LearningScene3D.displayName = "LearningScene3D";
14991
+ }
14992
+ });
14993
+ var biologyLog, BiologyCanvas;
14833
14994
  var init_BiologyCanvas = __esm({
14834
14995
  "components/learning/molecules/BiologyCanvas.tsx"() {
14835
14996
  "use client";
14836
14997
  init_atoms();
14837
14998
  init_Stack();
14838
14999
  init_LearningCanvas();
15000
+ init_learningScene3D();
15001
+ biologyLog = createLogger("almadar:ui:biology-canvas");
14839
15002
  BiologyCanvas = ({
14840
15003
  className,
14841
15004
  width = 600,
14842
15005
  height = 400,
14843
15006
  title,
14844
15007
  backgroundColor,
15008
+ mode = "2d",
15009
+ camera,
15010
+ lighting,
15011
+ post,
14845
15012
  nodes = [],
14846
15013
  edges = [],
14847
15014
  shapes = [],
14848
- interactive = false,
15015
+ showGrid,
15016
+ shadows,
15017
+ interactive,
14849
15018
  animate = false,
14850
15019
  onShapeClick,
14851
15020
  isLoading,
@@ -14906,6 +15075,75 @@ var init_BiologyCanvas = __esm({
14906
15075
  out.push(...shapes);
14907
15076
  return out;
14908
15077
  }, [nodes, edges, shapes]);
15078
+ const drawables3D = useMemo(() => {
15079
+ if (mode !== "3d") return [];
15080
+ if (shapes.length > 0) {
15081
+ biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
15082
+ }
15083
+ const out = [];
15084
+ const labelColor = labelColorForBackground(backgroundColor);
15085
+ const nodeById = /* @__PURE__ */ new Map();
15086
+ for (const n of nodes) {
15087
+ if (n.id) nodeById.set(n.id, n);
15088
+ }
15089
+ for (const e of edges) {
15090
+ const a = nodeById.get(e.from);
15091
+ const b = nodeById.get(e.to);
15092
+ if (!a || !b) continue;
15093
+ const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
15094
+ const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
15095
+ if (edge) out.push(edge);
15096
+ if (e.label) {
15097
+ out.push(
15098
+ billboardLabel(
15099
+ e.label,
15100
+ (a.x + b.x) / 2,
15101
+ (a.y + b.y) / 2,
15102
+ ((a.z ?? 0) + (b.z ?? 0)) / 2,
15103
+ { color: labelColor }
15104
+ )
15105
+ );
15106
+ }
15107
+ }
15108
+ for (const n of nodes) {
15109
+ const radius = n.radius ?? 0.5;
15110
+ const nz = n.z ?? 0;
15111
+ out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
15112
+ if (n.label) {
15113
+ out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
15114
+ }
15115
+ }
15116
+ return out;
15117
+ }, [mode, nodes, edges, shapes, backgroundColor]);
15118
+ const nodeIndexById = useMemo(() => {
15119
+ const m = /* @__PURE__ */ new Map();
15120
+ nodes.forEach((n, i) => {
15121
+ if (n.id) m.set(n.id, i);
15122
+ });
15123
+ return m;
15124
+ }, [nodes]);
15125
+ if (mode === "3d") {
15126
+ return /* @__PURE__ */ jsx(
15127
+ LearningScene3D,
15128
+ {
15129
+ className,
15130
+ width,
15131
+ height,
15132
+ title,
15133
+ backgroundColor,
15134
+ drawables: drawables3D,
15135
+ camera,
15136
+ lighting,
15137
+ post,
15138
+ showGrid,
15139
+ shadows,
15140
+ interactive,
15141
+ isLoading,
15142
+ error,
15143
+ onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
15144
+ }
15145
+ );
15146
+ }
14909
15147
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
14910
15148
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
14911
15149
  /* @__PURE__ */ jsx(
@@ -14915,7 +15153,7 @@ var init_BiologyCanvas = __esm({
14915
15153
  height,
14916
15154
  backgroundColor,
14917
15155
  shapes: derivedShapes,
14918
- interactive,
15156
+ interactive: interactive ?? false,
14919
15157
  animate,
14920
15158
  onShapeClick,
14921
15159
  isLoading,
@@ -21755,24 +21993,41 @@ var init_ChatBar = __esm({
21755
21993
  ChatBar.displayName = "ChatBar";
21756
21994
  }
21757
21995
  });
21758
- var ChemistryCanvas;
21996
+ function bondPerpendicular(a, b) {
21997
+ const dx = b[0] - a[0];
21998
+ const dy = b[1] - a[1];
21999
+ const px = dy;
22000
+ const py = -dx;
22001
+ const len = Math.sqrt(px * px + py * py);
22002
+ if (len < 1e-6) return [1, 0, 0];
22003
+ return [px / len, py / len, 0];
22004
+ }
22005
+ var chemistryLog, ChemistryCanvas;
21759
22006
  var init_ChemistryCanvas = __esm({
21760
22007
  "components/learning/molecules/ChemistryCanvas.tsx"() {
21761
22008
  "use client";
21762
22009
  init_atoms();
21763
22010
  init_Stack();
21764
22011
  init_LearningCanvas();
22012
+ init_learningScene3D();
22013
+ chemistryLog = createLogger("almadar:ui:chemistry-canvas");
21765
22014
  ChemistryCanvas = ({
21766
22015
  className,
21767
22016
  width = 600,
21768
22017
  height = 400,
21769
22018
  title,
21770
22019
  backgroundColor,
22020
+ mode = "2d",
22021
+ camera,
22022
+ lighting,
22023
+ post,
21771
22024
  atoms = [],
21772
22025
  bonds = [],
21773
22026
  arrows = [],
21774
22027
  shapes = [],
21775
- interactive = false,
22028
+ showGrid,
22029
+ shadows,
22030
+ interactive,
21776
22031
  animate = false,
21777
22032
  onShapeClick,
21778
22033
  isLoading,
@@ -21851,6 +22106,88 @@ var init_ChemistryCanvas = __esm({
21851
22106
  out.push(...shapes);
21852
22107
  return out;
21853
22108
  }, [atoms, bonds, arrows, shapes]);
22109
+ const drawables3D = useMemo(() => {
22110
+ if (mode !== "3d") return [];
22111
+ if (shapes.length > 0) {
22112
+ chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
22113
+ }
22114
+ const out = [];
22115
+ const labelColor = labelColorForBackground(backgroundColor);
22116
+ const atomById = /* @__PURE__ */ new Map();
22117
+ for (const a of atoms) {
22118
+ if (a.id) atomById.set(a.id, a);
22119
+ }
22120
+ for (const b of bonds) {
22121
+ const a = atomById.get(b.from);
22122
+ const c = atomById.get(b.to);
22123
+ if (!a || !c) continue;
22124
+ const color = b.color ?? "#6b7280";
22125
+ const from = [a.x, a.y, a.z ?? 0];
22126
+ const to = [c.x, c.y, c.z ?? 0];
22127
+ const perp = bondPerpendicular(from, to);
22128
+ const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
22129
+ const step = bondRadius * 2.2;
22130
+ const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
22131
+ for (const off of offsets) {
22132
+ const bond = cylinderBetween(
22133
+ [from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
22134
+ [to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
22135
+ bondRadius,
22136
+ color
22137
+ );
22138
+ if (bond) out.push(bond);
22139
+ }
22140
+ }
22141
+ for (const a of arrows) {
22142
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
22143
+ const len = a.length ?? 60;
22144
+ const x2 = a.x + Math.cos(angle) * len;
22145
+ const y2 = a.y + Math.sin(angle) * len;
22146
+ const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
22147
+ if (arrow) out.push(arrow);
22148
+ if (a.label) {
22149
+ out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
22150
+ }
22151
+ }
22152
+ for (const a of atoms) {
22153
+ const radius = a.radius ?? 0.45;
22154
+ const az = a.z ?? 0;
22155
+ out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
22156
+ if (a.element) {
22157
+ out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
22158
+ }
22159
+ }
22160
+ return out;
22161
+ }, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
22162
+ const atomIndexById = useMemo(() => {
22163
+ const m = /* @__PURE__ */ new Map();
22164
+ atoms.forEach((a, i) => {
22165
+ if (a.id) m.set(a.id, i);
22166
+ });
22167
+ return m;
22168
+ }, [atoms]);
22169
+ if (mode === "3d") {
22170
+ return /* @__PURE__ */ jsx(
22171
+ LearningScene3D,
22172
+ {
22173
+ className,
22174
+ width,
22175
+ height,
22176
+ title,
22177
+ backgroundColor,
22178
+ drawables: drawables3D,
22179
+ camera,
22180
+ lighting,
22181
+ post,
22182
+ showGrid,
22183
+ shadows,
22184
+ interactive,
22185
+ isLoading,
22186
+ error,
22187
+ onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
22188
+ }
22189
+ );
22190
+ }
21854
22191
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
21855
22192
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
21856
22193
  /* @__PURE__ */ jsx(
@@ -21860,7 +22197,7 @@ var init_ChemistryCanvas = __esm({
21860
22197
  height,
21861
22198
  backgroundColor,
21862
22199
  shapes: derivedShapes,
21863
- interactive,
22200
+ interactive: interactive ?? false,
21864
22201
  animate,
21865
22202
  onShapeClick,
21866
22203
  isLoading,
@@ -25331,6 +25668,7 @@ var init_FilterGroup = __esm({
25331
25668
  init_Badge();
25332
25669
  init_Stack();
25333
25670
  init_Icon();
25671
+ init_RangeSlider();
25334
25672
  init_useEventBus();
25335
25673
  init_useQuerySingleton();
25336
25674
  resolveFilterType = (filter) => filter.filterType ?? filter.type;
@@ -25525,6 +25863,35 @@ var init_FilterGroup = __esm({
25525
25863
  onClear: () => handleFilterSelect(`${filter.field}_to`, null)
25526
25864
  }
25527
25865
  )
25866
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
25867
+ /* @__PURE__ */ jsx(
25868
+ RangeSlider,
25869
+ {
25870
+ min: filter.min ?? 0,
25871
+ max: filter.max ?? 100,
25872
+ step: filter.step ?? 1,
25873
+ value: Number(
25874
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
25875
+ ),
25876
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
25877
+ showTooltip: true,
25878
+ "aria-label": t("filterGroup.from")
25879
+ }
25880
+ ),
25881
+ /* @__PURE__ */ jsx(
25882
+ RangeSlider,
25883
+ {
25884
+ min: filter.min ?? 0,
25885
+ max: filter.max ?? 100,
25886
+ step: filter.step ?? 1,
25887
+ value: Number(
25888
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
25889
+ ),
25890
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
25891
+ showTooltip: true,
25892
+ "aria-label": t("filterGroup.to")
25893
+ }
25894
+ )
25528
25895
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsx(
25529
25896
  Input,
25530
25897
  {
@@ -25607,6 +25974,36 @@ var init_FilterGroup = __esm({
25607
25974
  className: "text-sm min-w-[100px]"
25608
25975
  }
25609
25976
  )
25977
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
25978
+ /* @__PURE__ */ jsx(
25979
+ RangeSlider,
25980
+ {
25981
+ min: filter.min ?? 0,
25982
+ max: filter.max ?? 100,
25983
+ step: filter.step ?? 1,
25984
+ value: Number(
25985
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
25986
+ ),
25987
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
25988
+ className: "min-w-[100px]",
25989
+ "aria-label": t("filterGroup.from")
25990
+ }
25991
+ ),
25992
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "-" }),
25993
+ /* @__PURE__ */ jsx(
25994
+ RangeSlider,
25995
+ {
25996
+ min: filter.min ?? 0,
25997
+ max: filter.max ?? 100,
25998
+ step: filter.step ?? 1,
25999
+ value: Number(
26000
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
26001
+ ),
26002
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
26003
+ className: "min-w-[100px]",
26004
+ "aria-label": t("filterGroup.to")
26005
+ }
26006
+ )
25610
26007
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsx(
25611
26008
  Input,
25612
26009
  {
@@ -25725,6 +26122,38 @@ var init_FilterGroup = __esm({
25725
26122
  className: "min-w-[130px]"
25726
26123
  }
25727
26124
  )
26125
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
26126
+ /* @__PURE__ */ jsx(
26127
+ RangeSlider,
26128
+ {
26129
+ min: filter.min ?? 0,
26130
+ max: filter.max ?? 100,
26131
+ step: filter.step ?? 1,
26132
+ value: Number(
26133
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
26134
+ ),
26135
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
26136
+ showTooltip: true,
26137
+ className: "min-w-[130px]",
26138
+ "aria-label": t("filterGroup.from")
26139
+ }
26140
+ ),
26141
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "-" }),
26142
+ /* @__PURE__ */ jsx(
26143
+ RangeSlider,
26144
+ {
26145
+ min: filter.min ?? 0,
26146
+ max: filter.max ?? 100,
26147
+ step: filter.step ?? 1,
26148
+ value: Number(
26149
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
26150
+ ),
26151
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
26152
+ showTooltip: true,
26153
+ className: "min-w-[130px]",
26154
+ "aria-label": t("filterGroup.to")
26155
+ }
26156
+ )
25728
26157
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsx(
25729
26158
  Input,
25730
26159
  {
@@ -28073,19 +28502,25 @@ var init_MathCanvas = __esm({
28073
28502
  };
28074
28503
  }
28075
28504
  });
28076
- var PhysicsCanvas;
28505
+ var physicsLog2, PhysicsCanvas;
28077
28506
  var init_PhysicsCanvas = __esm({
28078
28507
  "components/learning/molecules/PhysicsCanvas.tsx"() {
28079
28508
  "use client";
28080
28509
  init_atoms();
28081
28510
  init_Stack();
28082
28511
  init_LearningCanvas();
28512
+ init_learningScene3D();
28513
+ physicsLog2 = createLogger("almadar:ui:physics-canvas");
28083
28514
  PhysicsCanvas = ({
28084
28515
  className,
28085
28516
  width = 600,
28086
28517
  height = 400,
28087
28518
  title,
28088
28519
  backgroundColor,
28520
+ mode = "2d",
28521
+ camera,
28522
+ lighting,
28523
+ post,
28089
28524
  bodies = [],
28090
28525
  constraints = [],
28091
28526
  showVelocity = true,
@@ -28093,7 +28528,9 @@ var init_PhysicsCanvas = __esm({
28093
28528
  velocityScale = 20,
28094
28529
  forceScale = 20,
28095
28530
  shapes = [],
28096
- interactive = false,
28531
+ showGrid,
28532
+ shadows,
28533
+ interactive,
28097
28534
  animate = false,
28098
28535
  onShapeClick,
28099
28536
  isLoading,
@@ -28165,6 +28602,89 @@ var init_PhysicsCanvas = __esm({
28165
28602
  out.push(...shapes);
28166
28603
  return out;
28167
28604
  }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
28605
+ const drawables3D = useMemo(() => {
28606
+ if (mode !== "3d") return [];
28607
+ if (shapes.length > 0) {
28608
+ physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
28609
+ }
28610
+ const out = [];
28611
+ const labelColor = labelColorForBackground(backgroundColor);
28612
+ const bodyById = /* @__PURE__ */ new Map();
28613
+ for (const b of bodies) {
28614
+ if (b.id) bodyById.set(b.id, b);
28615
+ }
28616
+ for (const c of constraints) {
28617
+ const a = bodyById.get(c.from);
28618
+ const b = bodyById.get(c.to);
28619
+ if (!a || !b) continue;
28620
+ const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
28621
+ const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
28622
+ if (rod) out.push(rod);
28623
+ }
28624
+ for (const b of bodies) {
28625
+ const radius = b.radius ?? 0.5;
28626
+ const bz = b.z ?? 0;
28627
+ out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
28628
+ if (b.label) {
28629
+ out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
28630
+ }
28631
+ const vx = b.vx ?? 0;
28632
+ const vy = b.vy ?? 0;
28633
+ const vz = b.vz ?? 0;
28634
+ const arrowRadius = Math.max(0.05, radius * 0.15);
28635
+ if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
28636
+ const arrow = arrowBetween(
28637
+ [b.x, b.y, bz],
28638
+ [b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
28639
+ "#16a34a",
28640
+ arrowRadius
28641
+ );
28642
+ if (arrow) out.push(arrow);
28643
+ }
28644
+ const fx = b.fx ?? 0;
28645
+ const fy = b.fy ?? 0;
28646
+ const fz = b.fz ?? 0;
28647
+ if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
28648
+ const arrow = arrowBetween(
28649
+ [b.x, b.y, bz],
28650
+ [b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
28651
+ "#dc2626",
28652
+ arrowRadius
28653
+ );
28654
+ if (arrow) out.push(arrow);
28655
+ }
28656
+ }
28657
+ return out;
28658
+ }, [mode, bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes, backgroundColor]);
28659
+ const bodyIndexById = useMemo(() => {
28660
+ const m = /* @__PURE__ */ new Map();
28661
+ bodies.forEach((b, i) => {
28662
+ if (b.id) m.set(b.id, i);
28663
+ });
28664
+ return m;
28665
+ }, [bodies]);
28666
+ if (mode === "3d") {
28667
+ return /* @__PURE__ */ jsx(
28668
+ LearningScene3D,
28669
+ {
28670
+ className,
28671
+ width,
28672
+ height,
28673
+ title,
28674
+ backgroundColor,
28675
+ drawables: drawables3D,
28676
+ camera,
28677
+ lighting,
28678
+ post,
28679
+ showGrid,
28680
+ shadows,
28681
+ interactive,
28682
+ isLoading,
28683
+ error,
28684
+ onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
28685
+ }
28686
+ );
28687
+ }
28168
28688
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
28169
28689
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
28170
28690
  /* @__PURE__ */ jsx(
@@ -28174,7 +28694,7 @@ var init_PhysicsCanvas = __esm({
28174
28694
  height,
28175
28695
  backgroundColor,
28176
28696
  shapes: derivedShapes,
28177
- interactive,
28697
+ interactive: interactive ?? false,
28178
28698
  animate,
28179
28699
  onShapeClick,
28180
28700
  isLoading,
@@ -28792,12 +29312,12 @@ var init_MapView = __esm({
28792
29312
  shadowSize: [41, 41]
28793
29313
  });
28794
29314
  L.Marker.prototype.options.icon = defaultIcon;
28795
- const { useEffect: useEffect63, useRef: useRef62, useCallback: useCallback96, useState: useState94 } = React87__default;
29315
+ const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback96, useState: useState94 } = React87__default;
28796
29316
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
28797
29317
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
28798
29318
  function MapUpdater({ centerLat, centerLng, zoom }) {
28799
29319
  const map = useMap();
28800
- const prevRef = useRef62({ centerLat, centerLng, zoom });
29320
+ const prevRef = useRef63({ centerLat, centerLng, zoom });
28801
29321
  useEffect63(() => {
28802
29322
  const prev = prevRef.current;
28803
29323
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -39620,7 +40140,7 @@ function getEnumOptions(field) {
39620
40140
  }));
39621
40141
  }
39622
40142
  const validation = field.validation;
39623
- if (validation?.enum && Array.isArray(validation.enum)) {
40143
+ if (validation?.enum && validation.enum.length > 0) {
39624
40144
  return validation.enum.map((v) => ({
39625
40145
  value: v,
39626
40146
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")