@almadar/ui 5.76.7 → 5.78.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 (70) hide show
  1. package/dist/avl/index.cjs +1121 -535
  2. package/dist/avl/index.css +3 -3
  3. package/dist/avl/index.js +1122 -536
  4. package/dist/components/core/atoms/FlipContainer.d.ts +1 -1
  5. package/dist/components/core/atoms/index.d.ts +2 -1
  6. package/dist/components/core/molecules/Card.d.ts +1 -1
  7. package/dist/components/core/molecules/DataGrid.d.ts +1 -1
  8. package/dist/components/core/molecules/DataList.d.ts +1 -1
  9. package/dist/components/core/molecules/DocPagination.d.ts +1 -8
  10. package/dist/components/core/molecules/DocSearch.d.ts +1 -8
  11. package/dist/components/core/molecules/FlipCard.d.ts +1 -1
  12. package/dist/components/core/molecules/MapView.d.ts +1 -18
  13. package/dist/components/core/molecules/SortableList.d.ts +1 -1
  14. package/dist/components/core/molecules/TableView.d.ts +1 -1
  15. package/dist/components/core/molecules/index.d.ts +4 -0
  16. package/dist/components/core/organisms/DataTable.d.ts +1 -1
  17. package/dist/components/core/organisms/debug/RuntimeDebugger.d.ts +1 -10
  18. package/dist/components/core/organisms/debug/tabs/EntitiesTab.d.ts +1 -2
  19. package/dist/components/core/organisms/debug/tabs/EventDispatcherTab.d.ts +1 -2
  20. package/dist/components/core/organisms/debug/tabs/EventFlowTab.d.ts +1 -6
  21. package/dist/components/core/organisms/debug/tabs/GuardsPanel.d.ts +1 -6
  22. package/dist/components/core/organisms/debug/tabs/ServerBridgeTab.d.ts +1 -12
  23. package/dist/components/core/organisms/debug/tabs/TicksTab.d.ts +1 -2
  24. package/dist/components/core/organisms/debug/tabs/TraitsTab.d.ts +1 -2
  25. package/dist/components/core/organisms/debug/tabs/TransitionTimeline.d.ts +1 -11
  26. package/dist/components/core/organisms/debug/tabs/VerificationTab.d.ts +1 -8
  27. package/dist/components/game/2d/atoms/ActionButton.d.ts +1 -2
  28. package/dist/components/game/2d/atoms/ChoiceButton.d.ts +1 -2
  29. package/dist/components/game/2d/atoms/ComboCounter.d.ts +1 -2
  30. package/dist/components/game/2d/atoms/ControlButton.d.ts +1 -2
  31. package/dist/components/game/2d/atoms/DamageNumber.d.ts +1 -2
  32. package/dist/components/game/2d/atoms/DialogueBubble.d.ts +1 -2
  33. package/dist/components/game/2d/atoms/GameIcon.d.ts +1 -2
  34. package/dist/components/game/2d/atoms/HealthBar.d.ts +1 -2
  35. package/dist/components/game/2d/atoms/ItemSlot.d.ts +1 -2
  36. package/dist/components/game/2d/atoms/MiniMap.d.ts +1 -2
  37. package/dist/components/game/2d/atoms/ResourceCounter.d.ts +1 -2
  38. package/dist/components/game/2d/atoms/ScoreDisplay.d.ts +1 -2
  39. package/dist/components/game/2d/atoms/StatusEffect.d.ts +1 -2
  40. package/dist/components/game/2d/atoms/TimerDisplay.d.ts +1 -2
  41. package/dist/components/game/2d/atoms/TurnIndicator.d.ts +1 -2
  42. package/dist/components/game/2d/atoms/WaypointMarker.d.ts +1 -2
  43. package/dist/components/game/2d/molecules/GameHud.d.ts +1 -2
  44. package/dist/components/game/2d/molecules/GameMenu.d.ts +1 -2
  45. package/dist/components/game/2d/molecules/InventoryGrid.d.ts +1 -2
  46. package/dist/components/game/2d/molecules/ResourceBar.d.ts +1 -2
  47. package/dist/components/game/2d/molecules/StatBadge.d.ts +1 -2
  48. package/dist/components/game/shared/lib/editorUtils.d.ts +8 -8
  49. package/dist/components/index.cjs +2334 -1653
  50. package/dist/components/index.css +3 -3
  51. package/dist/components/index.js +1124 -443
  52. package/dist/components/learning/atoms/LearningCanvas.d.ts +80 -0
  53. package/dist/components/learning/molecules/BiologyCanvas.d.ts +47 -0
  54. package/dist/components/learning/molecules/ChemistryCanvas.d.ts +55 -0
  55. package/dist/components/learning/molecules/MathCanvas.d.ts +61 -0
  56. package/dist/components/learning/molecules/PhysicsCanvas.d.ts +54 -0
  57. package/dist/components/marketing/atoms/MarketingStatCard.d.ts +2 -2
  58. package/dist/hooks/useGitHub.d.ts +7 -7
  59. package/dist/marketing/index.cjs +2 -2
  60. package/dist/marketing/index.js +2 -2
  61. package/dist/providers/EventBusProvider.d.ts +1 -1
  62. package/dist/providers/SelectionProvider.d.ts +1 -1
  63. package/dist/providers/ServerBridge.d.ts +1 -1
  64. package/dist/providers/index.cjs +1117 -531
  65. package/dist/providers/index.css +3 -3
  66. package/dist/providers/index.js +1118 -532
  67. package/dist/runtime/index.cjs +1117 -531
  68. package/dist/runtime/index.css +3 -3
  69. package/dist/runtime/index.js +1118 -532
  70. package/package.json +2 -2
@@ -5676,6 +5676,66 @@ var init_RangeSlider = __esm({
5676
5676
  RangeSlider.displayName = "RangeSlider";
5677
5677
  }
5678
5678
  });
5679
+ function easeOut(t) {
5680
+ return t * (2 - t);
5681
+ }
5682
+ var AnimatedCounter;
5683
+ var init_AnimatedCounter = __esm({
5684
+ "components/marketing/atoms/AnimatedCounter.tsx"() {
5685
+ "use client";
5686
+ init_cn();
5687
+ init_Typography();
5688
+ AnimatedCounter = ({
5689
+ value: rawValue,
5690
+ duration = 600,
5691
+ prefix,
5692
+ suffix,
5693
+ className
5694
+ }) => {
5695
+ const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
5696
+ const value = !Number.isNaN(numericRaw) ? numericRaw : 0;
5697
+ const [displayValue, setDisplayValue] = React107.useState(value);
5698
+ const previousValueRef = React107.useRef(value);
5699
+ const animationFrameRef = React107.useRef(null);
5700
+ React107.useEffect(() => {
5701
+ const from = previousValueRef.current;
5702
+ const to = value;
5703
+ previousValueRef.current = value;
5704
+ if (from === to) {
5705
+ setDisplayValue(to);
5706
+ return;
5707
+ }
5708
+ const startTime = performance.now();
5709
+ const diff = to - from;
5710
+ function animate(currentTime) {
5711
+ const elapsed = currentTime - startTime;
5712
+ const progress = Math.min(elapsed / duration, 1);
5713
+ const easedProgress = easeOut(progress);
5714
+ setDisplayValue(from + diff * easedProgress);
5715
+ if (progress < 1) {
5716
+ animationFrameRef.current = requestAnimationFrame(animate);
5717
+ } else {
5718
+ setDisplayValue(to);
5719
+ }
5720
+ }
5721
+ animationFrameRef.current = requestAnimationFrame(animate);
5722
+ return () => {
5723
+ if (animationFrameRef.current !== null) {
5724
+ cancelAnimationFrame(animationFrameRef.current);
5725
+ }
5726
+ };
5727
+ }, [value, duration]);
5728
+ const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
5729
+ const formattedValue = displayValue.toFixed(decimalPlaces);
5730
+ return /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
5731
+ prefix,
5732
+ formattedValue,
5733
+ suffix
5734
+ ] });
5735
+ };
5736
+ AnimatedCounter.displayName = "AnimatedCounter";
5737
+ }
5738
+ });
5679
5739
  function useInfiniteScroll(onLoadMore, options = {}) {
5680
5740
  const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
5681
5741
  const observerRef = React107.useRef(null);
@@ -5972,6 +6032,34 @@ var init_SectionHeader = __esm({
5972
6032
  SectionHeader.displayName = "SectionHeader";
5973
6033
  }
5974
6034
  });
6035
+ var sizeClasses6, MarketingStatCard;
6036
+ var init_MarketingStatCard = __esm({
6037
+ "components/marketing/atoms/MarketingStatCard.tsx"() {
6038
+ init_cn();
6039
+ init_Stack();
6040
+ init_Typography();
6041
+ sizeClasses6 = {
6042
+ sm: "text-2xl",
6043
+ md: "text-4xl",
6044
+ lg: "text-5xl"
6045
+ };
6046
+ MarketingStatCard = ({ value, label, size = "md", className }) => {
6047
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", align: "center", className: cn(className), children: [
6048
+ /* @__PURE__ */ jsxRuntime.jsx(
6049
+ Typography,
6050
+ {
6051
+ weight: "bold",
6052
+ color: "primary",
6053
+ className: cn(sizeClasses6[size]),
6054
+ children: value
6055
+ }
6056
+ ),
6057
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", children: label })
6058
+ ] });
6059
+ };
6060
+ MarketingStatCard.displayName = "MarketingStatCard";
6061
+ }
6062
+ });
5975
6063
  var backgroundClasses, paddingClasses, ContentSection;
