@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
package/dist/avl/index.js CHANGED
@@ -14041,9 +14041,10 @@ function Canvas({
14041
14041
  drawables: [...drawables ?? [], ...childDrawables],
14042
14042
  isLoading,
14043
14043
  cameraMode: to3DCameraMode(camera?.mode),
14044
- ...zoom !== void 0 ? { scale: zoom } : {},
14044
+ ...zoom !== void 0 ? { zoom } : {},
14045
14045
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
14046
14046
  ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
14047
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
14047
14048
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
14048
14049
  unitScale,
14049
14050
  backgroundColor,
@@ -15252,7 +15253,10 @@ var init_LearningCanvas = __esm({
15252
15253
  ctx.fillRect(0, 0, width, height);
15253
15254
  }
15254
15255
  for (const shape of shapes) {
15255
- drawShape(ctx, shape, width, height);
15256
+ if (shape.type !== "text") drawShape(ctx, shape, width, height);
15257
+ }
15258
+ for (const shape of shapes) {
15259
+ if (shape.type === "text") drawShape(ctx, shape, width, height);
15256
15260
  }
15257
15261
  }, [width, height, backgroundColor, shapes]);
15258
15262
  useEffect(() => {
@@ -16815,7 +16819,7 @@ var init_AlgorithmCanvas = __esm({
16815
16819
  DEFAULT_CELL_COLOR = "#e5e7eb";
16816
16820
  DEFAULT_POINTER_COLOR = "#dc2626";
16817
16821
  POINTER_BAND = 34;
16818
- TOP_PAD = 12;
16822
+ TOP_PAD = 26;
16819
16823
  AlgorithmCanvas = ({
16820
16824
  className,
16821
16825
  width = 600,
@@ -17092,23 +17096,188 @@ var init_AuthLayout = __esm({
17092
17096
  AuthLayout.displayName = "AuthLayout";
17093
17097
  }
17094
17098
  });
17095
- var BiologyCanvas;
17099
+ function LearningScene3D({
17100
+ className,
17101
+ width = 600,
17102
+ height = 400,
17103
+ title,
17104
+ backgroundColor,
17105
+ drawables,
17106
+ camera,
17107
+ lighting,
17108
+ post,
17109
+ showGrid = false,
17110
+ shadows,
17111
+ interactive,
17112
+ isLoading,
17113
+ error,
17114
+ onItemClick
17115
+ }) {
17116
+ const instanceId = useId().replace(/[^a-zA-Z0-9]/g, "");
17117
+ const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
17118
+ const onItemClickRef = useRef(onItemClick);
17119
+ onItemClickRef.current = onItemClick;
17120
+ useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
17121
+ const unitId = event.payload?.unitId;
17122
+ if (typeof unitId === "string") onItemClickRef.current?.(unitId);
17123
+ });
17124
+ const props3d = {
17125
+ drawables,
17126
+ isLoading,
17127
+ error: error ? error.message : null,
17128
+ cameraMode: camera?.mode ?? "perspective",
17129
+ ...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
17130
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
17131
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
17132
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
17133
+ ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
17134
+ backgroundColor,
17135
+ showGrid,
17136
+ ...shadows !== void 0 ? { shadows } : {},
17137
+ ...interactive !== void 0 ? { controlsEnabled: interactive } : {},
17138
+ lighting,
17139
+ post,
17140
+ ...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
17141
+ };
17142
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
17143
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
17144
+ /* @__PURE__ */ jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Canvas3DHost2, { ...props3d }) }) })
17145
+ ] }) });
17146
+ }
17147
+ function meshSphere(id, x, y, z, radius, color, opts) {
17148
+ return {
17149
+ type: "draw-mesh",
17150
+ ...id !== void 0 ? { id } : {},
17151
+ shape: opts?.shape ?? "sphere",
17152
+ position: { x, y, z },
17153
+ radius,
17154
+ pivot: "center",
17155
+ segments: opts?.segments ?? 24,
17156
+ material: { color, ...opts?.material },
17157
+ ...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
17158
+ };
17159
+ }
17160
+ function axisRotation(from, to) {
17161
+ const wx = to[0] - from[0];
17162
+ const wy = to[2] - from[2];
17163
+ const wz = to[1] - from[1];
17164
+ const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
17165
+ const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
17166
+ return [0, Math.atan2(wz, -wx), tilt];
17167
+ }
17168
+ function segmentLength(from, to) {
17169
+ const dx = to[0] - from[0];
17170
+ const dy = to[1] - from[1];
17171
+ const dz = to[2] - from[2];
17172
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
17173
+ }
17174
+ function cylinderBetween(from, to, radius, color) {
17175
+ const len = segmentLength(from, to);
17176
+ if (len < 1e-6) return null;
17177
+ return {
17178
+ type: "draw-mesh",
17179
+ shape: "cylinder",
17180
+ position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
17181
+ radius,
17182
+ height: len,
17183
+ rotation: axisRotation(from, to),
17184
+ pivot: "center",
17185
+ segments: 12,
17186
+ material: { color }
17187
+ };
17188
+ }
17189
+ function arrowBetween(from, to, color, shaftRadius = 0.08) {
17190
+ const len = segmentLength(from, to);
17191
+ if (len < 1e-6) return null;
17192
+ const tipLen = Math.min(shaftRadius * 8, len * 0.35);
17193
+ const k = (len - tipLen) / len;
17194
+ const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
17195
+ const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
17196
+ const tipStart = shaftEnd;
17197
+ const tipEnd = delta;
17198
+ const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
17199
+ const tipLenActual = segmentLength(tipStart, tipEnd);
17200
+ const tip = {
17201
+ type: "draw-mesh",
17202
+ shape: "cone",
17203
+ position: {
17204
+ x: (tipStart[0] + tipEnd[0]) / 2,
17205
+ y: (tipStart[1] + tipEnd[1]) / 2,
17206
+ z: (tipStart[2] + tipEnd[2]) / 2
17207
+ },
17208
+ radius: shaftRadius * 3,
17209
+ height: tipLenActual,
17210
+ rotation: axisRotation(tipStart, tipEnd),
17211
+ pivot: "center",
17212
+ segments: 12,
17213
+ material: { color }
17214
+ };
17215
+ return {
17216
+ type: "draw-group",
17217
+ position: { x: from[0], y: from[1], z: from[2] },
17218
+ items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
17219
+ };
17220
+ }
17221
+ function billboardLabel(text, x, y, z, opts) {
17222
+ return {
17223
+ type: "draw-text",
17224
+ text,
17225
+ position: { x, y, z },
17226
+ color: opts?.color ?? "#111827"
17227
+ };
17228
+ }
17229
+ function labelColorForBackground(backgroundColor) {
17230
+ const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
17231
+ if (!m) return "#111827";
17232
+ const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
17233
+ const r2 = parseInt(hex.slice(0, 2), 16) / 255;
17234
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
17235
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
17236
+ const luminance = 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
17237
+ return luminance < 0.5 ? "#e5e7eb" : "#111827";
17238
+ }
17239
+ function get3DClickPayload(onShapeClick, idToIndex) {
17240
+ if (!onShapeClick) return void 0;
17241
+ return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
17242
+ }
17243
+ var Canvas3DHost2;
17244
+ var init_learningScene3D = __esm({
17245
+ "components/learning/molecules/learningScene3D.tsx"() {
17246
+ "use client";
17247
+ init_atoms();
17248
+ init_Stack();
17249
+ init_useEventBus();
17250
+ Canvas3DHost2 = lazy(
17251
+ () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
17252
+ );
17253
+ LearningScene3D.displayName = "LearningScene3D";
17254
+ }
17255
+ });
17256
+ var biologyLog, BiologyCanvas;
17096
17257
  var init_BiologyCanvas = __esm({
17097
17258
  "components/learning/molecules/BiologyCanvas.tsx"() {
17098
17259
  "use client";
17099
17260
  init_atoms();
17100
17261
  init_Stack();
17101
17262
  init_LearningCanvas();
17263
+ init_learningScene3D();
17264
+ biologyLog = createLogger("almadar:ui:biology-canvas");
17102
17265
  BiologyCanvas = ({
17103
17266
  className,
17104
17267
  width = 600,
17105
17268
  height = 400,
17106
17269
  title,
17107
17270
  backgroundColor,
17271
+ mode = "2d",
17272
+ camera,
17273
+ lighting,
17274
+ post,
17108
17275
  nodes = [],
17109
17276
  edges = [],
17110
17277
  shapes = [],
17111
- interactive = false,
17278
+ showGrid,
17279
+ shadows,
17280
+ interactive,
17112
17281
  animate = false,
17113
17282
  onShapeClick,
17114
17283
  isLoading,
@@ -17169,6 +17338,75 @@ var init_BiologyCanvas = __esm({
17169
17338
  out.push(...shapes);
17170
17339
  return out;
17171
17340
  }, [nodes, edges, shapes]);
17341
+ const drawables3D = useMemo(() => {
17342
+ if (mode !== "3d") return [];
17343
+ if (shapes.length > 0) {
17344
+ biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
17345
+ }
17346
+ const out = [];
17347
+ const labelColor = labelColorForBackground(backgroundColor);
17348
+ const nodeById = /* @__PURE__ */ new Map();
17349
+ for (const n of nodes) {
17350
+ if (n.id) nodeById.set(n.id, n);
17351
+ }
17352
+ for (const e of edges) {
17353
+ const a = nodeById.get(e.from);
17354
+ const b = nodeById.get(e.to);
17355
+ if (!a || !b) continue;
17356
+ const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
17357
+ const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
17358
+ if (edge) out.push(edge);
17359
+ if (e.label) {
17360
+ out.push(
17361
+ billboardLabel(
17362
+ e.label,
17363
+ (a.x + b.x) / 2,
17364
+ (a.y + b.y) / 2,
17365
+ ((a.z ?? 0) + (b.z ?? 0)) / 2,
17366
+ { color: labelColor }
17367
+ )
17368
+ );
17369
+ }
17370
+ }
17371
+ for (const n of nodes) {
17372
+ const radius = n.radius ?? 0.5;
17373
+ const nz = n.z ?? 0;
17374
+ out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
17375
+ if (n.label) {
17376
+ out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
17377
+ }
17378
+ }
17379
+ return out;
17380
+ }, [mode, nodes, edges, shapes, backgroundColor]);
17381
+ const nodeIndexById = useMemo(() => {
17382
+ const m = /* @__PURE__ */ new Map();
17383
+ nodes.forEach((n, i) => {
17384
+ if (n.id) m.set(n.id, i);
17385
+ });
17386
+ return m;
17387
+ }, [nodes]);
17388
+ if (mode === "3d") {
17389
+ return /* @__PURE__ */ jsx(
17390
+ LearningScene3D,
17391
+ {
17392
+ className,
17393
+ width,
17394
+ height,
17395
+ title,
17396
+ backgroundColor,
17397
+ drawables: drawables3D,
17398
+ camera,
17399
+ lighting,
17400
+ post,
17401
+ showGrid,
17402
+ shadows,
17403
+ interactive,
17404
+ isLoading,
17405
+ error,
17406
+ onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
17407
+ }
17408
+ );
17409
+ }
17172
17410
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
17173
17411
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
17174
17412
  /* @__PURE__ */ jsx(
@@ -17178,7 +17416,7 @@ var init_BiologyCanvas = __esm({
17178
17416
  height,
17179
17417
  backgroundColor,
17180
17418
  shapes: derivedShapes,
17181
- interactive,
17419
+ interactive: interactive ?? false,
17182
17420
  animate,
17183
17421
  onShapeClick,
17184
17422
  isLoading,
@@ -24018,24 +24256,41 @@ var init_ChatBar = __esm({
24018
24256
  ChatBar.displayName = "ChatBar";
24019
24257
  }
24020
24258
  });
24021
- var ChemistryCanvas;
24259
+ function bondPerpendicular(a, b) {
24260
+ const dx = b[0] - a[0];
24261
+ const dy = b[1] - a[1];
24262
+ const px = dy;
24263
+ const py = -dx;
24264
+ const len = Math.sqrt(px * px + py * py);
24265
+ if (len < 1e-6) return [1, 0, 0];
24266
+ return [px / len, py / len, 0];
24267
+ }
24268
+ var chemistryLog, ChemistryCanvas;
24022
24269
  var init_ChemistryCanvas = __esm({
24023
24270
  "components/learning/molecules/ChemistryCanvas.tsx"() {
24024
24271
  "use client";
24025
24272
  init_atoms();
24026
24273
  init_Stack();
24027
24274
  init_LearningCanvas();
24275
+ init_learningScene3D();
24276
+ chemistryLog = createLogger("almadar:ui:chemistry-canvas");
24028
24277
  ChemistryCanvas = ({
24029
24278
  className,
24030
24279
  width = 600,
24031
24280
  height = 400,
24032
24281
  title,
24033
24282
  backgroundColor,
24283
+ mode = "2d",
24284
+ camera,
24285
+ lighting,
24286
+ post,
24034
24287
  atoms = [],
24035
24288
  bonds = [],
24036
24289
  arrows = [],
24037
24290
  shapes = [],
24038
- interactive = false,
24291
+ showGrid,
24292
+ shadows,
24293
+ interactive,
24039
24294
  animate = false,
24040
24295
  onShapeClick,
24041
24296
  isLoading,
@@ -24114,6 +24369,88 @@ var init_ChemistryCanvas = __esm({
24114
24369
  out.push(...shapes);
24115
24370
  return out;
24116
24371
  }, [atoms, bonds, arrows, shapes]);
24372
+ const drawables3D = useMemo(() => {
24373
+ if (mode !== "3d") return [];
24374
+ if (shapes.length > 0) {
24375
+ chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
24376
+ }
24377
+ const out = [];
24378
+ const labelColor = labelColorForBackground(backgroundColor);
24379
+ const atomById = /* @__PURE__ */ new Map();
24380
+ for (const a of atoms) {
24381
+ if (a.id) atomById.set(a.id, a);
24382
+ }
24383
+ for (const b of bonds) {
24384
+ const a = atomById.get(b.from);
24385
+ const c = atomById.get(b.to);
24386
+ if (!a || !c) continue;
24387
+ const color = b.color ?? "#6b7280";
24388
+ const from = [a.x, a.y, a.z ?? 0];
24389
+ const to = [c.x, c.y, c.z ?? 0];
24390
+ const perp = bondPerpendicular(from, to);
24391
+ const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
24392
+ const step = bondRadius * 2.2;
24393
+ const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
24394
+ for (const off of offsets) {
24395
+ const bond = cylinderBetween(
24396
+ [from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
24397
+ [to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
24398
+ bondRadius,
24399
+ color
24400
+ );
24401
+ if (bond) out.push(bond);
24402
+ }
24403
+ }
24404
+ for (const a of arrows) {
24405
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
24406
+ const len = a.length ?? 60;
24407
+ const x2 = a.x + Math.cos(angle) * len;
24408
+ const y2 = a.y + Math.sin(angle) * len;
24409
+ const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
24410
+ if (arrow) out.push(arrow);
24411
+ if (a.label) {
24412
+ out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
24413
+ }
24414
+ }
24415
+ for (const a of atoms) {
24416
+ const radius = a.radius ?? 0.45;
24417
+ const az = a.z ?? 0;
24418
+ out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
24419
+ if (a.element) {
24420
+ out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
24421
+ }
24422
+ }
24423
+ return out;
24424
+ }, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
24425
+ const atomIndexById = useMemo(() => {
24426
+ const m = /* @__PURE__ */ new Map();
24427
+ atoms.forEach((a, i) => {
24428
+ if (a.id) m.set(a.id, i);
24429
+ });
24430
+ return m;
24431
+ }, [atoms]);
24432
+ if (mode === "3d") {
24433
+ return /* @__PURE__ */ jsx(
24434
+ LearningScene3D,
24435
+ {
24436
+ className,
24437
+ width,
24438
+ height,
24439
+ title,
24440
+ backgroundColor,
24441
+ drawables: drawables3D,
24442
+ camera,
24443
+ lighting,
24444
+ post,
24445
+ showGrid,
24446
+ shadows,
24447
+ interactive,
24448
+ isLoading,
24449
+ error,
24450
+ onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
24451
+ }
24452
+ );
24453
+ }
24117
24454
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
24118
24455
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
24119
24456
  /* @__PURE__ */ jsx(
@@ -24123,7 +24460,7 @@ var init_ChemistryCanvas = __esm({
24123
24460
  height,
24124
24461
  backgroundColor,
24125
24462
  shapes: derivedShapes,
24126
- interactive,
24463
+ interactive: interactive ?? false,
24127
24464
  animate,
24128
24465
  onShapeClick,
24129
24466
  isLoading,
@@ -27594,6 +27931,7 @@ var init_FilterGroup = __esm({
27594
27931
  init_Badge();
27595
27932
  init_Stack();
27596
27933
  init_Icon();
27934
+ init_RangeSlider();
27597
27935
  init_useEventBus();
27598
27936
  init_useQuerySingleton();
27599
27937
  resolveFilterType = (filter) => filter.filterType ?? filter.type;
@@ -27788,6 +28126,35 @@ var init_FilterGroup = __esm({
27788
28126
  onClear: () => handleFilterSelect(`${filter.field}_to`, null)
27789
28127
  }
27790
28128
  )
28129
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
28130
+ /* @__PURE__ */ jsx(
28131
+ RangeSlider,
28132
+ {
28133
+ min: filter.min ?? 0,
28134
+ max: filter.max ?? 100,
28135
+ step: filter.step ?? 1,
28136
+ value: Number(
28137
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
28138
+ ),
28139
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
28140
+ showTooltip: true,
28141
+ "aria-label": t("filterGroup.from")
28142
+ }
28143
+ ),
28144
+ /* @__PURE__ */ jsx(
28145
+ RangeSlider,
28146
+ {
28147
+ min: filter.min ?? 0,
28148
+ max: filter.max ?? 100,
28149
+ step: filter.step ?? 1,
28150
+ value: Number(
28151
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
28152
+ ),
28153
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
28154
+ showTooltip: true,
28155
+ "aria-label": t("filterGroup.to")
28156
+ }
28157
+ )
27791
28158
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsx(
27792
28159
  Input,
27793
28160
  {
@@ -27870,6 +28237,36 @@ var init_FilterGroup = __esm({
27870
28237
  className: "text-sm min-w-[100px]"
27871
28238
  }
27872
28239
  )
28240
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
28241
+ /* @__PURE__ */ jsx(
28242
+ RangeSlider,
28243
+ {
28244
+ min: filter.min ?? 0,
28245
+ max: filter.max ?? 100,
28246
+ step: filter.step ?? 1,
28247
+ value: Number(
28248
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
28249
+ ),
28250
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
28251
+ className: "min-w-[100px]",
28252
+ "aria-label": t("filterGroup.from")
28253
+ }
28254
+ ),
28255
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "-" }),
28256
+ /* @__PURE__ */ jsx(
28257
+ RangeSlider,
28258
+ {
28259
+ min: filter.min ?? 0,
28260
+ max: filter.max ?? 100,
28261
+ step: filter.step ?? 1,
28262
+ value: Number(
28263
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
28264
+ ),
28265
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
28266
+ className: "min-w-[100px]",
28267
+ "aria-label": t("filterGroup.to")
28268
+ }
28269
+ )
27873
28270
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsx(
27874
28271
  Input,
27875
28272
  {
@@ -27988,6 +28385,38 @@ var init_FilterGroup = __esm({
27988
28385
  className: "min-w-[130px]"
27989
28386
  }
27990
28387
  )
28388
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
28389
+ /* @__PURE__ */ jsx(
28390
+ RangeSlider,
28391
+ {
28392
+ min: filter.min ?? 0,
28393
+ max: filter.max ?? 100,
28394
+ step: filter.step ?? 1,
28395
+ value: Number(
28396
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
28397
+ ),
28398
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
28399
+ showTooltip: true,
28400
+ className: "min-w-[130px]",
28401
+ "aria-label": t("filterGroup.from")
28402
+ }
28403
+ ),
28404
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "-" }),
28405
+ /* @__PURE__ */ jsx(
28406
+ RangeSlider,
28407
+ {
28408
+ min: filter.min ?? 0,
28409
+ max: filter.max ?? 100,
28410
+ step: filter.step ?? 1,
28411
+ value: Number(
28412
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
28413
+ ),
28414
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
28415
+ showTooltip: true,
28416
+ className: "min-w-[130px]",
28417
+ "aria-label": t("filterGroup.to")
28418
+ }
28419
+ )
27991
28420
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsx(
27992
28421
  Input,
27993
28422
  {
@@ -30336,19 +30765,25 @@ var init_MathCanvas = __esm({
30336
30765
  };
30337
30766
  }
30338
30767
  });
30339
- var PhysicsCanvas;
30768
+ var physicsLog2, PhysicsCanvas;
30340
30769
  var init_PhysicsCanvas = __esm({
30341
30770
  "components/learning/molecules/PhysicsCanvas.tsx"() {
30342
30771
  "use client";
30343
30772
  init_atoms();
30344
30773
  init_Stack();
30345
30774
  init_LearningCanvas();
30775
+ init_learningScene3D();
30776
+ physicsLog2 = createLogger("almadar:ui:physics-canvas");
30346
30777
  PhysicsCanvas = ({
30347
30778
  className,
30348
30779
  width = 600,
30349
30780
  height = 400,
30350
30781
  title,
30351
30782
  backgroundColor,
30783
+ mode = "2d",
30784
+ camera,
30785
+ lighting,
30786
+ post,
30352
30787
  bodies = [],
30353
30788
  constraints = [],
30354
30789
  showVelocity = true,
@@ -30356,7 +30791,9 @@ var init_PhysicsCanvas = __esm({
30356
30791
  velocityScale = 20,
30357
30792
  forceScale = 20,
30358
30793
  shapes = [],
30359
- interactive = false,
30794
+ showGrid,
30795
+ shadows,
30796
+ interactive,
30360
30797
  animate = false,
30361
30798
  onShapeClick,
30362
30799
  isLoading,
@@ -30428,6 +30865,89 @@ var init_PhysicsCanvas = __esm({
30428
30865
  out.push(...shapes);
30429
30866
  return out;
30430
30867
  }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
30868
+ const drawables3D = useMemo(() => {
30869
+ if (mode !== "3d") return [];
30870
+ if (shapes.length > 0) {
30871
+ physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
30872
+ }
30873
+ const out = [];
30874
+ const labelColor = labelColorForBackground(backgroundColor);
30875
+ const bodyById = /* @__PURE__ */ new Map();
30876
+ for (const b of bodies) {
30877
+ if (b.id) bodyById.set(b.id, b);
30878
+ }
30879
+ for (const c of constraints) {
30880
+ const a = bodyById.get(c.from);
30881
+ const b = bodyById.get(c.to);
30882
+ if (!a || !b) continue;
30883
+ const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
30884
+ const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
30885
+ if (rod) out.push(rod);
30886
+ }
30887
+ for (const b of bodies) {
30888
+ const radius = b.radius ?? 0.5;
30889
+ const bz = b.z ?? 0;
30890
+ out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
30891
+ if (b.label) {
30892
+ out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
30893
+ }
30894
+ const vx = b.vx ?? 0;
30895
+ const vy = b.vy ?? 0;
30896
+ const vz = b.vz ?? 0;
30897
+ const arrowRadius = Math.max(0.05, radius * 0.15);
30898
+ if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
30899
+ const arrow = arrowBetween(
30900
+ [b.x, b.y, bz],
30901
+ [b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
30902
+ "#16a34a",
30903
+ arrowRadius
30904
+ );
30905
+ if (arrow) out.push(arrow);
30906
+ }
30907
+ const fx = b.fx ?? 0;
30908
+ const fy = b.fy ?? 0;
30909
+ const fz = b.fz ?? 0;
30910
+ if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
30911
+ const arrow = arrowBetween(
30912
+ [b.x, b.y, bz],
30913
+ [b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
30914
+ "#dc2626",
30915
+ arrowRadius
30916
+ );
30917
+ if (arrow) out.push(arrow);
30918
+ }
30919
+ }
30920
+ return out;
30921
+ }, [mode, bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes, backgroundColor]);
30922
+ const bodyIndexById = useMemo(() => {
30923
+ const m = /* @__PURE__ */ new Map();
30924
+ bodies.forEach((b, i) => {
30925
+ if (b.id) m.set(b.id, i);
30926
+ });
30927
+ return m;
30928
+ }, [bodies]);
30929
+ if (mode === "3d") {
30930
+ return /* @__PURE__ */ jsx(
30931
+ LearningScene3D,
30932
+ {
30933
+ className,
30934
+ width,
30935
+ height,
30936
+ title,
30937
+ backgroundColor,
30938
+ drawables: drawables3D,
30939
+ camera,
30940
+ lighting,
30941
+ post,
30942
+ showGrid,
30943
+ shadows,
30944
+ interactive,
30945
+ isLoading,
30946
+ error,
30947
+ onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
30948
+ }
30949
+ );
30950
+ }
30431
30951
  return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
30432
30952
  title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
30433
30953
  /* @__PURE__ */ jsx(
@@ -30437,7 +30957,7 @@ var init_PhysicsCanvas = __esm({
30437
30957
  height,
30438
30958
  backgroundColor,
30439
30959
  shapes: derivedShapes,
30440
- interactive,
30960
+ interactive: interactive ?? false,
30441
30961
  animate,
30442
30962
  onShapeClick,
30443
30963
  isLoading,
@@ -31055,12 +31575,12 @@ var init_MapView = __esm({
31055
31575
  shadowSize: [41, 41]
31056
31576
  });
31057
31577
  L.Marker.prototype.options.icon = defaultIcon;
31058
- const { useEffect: useEffect64, useRef: useRef64, useCallback: useCallback96, useState: useState100 } = React94__default;
31578
+ const { useEffect: useEffect64, useRef: useRef65, useCallback: useCallback96, useState: useState100 } = React94__default;
31059
31579
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31060
31580
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31061
31581
  function MapUpdater({ centerLat, centerLng, zoom }) {
31062
31582
  const map = useMap();
31063
- const prevRef = useRef64({ centerLat, centerLng, zoom });
31583
+ const prevRef = useRef65({ centerLat, centerLng, zoom });
31064
31584
  useEffect64(() => {
31065
31585
  const prev = prevRef.current;
31066
31586
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -41474,7 +41994,7 @@ function getEnumOptions(field) {
41474
41994
  }));
41475
41995
  }
41476
41996
  const validation = field.validation;
41477
- if (validation?.enum && Array.isArray(validation.enum)) {
41997
+ if (validation?.enum && validation.enum.length > 0) {
41478
41998
  return validation.enum.map((v) => ({
41479
41999
  value: v,
41480
42000
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")