@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
@@ -10375,9 +10375,10 @@ function Canvas({
10375
10375
  drawables: [...drawables ?? [], ...childDrawables],
10376
10376
  isLoading,
10377
10377
  cameraMode: to3DCameraMode(camera?.mode),
10378
- ...zoom !== void 0 ? { scale: zoom } : {},
10378
+ ...zoom !== void 0 ? { zoom } : {},
10379
10379
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
10380
10380
  ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
10381
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
10381
10382
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
10382
10383
  unitScale,
10383
10384
  backgroundColor,
@@ -11853,7 +11854,10 @@ var init_LearningCanvas = __esm({
11853
11854
  ctx.fillRect(0, 0, width, height);
11854
11855
  }
11855
11856
  for (const shape of shapes) {
11856
- drawShape(ctx, shape, width, height);
11857
+ if (shape.type !== "text") drawShape(ctx, shape, width, height);
11858
+ }
11859
+ for (const shape of shapes) {
11860
+ if (shape.type === "text") drawShape(ctx, shape, width, height);
11857
11861
  }
11858
11862
  }, [width, height, backgroundColor, shapes]);
11859
11863
  React87.useEffect(() => {
@@ -13799,7 +13803,7 @@ var init_AlgorithmCanvas = __esm({
13799
13803
  DEFAULT_CELL_COLOR = "#e5e7eb";
13800
13804
  DEFAULT_POINTER_COLOR = "#dc2626";
13801
13805
  POINTER_BAND = 34;
13802
- TOP_PAD = 12;
13806
+ TOP_PAD = 26;
13803
13807
  AlgorithmCanvas = ({
13804
13808
  className,
13805
13809
  width = 600,
@@ -14903,23 +14907,188 @@ var init_BehaviorView = __esm({
14903
14907
  BehaviorView.displayName = "BehaviorView";
14904
14908
  }
14905
14909
  });
14906
- var BiologyCanvas;
14910
+ function LearningScene3D({
14911
+ className,
14912
+ width = 600,
14913
+ height = 400,
14914
+ title,
14915
+ backgroundColor,
14916
+ drawables,
14917
+ camera,
14918
+ lighting,
14919
+ post,
14920
+ showGrid = false,
14921
+ shadows,
14922
+ interactive,
14923
+ isLoading,
14924
+ error,
14925
+ onItemClick
14926
+ }) {
14927
+ const instanceId = React87.useId().replace(/[^a-zA-Z0-9]/g, "");
14928
+ const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
14929
+ const onItemClickRef = React87.useRef(onItemClick);
14930
+ onItemClickRef.current = onItemClick;
14931
+ useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
14932
+ const unitId = event.payload?.unitId;
14933
+ if (typeof unitId === "string") onItemClickRef.current?.(unitId);
14934
+ });
14935
+ const props3d = {
14936
+ drawables,
14937
+ isLoading,
14938
+ error: error ? error.message : null,
14939
+ cameraMode: camera?.mode ?? "perspective",
14940
+ ...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
14941
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
14942
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
14943
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
14944
+ ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
14945
+ backgroundColor,
14946
+ showGrid,
14947
+ ...shadows !== void 0 ? { shadows } : {},
14948
+ ...interactive !== void 0 ? { controlsEnabled: interactive } : {},
14949
+ lighting,
14950
+ post,
14951
+ ...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
14952
+ };
14953
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
14954
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
14955
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsxRuntime.jsx(React87.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(Canvas3DHost2, { ...props3d }) }) })
14956
+ ] }) });
14957
+ }
14958
+ function meshSphere(id, x, y, z, radius, color, opts) {
14959
+ return {
14960
+ type: "draw-mesh",
14961
+ ...id !== void 0 ? { id } : {},
14962
+ shape: opts?.shape ?? "sphere",
14963
+ position: { x, y, z },
14964
+ radius,
14965
+ pivot: "center",
14966
+ segments: opts?.segments ?? 24,
14967
+ material: { color, ...opts?.material },
14968
+ ...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
14969
+ };
14970
+ }
14971
+ function axisRotation(from, to) {
14972
+ const wx = to[0] - from[0];
14973
+ const wy = to[2] - from[2];
14974
+ const wz = to[1] - from[1];
14975
+ const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
14976
+ const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
14977
+ return [0, Math.atan2(wz, -wx), tilt];
14978
+ }
14979
+ function segmentLength(from, to) {
14980
+ const dx = to[0] - from[0];
14981
+ const dy = to[1] - from[1];
14982
+ const dz = to[2] - from[2];
14983
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
14984
+ }
14985
+ function cylinderBetween(from, to, radius, color) {
14986
+ const len = segmentLength(from, to);
14987
+ if (len < 1e-6) return null;
14988
+ return {
14989
+ type: "draw-mesh",
14990
+ shape: "cylinder",
14991
+ position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
14992
+ radius,
14993
+ height: len,
14994
+ rotation: axisRotation(from, to),
14995
+ pivot: "center",
14996
+ segments: 12,
14997
+ material: { color }
14998
+ };
14999
+ }
15000
+ function arrowBetween(from, to, color, shaftRadius = 0.08) {
15001
+ const len = segmentLength(from, to);
15002
+ if (len < 1e-6) return null;
15003
+ const tipLen = Math.min(shaftRadius * 8, len * 0.35);
15004
+ const k = (len - tipLen) / len;
15005
+ const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
15006
+ const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
15007
+ const tipStart = shaftEnd;
15008
+ const tipEnd = delta;
15009
+ const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
15010
+ const tipLenActual = segmentLength(tipStart, tipEnd);
15011
+ const tip = {
15012
+ type: "draw-mesh",
15013
+ shape: "cone",
15014
+ position: {
15015
+ x: (tipStart[0] + tipEnd[0]) / 2,
15016
+ y: (tipStart[1] + tipEnd[1]) / 2,
15017
+ z: (tipStart[2] + tipEnd[2]) / 2
15018
+ },
15019
+ radius: shaftRadius * 3,
15020
+ height: tipLenActual,
15021
+ rotation: axisRotation(tipStart, tipEnd),
15022
+ pivot: "center",
15023
+ segments: 12,
15024
+ material: { color }
15025
+ };
15026
+ return {
15027
+ type: "draw-group",
15028
+ position: { x: from[0], y: from[1], z: from[2] },
15029
+ items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
15030
+ };
15031
+ }
15032
+ function billboardLabel(text, x, y, z, opts) {
15033
+ return {
15034
+ type: "draw-text",
15035
+ text,
15036
+ position: { x, y, z },
15037
+ color: opts?.color ?? "#111827"
15038
+ };
15039
+ }
15040
+ function labelColorForBackground(backgroundColor) {
15041
+ const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
15042
+ if (!m) return "#111827";
15043
+ const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
15044
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
15045
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
15046
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
15047
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
15048
+ return luminance < 0.5 ? "#e5e7eb" : "#111827";
15049
+ }
15050
+ function get3DClickPayload(onShapeClick, idToIndex) {
15051
+ if (!onShapeClick) return void 0;
15052
+ return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
15053
+ }
15054
+ var Canvas3DHost2;
15055
+ var init_learningScene3D = __esm({
15056
+ "components/learning/molecules/learningScene3D.tsx"() {
15057
+ "use client";
15058
+ init_atoms();
15059
+ init_Stack();
15060
+ init_useEventBus();
15061
+ Canvas3DHost2 = React87.lazy(
15062
+ () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
15063
+ );
15064
+ LearningScene3D.displayName = "LearningScene3D";
15065
+ }
15066
+ });
15067
+ var biologyLog, BiologyCanvas;
14907
15068
  var init_BiologyCanvas = __esm({
14908
15069
  "components/learning/molecules/BiologyCanvas.tsx"() {
14909
15070
  "use client";
14910
15071
  init_atoms();
14911
15072
  init_Stack();
14912
15073
  init_LearningCanvas();
15074
+ init_learningScene3D();
15075
+ biologyLog = logger.createLogger("almadar:ui:biology-canvas");
14913
15076
  BiologyCanvas = ({
14914
15077
  className,
14915
15078
  width = 600,
14916
15079
  height = 400,
14917
15080
  title,
14918
15081
  backgroundColor,
15082
+ mode = "2d",
15083
+ camera,
15084
+ lighting,
15085
+ post,
14919
15086
  nodes = [],
14920
15087
  edges = [],
14921
15088
  shapes = [],
14922
- interactive = false,
15089
+ showGrid,
15090
+ shadows,
15091
+ interactive,
14923
15092
  animate = false,
14924
15093
  onShapeClick,
14925
15094
  isLoading,
@@ -14980,6 +15149,75 @@ var init_BiologyCanvas = __esm({
14980
15149
  out.push(...shapes);
14981
15150
  return out;
14982
15151
  }, [nodes, edges, shapes]);
15152
+ const drawables3D = React87.useMemo(() => {
15153
+ if (mode !== "3d") return [];
15154
+ if (shapes.length > 0) {
15155
+ biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
15156
+ }
15157
+ const out = [];
15158
+ const labelColor = labelColorForBackground(backgroundColor);
15159
+ const nodeById = /* @__PURE__ */ new Map();
15160
+ for (const n of nodes) {
15161
+ if (n.id) nodeById.set(n.id, n);
15162
+ }
15163
+ for (const e of edges) {
15164
+ const a = nodeById.get(e.from);
15165
+ const b = nodeById.get(e.to);
15166
+ if (!a || !b) continue;
15167
+ const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
15168
+ const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
15169
+ if (edge) out.push(edge);
15170
+ if (e.label) {
15171
+ out.push(
15172
+ billboardLabel(
15173
+ e.label,
15174
+ (a.x + b.x) / 2,
15175
+ (a.y + b.y) / 2,
15176
+ ((a.z ?? 0) + (b.z ?? 0)) / 2,
15177
+ { color: labelColor }
15178
+ )
15179
+ );
15180
+ }
15181
+ }
15182
+ for (const n of nodes) {
15183
+ const radius = n.radius ?? 0.5;
15184
+ const nz = n.z ?? 0;
15185
+ out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
15186
+ if (n.label) {
15187
+ out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
15188
+ }
15189
+ }
15190
+ return out;
15191
+ }, [mode, nodes, edges, shapes, backgroundColor]);
15192
+ const nodeIndexById = React87.useMemo(() => {
15193
+ const m = /* @__PURE__ */ new Map();
15194
+ nodes.forEach((n, i) => {
15195
+ if (n.id) m.set(n.id, i);
15196
+ });
15197
+ return m;
15198
+ }, [nodes]);
15199
+ if (mode === "3d") {
15200
+ return /* @__PURE__ */ jsxRuntime.jsx(
15201
+ LearningScene3D,
15202
+ {
15203
+ className,
15204
+ width,
15205
+ height,
15206
+ title,
15207
+ backgroundColor,
15208
+ drawables: drawables3D,
15209
+ camera,
15210
+ lighting,
15211
+ post,
15212
+ showGrid,
15213
+ shadows,
15214
+ interactive,
15215
+ isLoading,
15216
+ error,
15217
+ onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
15218
+ }
15219
+ );
15220
+ }
14983
15221
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
14984
15222
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
14985
15223
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -14989,7 +15227,7 @@ var init_BiologyCanvas = __esm({
14989
15227
  height,
14990
15228
  backgroundColor,
14991
15229
  shapes: derivedShapes,
14992
- interactive,
15230
+ interactive: interactive ?? false,
14993
15231
  animate,
14994
15232
  onShapeClick,
14995
15233
  isLoading,
@@ -21829,24 +22067,41 @@ var init_ChatBar = __esm({
21829
22067
  ChatBar.displayName = "ChatBar";
21830
22068
  }
21831
22069
  });
21832
- var ChemistryCanvas;
22070
+ function bondPerpendicular(a, b) {
22071
+ const dx = b[0] - a[0];
22072
+ const dy = b[1] - a[1];
22073
+ const px = dy;
22074
+ const py = -dx;
22075
+ const len = Math.sqrt(px * px + py * py);
22076
+ if (len < 1e-6) return [1, 0, 0];
22077
+ return [px / len, py / len, 0];
22078
+ }
22079
+ var chemistryLog, ChemistryCanvas;
21833
22080
  var init_ChemistryCanvas = __esm({
21834
22081
  "components/learning/molecules/ChemistryCanvas.tsx"() {
21835
22082
  "use client";
21836
22083
  init_atoms();
21837
22084
  init_Stack();
21838
22085
  init_LearningCanvas();
22086
+ init_learningScene3D();
22087
+ chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
21839
22088
  ChemistryCanvas = ({
21840
22089
  className,
21841
22090
  width = 600,
21842
22091
  height = 400,
21843
22092
  title,
21844
22093
  backgroundColor,
22094
+ mode = "2d",
22095
+ camera,
22096
+ lighting,
22097
+ post,
21845
22098
  atoms = [],
21846
22099
  bonds = [],
21847
22100
  arrows = [],
21848
22101
  shapes = [],
21849
- interactive = false,
22102
+ showGrid,
22103
+ shadows,
22104
+ interactive,
21850
22105
  animate = false,
21851
22106
  onShapeClick,
21852
22107
  isLoading,
@@ -21925,6 +22180,88 @@ var init_ChemistryCanvas = __esm({
21925
22180
  out.push(...shapes);
21926
22181
  return out;
21927
22182
  }, [atoms, bonds, arrows, shapes]);
22183
+ const drawables3D = React87.useMemo(() => {
22184
+ if (mode !== "3d") return [];
22185
+ if (shapes.length > 0) {
22186
+ chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
22187
+ }
22188
+ const out = [];
22189
+ const labelColor = labelColorForBackground(backgroundColor);
22190
+ const atomById = /* @__PURE__ */ new Map();
22191
+ for (const a of atoms) {
22192
+ if (a.id) atomById.set(a.id, a);
22193
+ }
22194
+ for (const b of bonds) {
22195
+ const a = atomById.get(b.from);
22196
+ const c = atomById.get(b.to);
22197
+ if (!a || !c) continue;
22198
+ const color = b.color ?? "#6b7280";
22199
+ const from = [a.x, a.y, a.z ?? 0];
22200
+ const to = [c.x, c.y, c.z ?? 0];
22201
+ const perp = bondPerpendicular(from, to);
22202
+ const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
22203
+ const step = bondRadius * 2.2;
22204
+ const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
22205
+ for (const off of offsets) {
22206
+ const bond = cylinderBetween(
22207
+ [from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
22208
+ [to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
22209
+ bondRadius,
22210
+ color
22211
+ );
22212
+ if (bond) out.push(bond);
22213
+ }
22214
+ }
22215
+ for (const a of arrows) {
22216
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
22217
+ const len = a.length ?? 60;
22218
+ const x2 = a.x + Math.cos(angle) * len;
22219
+ const y2 = a.y + Math.sin(angle) * len;
22220
+ const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
22221
+ if (arrow) out.push(arrow);
22222
+ if (a.label) {
22223
+ out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
22224
+ }
22225
+ }
22226
+ for (const a of atoms) {
22227
+ const radius = a.radius ?? 0.45;
22228
+ const az = a.z ?? 0;
22229
+ out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
22230
+ if (a.element) {
22231
+ out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
22232
+ }
22233
+ }
22234
+ return out;
22235
+ }, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
22236
+ const atomIndexById = React87.useMemo(() => {
22237
+ const m = /* @__PURE__ */ new Map();
22238
+ atoms.forEach((a, i) => {
22239
+ if (a.id) m.set(a.id, i);
22240
+ });
22241
+ return m;
22242
+ }, [atoms]);
22243
+ if (mode === "3d") {
22244
+ return /* @__PURE__ */ jsxRuntime.jsx(
22245
+ LearningScene3D,
22246
+ {
22247
+ className,
22248
+ width,
22249
+ height,
22250
+ title,
22251
+ backgroundColor,
22252
+ drawables: drawables3D,
22253
+ camera,
22254
+ lighting,
22255
+ post,
22256
+ showGrid,
22257
+ shadows,
22258
+ interactive,
22259
+ isLoading,
22260
+ error,
22261
+ onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
22262
+ }
22263
+ );
22264
+ }
21928
22265
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
21929
22266
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
21930
22267
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -21934,7 +22271,7 @@ var init_ChemistryCanvas = __esm({
21934
22271
  height,
21935
22272
  backgroundColor,
21936
22273
  shapes: derivedShapes,
21937
- interactive,
22274
+ interactive: interactive ?? false,
21938
22275
  animate,
21939
22276
  onShapeClick,
21940
22277
  isLoading,
@@ -25405,6 +25742,7 @@ var init_FilterGroup = __esm({
25405
25742
  init_Badge();
25406
25743
  init_Stack();
25407
25744
  init_Icon();
25745
+ init_RangeSlider();
25408
25746
  init_useEventBus();
25409
25747
  init_useQuerySingleton();
25410
25748
  resolveFilterType = (filter) => filter.filterType ?? filter.type;
@@ -25599,6 +25937,35 @@ var init_FilterGroup = __esm({
25599
25937
  onClear: () => handleFilterSelect(`${filter.field}_to`, null)
25600
25938
  }
25601
25939
  )
25940
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
25941
+ /* @__PURE__ */ jsxRuntime.jsx(
25942
+ RangeSlider,
25943
+ {
25944
+ min: filter.min ?? 0,
25945
+ max: filter.max ?? 100,
25946
+ step: filter.step ?? 1,
25947
+ value: Number(
25948
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
25949
+ ),
25950
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
25951
+ showTooltip: true,
25952
+ "aria-label": t("filterGroup.from")
25953
+ }
25954
+ ),
25955
+ /* @__PURE__ */ jsxRuntime.jsx(
25956
+ RangeSlider,
25957
+ {
25958
+ min: filter.min ?? 0,
25959
+ max: filter.max ?? 100,
25960
+ step: filter.step ?? 1,
25961
+ value: Number(
25962
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
25963
+ ),
25964
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
25965
+ showTooltip: true,
25966
+ "aria-label": t("filterGroup.to")
25967
+ }
25968
+ )
25602
25969
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsxRuntime.jsx(
25603
25970
  Input,
25604
25971
  {
@@ -25681,6 +26048,36 @@ var init_FilterGroup = __esm({
25681
26048
  className: "text-sm min-w-[100px]"
25682
26049
  }
25683
26050
  )
26051
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
26052
+ /* @__PURE__ */ jsxRuntime.jsx(
26053
+ RangeSlider,
26054
+ {
26055
+ min: filter.min ?? 0,
26056
+ max: filter.max ?? 100,
26057
+ step: filter.step ?? 1,
26058
+ value: Number(
26059
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
26060
+ ),
26061
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
26062
+ className: "min-w-[100px]",
26063
+ "aria-label": t("filterGroup.from")
26064
+ }
26065
+ ),
26066
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "-" }),
26067
+ /* @__PURE__ */ jsxRuntime.jsx(
26068
+ RangeSlider,
26069
+ {
26070
+ min: filter.min ?? 0,
26071
+ max: filter.max ?? 100,
26072
+ step: filter.step ?? 1,
26073
+ value: Number(
26074
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
26075
+ ),
26076
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
26077
+ className: "min-w-[100px]",
26078
+ "aria-label": t("filterGroup.to")
26079
+ }
26080
+ )
25684
26081
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsxRuntime.jsx(
25685
26082
  Input,
25686
26083
  {
@@ -25799,6 +26196,38 @@ var init_FilterGroup = __esm({
25799
26196
  className: "min-w-[130px]"
25800
26197
  }
25801
26198
  )
26199
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
26200
+ /* @__PURE__ */ jsxRuntime.jsx(
26201
+ RangeSlider,
26202
+ {
26203
+ min: filter.min ?? 0,
26204
+ max: filter.max ?? 100,
26205
+ step: filter.step ?? 1,
26206
+ value: Number(
26207
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
26208
+ ),
26209
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
26210
+ showTooltip: true,
26211
+ className: "min-w-[130px]",
26212
+ "aria-label": t("filterGroup.from")
26213
+ }
26214
+ ),
26215
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "-" }),
26216
+ /* @__PURE__ */ jsxRuntime.jsx(
26217
+ RangeSlider,
26218
+ {
26219
+ min: filter.min ?? 0,
26220
+ max: filter.max ?? 100,
26221
+ step: filter.step ?? 1,
26222
+ value: Number(
26223
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
26224
+ ),
26225
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
26226
+ showTooltip: true,
26227
+ className: "min-w-[130px]",
26228
+ "aria-label": t("filterGroup.to")
26229
+ }
26230
+ )
25802
26231
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsxRuntime.jsx(
25803
26232
  Input,
25804
26233
  {
@@ -28147,19 +28576,25 @@ var init_MathCanvas = __esm({
28147
28576
  };
28148
28577
  }
28149
28578
  });
28150
- var PhysicsCanvas;
28579
+ var physicsLog2, PhysicsCanvas;
28151
28580
  var init_PhysicsCanvas = __esm({
28152
28581
  "components/learning/molecules/PhysicsCanvas.tsx"() {
28153
28582
  "use client";
28154
28583
  init_atoms();
28155
28584
  init_Stack();
28156
28585
  init_LearningCanvas();
28586
+ init_learningScene3D();
28587
+ physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
28157
28588
  PhysicsCanvas = ({
28158
28589
  className,
28159
28590
  width = 600,
28160
28591
  height = 400,
28161
28592
  title,
28162
28593
  backgroundColor,
28594
+ mode = "2d",
28595
+ camera,
28596
+ lighting,
28597
+ post,
28163
28598
  bodies = [],
28164
28599
  constraints = [],
28165
28600
  showVelocity = true,
@@ -28167,7 +28602,9 @@ var init_PhysicsCanvas = __esm({
28167
28602
  velocityScale = 20,
28168
28603
  forceScale = 20,
28169
28604
  shapes = [],
28170
- interactive = false,
28605
+ showGrid,
28606
+ shadows,
28607
+ interactive,
28171
28608
  animate = false,
28172
28609
  onShapeClick,
28173
28610
  isLoading,
@@ -28239,6 +28676,89 @@ var init_PhysicsCanvas = __esm({
28239
28676
  out.push(...shapes);
28240
28677
  return out;
28241
28678
  }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
28679
+ const drawables3D = React87.useMemo(() => {
28680
+ if (mode !== "3d") return [];
28681
+ if (shapes.length > 0) {
28682
+ physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
28683
+ }
28684
+ const out = [];
28685
+ const labelColor = labelColorForBackground(backgroundColor);
28686
+ const bodyById = /* @__PURE__ */ new Map();
28687
+ for (const b of bodies) {
28688
+ if (b.id) bodyById.set(b.id, b);
28689
+ }
28690
+ for (const c of constraints) {
28691
+ const a = bodyById.get(c.from);
28692
+ const b = bodyById.get(c.to);
28693
+ if (!a || !b) continue;
28694
+ const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
28695
+ const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
28696
+ if (rod) out.push(rod);
28697
+ }
28698
+ for (const b of bodies) {
28699
+ const radius = b.radius ?? 0.5;
28700
+ const bz = b.z ?? 0;
28701
+ out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
28702
+ if (b.label) {
28703
+ out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
28704
+ }
28705
+ const vx = b.vx ?? 0;
28706
+ const vy = b.vy ?? 0;
28707
+ const vz = b.vz ?? 0;
28708
+ const arrowRadius = Math.max(0.05, radius * 0.15);
28709
+ if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
28710
+ const arrow = arrowBetween(
28711
+ [b.x, b.y, bz],
28712
+ [b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
28713
+ "#16a34a",
28714
+ arrowRadius
28715
+ );
28716
+ if (arrow) out.push(arrow);
28717
+ }
28718
+ const fx = b.fx ?? 0;
28719
+ const fy = b.fy ?? 0;
28720
+ const fz = b.fz ?? 0;
28721
+ if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
28722
+ const arrow = arrowBetween(
28723
+ [b.x, b.y, bz],
28724
+ [b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
28725
+ "#dc2626",
28726
+ arrowRadius
28727
+ );
28728
+ if (arrow) out.push(arrow);
28729
+ }
28730
+ }
28731
+ return out;
28732
+ }, [mode, bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes, backgroundColor]);
28733
+ const bodyIndexById = React87.useMemo(() => {
28734
+ const m = /* @__PURE__ */ new Map();
28735
+ bodies.forEach((b, i) => {
28736
+ if (b.id) m.set(b.id, i);
28737
+ });
28738
+ return m;
28739
+ }, [bodies]);
28740
+ if (mode === "3d") {
28741
+ return /* @__PURE__ */ jsxRuntime.jsx(
28742
+ LearningScene3D,
28743
+ {
28744
+ className,
28745
+ width,
28746
+ height,
28747
+ title,
28748
+ backgroundColor,
28749
+ drawables: drawables3D,
28750
+ camera,
28751
+ lighting,
28752
+ post,
28753
+ showGrid,
28754
+ shadows,
28755
+ interactive,
28756
+ isLoading,
28757
+ error,
28758
+ onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
28759
+ }
28760
+ );
28761
+ }
28242
28762
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
28243
28763
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
28244
28764
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -28248,7 +28768,7 @@ var init_PhysicsCanvas = __esm({
28248
28768
  height,
28249
28769
  backgroundColor,
28250
28770
  shapes: derivedShapes,
28251
- interactive,
28771
+ interactive: interactive ?? false,
28252
28772
  animate,
28253
28773
  onShapeClick,
28254
28774
  isLoading,
@@ -28866,12 +29386,12 @@ var init_MapView = __esm({
28866
29386
  shadowSize: [41, 41]
28867
29387
  });
28868
29388
  L.Marker.prototype.options.icon = defaultIcon;
28869
- const { useEffect: useEffect63, useRef: useRef62, useCallback: useCallback96, useState: useState94 } = React87__namespace.default;
29389
+ const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback96, useState: useState94 } = React87__namespace.default;
28870
29390
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
28871
29391
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
28872
29392
  function MapUpdater({ centerLat, centerLng, zoom }) {
28873
29393
  const map = useMap();
28874
- const prevRef = useRef62({ centerLat, centerLng, zoom });
29394
+ const prevRef = useRef63({ centerLat, centerLng, zoom });
28875
29395
  useEffect63(() => {
28876
29396
  const prev = prevRef.current;
28877
29397
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -39694,7 +40214,7 @@ function getEnumOptions(field) {
39694
40214
  }));
39695
40215
  }
39696
40216
  const validation = field.validation;
39697
- if (validation?.enum && Array.isArray(validation.enum)) {
40217
+ if (validation?.enum && validation.enum.length > 0) {
39698
40218
  return validation.enum.map((v) => ({
39699
40219
  value: v,
39700
40220
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")