5976
6064
  var init_ContentSection = __esm({
5977
6065
  "components/marketing/atoms/ContentSection.tsx"() {
@@ -12153,318 +12241,14 @@ var init_useCanvasEffects = __esm({
12153
12241
  "use client";
12154
12242
  }
12155
12243
  });
12156
- function pickPath(entry) {
12157
- if (Array.isArray(entry.path)) {
12158
- return entry.path[Math.floor(Math.random() * entry.path.length)];
12159
- }
12160
- return entry.path;
12161
- }
12162
- function useGameAudio({
12163
- manifest,
12164
- baseUrl = "",
12165
- initialMuted = false,
12166
- initialVolume = 1
12167
- }) {
12168
- const [muted, setMutedState] = React107.useState(initialMuted);
12169
- const [masterVolume, setMasterVolumeState] = React107.useState(initialVolume);
12170
- const mutedRef = React107.useRef(muted);
12171
- const volumeRef = React107.useRef(masterVolume);
12172
- const manifestRef = React107.useRef(manifest);
12173
- mutedRef.current = muted;
12174
- volumeRef.current = masterVolume;
12175
- manifestRef.current = manifest;
12176
- const poolsRef = React107.useRef(/* @__PURE__ */ new Map());
12177
- const getOrCreateElement = React107.useCallback((key) => {
12178
- const entry = manifestRef.current[key];
12179
- if (!entry) return null;
12180
- let pool = poolsRef.current.get(key);
12181
- if (!pool) {
12182
- pool = [];
12183
- poolsRef.current.set(key, pool);
12184
- }
12185
- const maxSize = entry.poolSize ?? 1;
12186
- for (const audio of pool) {
12187
- if (audio.paused && (audio.ended || audio.currentTime === 0)) {
12188
- return audio;
12189
- }
12190
- }
12191
- if (pool.length < maxSize) {
12192
- const src = baseUrl + pickPath(entry);
12193
- const audio = new Audio(src);
12194
- audio.loop = entry.loop ?? false;
12195
- pool.push(audio);
12196
- return audio;
12197
- }
12198
- if (!entry.loop) {
12199
- let oldest = pool[0];
12200
- for (const audio of pool) {
12201
- if (audio.currentTime > oldest.currentTime) {
12202
- oldest = audio;
12203
- }
12204
- }
12205
- oldest.pause();
12206
- oldest.currentTime = 0;
12207
- return oldest;
12208
- }
12209
- return null;
12210
- }, [baseUrl]);
12211
- const play = React107.useCallback((key) => {
12212
- if (mutedRef.current) return;
12213
- const entry = manifestRef.current[key];
12214
- if (!entry) return;
12215
- const audio = getOrCreateElement(key);
12216
- if (!audio) return;
12217
- audio.volume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
12218
- if (!entry.loop) {
12219
- audio.currentTime = 0;
12220
- }
12221
- const promise = audio.play();
12222
- if (promise) {
12223
- promise.catch(() => {
12224
- });
12225
- }
12226
- }, [getOrCreateElement]);
12227
- const stop = React107.useCallback((key) => {
12228
- const pool = poolsRef.current.get(key);
12229
- if (!pool) return;
12230
- for (const audio of pool) {
12231
- audio.pause();
12232
- audio.currentTime = 0;
12233
- }
12234
- }, []);
12235
- const currentMusicKeyRef = React107.useRef(null);
12236
- const currentMusicElRef = React107.useRef(null);
12237
- const musicFadeRef = React107.useRef(null);
12238
- const pendingMusicKeyRef = React107.useRef(null);
12239
- const clearMusicFade = React107.useCallback(() => {
12240
- if (musicFadeRef.current) {
12241
- clearInterval(musicFadeRef.current);
12242
- musicFadeRef.current = null;
12243
- }
12244
- }, []);
12245
- const playMusic = React107.useCallback((key) => {
12246
- if (key === currentMusicKeyRef.current) return;
12247
- pendingMusicKeyRef.current = key;
12248
- const entry = manifestRef.current[key];
12249
- if (!entry) return;
12250
- const fadeDurationMs = entry.crossfadeDurationMs ?? 1500;
12251
- const stepMs = 50;
12252
- const totalSteps = Math.max(1, fadeDurationMs / stepMs);
12253
- const targetVolume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
12254
- clearMusicFade();
12255
- const src = baseUrl + (Array.isArray(entry.path) ? entry.path[0] : entry.path);
12256
- const incoming = new Audio(src);
12257
- incoming.loop = true;
12258
- incoming.volume = 0;
12259
- const outgoing = currentMusicElRef.current;
12260
- const outgoingStartVol = outgoing?.volume ?? 0;
12261
- currentMusicKeyRef.current = key;
12262
- currentMusicElRef.current = incoming;
12263
- if (!mutedRef.current) {
12264
- incoming.play().catch(() => {
12265
- currentMusicKeyRef.current = null;
12266
- currentMusicElRef.current = outgoing;
12267
- });
12268
- }
12269
- let step = 0;
12270
- musicFadeRef.current = setInterval(() => {
12271
- step++;
12272
- const progress = Math.min(step / totalSteps, 1);
12273
- incoming.volume = Math.min(1, targetVolume * progress);
12274
- if (outgoing) {
12275
- outgoing.volume = Math.max(0, outgoingStartVol * (1 - progress));
12276
- }
12277
- if (progress >= 1) {
12278
- clearMusicFade();
12279
- if (outgoing) {
12280
- outgoing.pause();
12281
- outgoing.src = "";
12282
- }
12283
- }
12284
- }, stepMs);
12285
- }, [baseUrl, clearMusicFade]);
12286
- const stopMusic = React107.useCallback((fadeDurationMs = 1e3) => {
12287
- const outgoing = currentMusicElRef.current;
12288
- if (!outgoing) return;
12289
- currentMusicKeyRef.current = null;
12290
- currentMusicElRef.current = null;
12291
- pendingMusicKeyRef.current = null;
12292
- clearMusicFade();
12293
- const startVolume = outgoing.volume;
12294
- const stepMs = 50;
12295
- const totalSteps = Math.max(1, fadeDurationMs / stepMs);
12296
- let step = 0;
12297
- musicFadeRef.current = setInterval(() => {
12298
- step++;
12299
- const progress = step / totalSteps;
12300
- outgoing.volume = Math.max(0, startVolume * (1 - progress));
12301
- if (progress >= 1) {
12302
- clearMusicFade();
12303
- outgoing.pause();
12304
- outgoing.src = "";
12305
- }
12306
- }, stepMs);
12307
- }, [clearMusicFade]);
12308
- const stopAll = React107.useCallback(() => {
12309
- for (const pool of poolsRef.current.values()) {
12310
- for (const audio of pool) {
12311
- audio.pause();
12312
- audio.currentTime = 0;
12313
- }
12314
- }
12315
- stopMusic(0);
12316
- }, [stopMusic]);
12317
- const setMuted = React107.useCallback((value) => {
12318
- setMutedState(value);
12319
- if (value) {
12320
- for (const [key, pool] of poolsRef.current.entries()) {
12321
- if (manifestRef.current[key]?.loop) {
12322
- for (const audio of pool) {
12323
- if (!audio.paused) audio.pause();
12324
- }
12325
- }
12326
- }
12327
- currentMusicElRef.current?.pause();
12328
- } else {
12329
- for (const [key, pool] of poolsRef.current.entries()) {
12330
- const entry = manifestRef.current[key];
12331
- if (entry?.loop && entry?.autostart) {
12332
- for (const audio of pool) {
12333
- if (audio.paused) audio.play().catch(() => {
12334
- });
12335
- }
12336
- }
12337
- }
12338
- const musicEl = currentMusicElRef.current;
12339
- if (musicEl) {
12340
- musicEl.play().catch(() => {
12341
- });
12342
- }
12343
- }
12344
- }, []);
12345
- const setMasterVolume = React107.useCallback((volume) => {
12346
- const clamped = Math.max(0, Math.min(1, volume));
12347
- setMasterVolumeState(clamped);
12348
- for (const [key, pool] of poolsRef.current.entries()) {
12349
- const entryVol = manifestRef.current[key]?.volume ?? 1;
12350
- for (const audio of pool) {
12351
- audio.volume = Math.min(1, entryVol * clamped);
12352
- }
12353
- }
12354
- if (!musicFadeRef.current && currentMusicElRef.current) {
12355
- const key = currentMusicKeyRef.current;
12356
- const entryVol = key ? manifestRef.current[key]?.volume ?? 1 : 1;
12357
- currentMusicElRef.current.volume = Math.min(1, entryVol * clamped);
12358
- }
12359
- }, []);
12360
- const unlockedRef = React107.useRef(false);
12361
- React107.useEffect(() => {
12362
- const autoKeys = Object.keys(manifest).filter((k) => manifest[k].autostart);
12363
- const hasPendingMusic = () => pendingMusicKeyRef.current !== null;
12364
- const hasAutoStart = autoKeys.length > 0;
12365
- if (!hasAutoStart && !hasPendingMusic()) return;
12366
- const unlock = () => {
12367
- if (unlockedRef.current) return;
12368
- unlockedRef.current = true;
12369
- if (!mutedRef.current) {
12370
- for (const key of autoKeys) {
12371
- play(key);
12372
- }
12373
- const pending = pendingMusicKeyRef.current;
12374
- if (pending && pending !== currentMusicKeyRef.current) {
12375
- playMusic(pending);
12376
- }
12377
- }
12378
- };
12379
- document.addEventListener("click", unlock, { once: true });
12380
- document.addEventListener("keydown", unlock, { once: true });
12381
- document.addEventListener("touchstart", unlock, { once: true });
12382
- return () => {
12383
- document.removeEventListener("click", unlock);
12384
- document.removeEventListener("keydown", unlock);
12385
- document.removeEventListener("touchstart", unlock);
12386
- };
12387
- }, [manifest, play, playMusic]);
12388
- React107.useEffect(() => {
12389
- return () => {
12390
- clearMusicFade();
12391
- for (const pool of poolsRef.current.values()) {
12392
- for (const audio of pool) {
12393
- audio.pause();
12394
- audio.src = "";
12395
- }
12396
- }
12397
- poolsRef.current.clear();
12398
- if (currentMusicElRef.current) {
12399
- currentMusicElRef.current.pause();
12400
- currentMusicElRef.current.src = "";
12401
- currentMusicElRef.current = null;
12402
- }
12403
- };
12404
- }, [clearMusicFade]);
12405
- return {
12406
- play,
12407
- stop,
12408
- stopAll,
12409
- playMusic,
12410
- stopMusic,
12411
- muted,
12412
- setMuted,
12413
- masterVolume,
12414
- setMasterVolume
12415
- };
12416
- }
12417
12244
  var init_useGameAudio = __esm({
12418
12245
  "components/game/shared/hooks/useGameAudio.ts"() {
12419
12246
  "use client";
12420
- useGameAudio.displayName = "useGameAudio";
12421
12247
  }
12422
12248
  });
12423
12249
  function useGameAudioContextOptional() {
12424
12250
  return React107.useContext(GameAudioContext);
12425
12251
  }
12426
- function GameAudioProvider({
12427
- manifest,
12428
- baseUrl = "",
12429
- children,
12430
- initialMuted = false
12431
- }) {
12432
- const eventBus = useEventBus();
12433
- const { play, stop, stopAll, playMusic, stopMusic, muted, setMuted, masterVolume, setMasterVolume } = useGameAudio({ manifest, baseUrl, initialMuted });
12434
- React107.useEffect(() => {
12435
- const unsubPlay = eventBus.on("UI:PLAY_SOUND", (event) => {
12436
- const key = event.payload?.key;
12437
- if (key) play(key);
12438
- });
12439
- const unsubStop = eventBus.on("UI:STOP_SOUND", (event) => {
12440
- const key = event.payload?.key;
12441
- if (key) {
12442
- stop(key);
12443
- } else {
12444
- stopAll();
12445
- }
12446
- });
12447
- const unsubChangeMusic = eventBus.on("UI:CHANGE_MUSIC", (event) => {
12448
- const key = event.payload?.key;
12449
- if (key) {
12450
- playMusic(key);
12451
- } else {
12452
- stopMusic();
12453
- }
12454
- });
12455
- const unsubStopMusic = eventBus.on("UI:STOP_MUSIC", () => {
12456
- stopMusic();
12457
- });
12458
- return () => {
12459
- unsubPlay();
12460
- unsubStop();
12461
- unsubChangeMusic();
12462
- unsubStopMusic();
12463
- };
12464
- }, [eventBus, play, stop, stopAll, playMusic, stopMusic]);
12465
- const value = { muted, setMuted, masterVolume, setMasterVolume, play, playMusic, stopMusic };
12466
- return /* @__PURE__ */ jsxRuntime.jsx(GameAudioContext.Provider, { value, children });
12467
- }
12468
12252
  var GameAudioContext;
12469
12253
  var init_GameAudioProvider = __esm({
12470
12254
  "components/game/shared/providers/GameAudioProvider.tsx"() {
@@ -12473,7 +12257,6 @@ var init_GameAudioProvider = __esm({
12473
12257
  init_useGameAudio();
12474
12258
  GameAudioContext = React107.createContext(null);
12475
12259
  GameAudioContext.displayName = "GameAudioContext";
12476
- GameAudioProvider.displayName = "GameAudioProvider";
12477
12260
  }
12478
12261
  });
12479
12262
  function GameAudioToggle({
@@ -20109,6 +19892,315 @@ var init_molecules = __esm({
20109
19892
  init_puzzleObject();
20110
19893
  }
20111
19894
  });
19895
+ function resolveColor2(color, ctx, fallback) {
19896
+ if (!color) return fallback;
19897
+ if (color.startsWith("var(")) {
19898
+ ctx.canvas.style;
19899
+ const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color);
19900
+ if (m) {
19901
+ const computed = getComputedStyle(ctx.canvas).getPropertyValue(m[1]).trim();
19902
+ return computed || m[2]?.trim() || fallback;
19903
+ }
19904
+ }
19905
+ return color;
19906
+ }
19907
+ function shapeBounds(shape) {
19908
+ switch (shape.type) {
19909
+ case "line":
19910
+ case "arrow":
19911
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) return null;
19912
+ return {
19913
+ x: Math.min(shape.x1, shape.x2) - 6,
19914
+ y: Math.min(shape.y1, shape.y2) - 6,
19915
+ w: Math.abs(shape.x2 - shape.x1) + 12,
19916
+ h: Math.abs(shape.y2 - shape.y1) + 12
19917
+ };
19918
+ case "circle":
19919
+ if (shape.x == null || shape.y == null || shape.radius == null) return null;
19920
+ return {
19921
+ x: shape.x - shape.radius - 4,
19922
+ y: shape.y - shape.radius - 4,
19923
+ w: shape.radius * 2 + 8,
19924
+ h: shape.radius * 2 + 8
19925
+ };
19926
+ case "rect":
19927
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
19928
+ return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
19929
+ case "polygon":
19930
+ if (!shape.points || shape.points.length === 0) return null;
19931
+ {
19932
+ const xs = shape.points.map((p2) => p2.x);
19933
+ const ys = shape.points.map((p2) => p2.y);
19934
+ const minX = Math.min(...xs);
19935
+ const minY = Math.min(...ys);
19936
+ return {
19937
+ x: minX - 4,
19938
+ y: minY - 4,
19939
+ w: Math.max(...xs) - minX + 8,
19940
+ h: Math.max(...ys) - minY + 8
19941
+ };
19942
+ }
19943
+ case "text":
19944
+ if (shape.x == null || shape.y == null) return null;
19945
+ return { x: shape.x - 4, y: shape.y - (shape.fontSize ?? 14) - 4, w: 120, h: (shape.fontSize ?? 14) + 8 };
19946
+ default:
19947
+ return null;
19948
+ }
19949
+ }
19950
+ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
19951
+ const angle = Math.atan2(y2 - y1, x2 - x1);
19952
+ ctx.beginPath();
19953
+ ctx.moveTo(x2, y2);
19954
+ ctx.lineTo(x2 - size * Math.cos(angle - Math.PI / 6), y2 - size * Math.sin(angle - Math.PI / 6));
19955
+ ctx.lineTo(x2 - size * Math.cos(angle + Math.PI / 6), y2 - size * Math.sin(angle + Math.PI / 6));
19956
+ ctx.closePath();
19957
+ ctx.fill();
19958
+ }
19959
+ function drawShape(ctx, shape, width, height) {
19960
+ ctx.save();
19961
+ const opacity = shape.opacity ?? 1;
19962
+ ctx.globalAlpha = opacity;
19963
+ const stroke = resolveColor2(shape.color, ctx, "#333333");
19964
+ const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
19965
+ ctx.lineWidth = shape.lineWidth ?? 2;
19966
+ switch (shape.type) {
19967
+ case "grid": {
19968
+ const step = shape.step ?? 40;
19969
+ ctx.strokeStyle = stroke;
19970
+ ctx.globalAlpha = opacity * 0.25;
19971
+ ctx.lineWidth = 1;
19972
+ ctx.beginPath();
19973
+ for (let x = 0; x <= width; x += step) {
19974
+ ctx.moveTo(x, 0);
19975
+ ctx.lineTo(x, height);
19976
+ }
19977
+ for (let y = 0; y <= height; y += step) {
19978
+ ctx.moveTo(0, y);
19979
+ ctx.lineTo(width, y);
19980
+ }
19981
+ ctx.stroke();
19982
+ break;
19983
+ }
19984
+ case "axis": {
19985
+ const axis = shape.axis ?? "x";
19986
+ ctx.strokeStyle = stroke;
19987
+ ctx.lineWidth = shape.lineWidth ?? 2;
19988
+ ctx.beginPath();
19989
+ if (axis === "x") {
19990
+ ctx.moveTo(0, height / 2);
19991
+ ctx.lineTo(width, height / 2);
19992
+ } else {
19993
+ ctx.moveTo(width / 2, 0);
19994
+ ctx.lineTo(width / 2, height);
19995
+ }
19996
+ ctx.stroke();
19997
+ break;
19998
+ }
19999
+ case "line": {
20000
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
20001
+ ctx.strokeStyle = stroke;
20002
+ ctx.beginPath();
20003
+ ctx.moveTo(shape.x1, shape.y1);
20004
+ ctx.lineTo(shape.x2, shape.y2);
20005
+ ctx.stroke();
20006
+ break;
20007
+ }
20008
+ case "arrow": {
20009
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
20010
+ ctx.strokeStyle = stroke;
20011
+ ctx.fillStyle = stroke;
20012
+ ctx.beginPath();
20013
+ ctx.moveTo(shape.x1, shape.y1);
20014
+ ctx.lineTo(shape.x2, shape.y2);
20015
+ ctx.stroke();
20016
+ drawArrowHead(ctx, shape.x1, shape.y1, shape.x2, shape.y2, 10);
20017
+ break;
20018
+ }
20019
+ case "circle": {
20020
+ if (shape.x == null || shape.y == null || shape.radius == null) break;
20021
+ ctx.beginPath();
20022
+ ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
20023
+ if (fill) {
20024
+ ctx.fillStyle = fill;
20025
+ ctx.fill();
20026
+ }
20027
+ ctx.strokeStyle = stroke;
20028
+ ctx.stroke();
20029
+ break;
20030
+ }
20031
+ case "rect": {
20032
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
20033
+ if (fill) {
20034
+ ctx.fillStyle = fill;
20035
+ ctx.fillRect(shape.x, shape.y, shape.width, shape.height);
20036
+ }
20037
+ ctx.strokeStyle = stroke;
20038
+ ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
20039
+ break;
20040
+ }
20041
+ case "polygon": {
20042
+ if (!shape.points || shape.points.length < 2) break;
20043
+ ctx.beginPath();
20044
+ ctx.moveTo(shape.points[0].x, shape.points[0].y);
20045
+ for (let i = 1; i < shape.points.length; i++) {
20046
+ ctx.lineTo(shape.points[i].x, shape.points[i].y);
20047
+ }
20048
+ ctx.closePath();
20049
+ if (fill) {
20050
+ ctx.fillStyle = fill;
20051
+ ctx.fill();
20052
+ }
20053
+ ctx.strokeStyle = stroke;
20054
+ ctx.stroke();
20055
+ break;
20056
+ }
20057
+ case "path": {
20058
+ if (!shape.path) break;
20059
+ const p2 = new Path2D(shape.path);
20060
+ if (fill) {
20061
+ ctx.fillStyle = fill;
20062
+ ctx.fill(p2);
20063
+ }
20064
+ ctx.strokeStyle = stroke;
20065
+ ctx.stroke(p2);
20066
+ break;
20067
+ }
20068
+ case "text": {
20069
+ if (shape.x == null || shape.y == null || !shape.text) break;
20070
+ ctx.fillStyle = stroke;
20071
+ ctx.font = `${shape.fontSize ?? 14}px system-ui, sans-serif`;
20072
+ ctx.textAlign = shape.align ?? "left";
20073
+ ctx.textBaseline = "middle";
20074
+ ctx.fillText(shape.text, shape.x, shape.y);
20075
+ break;
20076
+ }
20077
+ }
20078
+ ctx.restore();
20079
+ }
20080
+ var LearningCanvas;
20081
+ var init_LearningCanvas = __esm({
20082
+ "components/learning/atoms/LearningCanvas.tsx"() {
20083
+ "use client";
20084
+ init_cn();
20085
+ init_useEventBus();
20086
+ LearningCanvas = ({
20087
+ className,
20088
+ width = 600,
20089
+ height = 400,
20090
+ backgroundColor,
20091
+ shapes = [],
20092
+ interactive = false,
20093
+ animate = false,
20094
+ onShapeClick,
20095
+ onShapeHover,
20096
+ isLoading,
20097
+ error
20098
+ }) => {
20099
+ const canvasRef = React107.useRef(null);
20100
+ const eventBus = useEventBus();
20101
+ const animRef = React107.useRef(0);
20102
+ const hoverIndexRef = React107.useRef(-1);
20103
+ const findShapeAt = React107.useCallback((clientX, clientY) => {
20104
+ const canvas = canvasRef.current;
20105
+ if (!canvas) return -1;
20106
+ const rect = canvas.getBoundingClientRect();
20107
+ const x = clientX - rect.left;
20108
+ const y = clientY - rect.top;
20109
+ for (let i = shapes.length - 1; i >= 0; i--) {
20110
+ const b = shapeBounds(shapes[i]);
20111
+ if (b && x >= b.x && x <= b.x + b.w && y >= b.y && y <= b.y + b.h) {
20112
+ return i;
20113
+ }
20114
+ }
20115
+ return -1;
20116
+ }, [shapes]);
20117
+ const draw = React107.useCallback(() => {
20118
+ const canvas = canvasRef.current;
20119
+ if (!canvas) return;
20120
+ const ctx = canvas.getContext("2d");
20121
+ if (!ctx) return;
20122
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
20123
+ canvas.width = Math.max(1, Math.floor(width * dpr));
20124
+ canvas.height = Math.max(1, Math.floor(height * dpr));
20125
+ canvas.style.width = `${width}px`;
20126
+ canvas.style.height = `${height}px`;
20127
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
20128
+ ctx.clearRect(0, 0, width, height);
20129
+ if (backgroundColor) {
20130
+ ctx.fillStyle = backgroundColor;
20131
+ ctx.fillRect(0, 0, width, height);
20132
+ }
20133
+ for (const shape of shapes) {
20134
+ drawShape(ctx, shape, width, height);
20135
+ }
20136
+ }, [width, height, backgroundColor, shapes]);
20137
+ React107.useEffect(() => {
20138
+ draw();
20139
+ }, [draw]);
20140
+ React107.useEffect(() => {
20141
+ if (!animate) return;
20142
+ const loop = () => {
20143
+ draw();
20144
+ animRef.current = requestAnimationFrame(loop);
20145
+ };
20146
+ animRef.current = requestAnimationFrame(loop);
20147
+ return () => cancelAnimationFrame(animRef.current);
20148
+ }, [animate, draw]);
20149
+ const handlePointerMove = React107.useCallback(
20150
+ (e) => {
20151
+ if (!interactive) return;
20152
+ const idx = findShapeAt(e.clientX, e.clientY);
20153
+ if (idx !== hoverIndexRef.current) {
20154
+ hoverIndexRef.current = idx;
20155
+ if (idx >= 0) {
20156
+ const shape = shapes[idx];
20157
+ const payload = { id: shape.id, type: shape.type, index: idx };
20158
+ if (onShapeHover) onShapeHover(payload);
20159
+ else if (eventBus) eventBus.emit(`UI:SHAPE_HOVER`, payload);
20160
+ }
20161
+ }
20162
+ },
20163
+ [interactive, onShapeHover, eventBus, findShapeAt, shapes]
20164
+ );
20165
+ const handleClick = React107.useCallback(
20166
+ (e) => {
20167
+ if (!interactive) return;
20168
+ const idx = findShapeAt(e.clientX, e.clientY);
20169
+ if (idx >= 0) {
20170
+ const shape = shapes[idx];
20171
+ const payload = { id: shape.id, type: shape.type, index: idx };
20172
+ if (onShapeClick) onShapeClick(payload);
20173
+ else if (eventBus) eventBus.emit(`UI:SHAPE_CLICK`, payload);
20174
+ }
20175
+ },
20176
+ [interactive, onShapeClick, eventBus, findShapeAt, shapes]
20177
+ );
20178
+ if (isLoading || error) {
20179
+ return /* @__PURE__ */ jsxRuntime.jsx(
20180
+ "div",
20181
+ {
20182
+ className: cn(
20183
+ "flex items-center justify-center rounded border border-border bg-surface",
20184
+ className
20185
+ ),
20186
+ style: { width, height },
20187
+ children: error ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-destructive", children: error.message }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: "Loading canvas\u2026" })
20188
+ }
20189
+ );
20190
+ }
20191
+ return /* @__PURE__ */ jsxRuntime.jsx(
20192
+ "canvas",
20193
+ {
20194
+ ref: canvasRef,
20195
+ className: cn("block touch-none rounded border border-border", className),
20196
+ style: { width, height },
20197
+ onClick: handleClick,
20198
+ onPointerMove: handlePointerMove
20199
+ }
20200
+ );
20201
+ };
20202
+ }
20203
+ });
20112
20204
 
20113
20205
  // components/core/atoms/index.ts
20114
20206
  var init_atoms = __esm({
@@ -21977,91 +22069,6 @@ var init_ComponentPatterns = __esm({
21977
22069
  AlertPattern.displayName = "AlertPattern";
21978
22070
  }
21979
22071
  });
21980
- function parseValue(value) {
21981
- if (value === "" || value == null) return { num: 0, prefix: "", suffix: "", decimals: 0 };
21982
- const match = String(value).match(/^([^0-9]*)([0-9]+(?:\.[0-9]+)?)(.*)$/);
21983
- if (!match) {
21984
- return { num: 0, prefix: "", suffix: String(value), decimals: 0 };
21985
- }
21986
- const numStr = match[2];
21987
- const decimalIdx = numStr.indexOf(".");
21988
- const decimals = decimalIdx >= 0 ? numStr.length - decimalIdx - 1 : 0;
21989
- return {
21990
- prefix: match[1],
21991
- num: parseFloat(numStr),
21992
- suffix: match[3],
21993
- decimals
21994
- };
21995
- }
21996
- var AnimatedCounter;
21997
- var init_AnimatedCounter = __esm({
21998
- "components/core/molecules/AnimatedCounter.tsx"() {
21999
- "use client";
22000
- init_cn();
22001
- init_Box();
22002
- init_Typography();
22003
- AnimatedCounter = ({
22004
- value,
22005
- label,
22006
- duration = 1500,
22007
- className
22008
- }) => {
22009
- const ref = React107.useRef(null);
22010
- const [displayValue, setDisplayValue] = React107.useState("0");
22011
- const [hasAnimated, setHasAnimated] = React107.useState(false);
22012
- const animate = React107.useCallback(() => {
22013
- const { num: num2, prefix, suffix, decimals } = parseValue(value);
22014
- if (num2 === 0) {
22015
- setDisplayValue(String(value));
22016
- return;
22017
- }
22018
- const startTime = performance.now();
22019
- const tick = (now) => {
22020
- const elapsed = now - startTime;
22021
- const progress = Math.min(elapsed / duration, 1);
22022
- const eased = 1 - Math.pow(1 - progress, 3);
22023
- const current = eased * num2;
22024
- setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
22025
- if (progress < 1) {
22026
- requestAnimationFrame(tick);
22027
- } else {
22028
- setDisplayValue(String(value));
22029
- }
22030
- };
22031
- requestAnimationFrame(tick);
22032
- }, [value, duration]);
22033
- React107.useEffect(() => {
22034
- if (hasAnimated) return;
22035
- const el = ref.current;
22036
- if (!el) return;
22037
- const observer2 = new IntersectionObserver(
22038
- (entries) => {
22039
- if (entries[0].isIntersecting) {
22040
- setHasAnimated(true);
22041
- animate();
22042
- observer2.disconnect();
22043
- }
22044
- },
22045
- { threshold: 0.3 }
22046
- );
22047
- observer2.observe(el);
22048
- return () => observer2.disconnect();
22049
- }, [hasAnimated, animate]);
22050
- return /* @__PURE__ */ jsxRuntime.jsxs(Box, { ref, className: cn("flex flex-col items-center gap-1 p-4", className), children: [
22051
- /* @__PURE__ */ jsxRuntime.jsx(
22052
- Typography,
22053
- {
22054
- variant: "h2",
22055
- className: "text-primary font-bold tabular-nums",
22056
- children: hasAnimated ? displayValue : "0"
22057
- }
22058
- ),
22059
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", color: "muted", className: "text-center", children: label })
22060
- ] });
22061
- };
22062
- AnimatedCounter.displayName = "AnimatedCounter";
22063
- }
22064
- });
22065
22072
  var AuthLayout;
22066
22073
  var init_AuthLayout = __esm({
22067
22074
  "components/marketing/templates/AuthLayout.tsx"() {
@@ -23030,10 +23037,107 @@ var init_BehaviorView = __esm({
23030
23037
  BehaviorView.displayName = "BehaviorView";
23031
23038
  }
23032
23039
  });
23040
+ var BiologyCanvas;
23041
+ var init_BiologyCanvas = __esm({
23042
+ "components/learning/molecules/BiologyCanvas.tsx"() {
23043
+ "use client";
23044
+ init_atoms();
23045
+ init_Stack();
23046
+ init_LearningCanvas();
23047
+ BiologyCanvas = ({
23048
+ className,
23049
+ width = 600,
23050
+ height = 400,
23051
+ title,
23052
+ backgroundColor,
23053
+ nodes = [],
23054
+ edges = [],
23055
+ shapes = [],
23056
+ interactive = false,
23057
+ animate = false,
23058
+ onShapeClick,
23059
+ isLoading,
23060
+ error
23061
+ }) => {
23062
+ const derivedShapes = React107.useMemo(() => {
23063
+ const out = [];
23064
+ const nodeById = /* @__PURE__ */ new Map();
23065
+ for (const n of nodes) {
23066
+ if (n.id) nodeById.set(n.id, n);
23067
+ }
23068
+ for (const e of edges) {
23069
+ const a = nodeById.get(e.from);
23070
+ const b = nodeById.get(e.to);
23071
+ if (!a || !b) continue;
23072
+ out.push({
23073
+ type: "line",
23074
+ x1: a.x,
23075
+ y1: a.y,
23076
+ x2: b.x,
23077
+ y2: b.y,
23078
+ color: e.color ?? "#9ca3af",
23079
+ lineWidth: 2
23080
+ });
23081
+ if (e.label) {
23082
+ out.push({
23083
+ type: "text",
23084
+ x: (a.x + b.x) / 2 + 4,
23085
+ y: (a.y + b.y) / 2 - 4,
23086
+ text: e.label,
23087
+ color: "#374151",
23088
+ fontSize: 11
23089
+ });
23090
+ }
23091
+ }
23092
+ for (const n of nodes) {
23093
+ out.push({
23094
+ type: "circle",
23095
+ x: n.x,
23096
+ y: n.y,
23097
+ radius: n.radius ?? 16,
23098
+ color: n.color ?? "#16a34a",
23099
+ fill: `${n.color ?? "#16a34a"}33`,
23100
+ id: n.id
23101
+ });
23102
+ if (n.label) {
23103
+ out.push({
23104
+ type: "text",
23105
+ x: n.x,
23106
+ y: n.y + (n.radius ?? 16) + 14,
23107
+ text: n.label,
23108
+ color: "#111827",
23109
+ fontSize: 12,
23110
+ align: "center"
23111
+ });
23112
+ }
23113
+ }
23114
+ out.push(...shapes);
23115
+ return out;
23116
+ }, [nodes, edges, shapes]);
23117
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
23118
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
23119
+ /* @__PURE__ */ jsxRuntime.jsx(
23120
+ LearningCanvas,
23121
+ {
23122
+ width,
23123
+ height,
23124
+ backgroundColor,
23125
+ shapes: derivedShapes,
23126
+ interactive,
23127
+ animate,
23128
+ onShapeClick,
23129
+ isLoading,
23130
+ error
23131
+ }
23132
+ )
23133
+ ] }) });
23134
+ };
23135
+ }
23136
+ });
23033
23137
 
