@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
@@ -14117,9 +14117,10 @@ function Canvas({
14117
14117
  drawables: [...drawables ?? [], ...childDrawables],
14118
14118
  isLoading,
14119
14119
  cameraMode: to3DCameraMode(camera?.mode),
14120
- ...zoom !== void 0 ? { scale: zoom } : {},
14120
+ ...zoom !== void 0 ? { zoom } : {},
14121
14121
  ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
14122
14122
  ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
14123
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
14123
14124
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
14124
14125
  unitScale,
14125
14126
  backgroundColor,
@@ -15328,7 +15329,10 @@ var init_LearningCanvas = __esm({
15328
15329
  ctx.fillRect(0, 0, width, height);
15329
15330
  }
15330
15331
  for (const shape of shapes) {
15331
- drawShape(ctx, shape, width, height);
15332
+ if (shape.type !== "text") drawShape(ctx, shape, width, height);
15333
+ }
15334
+ for (const shape of shapes) {
15335
+ if (shape.type === "text") drawShape(ctx, shape, width, height);
15332
15336
  }
15333
15337
  }, [width, height, backgroundColor, shapes]);
15334
15338
  React94.useEffect(() => {
@@ -16891,7 +16895,7 @@ var init_AlgorithmCanvas = __esm({
16891
16895
  DEFAULT_CELL_COLOR = "#e5e7eb";
16892
16896
  DEFAULT_POINTER_COLOR = "#dc2626";
16893
16897
  POINTER_BAND = 34;
16894
- TOP_PAD = 12;
16898
+ TOP_PAD = 26;
16895
16899
  AlgorithmCanvas = ({
16896
16900
  className,
16897
16901
  width = 600,
@@ -17168,23 +17172,188 @@ var init_AuthLayout = __esm({
17168
17172
  AuthLayout.displayName = "AuthLayout";
17169
17173
  }
17170
17174
  });
17171
- var BiologyCanvas;
17175
+ function LearningScene3D({
17176
+ className,
17177
+ width = 600,
17178
+ height = 400,
17179
+ title,
17180
+ backgroundColor,
17181
+ drawables,
17182
+ camera,
17183
+ lighting,
17184
+ post,
17185
+ showGrid = false,
17186
+ shadows,
17187
+ interactive,
17188
+ isLoading,
17189
+ error,
17190
+ onItemClick
17191
+ }) {
17192
+ const instanceId = React94.useId().replace(/[^a-zA-Z0-9]/g, "");
17193
+ const clickEvent = onItemClick ? `LEARNING_SCENE_3D.ITEM_CLICK.${instanceId}` : void 0;
17194
+ const onItemClickRef = React94.useRef(onItemClick);
17195
+ onItemClickRef.current = onItemClick;
17196
+ useEventListener(`UI:${clickEvent ?? "LEARNING_SCENE_3D.ITEM_CLICK.__disabled"}`, (event) => {
17197
+ const unitId = event.payload?.unitId;
17198
+ if (typeof unitId === "string") onItemClickRef.current?.(unitId);
17199
+ });
17200
+ const props3d = {
17201
+ drawables,
17202
+ isLoading,
17203
+ error: error ? error.message : null,
17204
+ cameraMode: camera?.mode ?? "perspective",
17205
+ ...camera?.zoom !== void 0 ? { zoom: camera.zoom } : {},
17206
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
17207
+ ...camera?.azimuth !== void 0 ? { azimuth: camera.azimuth } : {},
17208
+ ...camera?.elevation !== void 0 ? { elevation: camera.elevation } : {},
17209
+ ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
17210
+ backgroundColor,
17211
+ showGrid,
17212
+ ...shadows !== void 0 ? { shadows } : {},
17213
+ ...interactive !== void 0 ? { controlsEnabled: interactive } : {},
17214
+ lighting,
17215
+ post,
17216
+ ...clickEvent !== void 0 ? { unitClickEvent: clickEvent } : {}
17217
+ };
17218
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
17219
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
17220
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width, height, display: "flex" }, children: /* @__PURE__ */ jsxRuntime.jsx(React94.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(Canvas3DHost2, { ...props3d }) }) })
17221
+ ] }) });
17222
+ }
17223
+ function meshSphere(id, x, y, z, radius, color, opts) {
17224
+ return {
17225
+ type: "draw-mesh",
17226
+ ...id !== void 0 ? { id } : {},
17227
+ shape: opts?.shape ?? "sphere",
17228
+ position: { x, y, z },
17229
+ radius,
17230
+ pivot: "center",
17231
+ segments: opts?.segments ?? 24,
17232
+ material: { color, ...opts?.material },
17233
+ ...opts?.opacity !== void 0 ? { opacity: opts.opacity } : {}
17234
+ };
17235
+ }
17236
+ function axisRotation(from, to) {
17237
+ const wx = to[0] - from[0];
17238
+ const wy = to[2] - from[2];
17239
+ const wz = to[1] - from[1];
17240
+ const len = Math.sqrt(wx * wx + wy * wy + wz * wz);
17241
+ const tilt = Math.acos(Math.min(1, Math.max(-1, len > 0 ? wy / len : 1)));
17242
+ return [0, Math.atan2(wz, -wx), tilt];
17243
+ }
17244
+ function segmentLength(from, to) {
17245
+ const dx = to[0] - from[0];
17246
+ const dy = to[1] - from[1];
17247
+ const dz = to[2] - from[2];
17248
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
17249
+ }
17250
+ function cylinderBetween(from, to, radius, color) {
17251
+ const len = segmentLength(from, to);
17252
+ if (len < 1e-6) return null;
17253
+ return {
17254
+ type: "draw-mesh",
17255
+ shape: "cylinder",
17256
+ position: { x: (from[0] + to[0]) / 2, y: (from[1] + to[1]) / 2, z: (from[2] + to[2]) / 2 },
17257
+ radius,
17258
+ height: len,
17259
+ rotation: axisRotation(from, to),
17260
+ pivot: "center",
17261
+ segments: 12,
17262
+ material: { color }
17263
+ };
17264
+ }
17265
+ function arrowBetween(from, to, color, shaftRadius = 0.08) {
17266
+ const len = segmentLength(from, to);
17267
+ if (len < 1e-6) return null;
17268
+ const tipLen = Math.min(shaftRadius * 8, len * 0.35);
17269
+ const k = (len - tipLen) / len;
17270
+ const delta = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
17271
+ const shaftEnd = [delta[0] * k, delta[1] * k, delta[2] * k];
17272
+ const tipStart = shaftEnd;
17273
+ const tipEnd = delta;
17274
+ const shaft = cylinderBetween([0, 0, 0], shaftEnd, shaftRadius, color);
17275
+ const tipLenActual = segmentLength(tipStart, tipEnd);
17276
+ const tip = {
17277
+ type: "draw-mesh",
17278
+ shape: "cone",
17279
+ position: {
17280
+ x: (tipStart[0] + tipEnd[0]) / 2,
17281
+ y: (tipStart[1] + tipEnd[1]) / 2,
17282
+ z: (tipStart[2] + tipEnd[2]) / 2
17283
+ },
17284
+ radius: shaftRadius * 3,
17285
+ height: tipLenActual,
17286
+ rotation: axisRotation(tipStart, tipEnd),
17287
+ pivot: "center",
17288
+ segments: 12,
17289
+ material: { color }
17290
+ };
17291
+ return {
17292
+ type: "draw-group",
17293
+ position: { x: from[0], y: from[1], z: from[2] },
17294
+ items: tipLenActual < 1e-6 ? shaft ? [shaft] : [] : shaft ? [shaft, tip] : [tip]
17295
+ };
17296
+ }
17297
+ function billboardLabel(text, x, y, z, opts) {
17298
+ return {
17299
+ type: "draw-text",
17300
+ text,
17301
+ position: { x, y, z },
17302
+ color: opts?.color ?? "#111827"
17303
+ };
17304
+ }
17305
+ function labelColorForBackground(backgroundColor) {
17306
+ const m = /^#(?:([0-9a-f]{3})|([0-9a-f]{6}))$/i.exec(backgroundColor ?? "");
17307
+ if (!m) return "#111827";
17308
+ const hex = m[1] !== void 0 ? m[1].split("").map((c) => c + c).join("") : m[2];
17309
+ const r2 = parseInt(hex.slice(0, 2), 16) / 255;
17310
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
17311
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
17312
+ const luminance = 0.2126 * r2 + 0.7152 * g + 0.0722 * b;
17313
+ return luminance < 0.5 ? "#e5e7eb" : "#111827";
17314
+ }
17315
+ function get3DClickPayload(onShapeClick, idToIndex) {
17316
+ if (!onShapeClick) return void 0;
17317
+ return (id) => onShapeClick({ id, index: idToIndex.get(id) ?? -1 });
17318
+ }
17319
+ var Canvas3DHost2;
17320
+ var init_learningScene3D = __esm({
17321
+ "components/learning/molecules/learningScene3D.tsx"() {
17322
+ "use client";
17323
+ init_atoms();
17324
+ init_Stack();
17325
+ init_useEventBus();
17326
+ Canvas3DHost2 = React94.lazy(
17327
+ () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
17328
+ );
17329
+ LearningScene3D.displayName = "LearningScene3D";
17330
+ }
17331
+ });
17332
+ var biologyLog, BiologyCanvas;
17172
17333
  var init_BiologyCanvas = __esm({
17173
17334
  "components/learning/molecules/BiologyCanvas.tsx"() {
17174
17335
  "use client";
17175
17336
  init_atoms();
17176
17337
  init_Stack();
17177
17338
  init_LearningCanvas();
17339
+ init_learningScene3D();
17340
+ biologyLog = logger.createLogger("almadar:ui:biology-canvas");
17178
17341
  BiologyCanvas = ({
17179
17342
  className,
17180
17343
  width = 600,
17181
17344
  height = 400,
17182
17345
  title,
17183
17346
  backgroundColor,
17347
+ mode = "2d",
17348
+ camera,
17349
+ lighting,
17350
+ post,
17184
17351
  nodes = [],
17185
17352
  edges = [],
17186
17353
  shapes = [],
17187
- interactive = false,
17354
+ showGrid,
17355
+ shadows,
17356
+ interactive,
17188
17357
  animate = false,
17189
17358
  onShapeClick,
17190
17359
  isLoading,
@@ -17245,6 +17414,75 @@ var init_BiologyCanvas = __esm({
17245
17414
  out.push(...shapes);
17246
17415
  return out;
17247
17416
  }, [nodes, edges, shapes]);
17417
+ const drawables3D = React94.useMemo(() => {
17418
+ if (mode !== "3d") return [];
17419
+ if (shapes.length > 0) {
17420
+ biologyLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
17421
+ }
17422
+ const out = [];
17423
+ const labelColor = labelColorForBackground(backgroundColor);
17424
+ const nodeById = /* @__PURE__ */ new Map();
17425
+ for (const n of nodes) {
17426
+ if (n.id) nodeById.set(n.id, n);
17427
+ }
17428
+ for (const e of edges) {
17429
+ const a = nodeById.get(e.from);
17430
+ const b = nodeById.get(e.to);
17431
+ if (!a || !b) continue;
17432
+ const edgeRadius = Math.max(0.04, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.12);
17433
+ const edge = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], edgeRadius, e.color ?? "#9ca3af");
17434
+ if (edge) out.push(edge);
17435
+ if (e.label) {
17436
+ out.push(
17437
+ billboardLabel(
17438
+ e.label,
17439
+ (a.x + b.x) / 2,
17440
+ (a.y + b.y) / 2,
17441
+ ((a.z ?? 0) + (b.z ?? 0)) / 2,
17442
+ { color: labelColor }
17443
+ )
17444
+ );
17445
+ }
17446
+ }
17447
+ for (const n of nodes) {
17448
+ const radius = n.radius ?? 0.5;
17449
+ const nz = n.z ?? 0;
17450
+ out.push(meshSphere(n.id, n.x, n.y, nz, radius, n.color ?? "#16a34a", { shape: n.shape, ...n.opacity !== void 0 ? { opacity: n.opacity } : {} }));
17451
+ if (n.label) {
17452
+ out.push(billboardLabel(n.label, n.x, n.y, nz + radius, { color: labelColor }));
17453
+ }
17454
+ }
17455
+ return out;
17456
+ }, [mode, nodes, edges, shapes, backgroundColor]);
17457
+ const nodeIndexById = React94.useMemo(() => {
17458
+ const m = /* @__PURE__ */ new Map();
17459
+ nodes.forEach((n, i) => {
17460
+ if (n.id) m.set(n.id, i);
17461
+ });
17462
+ return m;
17463
+ }, [nodes]);
17464
+ if (mode === "3d") {
17465
+ return /* @__PURE__ */ jsxRuntime.jsx(
17466
+ LearningScene3D,
17467
+ {
17468
+ className,
17469
+ width,
17470
+ height,
17471
+ title,
17472
+ backgroundColor,
17473
+ drawables: drawables3D,
17474
+ camera,
17475
+ lighting,
17476
+ post,
17477
+ showGrid,
17478
+ shadows,
17479
+ interactive,
17480
+ isLoading,
17481
+ error,
17482
+ onItemClick: get3DClickPayload(onShapeClick, nodeIndexById)
17483
+ }
17484
+ );
17485
+ }
17248
17486
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
17249
17487
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
17250
17488
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -17254,7 +17492,7 @@ var init_BiologyCanvas = __esm({
17254
17492
  height,
17255
17493
  backgroundColor,
17256
17494
  shapes: derivedShapes,
17257
- interactive,
17495
+ interactive: interactive ?? false,
17258
17496
  animate,
17259
17497
  onShapeClick,
17260
17498
  isLoading,
@@ -24094,24 +24332,41 @@ var init_ChatBar = __esm({
24094
24332
  ChatBar.displayName = "ChatBar";
24095
24333
  }
24096
24334
  });
24097
- var ChemistryCanvas;
24335
+ function bondPerpendicular(a, b) {
24336
+ const dx = b[0] - a[0];
24337
+ const dy = b[1] - a[1];
24338
+ const px = dy;
24339
+ const py = -dx;
24340
+ const len = Math.sqrt(px * px + py * py);
24341
+ if (len < 1e-6) return [1, 0, 0];
24342
+ return [px / len, py / len, 0];
24343
+ }
24344
+ var chemistryLog, ChemistryCanvas;
24098
24345
  var init_ChemistryCanvas = __esm({
24099
24346
  "components/learning/molecules/ChemistryCanvas.tsx"() {
24100
24347
  "use client";
24101
24348
  init_atoms();
24102
24349
  init_Stack();
24103
24350
  init_LearningCanvas();
24351
+ init_learningScene3D();
24352
+ chemistryLog = logger.createLogger("almadar:ui:chemistry-canvas");
24104
24353
  ChemistryCanvas = ({
24105
24354
  className,
24106
24355
  width = 600,
24107
24356
  height = 400,
24108
24357
  title,
24109
24358
  backgroundColor,
24359
+ mode = "2d",
24360
+ camera,
24361
+ lighting,
24362
+ post,
24110
24363
  atoms = [],
24111
24364
  bonds = [],
24112
24365
  arrows = [],
24113
24366
  shapes = [],
24114
- interactive = false,
24367
+ showGrid,
24368
+ shadows,
24369
+ interactive,
24115
24370
  animate = false,
24116
24371
  onShapeClick,
24117
24372
  isLoading,
@@ -24190,6 +24445,88 @@ var init_ChemistryCanvas = __esm({
24190
24445
  out.push(...shapes);
24191
24446
  return out;
24192
24447
  }, [atoms, bonds, arrows, shapes]);
24448
+ const drawables3D = React94.useMemo(() => {
24449
+ if (mode !== "3d") return [];
24450
+ if (shapes.length > 0) {
24451
+ chemistryLog.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
24452
+ }
24453
+ const out = [];
24454
+ const labelColor = labelColorForBackground(backgroundColor);
24455
+ const atomById = /* @__PURE__ */ new Map();
24456
+ for (const a of atoms) {
24457
+ if (a.id) atomById.set(a.id, a);
24458
+ }
24459
+ for (const b of bonds) {
24460
+ const a = atomById.get(b.from);
24461
+ const c = atomById.get(b.to);
24462
+ if (!a || !c) continue;
24463
+ const color = b.color ?? "#6b7280";
24464
+ const from = [a.x, a.y, a.z ?? 0];
24465
+ const to = [c.x, c.y, c.z ?? 0];
24466
+ const perp = bondPerpendicular(from, to);
24467
+ const bondRadius = Math.max(0.06, Math.min(a.radius ?? 0.45, c.radius ?? 0.45) * 0.22);
24468
+ const step = bondRadius * 2.2;
24469
+ const offsets = b.type === "double" ? [-step, step] : b.type === "triple" ? [-step, 0, step] : [0];
24470
+ for (const off of offsets) {
24471
+ const bond = cylinderBetween(
24472
+ [from[0] + perp[0] * off, from[1] + perp[1] * off, from[2] + perp[2] * off],
24473
+ [to[0] + perp[0] * off, to[1] + perp[1] * off, to[2] + perp[2] * off],
24474
+ bondRadius,
24475
+ color
24476
+ );
24477
+ if (bond) out.push(bond);
24478
+ }
24479
+ }
24480
+ for (const a of arrows) {
24481
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
24482
+ const len = a.length ?? 60;
24483
+ const x2 = a.x + Math.cos(angle) * len;
24484
+ const y2 = a.y + Math.sin(angle) * len;
24485
+ const arrow = arrowBetween([a.x, a.y, 0], [x2, y2, 0], a.color ?? "#dc2626");
24486
+ if (arrow) out.push(arrow);
24487
+ if (a.label) {
24488
+ out.push(billboardLabel(a.label, (a.x + x2) / 2, (a.y + y2) / 2, 0, { color: labelColor }));
24489
+ }
24490
+ }
24491
+ for (const a of atoms) {
24492
+ const radius = a.radius ?? 0.45;
24493
+ const az = a.z ?? 0;
24494
+ out.push(meshSphere(a.id, a.x, a.y, az, radius, a.color ?? "#2563eb"));
24495
+ if (a.element) {
24496
+ out.push(billboardLabel(a.element, a.x, a.y, az + radius, { color: labelColor }));
24497
+ }
24498
+ }
24499
+ return out;
24500
+ }, [mode, atoms, bonds, arrows, shapes, backgroundColor]);
24501
+ const atomIndexById = React94.useMemo(() => {
24502
+ const m = /* @__PURE__ */ new Map();
24503
+ atoms.forEach((a, i) => {
24504
+ if (a.id) m.set(a.id, i);
24505
+ });
24506
+ return m;
24507
+ }, [atoms]);
24508
+ if (mode === "3d") {
24509
+ return /* @__PURE__ */ jsxRuntime.jsx(
24510
+ LearningScene3D,
24511
+ {
24512
+ className,
24513
+ width,
24514
+ height,
24515
+ title,
24516
+ backgroundColor,
24517
+ drawables: drawables3D,
24518
+ camera,
24519
+ lighting,
24520
+ post,
24521
+ showGrid,
24522
+ shadows,
24523
+ interactive,
24524
+ isLoading,
24525
+ error,
24526
+ onItemClick: get3DClickPayload(onShapeClick, atomIndexById)
24527
+ }
24528
+ );
24529
+ }
24193
24530
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
24194
24531
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
24195
24532
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -24199,7 +24536,7 @@ var init_ChemistryCanvas = __esm({
24199
24536
  height,
24200
24537
  backgroundColor,
24201
24538
  shapes: derivedShapes,
24202
- interactive,
24539
+ interactive: interactive ?? false,
24203
24540
  animate,
24204
24541
  onShapeClick,
24205
24542
  isLoading,
@@ -27670,6 +28007,7 @@ var init_FilterGroup = __esm({
27670
28007
  init_Badge();
27671
28008
  init_Stack();
27672
28009
  init_Icon();
28010
+ init_RangeSlider();
27673
28011
  init_useEventBus();
27674
28012
  init_useQuerySingleton();
27675
28013
  resolveFilterType = (filter) => filter.filterType ?? filter.type;
@@ -27864,6 +28202,35 @@ var init_FilterGroup = __esm({
27864
28202
  onClear: () => handleFilterSelect(`${filter.field}_to`, null)
27865
28203
  }
27866
28204
  )
28205
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
28206
+ /* @__PURE__ */ jsxRuntime.jsx(
28207
+ RangeSlider,
28208
+ {
28209
+ min: filter.min ?? 0,
28210
+ max: filter.max ?? 100,
28211
+ step: filter.step ?? 1,
28212
+ value: Number(
28213
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
28214
+ ),
28215
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
28216
+ showTooltip: true,
28217
+ "aria-label": t("filterGroup.from")
28218
+ }
28219
+ ),
28220
+ /* @__PURE__ */ jsxRuntime.jsx(
28221
+ RangeSlider,
28222
+ {
28223
+ min: filter.min ?? 0,
28224
+ max: filter.max ?? 100,
28225
+ step: filter.step ?? 1,
28226
+ value: Number(
28227
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
28228
+ ),
28229
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
28230
+ showTooltip: true,
28231
+ "aria-label": t("filterGroup.to")
28232
+ }
28233
+ )
27867
28234
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsxRuntime.jsx(
27868
28235
  Input,
27869
28236
  {
@@ -27946,6 +28313,36 @@ var init_FilterGroup = __esm({
27946
28313
  className: "text-sm min-w-[100px]"
27947
28314
  }
27948
28315
  )
28316
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
28317
+ /* @__PURE__ */ jsxRuntime.jsx(
28318
+ RangeSlider,
28319
+ {
28320
+ min: filter.min ?? 0,
28321
+ max: filter.max ?? 100,
28322
+ step: filter.step ?? 1,
28323
+ value: Number(
28324
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
28325
+ ),
28326
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
28327
+ className: "min-w-[100px]",
28328
+ "aria-label": t("filterGroup.from")
28329
+ }
28330
+ ),
28331
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "-" }),
28332
+ /* @__PURE__ */ jsxRuntime.jsx(
28333
+ RangeSlider,
28334
+ {
28335
+ min: filter.min ?? 0,
28336
+ max: filter.max ?? 100,
28337
+ step: filter.step ?? 1,
28338
+ value: Number(
28339
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
28340
+ ),
28341
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
28342
+ className: "min-w-[100px]",
28343
+ "aria-label": t("filterGroup.to")
28344
+ }
28345
+ )
27949
28346
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsxRuntime.jsx(
27950
28347
  Input,
27951
28348
  {
@@ -28064,6 +28461,38 @@ var init_FilterGroup = __esm({
28064
28461
  className: "min-w-[130px]"
28065
28462
  }
28066
28463
  )
28464
+ ] }) : resolveFilterType(filter) === "numberrange" || resolveFilterType(filter) === "number-range" ? /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
28465
+ /* @__PURE__ */ jsxRuntime.jsx(
28466
+ RangeSlider,
28467
+ {
28468
+ min: filter.min ?? 0,
28469
+ max: filter.max ?? 100,
28470
+ step: filter.step ?? 1,
28471
+ value: Number(
28472
+ selectedValues[`${filter.field}_min`] ?? filter.min ?? 0
28473
+ ),
28474
+ onChange: (v) => handleFilterSelect(`${filter.field}_min`, String(v)),
28475
+ showTooltip: true,
28476
+ className: "min-w-[130px]",
28477
+ "aria-label": t("filterGroup.from")
28478
+ }
28479
+ ),
28480
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "-" }),
28481
+ /* @__PURE__ */ jsxRuntime.jsx(
28482
+ RangeSlider,
28483
+ {
28484
+ min: filter.min ?? 0,
28485
+ max: filter.max ?? 100,
28486
+ step: filter.step ?? 1,
28487
+ value: Number(
28488
+ selectedValues[`${filter.field}_max`] ?? filter.max ?? 100
28489
+ ),
28490
+ onChange: (v) => handleFilterSelect(`${filter.field}_max`, String(v)),
28491
+ showTooltip: true,
28492
+ className: "min-w-[130px]",
28493
+ "aria-label": t("filterGroup.to")
28494
+ }
28495
+ )
28067
28496
  ] }) : resolveFilterType(filter) === "text" ? /* @__PURE__ */ jsxRuntime.jsx(
28068
28497
  Input,
28069
28498
  {
@@ -30412,19 +30841,25 @@ var init_MathCanvas = __esm({
30412
30841
  };
30413
30842
  }
30414
30843
  });
30415
- var PhysicsCanvas;
30844
+ var physicsLog2, PhysicsCanvas;
30416
30845
  var init_PhysicsCanvas = __esm({
30417
30846
  "components/learning/molecules/PhysicsCanvas.tsx"() {
30418
30847
  "use client";
30419
30848
  init_atoms();
30420
30849
  init_Stack();
30421
30850
  init_LearningCanvas();
30851
+ init_learningScene3D();
30852
+ physicsLog2 = logger.createLogger("almadar:ui:physics-canvas");
30422
30853
  PhysicsCanvas = ({
30423
30854
  className,
30424
30855
  width = 600,
30425
30856
  height = 400,
30426
30857
  title,
30427
30858
  backgroundColor,
30859
+ mode = "2d",
30860
+ camera,
30861
+ lighting,
30862
+ post,
30428
30863
  bodies = [],
30429
30864
  constraints = [],
30430
30865
  showVelocity = true,
@@ -30432,7 +30867,9 @@ var init_PhysicsCanvas = __esm({
30432
30867
  velocityScale = 20,
30433
30868
  forceScale = 20,
30434
30869
  shapes = [],
30435
- interactive = false,
30870
+ showGrid,
30871
+ shadows,
30872
+ interactive,
30436
30873
  animate = false,
30437
30874
  onShapeClick,
30438
30875
  isLoading,
@@ -30504,6 +30941,89 @@ var init_PhysicsCanvas = __esm({
30504
30941
  out.push(...shapes);
30505
30942
  return out;
30506
30943
  }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
30944
+ const drawables3D = React94.useMemo(() => {
30945
+ if (mode !== "3d") return [];
30946
+ if (shapes.length > 0) {
30947
+ physicsLog2.debug("shapes ignored in 3D mode (pixel-authored 2D vocabulary)", { count: shapes.length });
30948
+ }
30949
+ const out = [];
30950
+ const labelColor = labelColorForBackground(backgroundColor);
30951
+ const bodyById = /* @__PURE__ */ new Map();
30952
+ for (const b of bodies) {
30953
+ if (b.id) bodyById.set(b.id, b);
30954
+ }
30955
+ for (const c of constraints) {
30956
+ const a = bodyById.get(c.from);
30957
+ const b = bodyById.get(c.to);
30958
+ if (!a || !b) continue;
30959
+ const rodRadius = Math.max(0.05, Math.min(a.radius ?? 0.5, b.radius ?? 0.5) * 0.15);
30960
+ const rod = cylinderBetween([a.x, a.y, a.z ?? 0], [b.x, b.y, b.z ?? 0], rodRadius, c.color ?? "#9ca3af");
30961
+ if (rod) out.push(rod);
30962
+ }
30963
+ for (const b of bodies) {
30964
+ const radius = b.radius ?? 0.5;
30965
+ const bz = b.z ?? 0;
30966
+ out.push(meshSphere(b.id, b.x, b.y, bz, radius, b.color ?? "#2563eb"));
30967
+ if (b.label) {
30968
+ out.push(billboardLabel(b.label, b.x, b.y, bz + radius, { color: labelColor }));
30969
+ }
30970
+ const vx = b.vx ?? 0;
30971
+ const vy = b.vy ?? 0;
30972
+ const vz = b.vz ?? 0;
30973
+ const arrowRadius = Math.max(0.05, radius * 0.15);
30974
+ if (showVelocity && (vx !== 0 || vy !== 0 || vz !== 0)) {
30975
+ const arrow = arrowBetween(
30976
+ [b.x, b.y, bz],
30977
+ [b.x + vx * velocityScale, b.y + vy * velocityScale, bz + vz * velocityScale],
30978
+ "#16a34a",
30979
+ arrowRadius
30980
+ );
30981
+ if (arrow) out.push(arrow);
30982
+ }
30983
+ const fx = b.fx ?? 0;
30984
+ const fy = b.fy ?? 0;
30985
+ const fz = b.fz ?? 0;
30986
+ if (showForces && (fx !== 0 || fy !== 0 || fz !== 0)) {
30987
+ const arrow = arrowBetween(
30988
+ [b.x, b.y, bz],
30989
+ [b.x + fx * forceScale, b.y + fy * forceScale, bz + fz * forceScale],
30990
+ "#dc2626",
30991
+ arrowRadius
30992
+ );
30993
+ if (arrow) out.push(arrow);
30994
+ }
30995
+ }
30996
+ return out;
30997
+ }, [mode, bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes, backgroundColor]);
30998
+ const bodyIndexById = React94.useMemo(() => {
30999
+ const m = /* @__PURE__ */ new Map();
31000
+ bodies.forEach((b, i) => {
31001
+ if (b.id) m.set(b.id, i);
31002
+ });
31003
+ return m;
31004
+ }, [bodies]);
31005
+ if (mode === "3d") {
31006
+ return /* @__PURE__ */ jsxRuntime.jsx(
31007
+ LearningScene3D,
31008
+ {
31009
+ className,
31010
+ width,
31011
+ height,
31012
+ title,
31013
+ backgroundColor,
31014
+ drawables: drawables3D,
31015
+ camera,
31016
+ lighting,
31017
+ post,
31018
+ showGrid,
31019
+ shadows,
31020
+ interactive,
31021
+ isLoading,
31022
+ error,
31023
+ onItemClick: get3DClickPayload(onShapeClick, bodyIndexById)
31024
+ }
31025
+ );
31026
+ }
30507
31027
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
30508
31028
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
30509
31029
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -30513,7 +31033,7 @@ var init_PhysicsCanvas = __esm({
30513
31033
  height,
30514
31034
  backgroundColor,
30515
31035
  shapes: derivedShapes,
30516
- interactive,
31036
+ interactive: interactive ?? false,
30517
31037
  animate,
30518
31038
  onShapeClick,
30519
31039
  isLoading,
@@ -31131,12 +31651,12 @@ var init_MapView = __esm({
31131
31651
  shadowSize: [41, 41]
31132
31652
  });
31133
31653
  L.Marker.prototype.options.icon = defaultIcon;
31134
- const { useEffect: useEffect64, useRef: useRef64, useCallback: useCallback96, useState: useState100 } = React94__namespace.default;
31654
+ const { useEffect: useEffect64, useRef: useRef65, useCallback: useCallback96, useState: useState100 } = React94__namespace.default;
31135
31655
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31136
31656
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31137
31657
  function MapUpdater({ centerLat, centerLng, zoom }) {
31138
31658
  const map = useMap();
31139
- const prevRef = useRef64({ centerLat, centerLng, zoom });
31659
+ const prevRef = useRef65({ centerLat, centerLng, zoom });
31140
31660
  useEffect64(() => {
31141
31661
  const prev = prevRef.current;
31142
31662
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -41550,7 +42070,7 @@ function getEnumOptions(field) {
41550
42070
  }));
41551
42071
  }
41552
42072
  const validation = field.validation;
41553
- if (validation?.enum && Array.isArray(validation.enum)) {
42073
+ if (validation?.enum && validation.enum.length > 0) {
41554
42074
  return validation.enum.map((v) => ({
41555
42075
  value: v,
41556
42076
  label: v.charAt(0).toUpperCase() + v.slice(1).replace(/_/g, " ")