23034
- // node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css
23138
+ // node_modules/katex/dist/katex.min.css
23035
23139
  var init_katex_min = __esm({
23036
- "node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css"() {
23140
+ "node_modules/katex/dist/katex.min.css"() {
23037
23141
  }
23038
23142
  });
23039
23143
  var Tabs;
@@ -27426,129 +27530,6 @@ var init_Breadcrumb = __esm({
27426
27530
  Breadcrumb.displayName = "Breadcrumb";
27427
27531
  }
27428
27532
  });
27429
- function useSafeEventBus2() {
27430
- try {
27431
- return useEventBus();
27432
- } catch {
27433
- return { emit: () => {
27434
- }, on: () => () => {
27435
- }, once: () => {
27436
- } };
27437
- }
27438
- }
27439
- var log8, lookStyles4, ButtonGroup;
27440
- var init_ButtonGroup = __esm({
27441
- "components/core/molecules/ButtonGroup.tsx"() {
27442
- "use client";
27443
- init_cn();
27444
- init_atoms();
27445
- init_useEventBus();
27446
- log8 = logger.createLogger("almadar:ui:button-group");
27447
- lookStyles4 = {
27448
- "right-aligned-buttons": "",
27449
- "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
27450
- "inline-row": "gap-2 inline-flex",
27451
- "dropdown-menu": "[&>button:not(:first-child)]:hidden",
27452
- "command-palette-trigger": "[&>button:not(:first-child)]:hidden"
27453
- };
27454
- ButtonGroup = ({
27455
- children,
27456
- primary,
27457
- secondary,
27458
- variant = "default",
27459
- orientation = "horizontal",
27460
- className,
27461
- // Filter-group pattern props (entity and filters are used for schema-driven filtering)
27462
- entity: _entity,
27463
- filters,
27464
- look = "right-aligned-buttons"
27465
- }) => {
27466
- const eventBus = useSafeEventBus2();
27467
- const variantClasses2 = {
27468
- default: "gap-0",
27469
- segmented: "gap-0 [&>button]:rounded-none [&>button:first-child]:rounded-l-lg [&>button:last-child]:rounded-r-lg [&>button:not(:first-child)]:border-l-0",
27470
- toggle: "gap-0 [&>button]:rounded-none [&>button:first-child]:rounded-l-lg [&>button:last-child]:rounded-r-lg [&>button:not(:first-child)]:border-l-0"
27471
- };
27472
- const orientationClasses = {
27473
- horizontal: "flex-row",
27474
- vertical: "flex-col [&>button:first-child]:rounded-t-lg [&>button:last-child]:rounded-b-lg [&>button:not(:first-child)]:border-t-0 [&>button:not(:first-child)]:border-l"
27475
- };
27476
- const handleActionClick = (action) => {
27477
- if (action.event) {
27478
- eventBus.emit("UI:DISPATCH", { event: action.event });
27479
- }
27480
- if (action.navigatesTo) {
27481
- eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
27482
- }
27483
- };
27484
- const renderFormActions = () => {
27485
- const buttons = [];
27486
- if (secondary) {
27487
- secondary.forEach((action, index) => {
27488
- buttons.push(
27489
- /* @__PURE__ */ jsxRuntime.jsx(
27490
- Button,
27491
- {
27492
- type: action.actionType === "submit" ? "submit" : "button",
27493
- variant: action.variant || "ghost",
27494
- onClick: () => handleActionClick(action),
27495
- children: action.label
27496
- },
27497
- `secondary-${index}`
27498
- )
27499
- );
27500
- });
27501
- }
27502
- if (primary) {
27503
- const isSubmit = primary.actionType === "submit";
27504
- buttons.push(
27505
- /* @__PURE__ */ jsxRuntime.jsx(
27506
- Button,
27507
- {
27508
- type: isSubmit ? "submit" : "button",
27509
- variant: primary.variant || "primary",
27510
- onClick: () => handleActionClick(primary),
27511
- "data-testid": isSubmit ? "form-submit" : void 0,
27512
- children: primary.label
27513
- },
27514
- "primary"
27515
- )
27516
- );
27517
- }
27518
- return buttons;
27519
- };
27520
- const renderFilters = () => {
27521
- if (!filters || filters.length === 0) return null;
27522
- return filters.map((filter, index) => /* @__PURE__ */ jsxRuntime.jsx(
27523
- Button,
27524
- {
27525
- variant: "ghost",
27526
- onClick: () => {
27527
- log8.debug("Filter clicked", { field: filter.field });
27528
- },
27529
- children: filter.label
27530
- },
27531
- `filter-${filter.field}-${index}`
27532
- ));
27533
- };
27534
- return /* @__PURE__ */ jsxRuntime.jsx(
27535
- "div",
27536
- {
27537
- className: cn(
27538
- "inline-flex gap-2",
27539
- variantClasses2[variant],
27540
- orientationClasses[orientation],
27541
- lookStyles4[look],
27542
- className
27543
- ),
27544
- role: "group",
27545
- children: children || renderFilters() || renderFormActions()
27546
- }
27547
- );
27548
- };
27549
- ButtonGroup.displayName = "ButtonGroup";
27550
- }
27551
- });
27552
27533
  function useSwipeGesture(callbacks, options = {}) {
27553
27534
  const { threshold = 50, velocityThreshold = 0.3, preventDefault = false } = options;
27554
27535
  const startX = React107.useRef(0);
@@ -28358,7 +28339,7 @@ var init_CardGrid = __esm({
28358
28339
  CardGrid.displayName = "CardGrid";
28359
28340
  }
28360
28341
  });
28361
- function useSafeEventBus3() {
28342
+ function useSafeEventBus2() {
28362
28343
  try {
28363
28344
  return useEventBus();
28364
28345
  } catch {
@@ -28394,7 +28375,7 @@ var init_Carousel = __esm({
28394
28375
  const [activeIndex, setActiveIndex] = React107.useState(0);
28395
28376
  const scrollRef = React107.useRef(null);
28396
28377
  const autoPlayRef = React107.useRef(null);
28397
- const eventBus = useSafeEventBus3();
28378
+ const eventBus = useSafeEventBus2();
28398
28379
  const { t } = hooks.useTranslate();
28399
28380
  const safeItems = items ?? [];
28400
28381
  const totalSlides = safeItems.length;
@@ -29642,6 +29623,122 @@ var init_ChatBar = __esm({
29642
29623
  ChatBar.displayName = "ChatBar";
29643
29624
  }
29644
29625
  });
29626
+ var ChemistryCanvas;
29627
+ var init_ChemistryCanvas = __esm({
29628
+ "components/learning/molecules/ChemistryCanvas.tsx"() {
29629
+ "use client";
29630
+ init_atoms();
29631
+ init_Stack();
29632
+ init_LearningCanvas();
29633
+ ChemistryCanvas = ({
29634
+ className,
29635
+ width = 600,
29636
+ height = 400,
29637
+ title,
29638
+ backgroundColor,
29639
+ atoms = [],
29640
+ bonds = [],
29641
+ arrows = [],
29642
+ shapes = [],
29643
+ interactive = false,
29644
+ animate = false,
29645
+ onShapeClick,
29646
+ isLoading,
29647
+ error
29648
+ }) => {
29649
+ const derivedShapes = React107.useMemo(() => {
29650
+ const out = [];
29651
+ const atomById = /* @__PURE__ */ new Map();
29652
+ for (const a of atoms) {
29653
+ if (a.id) atomById.set(a.id, a);
29654
+ }
29655
+ for (const b of bonds) {
29656
+ const a = atomById.get(b.from);
29657
+ const c = atomById.get(b.to);
29658
+ if (!a || !c) continue;
29659
+ const color = b.color ?? "#6b7280";
29660
+ const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
29661
+ out.push({
29662
+ type: "line",
29663
+ x1: a.x,
29664
+ y1: a.y,
29665
+ x2: c.x,
29666
+ y2: c.y,
29667
+ color,
29668
+ lineWidth: strokeWidth
29669
+ });
29670
+ }
29671
+ for (const a of arrows) {
29672
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
29673
+ const len = a.length ?? 60;
29674
+ const x2 = a.x + Math.cos(angle) * len;
29675
+ const y2 = a.y + Math.sin(angle) * len;
29676
+ out.push({
29677
+ type: "arrow",
29678
+ x1: a.x,
29679
+ y1: a.y,
29680
+ x2,
29681
+ y2,
29682
+ color: a.color ?? "#dc2626",
29683
+ lineWidth: 2
29684
+ });
29685
+ if (a.label) {
29686
+ out.push({
29687
+ type: "text",
29688
+ x: (a.x + x2) / 2,
29689
+ y: (a.y + y2) / 2 - 10,
29690
+ text: a.label,
29691
+ color: "#111827",
29692
+ fontSize: 12,
29693
+ align: "center"
29694
+ });
29695
+ }
29696
+ }
29697
+ for (const a of atoms) {
29698
+ out.push({
29699
+ type: "circle",
29700
+ x: a.x,
29701
+ y: a.y,
29702
+ radius: a.radius ?? 14,
29703
+ color: a.color ?? "#2563eb",
29704
+ fill: a.color ?? "#2563eb",
29705
+ id: a.id
29706
+ });
29707
+ if (a.element) {
29708
+ out.push({
29709
+ type: "text",
29710
+ x: a.x,
29711
+ y: a.y,
29712
+ text: a.element,
29713
+ color: "#ffffff",
29714
+ fontSize: 12,
29715
+ align: "center"
29716
+ });
29717
+ }
29718
+ }
29719
+ out.push(...shapes);
29720
+ return out;
29721
+ }, [atoms, bonds, arrows, shapes]);
29722
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
29723
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
29724
+ /* @__PURE__ */ jsxRuntime.jsx(
29725
+ LearningCanvas,
29726
+ {
29727
+ width,
29728
+ height,
29729
+ backgroundColor,
29730
+ shapes: derivedShapes,
29731
+ interactive,
29732
+ animate,
29733
+ onShapeClick,
29734
+ isLoading,
29735
+ error
29736
+ }
29737
+ )
29738
+ ] }) });
29739
+ };
29740
+ }
29741
+ });
29645
29742
  var CodeRunnerPanel;
29646
29743
  var init_CodeRunnerPanel = __esm({
29647
29744
  "components/core/organisms/CodeRunnerPanel.tsx"() {
@@ -31730,7 +31827,7 @@ function DataGrid({
31730
31827
  /* @__PURE__ */ jsxRuntime.jsx(
31731
31828
  Box,
31732
31829
  {
31733
- className: cn("grid", gapStyles5[gap], scrollX ? "grid-flow-col overflow-x-auto" : colsClass, lookStyles5[look], className),
31830
+ className: cn("grid", gapStyles5[gap], scrollX ? "grid-flow-col overflow-x-auto" : colsClass, lookStyles4[look], className),
31734
31831
  style: scrollX ? { gridAutoFlow: "column", gridAutoColumns: `minmax(${minCardWidth}px, 1fr)` } : gridTemplateColumns ? { gridTemplateColumns } : void 0,
31735
31832
  children: data.map((item, index) => {
31736
31833
  const itemData = item;
@@ -31925,7 +32022,7 @@ function DataGrid({
31925
32022
  ] })
31926
32023
  );
31927
32024
  }
31928
- var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles5;
32025
+ var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles4;
31929
32026
  var init_DataGrid = __esm({
31930
32027
  "components/core/molecules/DataGrid.tsx"() {
31931
32028
  "use client";
@@ -31960,7 +32057,7 @@ var init_DataGrid = __esm({
31960
32057
  lg: "gap-6",
31961
32058
  xl: "gap-8"
31962
32059
  };
31963
- lookStyles5 = {
32060
+ lookStyles4 = {
31964
32061
  dense: "gap-2 [&>*]:p-card-sm",
31965
32062
  spacious: "gap-8 [&>*]:p-card-lg",
31966
32063
  striped: "[&>*:nth-child(even)]:bg-muted/30",
@@ -32387,6 +32484,133 @@ var init_DataList = __esm({
32387
32484
  DataList.displayName = "DataList";
32388
32485
  }
32389
32486
  });
32487
+ var FormSection, FormLayout, FormActions;
32488
+ var init_FormSection = __esm({
32489
+ "components/core/molecules/FormSection.tsx"() {
32490
+ "use client";
32491
+ init_cn();
32492
+ init_atoms();
32493
+ init_Box();
32494
+ init_Typography();
32495
+ init_Button();
32496
+ init_Stack();
32497
+ init_Icon();
32498
+ init_useEventBus();
32499
+ FormSection = ({
32500
+ title,
32501
+ description,
32502
+ children,
32503
+ collapsible = false,
32504
+ defaultCollapsed = false,
32505
+ card = false,
32506
+ columns = 1,
32507
+ className
32508
+ }) => {
32509
+ const [collapsed, setCollapsed] = React107__namespace.default.useState(defaultCollapsed);
32510
+ const { t } = hooks.useTranslate();
32511
+ const eventBus = useEventBus();
32512
+ const gridClass = {
32513
+ 1: "grid-cols-1",
32514
+ 2: "grid-cols-1 md:grid-cols-2",
32515
+ 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
32516
+ }[columns];
32517
+ React107__namespace.default.useCallback(() => {
32518
+ if (collapsible) {
32519
+ setCollapsed((prev) => !prev);
32520
+ eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
32521
+ }
32522
+ }, [collapsible, collapsed, eventBus]);
32523
+ const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
32524
+ (title || description) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "mb-4", children: [
32525
+ title && /* @__PURE__ */ jsxRuntime.jsxs(
32526
+ HStack,
32527
+ {
32528
+ justify: "between",
32529
+ align: "center",
32530
+ className: cn(collapsible && "cursor-pointer"),
32531
+ action: collapsible ? "TOGGLE_COLLAPSE" : void 0,
32532
+ children: [
32533
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h3", weight: "semibold", children: title }),
32534
+ collapsible && /* @__PURE__ */ jsxRuntime.jsx(
32535
+ Button,
32536
+ {
32537
+ variant: "ghost",
32538
+ size: "sm",
32539
+ action: "TOGGLE_COLLAPSE",
32540
+ children: /* @__PURE__ */ jsxRuntime.jsx(
32541
+ Icon,
32542
+ {
32543
+ icon: LucideIcons2.ChevronDown,
32544
+ size: "sm",
32545
+ className: cn(
32546
+ "text-muted-foreground transition-transform",
32547
+ collapsed && "rotate-180"
32548
+ )
32549
+ }
32550
+ )
32551
+ }
32552
+ )
32553
+ ]
32554
+ }
32555
+ ),
32556
+ description && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: description })
32557
+ ] }),
32558
+ (!collapsible || !collapsed) && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: cn("grid gap-4", gridClass), children })
32559
+ ] });
32560
+ if (card) {
32561
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: cn("p-6", className), children: content });
32562
+ }
32563
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className, children: content });
32564
+ };
32565
+ FormSection.displayName = "FormSection";
32566
+ FormLayout = ({
32567
+ children,
32568
+ dividers = true,
32569
+ className
32570
+ }) => {
32571
+ return /* @__PURE__ */ jsxRuntime.jsx(
32572
+ VStack,
32573
+ {
32574
+ gap: "lg",
32575
+ className: cn(
32576
+ dividers && "[&>*+*]:pt-8 [&>*+*]:border-t [&>*+*]:border-border",
32577
+ className
32578
+ ),
32579
+ children
32580
+ }
32581
+ );
32582
+ };
32583
+ FormLayout.displayName = "FormLayout";
32584
+ FormActions = ({
32585
+ children,
32586
+ sticky = false,
32587
+ align = "right",
32588
+ className
32589
+ }) => {
32590
+ const alignClass2 = {
32591
+ left: "justify-start",
32592
+ right: "justify-end",
32593
+ between: "justify-between",
32594
+ center: "justify-center"
32595
+ }[align];
32596
+ return /* @__PURE__ */ jsxRuntime.jsx(
32597
+ HStack,
32598
+ {
32599
+ gap: "sm",
32600
+ align: "center",
32601
+ className: cn(
32602
+ "pt-6 border-t border-border",
32603
+ alignClass2,
32604
+ sticky && "sticky bottom-0 bg-card py-4 -mx-6 px-6 shadow-[0_-4px_6px_-1px_rgb(0,0,0,0.05)]",
32605
+ className
32606
+ ),
32607
+ children
32608
+ }
32609
+ );
32610
+ };
32611
+ FormActions.displayName = "FormActions";
32612
+ }
32613
+ });
32390
32614
  function fileIcon(name) {
32391
32615
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
32392
32616
  switch (ext) {
@@ -32548,6 +32772,129 @@ var init_FormField = __esm({
32548
32772
  FormField.displayName = "FormField";
32549
32773
  }
32550
32774
  });
32775
+ function useSafeEventBus3() {
32776
+ try {
32777
+ return useEventBus();
32778
+ } catch {
32779
+ return { emit: () => {
32780
+ }, on: () => () => {
32781
+ }, once: () => {
32782
+ } };
32783
+ }
32784
+ }
32785
+ var log8, lookStyles5, ButtonGroup;
32786
+ var init_ButtonGroup = __esm({
32787
+ "components/core/molecules/ButtonGroup.tsx"() {
32788
+ "use client";
32789
+ init_cn();
32790
+ init_atoms();
32791
+ init_useEventBus();
32792
+ log8 = logger.createLogger("almadar:ui:button-group");
32793
+ lookStyles5 = {
32794
+ "right-aligned-buttons": "",
32795
+ "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
32796
+ "inline-row": "gap-2 inline-flex",
32797
+ "dropdown-menu": "[&>button:not(:first-child)]:hidden",
32798
+ "command-palette-trigger": "[&>button:not(:first-child)]:hidden"
32799
+ };
32800
+ ButtonGroup = ({
32801
+ children,
32802
+ primary,
32803
+ secondary,
32804
+ variant = "default",
32805
+ orientation = "horizontal",
32806
+ className,
32807
+ // Filter-group pattern props (entity and filters are used for schema-driven filtering)
32808
+ entity: _entity,
32809
+ filters,
32810
+ look = "right-aligned-buttons"
32811
+ }) => {
32812
+ const eventBus = useSafeEventBus3();
32813
+ const variantClasses2 = {
32814
+ default: "gap-0",
32815
+ segmented: "gap-0 [&>button]:rounded-none [&>button:first-child]:rounded-l-lg [&>button:last-child]:rounded-r-lg [&>button:not(:first-child)]:border-l-0",
32816
+ toggle: "gap-0 [&>button]:rounded-none [&>button:first-child]:rounded-l-lg [&>button:last-child]:rounded-r-lg [&>button:not(:first-child)]:border-l-0"
32817
+ };
32818
+ const orientationClasses = {
32819
+ horizontal: "flex-row",
32820
+ vertical: "flex-col [&>button:first-child]:rounded-t-lg [&>button:last-child]:rounded-b-lg [&>button:not(:first-child)]:border-t-0 [&>button:not(:first-child)]:border-l"
32821
+ };
32822
+ const handleActionClick = (action) => {
32823
+ if (action.event) {
32824
+ eventBus.emit("UI:DISPATCH", { event: action.event });
32825
+ }
32826
+ if (action.navigatesTo) {
32827
+ eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
32828
+ }
32829
+ };
32830
+ const renderFormActions = () => {
32831
+ const buttons = [];
32832
+ if (secondary) {
32833
+ secondary.forEach((action, index) => {
32834
+ buttons.push(
32835
+ /* @__PURE__ */ jsxRuntime.jsx(
32836
+ Button,
32837
+ {
32838
+ type: action.actionType === "submit" ? "submit" : "button",
32839
+ variant: action.variant || "ghost",
32840
+ onClick: () => handleActionClick(action),
32841
+ children: action.label
32842
+ },
32843
+ `secondary-${index}`
32844
+ )
32845
+ );
32846
+ });
32847
+ }
32848
+ if (primary) {
32849
+ const isSubmit = primary.actionType === "submit";
32850
+ buttons.push(
32851
+ /* @__PURE__ */ jsxRuntime.jsx(
32852
+ Button,
32853
+ {
32854
+ type: isSubmit ? "submit" : "button",
32855
+ variant: primary.variant || "primary",
32856
+ onClick: () => handleActionClick(primary),
32857
+ "data-testid": isSubmit ? "form-submit" : void 0,
32858
+ children: primary.label
32859
+ },
32860
+ "primary"
32861
+ )
32862
+ );
32863
+ }
32864
+ return buttons;
32865
+ };
32866
+ const renderFilters = () => {
32867
+ if (!filters || filters.length === 0) return null;
32868
+ return filters.map((filter, index) => /* @__PURE__ */ jsxRuntime.jsx(
32869
+ Button,
32870
+ {
32871
+ variant: "ghost",
32872
+ onClick: () => {
32873
+ log8.debug("Filter clicked", { field: filter.field });
32874
+ },
32875
+ children: filter.label
32876
+ },
32877
+ `filter-${filter.field}-${index}`
32878
+ ));
32879
+ };
32880
+ return /* @__PURE__ */ jsxRuntime.jsx(
32881
+ "div",
32882
+ {
32883
+ className: cn(
32884
+ "inline-flex gap-2",
32885
+ variantClasses2[variant],
32886
+ orientationClasses[orientation],
32887
+ lookStyles5[look],
32888
+ className
32889
+ ),
32890
+ role: "group",
32891
+ children: children || renderFilters() || renderFormActions()
32892
+ }
32893
+ );
32894
+ };
32895
+ ButtonGroup.displayName = "ButtonGroup";
32896
+ }
32897
+ });
32551
32898
  function getOrCreateStore(query) {
32552
32899
  if (!queryStores.has(query)) {
32553
32900
  queryStores.set(query, {
@@ -35089,6 +35436,234 @@ var init_ProgressDots = __esm({
35089
35436
  ProgressDots.displayName = "ProgressDots";
35090
35437
  }
35091
35438
  });
35439
+ var MathCanvas;
35440
+ var init_MathCanvas = __esm({
35441
+ "components/learning/molecules/MathCanvas.tsx"() {
35442
+ "use client";
35443
+ init_atoms();
35444
+ init_Stack();
35445
+ init_LearningCanvas();
35446
+ MathCanvas = ({
35447
+ className,
35448
+ width = 600,
35449
+ height = 400,
35450
+ title,
35451
+ xMin = -10,
35452
+ xMax = 10,
35453
+ yMin = -10,
35454
+ yMax = 10,
35455
+ showAxes = true,
35456
+ showGrid = true,
35457
+ gridStep = 1,
35458
+ curves = [],
35459
+ points = [],
35460
+ vectors = [],
35461
+ shapes = [],
35462
+ interactive = false,
35463
+ animate = false,
35464
+ onShapeClick,
35465
+ isLoading,
35466
+ error
35467
+ }) => {
35468
+ const derivedShapes = React107.useMemo(() => {
35469
+ const out = [];
35470
+ const margin = 24;
35471
+ const plotW = width - margin * 2;
35472
+ const plotH = height - margin * 2;
35473
+ const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
35474
+ const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
35475
+ if (showGrid) {
35476
+ for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
35477
+ const px = mapX(x);
35478
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#e5e7eb", lineWidth: 1 });
35479
+ }
35480
+ for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
35481
+ const py = mapY(y);
35482
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
35483
+ }
35484
+ }
35485
+ if (showAxes) {
35486
+ const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
35487
+ const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
35488
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
35489
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
35490
+ }
35491
+ for (const curve of curves) {
35492
+ if (!curve.samples || curve.samples.length < 2) continue;
35493
+ for (let i = 1; i < curve.samples.length; i++) {
35494
+ const a = curve.samples[i - 1];
35495
+ const b = curve.samples[i];
35496
+ if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
35497
+ out.push({
35498
+ type: "line",
35499
+ x1: mapX(a.x),
35500
+ y1: mapY(a.y),
35501
+ x2: mapX(b.x),
35502
+ y2: mapY(b.y),
35503
+ color: curve.color ?? "#2563eb",
35504
+ lineWidth: 2
35505
+ });
35506
+ }
35507
+ }
35508
+ for (const p2 of points) {
35509
+ if (p2.x < xMin || p2.x > xMax || p2.y < yMin || p2.y > yMax) continue;
35510
+ out.push({
35511
+ type: "circle",
35512
+ x: mapX(p2.x),
35513
+ y: mapY(p2.y),
35514
+ radius: p2.radius ?? 4,
35515
+ color: p2.color ?? "#dc2626",
35516
+ fill: p2.color ?? "#dc2626"
35517
+ });
35518
+ if (p2.label) {
35519
+ out.push({ type: "text", x: mapX(p2.x) + 8, y: mapY(p2.y) - 8, text: p2.label, color: "#111827", fontSize: 12 });
35520
+ }
35521
+ }
35522
+ for (const v of vectors) {
35523
+ if (v.x < xMin || v.x > xMax || v.y < yMin || v.y > yMax) continue;
35524
+ const x1 = mapX(v.x);
35525
+ const y1 = mapY(v.y);
35526
+ const x2 = mapX(v.x + v.vx);
35527
+ const y2 = mapY(v.y + v.vy);
35528
+ out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
35529
+ if (v.label) {
35530
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
35531
+ }
35532
+ }
35533
+ out.push(...shapes);
35534
+ return out;
35535
+ }, [width, height, xMin, xMax, yMin, yMax, showAxes, showGrid, gridStep, curves, points, vectors, shapes]);
35536
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
35537
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
35538
+ /* @__PURE__ */ jsxRuntime.jsx(
35539
+ LearningCanvas,
35540
+ {
35541
+ width,
35542
+ height,
35543
+ shapes: derivedShapes,
35544
+ interactive,
35545
+ animate,
35546
+ onShapeClick,
35547
+ isLoading,
35548
+ error
35549
+ }
35550
+ )
35551
+ ] }) });
35552
+ };
35553
+ }
35554
+ });
35555
+ var PhysicsCanvas;
35556
+ var init_PhysicsCanvas = __esm({
35557
+ "components/learning/molecules/PhysicsCanvas.tsx"() {
35558
+ "use client";
35559
+ init_atoms();
35560
+ init_Stack();
35561
+ init_LearningCanvas();
35562
+ PhysicsCanvas = ({
35563
+ className,
35564
+ width = 600,
35565
+ height = 400,
35566
+ title,
35567
+ backgroundColor,
35568
+ bodies = [],
35569
+ constraints = [],
35570
+ showVelocity = true,
35571
+ showForces = false,
35572
+ velocityScale = 20,
35573
+ forceScale = 20,
35574
+ shapes = [],
35575
+ interactive = false,
35576
+ animate = false,
35577
+ onShapeClick,
35578
+ isLoading,
35579
+ error
35580
+ }) => {
35581
+ const derivedShapes = React107.useMemo(() => {
35582
+ const out = [];
35583
+ const bodyById = /* @__PURE__ */ new Map();
35584
+ for (const b of bodies) {
35585
+ if (b.id) bodyById.set(b.id, b);
35586
+ }
35587
+ for (const c of constraints) {
35588
+ const a = bodyById.get(c.from);
35589
+ const b = bodyById.get(c.to);
35590
+ if (!a || !b) continue;
35591
+ out.push({
35592
+ type: "line",
35593
+ x1: a.x,
35594
+ y1: a.y,
35595
+ x2: b.x,
35596
+ y2: b.y,
35597
+ color: c.color ?? "#9ca3af",
35598
+ lineWidth: 2
35599
+ });
35600
+ }
35601
+ for (const b of bodies) {
35602
+ out.push({
35603
+ type: "circle",
35604
+ x: b.x,
35605
+ y: b.y,
35606
+ radius: b.radius ?? 12,
35607
+ color: b.color ?? "#2563eb",
35608
+ fill: b.color ?? "#2563eb",
35609
+ id: b.id
35610
+ });
35611
+ if (b.label) {
35612
+ out.push({
35613
+ type: "text",
35614
+ x: b.x + (b.radius ?? 12) + 6,
35615
+ y: b.y - (b.radius ?? 12) - 6,
35616
+ text: b.label,
35617
+ color: "#111827",
35618
+ fontSize: 12
35619
+ });
35620
+ }
35621
+ if (showVelocity && b.vx != null && b.vy != null && (b.vx !== 0 || b.vy !== 0)) {
35622
+ out.push({
35623
+ type: "arrow",
35624
+ x1: b.x,
35625
+ y1: b.y,
35626
+ x2: b.x + b.vx * velocityScale,
35627
+ y2: b.y + b.vy * velocityScale,
35628
+ color: "#16a34a",
35629
+ lineWidth: 2
35630
+ });
35631
+ }
35632
+ if (showForces && b.fx != null && b.fy != null && (b.fx !== 0 || b.fy !== 0)) {
35633
+ out.push({
35634
+ type: "arrow",
35635
+ x1: b.x,
35636
+ y1: b.y,
35637
+ x2: b.x + b.fx * forceScale,
35638
+ y2: b.y + b.fy * forceScale,
35639
+ color: "#dc2626",
35640
+ lineWidth: 2
35641
+ });
35642
+ }
35643
+ }
35644
+ out.push(...shapes);
35645
+ return out;
35646
+ }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
35647
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
35648
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
35649
+ /* @__PURE__ */ jsxRuntime.jsx(
35650
+ LearningCanvas,
35651
+ {
35652
+ width,
35653
+ height,
35654
+ backgroundColor,
35655
+ shapes: derivedShapes,
35656
+ interactive,
35657
+ animate,
35658
+ onShapeClick,
35659
+ isLoading,
35660
+ error
35661
+ }
35662
+ )
35663
+ ] }) });
35664
+ };
35665
+ }
35666
+ });
35092
35667
  function resolveNodeColor(node, groups) {
35093
35668
  if (node.color) return node.color;
35094
35669
  if (node.group) {
@@ -35460,13 +36035,13 @@ var init_MapView = __esm({
35460
36035
  shadowSize: [41, 41]
35461
36036
  });
35462
36037
  L.Marker.prototype.options.icon = defaultIcon;
35463
- const { useEffect: useEffect75, useRef: useRef76, useCallback: useCallback117, useState: useState108 } = React107__namespace.default;
36038
+ const { useEffect: useEffect76, useRef: useRef77, useCallback: useCallback117, useState: useState108 } = React107__namespace.default;
35464
36039
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
35465
36040
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
35466
36041
  function MapUpdater({ centerLat, centerLng, zoom }) {
35467
36042
  const map = useMap();
35468
- const prevRef = useRef76({ centerLat, centerLng, zoom });
35469
- useEffect75(() => {
36043
+ const prevRef = useRef77({ centerLat, centerLng, zoom });
36044
+ useEffect76(() => {
35470
36045
  const prev = prevRef.current;
35471
36046
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
35472
36047
  map.setView([centerLat, centerLng], zoom);
@@ -35477,7 +36052,7 @@ var init_MapView = __esm({
35477
36052
  }
35478
36053
  function MapClickHandler({ onMapClick }) {
35479
36054
  const map = useMap();
35480
- useEffect75(() => {
36055
+ useEffect76(() => {
35481
36056
  if (!onMapClick) return;
35482
36057
  const handler = (e) => {
35483
36058
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -43918,7 +44493,7 @@ function getGroupColor(group, groups) {
43918
44493
  const idx = groups.indexOf(group);
43919
44494
  return GROUP_COLORS2[idx % GROUP_COLORS2.length];
43920
44495
  }
43921
- function resolveColor2(color, el) {
44496
+ function resolveColor3(color, el) {
43922
44497
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
43923
44498
  if (!m) return color;
43924
44499
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
@@ -44179,7 +44754,7 @@ var init_GraphCanvas = __esm({
44179
44754
  const w = logicalW;
44180
44755
  const h = height;
44181
44756
  const nodes = nodesRef.current;
44182
- const accentColor = resolveColor2("var(--color-accent)", canvas);
44757
+ const accentColor = resolveColor3("var(--color-accent)", canvas);
44183
44758
  const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
44184
44759
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
44185
44760
  ctx.clearRect(0, 0, w, h);
@@ -44218,7 +44793,7 @@ var init_GraphCanvas = __esm({
44218
44793
  ctx.globalAlpha = 1;
44219
44794
  for (const node of nodes) {
44220
44795
  const size = node.size || 8;
44221
- const color = resolveColor2(node.color || getGroupColor(node.group, groups), canvas);
44796
+ const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
44222
44797
  const isHovered = hoveredNode === node.id;
44223
44798
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
44224
44799
  ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
@@ -51097,6 +51672,7 @@ var init_component_registry_generated = __esm({
51097
51672
  init_BattleBoard();
51098
51673
  init_BattleTemplate();
51099
51674
  init_BehaviorView();
51675
+ init_BiologyCanvas();
51100
51676
  init_BloomQuizBlock();
51101
51677
  init_BoardgameBoard();
51102
51678
  init_BookChapterView();
@@ -51108,7 +51684,6 @@ var init_component_registry_generated = __esm({
51108
51684
  init_BranchingLogicBuilder();
51109
51685
  init_Breadcrumb();
51110
51686
  init_BuilderBoard();
51111
- init_ButtonGroup();
51112
51687
  init_CTABanner();
51113
51688
  init_CalendarGrid();
51114
51689
  init_Canvas2D();
@@ -51127,6 +51702,7 @@ var init_component_registry_generated = __esm({
51127
51702
  init_ChartLegend();
51128
51703
  init_ChatBar();
51129
51704
  init_Checkbox();
51705
+ init_ChemistryCanvas();
51130
51706
  init_ChoiceButton();
51131
51707
  init_CityBuilderBoard();
51132
51708
  init_CityBuilderTemplate();
@@ -51185,9 +51761,9 @@ var init_component_registry_generated = __esm({
51185
51761
  init_FlipContainer();
51186
51762
  init_FloatingActionButton();
51187
51763
  init_Form();
51764
+ init_FormSection();
51188
51765
  init_FormField();
51189
51766
  init_FormSectionHeader();
51190
- init_GameAudioProvider();
51191
51767
  init_GameAudioToggle();
51192
51768
  init_GameCard();
51193
51769
  init_GameHud();
@@ -51218,6 +51794,7 @@ var init_component_registry_generated = __esm({
51218
51794
  init_JazariStateMachine();
51219
51795
  init_LandingPageTemplate();
51220
51796
  init_LawReferenceTooltip();
51797
+ init_LearningCanvas();
51221
51798
  init_Lightbox();
51222
51799
  init_LikertScale();
51223
51800
  init_LineChart();
@@ -51226,9 +51803,11 @@ var init_component_registry_generated = __esm({
51226
51803
  init_MapView();
51227
51804
  init_MarkdownContent();
51228
51805
  init_MarketingFooter();
51806
+ init_MarketingStatCard();
51229
51807
  init_MasterDetail();
51230
51808
  init_MasterDetailLayout();
51231
51809
  init_MatchPuzzleBoard();
51810
+ init_MathCanvas();
51232
51811
  init_MatrixQuestion();
51233
51812
  init_MediaGallery();
51234
51813
  init_Menu();
@@ -51247,6 +51826,7 @@ var init_component_registry_generated = __esm({
51247
51826
  init_PageHeader();
51248
51827
  init_Pagination();
51249
51828
  init_PatternTile();
51829
+ init_PhysicsCanvas();
51250
51830
  init_PinballBoard();
51251
51831
  init_PirateBoard();
51252
51832
  init_PlatformerBoard();
@@ -51423,6 +52003,7 @@ var init_component_registry_generated = __esm({
51423
52003
  "BattleBoard": BattleBoard,
51424
52004
  "BattleTemplate": BattleTemplate,
51425
52005
  "BehaviorView": BehaviorView,
52006
+ "BiologyCanvas": BiologyCanvas,
51426
52007
  "BloomQuizBlock": BloomQuizBlock,
51427
52008
  "BoardgameBoard": BoardgameBoard,
51428
52009
  "BookChapterView": BookChapterView,
@@ -51436,7 +52017,6 @@ var init_component_registry_generated = __esm({
51436
52017
  "Breadcrumb": Breadcrumb,
51437
52018
  "BuilderBoard": BuilderBoard,
51438
52019
  "Button": ButtonPattern,
51439
- "ButtonGroup": ButtonGroup,
51440
52020
  "ButtonPattern": ButtonPattern,
51441
52021
  "CTABanner": CTABanner,
51442
52022
  "CalendarGrid": CalendarGrid,
@@ -51456,6 +52036,7 @@ var init_component_registry_generated = __esm({
51456
52036
  "ChartLegend": ChartLegend,
51457
52037
  "ChatBar": ChatBar,
51458
52038
  "Checkbox": Checkbox,
52039
+ "ChemistryCanvas": ChemistryCanvas,
51459
52040
  "ChoiceButton": ChoiceButton,
51460
52041
  "CityBuilderBoard": CityBuilderBoard,
51461
52042
  "CityBuilderTemplate": CityBuilderTemplate,
@@ -51516,9 +52097,10 @@ var init_component_registry_generated = __esm({
51516
52097
  "FlipContainer": FlipContainer,
51517
52098
  "FloatingActionButton": FloatingActionButton,
51518
52099
  "Form": Form,
52100
+ "FormActions": FormActions,
51519
52101
  "FormField": FormField,
52102
+ "FormLayout": FormLayout,
51520
52103
  "FormSectionHeader": FormSectionHeader,
51521
- "GameAudioProvider": GameAudioProvider,
51522
52104
  "GameAudioToggle": GameAudioToggle,
51523
52105
  "GameBoard3D": GameBoard3D,
51524
52106
  "GameCanvas3D": GameCanvas3D,
@@ -51556,6 +52138,7 @@ var init_component_registry_generated = __esm({
51556
52138
  "LabelPattern": LabelPattern,
51557
52139
  "LandingPageTemplate": LandingPageTemplate,
51558
52140
  "LawReferenceTooltip": LawReferenceTooltip,
52141
+ "LearningCanvas": LearningCanvas,
51559
52142
  "Lightbox": Lightbox,
51560
52143
  "LikertScale": LikertScale,
51561
52144
  "LineChart": LineChart2,
@@ -51564,9 +52147,11 @@ var init_component_registry_generated = __esm({
51564
52147
  "MapView": MapView,
51565
52148
  "MarkdownContent": MarkdownContent,
51566
52149
  "MarketingFooter": MarketingFooter,
52150
+ "MarketingStatCard": MarketingStatCard,
51567
52151
  "MasterDetail": MasterDetail,
51568
52152
  "MasterDetailLayout": MasterDetailLayout,
51569
52153
  "MatchPuzzleBoard": MatchPuzzleBoard,
52154
+ "MathCanvas": MathCanvas,
51570
52155
  "MatrixQuestion": MatrixQuestion,
51571
52156
  "MediaGallery": MediaGallery,
51572
52157
  "Menu": Menu,
@@ -51585,6 +52170,7 @@ var init_component_registry_generated = __esm({
51585
52170
  "PageHeader": PageHeader,
51586
52171
  "Pagination": Pagination,
51587
52172
  "PatternTile": PatternTile,
52173
+ "PhysicsCanvas": PhysicsCanvas,
51588
52174
  "PinballBoard": PinballBoard,
51589
52175
  "PirateBoard": PirateBoard,
51590
52176
  "PlatformerBoard": PlatformerBoard,