@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
@@ -6225,6 +6225,66 @@ var init_RangeSlider = __esm({
6225
6225
  RangeSlider.displayName = "RangeSlider";
6226
6226
  }
6227
6227
  });
6228
+ function easeOut(t) {
6229
+ return t * (2 - t);
6230
+ }
6231
+ var AnimatedCounter;
6232
+ var init_AnimatedCounter = __esm({
6233
+ "components/marketing/atoms/AnimatedCounter.tsx"() {
6234
+ "use client";
6235
+ init_cn();
6236
+ init_Typography();
6237
+ AnimatedCounter = ({
6238
+ value: rawValue,
6239
+ duration = 600,
6240
+ prefix,
6241
+ suffix,
6242
+ className
6243
+ }) => {
6244
+ const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
6245
+ const value = !Number.isNaN(numericRaw) ? numericRaw : 0;
6246
+ const [displayValue, setDisplayValue] = React105.useState(value);
6247
+ const previousValueRef = React105.useRef(value);
6248
+ const animationFrameRef = React105.useRef(null);
6249
+ React105.useEffect(() => {
6250
+ const from = previousValueRef.current;
6251
+ const to = value;
6252
+ previousValueRef.current = value;
6253
+ if (from === to) {
6254
+ setDisplayValue(to);
6255
+ return;
6256
+ }
6257
+ const startTime = performance.now();
6258
+ const diff = to - from;
6259
+ function animate(currentTime) {
6260
+ const elapsed = currentTime - startTime;
6261
+ const progress = Math.min(elapsed / duration, 1);
6262
+ const easedProgress = easeOut(progress);
6263
+ setDisplayValue(from + diff * easedProgress);
6264
+ if (progress < 1) {
6265
+ animationFrameRef.current = requestAnimationFrame(animate);
6266
+ } else {
6267
+ setDisplayValue(to);
6268
+ }
6269
+ }
6270
+ animationFrameRef.current = requestAnimationFrame(animate);
6271
+ return () => {
6272
+ if (animationFrameRef.current !== null) {
6273
+ cancelAnimationFrame(animationFrameRef.current);
6274
+ }
6275
+ };
6276
+ }, [value, duration]);
6277
+ const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
6278
+ const formattedValue = displayValue.toFixed(decimalPlaces);
6279
+ return /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
6280
+ prefix,
6281
+ formattedValue,
6282
+ suffix
6283
+ ] });
6284
+ };
6285
+ AnimatedCounter.displayName = "AnimatedCounter";
6286
+ }
6287
+ });
6228
6288
  var InfiniteScrollSentinel;
6229
6289
  var init_InfiniteScrollSentinel = __esm({
6230
6290
  "components/core/atoms/InfiniteScrollSentinel.tsx"() {
@@ -6486,6 +6546,34 @@ var init_SectionHeader = __esm({
6486
6546
  SectionHeader.displayName = "SectionHeader";
6487
6547
  }
6488
6548
  });
6549
+ var sizeClasses6, MarketingStatCard;
6550
+ var init_MarketingStatCard = __esm({
6551
+ "components/marketing/atoms/MarketingStatCard.tsx"() {
6552
+ init_cn();
6553
+ init_Stack();
6554
+ init_Typography();
6555
+ sizeClasses6 = {
6556
+ sm: "text-2xl",
6557
+ md: "text-4xl",
6558
+ lg: "text-5xl"
6559
+ };
6560
+ MarketingStatCard = ({ value, label, size = "md", className }) => {
6561
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", align: "center", className: cn(className), children: [
6562
+ /* @__PURE__ */ jsxRuntime.jsx(
6563
+ Typography,
6564
+ {
6565
+ weight: "bold",
6566
+ color: "primary",
6567
+ className: cn(sizeClasses6[size]),
6568
+ children: value
6569
+ }
6570
+ ),
6571
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", children: label })
6572
+ ] });
6573
+ };
6574
+ MarketingStatCard.displayName = "MarketingStatCard";
6575
+ }
6576
+ });
6489
6577
  var backgroundClasses, paddingClasses, ContentSection;
6490
6578
  var init_ContentSection = __esm({
6491
6579
  "components/marketing/atoms/ContentSection.tsx"() {
@@ -12500,318 +12588,14 @@ var init_useCanvasEffects = __esm({
12500
12588
  "use client";
12501
12589
  }
12502
12590
  });
12503
- function pickPath(entry) {
12504
- if (Array.isArray(entry.path)) {
12505
- return entry.path[Math.floor(Math.random() * entry.path.length)];
12506
- }
12507
- return entry.path;
12508
- }
12509
- function useGameAudio({
12510
- manifest,
12511
- baseUrl = "",
12512
- initialMuted = false,
12513
- initialVolume = 1
12514
- }) {
12515
- const [muted, setMutedState] = React105.useState(initialMuted);
12516
- const [masterVolume, setMasterVolumeState] = React105.useState(initialVolume);
12517
- const mutedRef = React105.useRef(muted);
12518
- const volumeRef = React105.useRef(masterVolume);
12519
- const manifestRef = React105.useRef(manifest);
12520
- mutedRef.current = muted;
12521
- volumeRef.current = masterVolume;
12522
- manifestRef.current = manifest;
12523
- const poolsRef = React105.useRef(/* @__PURE__ */ new Map());
12524
- const getOrCreateElement = React105.useCallback((key) => {
12525
- const entry = manifestRef.current[key];
12526
- if (!entry) return null;
12527
- let pool = poolsRef.current.get(key);
12528
- if (!pool) {
12529
- pool = [];
12530
- poolsRef.current.set(key, pool);
12531
- }
12532
- const maxSize = entry.poolSize ?? 1;
12533
- for (const audio of pool) {
12534
- if (audio.paused && (audio.ended || audio.currentTime === 0)) {
12535
- return audio;
12536
- }
12537
- }
12538
- if (pool.length < maxSize) {
12539
- const src = baseUrl + pickPath(entry);
12540
- const audio = new Audio(src);
12541
- audio.loop = entry.loop ?? false;
12542
- pool.push(audio);
12543
- return audio;
12544
- }
12545
- if (!entry.loop) {
12546
- let oldest = pool[0];
12547
- for (const audio of pool) {
12548
- if (audio.currentTime > oldest.currentTime) {
12549
- oldest = audio;
12550
- }
12551
- }
12552
- oldest.pause();
12553
- oldest.currentTime = 0;
12554
- return oldest;
12555
- }
12556
- return null;
12557
- }, [baseUrl]);
12558
- const play = React105.useCallback((key) => {
12559
- if (mutedRef.current) return;
12560
- const entry = manifestRef.current[key];
12561
- if (!entry) return;
12562
- const audio = getOrCreateElement(key);
12563
- if (!audio) return;
12564
- audio.volume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
12565
- if (!entry.loop) {
12566
- audio.currentTime = 0;
12567
- }
12568
- const promise = audio.play();
12569
- if (promise) {
12570
- promise.catch(() => {
12571
- });
12572
- }
12573
- }, [getOrCreateElement]);
12574
- const stop = React105.useCallback((key) => {
12575
- const pool = poolsRef.current.get(key);
12576
- if (!pool) return;
12577
- for (const audio of pool) {
12578
- audio.pause();
12579
- audio.currentTime = 0;
12580
- }
12581
- }, []);
12582
- const currentMusicKeyRef = React105.useRef(null);
12583
- const currentMusicElRef = React105.useRef(null);
12584
- const musicFadeRef = React105.useRef(null);
12585
- const pendingMusicKeyRef = React105.useRef(null);
12586
- const clearMusicFade = React105.useCallback(() => {
12587
- if (musicFadeRef.current) {
12588
- clearInterval(musicFadeRef.current);
12589
- musicFadeRef.current = null;
12590
- }
12591
- }, []);
12592
- const playMusic = React105.useCallback((key) => {
12593
- if (key === currentMusicKeyRef.current) return;
12594
- pendingMusicKeyRef.current = key;
12595
- const entry = manifestRef.current[key];
12596
- if (!entry) return;
12597
- const fadeDurationMs = entry.crossfadeDurationMs ?? 1500;
12598
- const stepMs = 50;
12599
- const totalSteps = Math.max(1, fadeDurationMs / stepMs);
12600
- const targetVolume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
12601
- clearMusicFade();
12602
- const src = baseUrl + (Array.isArray(entry.path) ? entry.path[0] : entry.path);
12603
- const incoming = new Audio(src);
12604
- incoming.loop = true;
12605
- incoming.volume = 0;
12606
- const outgoing = currentMusicElRef.current;
12607
- const outgoingStartVol = outgoing?.volume ?? 0;
12608
- currentMusicKeyRef.current = key;
12609
- currentMusicElRef.current = incoming;
12610
- if (!mutedRef.current) {
12611
- incoming.play().catch(() => {
12612
- currentMusicKeyRef.current = null;
12613
- currentMusicElRef.current = outgoing;
12614
- });
12615
- }
12616
- let step = 0;
12617
- musicFadeRef.current = setInterval(() => {
12618
- step++;
12619
- const progress = Math.min(step / totalSteps, 1);
12620
- incoming.volume = Math.min(1, targetVolume * progress);
12621
- if (outgoing) {
12622
- outgoing.volume = Math.max(0, outgoingStartVol * (1 - progress));
12623
- }
12624
- if (progress >= 1) {
12625
- clearMusicFade();
12626
- if (outgoing) {
12627
- outgoing.pause();
12628
- outgoing.src = "";
12629
- }
12630
- }
12631
- }, stepMs);
12632
- }, [baseUrl, clearMusicFade]);
12633
- const stopMusic = React105.useCallback((fadeDurationMs = 1e3) => {
12634
- const outgoing = currentMusicElRef.current;
12635
- if (!outgoing) return;
12636
- currentMusicKeyRef.current = null;
12637
- currentMusicElRef.current = null;
12638
- pendingMusicKeyRef.current = null;
12639
- clearMusicFade();
12640
- const startVolume = outgoing.volume;
12641
- const stepMs = 50;
12642
- const totalSteps = Math.max(1, fadeDurationMs / stepMs);
12643
- let step = 0;
12644
- musicFadeRef.current = setInterval(() => {
12645
- step++;
12646
- const progress = step / totalSteps;
12647
- outgoing.volume = Math.max(0, startVolume * (1 - progress));
12648
- if (progress >= 1) {
12649
- clearMusicFade();
12650
- outgoing.pause();
12651
- outgoing.src = "";
12652
- }
12653
- }, stepMs);
12654
- }, [clearMusicFade]);
12655
- const stopAll = React105.useCallback(() => {
12656
- for (const pool of poolsRef.current.values()) {
12657
- for (const audio of pool) {
12658
- audio.pause();
12659
- audio.currentTime = 0;
12660
- }
12661
- }
12662
- stopMusic(0);
12663
- }, [stopMusic]);
12664
- const setMuted = React105.useCallback((value) => {
12665
- setMutedState(value);
12666
- if (value) {
12667
- for (const [key, pool] of poolsRef.current.entries()) {
12668
- if (manifestRef.current[key]?.loop) {
12669
- for (const audio of pool) {
12670
- if (!audio.paused) audio.pause();
12671
- }
12672
- }
12673
- }
12674
- currentMusicElRef.current?.pause();
12675
- } else {
12676
- for (const [key, pool] of poolsRef.current.entries()) {
12677
- const entry = manifestRef.current[key];
12678
- if (entry?.loop && entry?.autostart) {
12679
- for (const audio of pool) {
12680
- if (audio.paused) audio.play().catch(() => {
12681
- });
12682
- }
12683
- }
12684
- }
12685
- const musicEl = currentMusicElRef.current;
12686
- if (musicEl) {
12687
- musicEl.play().catch(() => {
12688
- });
12689
- }
12690
- }
12691
- }, []);
12692
- const setMasterVolume = React105.useCallback((volume) => {
12693
- const clamped = Math.max(0, Math.min(1, volume));
12694
- setMasterVolumeState(clamped);
12695
- for (const [key, pool] of poolsRef.current.entries()) {
12696
- const entryVol = manifestRef.current[key]?.volume ?? 1;
12697
- for (const audio of pool) {
12698
- audio.volume = Math.min(1, entryVol * clamped);
12699
- }
12700
- }
12701
- if (!musicFadeRef.current && currentMusicElRef.current) {
12702
- const key = currentMusicKeyRef.current;
12703
- const entryVol = key ? manifestRef.current[key]?.volume ?? 1 : 1;
12704
- currentMusicElRef.current.volume = Math.min(1, entryVol * clamped);
12705
- }
12706
- }, []);
12707
- const unlockedRef = React105.useRef(false);
12708
- React105.useEffect(() => {
12709
- const autoKeys = Object.keys(manifest).filter((k) => manifest[k].autostart);
12710
- const hasPendingMusic = () => pendingMusicKeyRef.current !== null;
12711
- const hasAutoStart = autoKeys.length > 0;
12712
- if (!hasAutoStart && !hasPendingMusic()) return;
12713
- const unlock = () => {
12714
- if (unlockedRef.current) return;
12715
- unlockedRef.current = true;
12716
- if (!mutedRef.current) {
12717
- for (const key of autoKeys) {
12718
- play(key);
12719
- }
12720
- const pending = pendingMusicKeyRef.current;
12721
- if (pending && pending !== currentMusicKeyRef.current) {
12722
- playMusic(pending);
12723
- }
12724
- }
12725
- };
12726
- document.addEventListener("click", unlock, { once: true });
12727
- document.addEventListener("keydown", unlock, { once: true });
12728
- document.addEventListener("touchstart", unlock, { once: true });
12729
- return () => {
12730
- document.removeEventListener("click", unlock);
12731
- document.removeEventListener("keydown", unlock);
12732
- document.removeEventListener("touchstart", unlock);
12733
- };
12734
- }, [manifest, play, playMusic]);
12735
- React105.useEffect(() => {
12736
- return () => {
12737
- clearMusicFade();
12738
- for (const pool of poolsRef.current.values()) {
12739
- for (const audio of pool) {
12740
- audio.pause();
12741
- audio.src = "";
12742
- }
12743
- }
12744
- poolsRef.current.clear();
12745
- if (currentMusicElRef.current) {
12746
- currentMusicElRef.current.pause();
12747
- currentMusicElRef.current.src = "";
12748
- currentMusicElRef.current = null;
12749
- }
12750
- };
12751
- }, [clearMusicFade]);
12752
- return {
12753
- play,
12754
- stop,
12755
- stopAll,
12756
- playMusic,
12757
- stopMusic,
12758
- muted,
12759
- setMuted,
12760
- masterVolume,
12761
- setMasterVolume
12762
- };
12763
- }
12764
12591
  var init_useGameAudio = __esm({
12765
12592
  "components/game/shared/hooks/useGameAudio.ts"() {
12766
12593
  "use client";
12767
- useGameAudio.displayName = "useGameAudio";
12768
12594
  }
12769
12595
  });
12770
12596
  function useGameAudioContextOptional() {
12771
12597
  return React105.useContext(GameAudioContext);
12772
12598
  }
12773
- function GameAudioProvider({
12774
- manifest,
12775
- baseUrl = "",
12776
- children,
12777
- initialMuted = false
12778
- }) {
12779
- const eventBus = useEventBus();
12780
- const { play, stop, stopAll, playMusic, stopMusic, muted, setMuted, masterVolume, setMasterVolume } = useGameAudio({ manifest, baseUrl, initialMuted });
12781
- React105.useEffect(() => {
12782
- const unsubPlay = eventBus.on("UI:PLAY_SOUND", (event) => {
12783
- const key = event.payload?.key;
12784
- if (key) play(key);
12785
- });
12786
- const unsubStop = eventBus.on("UI:STOP_SOUND", (event) => {
12787
- const key = event.payload?.key;
12788
- if (key) {
12789
- stop(key);
12790
- } else {
12791
- stopAll();
12792
- }
12793
- });
12794
- const unsubChangeMusic = eventBus.on("UI:CHANGE_MUSIC", (event) => {
12795
- const key = event.payload?.key;
12796
- if (key) {
12797
- playMusic(key);
12798
- } else {
12799
- stopMusic();
12800
- }
12801
- });
12802
- const unsubStopMusic = eventBus.on("UI:STOP_MUSIC", () => {
12803
- stopMusic();
12804
- });
12805
- return () => {
12806
- unsubPlay();
12807
- unsubStop();
12808
- unsubChangeMusic();
12809
- unsubStopMusic();
12810
- };
12811
- }, [eventBus, play, stop, stopAll, playMusic, stopMusic]);
12812
- const value = { muted, setMuted, masterVolume, setMasterVolume, play, playMusic, stopMusic };
12813
- return /* @__PURE__ */ jsxRuntime.jsx(GameAudioContext.Provider, { value, children });
12814
- }
12815
12599
  var GameAudioContext;
12816
12600
  var init_GameAudioProvider = __esm({
12817
12601
  "components/game/shared/providers/GameAudioProvider.tsx"() {
@@ -12820,7 +12604,6 @@ var init_GameAudioProvider = __esm({
12820
12604
  init_useGameAudio();
12821
12605
  GameAudioContext = React105.createContext(null);
12822
12606
  GameAudioContext.displayName = "GameAudioContext";
12823
- GameAudioProvider.displayName = "GameAudioProvider";
12824
12607
  }
12825
12608
  });
12826
12609
  function GameAudioToggle({
@@ -20456,6 +20239,315 @@ var init_molecules = __esm({
20456
20239
  init_puzzleObject();
20457
20240
  }
20458
20241
  });
20242
+ function resolveColor2(color, ctx, fallback) {
20243
+ if (!color) return fallback;
20244
+ if (color.startsWith("var(")) {
20245
+ ctx.canvas.style;
20246
+ const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color);
20247
+ if (m) {
20248
+ const computed = getComputedStyle(ctx.canvas).getPropertyValue(m[1]).trim();
20249
+ return computed || m[2]?.trim() || fallback;
20250
+ }
20251
+ }
20252
+ return color;
20253
+ }
20254
+ function shapeBounds(shape) {
20255
+ switch (shape.type) {
20256
+ case "line":
20257
+ case "arrow":
20258
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) return null;
20259
+ return {
20260
+ x: Math.min(shape.x1, shape.x2) - 6,
20261
+ y: Math.min(shape.y1, shape.y2) - 6,
20262
+ w: Math.abs(shape.x2 - shape.x1) + 12,
20263
+ h: Math.abs(shape.y2 - shape.y1) + 12
20264
+ };
20265
+ case "circle":
20266
+ if (shape.x == null || shape.y == null || shape.radius == null) return null;
20267
+ return {
20268
+ x: shape.x - shape.radius - 4,
20269
+ y: shape.y - shape.radius - 4,
20270
+ w: shape.radius * 2 + 8,
20271
+ h: shape.radius * 2 + 8
20272
+ };
20273
+ case "rect":
20274
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
20275
+ return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
20276
+ case "polygon":
20277
+ if (!shape.points || shape.points.length === 0) return null;
20278
+ {
20279
+ const xs = shape.points.map((p2) => p2.x);
20280
+ const ys = shape.points.map((p2) => p2.y);
20281
+ const minX = Math.min(...xs);
20282
+ const minY = Math.min(...ys);
20283
+ return {
20284
+ x: minX - 4,
20285
+ y: minY - 4,
20286
+ w: Math.max(...xs) - minX + 8,
20287
+ h: Math.max(...ys) - minY + 8
20288
+ };
20289
+ }
20290
+ case "text":
20291
+ if (shape.x == null || shape.y == null) return null;
20292
+ return { x: shape.x - 4, y: shape.y - (shape.fontSize ?? 14) - 4, w: 120, h: (shape.fontSize ?? 14) + 8 };
20293
+ default:
20294
+ return null;
20295
+ }
20296
+ }
20297
+ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
20298
+ const angle = Math.atan2(y2 - y1, x2 - x1);
20299
+ ctx.beginPath();
20300
+ ctx.moveTo(x2, y2);
20301
+ ctx.lineTo(x2 - size * Math.cos(angle - Math.PI / 6), y2 - size * Math.sin(angle - Math.PI / 6));
20302
+ ctx.lineTo(x2 - size * Math.cos(angle + Math.PI / 6), y2 - size * Math.sin(angle + Math.PI / 6));
20303
+ ctx.closePath();
20304
+ ctx.fill();
20305
+ }
20306
+ function drawShape(ctx, shape, width, height) {
20307
+ ctx.save();
20308
+ const opacity = shape.opacity ?? 1;
20309
+ ctx.globalAlpha = opacity;
20310
+ const stroke = resolveColor2(shape.color, ctx, "#333333");
20311
+ const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
20312
+ ctx.lineWidth = shape.lineWidth ?? 2;
20313
+ switch (shape.type) {
20314
+ case "grid": {
20315
+ const step = shape.step ?? 40;
20316
+ ctx.strokeStyle = stroke;
20317
+ ctx.globalAlpha = opacity * 0.25;
20318
+ ctx.lineWidth = 1;
20319
+ ctx.beginPath();
20320
+ for (let x = 0; x <= width; x += step) {
20321
+ ctx.moveTo(x, 0);
20322
+ ctx.lineTo(x, height);
20323
+ }
20324
+ for (let y = 0; y <= height; y += step) {
20325
+ ctx.moveTo(0, y);
20326
+ ctx.lineTo(width, y);
20327
+ }
20328
+ ctx.stroke();
20329
+ break;
20330
+ }
20331
+ case "axis": {
20332
+ const axis = shape.axis ?? "x";
20333
+ ctx.strokeStyle = stroke;
20334
+ ctx.lineWidth = shape.lineWidth ?? 2;
20335
+ ctx.beginPath();
20336
+ if (axis === "x") {
20337
+ ctx.moveTo(0, height / 2);
20338
+ ctx.lineTo(width, height / 2);
20339
+ } else {
20340
+ ctx.moveTo(width / 2, 0);
20341
+ ctx.lineTo(width / 2, height);
20342
+ }
20343
+ ctx.stroke();
20344
+ break;
20345
+ }
20346
+ case "line": {
20347
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
20348
+ ctx.strokeStyle = stroke;
20349
+ ctx.beginPath();
20350
+ ctx.moveTo(shape.x1, shape.y1);
20351
+ ctx.lineTo(shape.x2, shape.y2);
20352
+ ctx.stroke();
20353
+ break;
20354
+ }
20355
+ case "arrow": {
20356
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
20357
+ ctx.strokeStyle = stroke;
20358
+ ctx.fillStyle = stroke;
20359
+ ctx.beginPath();
20360
+ ctx.moveTo(shape.x1, shape.y1);
20361
+ ctx.lineTo(shape.x2, shape.y2);
20362
+ ctx.stroke();
20363
+ drawArrowHead(ctx, shape.x1, shape.y1, shape.x2, shape.y2, 10);
20364
+ break;
20365
+ }
20366
+ case "circle": {
20367
+ if (shape.x == null || shape.y == null || shape.radius == null) break;
20368
+ ctx.beginPath();
20369
+ ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
20370
+ if (fill) {
20371
+ ctx.fillStyle = fill;
20372
+ ctx.fill();
20373
+ }
20374
+ ctx.strokeStyle = stroke;
20375
+ ctx.stroke();
20376
+ break;
20377
+ }
20378
+ case "rect": {
20379
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
20380
+ if (fill) {
20381
+ ctx.fillStyle = fill;
20382
+ ctx.fillRect(shape.x, shape.y, shape.width, shape.height);
20383
+ }
20384
+ ctx.strokeStyle = stroke;
20385
+ ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
20386
+ break;
20387
+ }
20388
+ case "polygon": {
20389
+ if (!shape.points || shape.points.length < 2) break;
20390
+ ctx.beginPath();
20391
+ ctx.moveTo(shape.points[0].x, shape.points[0].y);
20392
+ for (let i = 1; i < shape.points.length; i++) {
20393
+ ctx.lineTo(shape.points[i].x, shape.points[i].y);
20394
+ }
20395
+ ctx.closePath();
20396
+ if (fill) {
20397
+ ctx.fillStyle = fill;
20398
+ ctx.fill();
20399
+ }
20400
+ ctx.strokeStyle = stroke;
20401
+ ctx.stroke();
20402
+ break;
20403
+ }
20404
+ case "path": {
20405
+ if (!shape.path) break;
20406
+ const p2 = new Path2D(shape.path);
20407
+ if (fill) {
20408
+ ctx.fillStyle = fill;
20409
+ ctx.fill(p2);
20410
+ }
20411
+ ctx.strokeStyle = stroke;
20412
+ ctx.stroke(p2);
20413
+ break;
20414
+ }
20415
+ case "text": {
20416
+ if (shape.x == null || shape.y == null || !shape.text) break;
20417
+ ctx.fillStyle = stroke;
20418
+ ctx.font = `${shape.fontSize ?? 14}px system-ui, sans-serif`;
20419
+ ctx.textAlign = shape.align ?? "left";
20420
+ ctx.textBaseline = "middle";
20421
+ ctx.fillText(shape.text, shape.x, shape.y);
20422
+ break;
20423
+ }
20424
+ }
20425
+ ctx.restore();
20426
+ }
20427
+ var LearningCanvas;
20428
+ var init_LearningCanvas = __esm({
20429
+ "components/learning/atoms/LearningCanvas.tsx"() {
20430
+ "use client";
20431
+ init_cn();
20432
+ init_useEventBus();
20433
+ LearningCanvas = ({
20434
+ className,
20435
+ width = 600,
20436
+ height = 400,
20437
+ backgroundColor,
20438
+ shapes = [],
20439
+ interactive = false,
20440
+ animate = false,
20441
+ onShapeClick,
20442
+ onShapeHover,
20443
+ isLoading,
20444
+ error
20445
+ }) => {
20446
+ const canvasRef = React105.useRef(null);
20447
+ const eventBus = useEventBus();
20448
+ const animRef = React105.useRef(0);
20449
+ const hoverIndexRef = React105.useRef(-1);
20450
+ const findShapeAt = React105.useCallback((clientX, clientY) => {
20451
+ const canvas = canvasRef.current;
20452
+ if (!canvas) return -1;
20453
+ const rect = canvas.getBoundingClientRect();
20454
+ const x = clientX - rect.left;
20455
+ const y = clientY - rect.top;
20456
+ for (let i = shapes.length - 1; i >= 0; i--) {
20457
+ const b = shapeBounds(shapes[i]);
20458
+ if (b && x >= b.x && x <= b.x + b.w && y >= b.y && y <= b.y + b.h) {
20459
+ return i;
20460
+ }
20461
+ }
20462
+ return -1;
20463
+ }, [shapes]);
20464
+ const draw = React105.useCallback(() => {
20465
+ const canvas = canvasRef.current;
20466
+ if (!canvas) return;
20467
+ const ctx = canvas.getContext("2d");
20468
+ if (!ctx) return;
20469
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
20470
+ canvas.width = Math.max(1, Math.floor(width * dpr));
20471
+ canvas.height = Math.max(1, Math.floor(height * dpr));
20472
+ canvas.style.width = `${width}px`;
20473
+ canvas.style.height = `${height}px`;
20474
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
20475
+ ctx.clearRect(0, 0, width, height);
20476
+ if (backgroundColor) {
20477
+ ctx.fillStyle = backgroundColor;
20478
+ ctx.fillRect(0, 0, width, height);
20479
+ }
20480
+ for (const shape of shapes) {
20481
+ drawShape(ctx, shape, width, height);
20482
+ }
20483
+ }, [width, height, backgroundColor, shapes]);
20484
+ React105.useEffect(() => {
20485
+ draw();
20486
+ }, [draw]);
20487
+ React105.useEffect(() => {
20488
+ if (!animate) return;
20489
+ const loop = () => {
20490
+ draw();
20491
+ animRef.current = requestAnimationFrame(loop);
20492
+ };
20493
+ animRef.current = requestAnimationFrame(loop);
20494
+ return () => cancelAnimationFrame(animRef.current);
20495
+ }, [animate, draw]);
20496
+ const handlePointerMove = React105.useCallback(
20497
+ (e) => {
20498
+ if (!interactive) return;
20499
+ const idx = findShapeAt(e.clientX, e.clientY);
20500
+ if (idx !== hoverIndexRef.current) {
20501
+ hoverIndexRef.current = idx;
20502
+ if (idx >= 0) {
20503
+ const shape = shapes[idx];
20504
+ const payload = { id: shape.id, type: shape.type, index: idx };
20505
+ if (onShapeHover) onShapeHover(payload);
20506
+ else if (eventBus) eventBus.emit(`UI:SHAPE_HOVER`, payload);
20507
+ }
20508
+ }
20509
+ },
20510
+ [interactive, onShapeHover, eventBus, findShapeAt, shapes]
20511
+ );
20512
+ const handleClick = React105.useCallback(
20513
+ (e) => {
20514
+ if (!interactive) return;
20515
+ const idx = findShapeAt(e.clientX, e.clientY);
20516
+ if (idx >= 0) {
20517
+ const shape = shapes[idx];
20518
+ const payload = { id: shape.id, type: shape.type, index: idx };
20519
+ if (onShapeClick) onShapeClick(payload);
20520
+ else if (eventBus) eventBus.emit(`UI:SHAPE_CLICK`, payload);
20521
+ }
20522
+ },
20523
+ [interactive, onShapeClick, eventBus, findShapeAt, shapes]
20524
+ );
20525
+ if (isLoading || error) {
20526
+ return /* @__PURE__ */ jsxRuntime.jsx(
20527
+ "div",
20528
+ {
20529
+ className: cn(
20530
+ "flex items-center justify-center rounded border border-border bg-surface",
20531
+ className
20532
+ ),
20533
+ style: { width, height },
20534
+ 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" })
20535
+ }
20536
+ );
20537
+ }
20538
+ return /* @__PURE__ */ jsxRuntime.jsx(
20539
+ "canvas",
20540
+ {
20541
+ ref: canvasRef,
20542
+ className: cn("block touch-none rounded border border-border", className),
20543
+ style: { width, height },
20544
+ onClick: handleClick,
20545
+ onPointerMove: handlePointerMove
20546
+ }
20547
+ );
20548
+ };
20549
+ }
20550
+ });
20459
20551
 
20460
20552
  // components/core/atoms/index.ts
20461
20553
  var init_atoms = __esm({
@@ -21941,91 +22033,6 @@ var init_ComponentPatterns = __esm({
21941
22033
  AlertPattern.displayName = "AlertPattern";
21942
22034
  }
21943
22035
  });
21944
- function parseValue(value) {
21945
- if (value === "" || value == null) return { num: 0, prefix: "", suffix: "", decimals: 0 };
21946
- const match = String(value).match(/^([^0-9]*)([0-9]+(?:\.[0-9]+)?)(.*)$/);
21947
- if (!match) {
21948
- return { num: 0, prefix: "", suffix: String(value), decimals: 0 };
21949
- }
21950
- const numStr = match[2];
21951
- const decimalIdx = numStr.indexOf(".");
21952
- const decimals = decimalIdx >= 0 ? numStr.length - decimalIdx - 1 : 0;
21953
- return {
21954
- prefix: match[1],
21955
- num: parseFloat(numStr),
21956
- suffix: match[3],
21957
- decimals
21958
- };
21959
- }
21960
- var AnimatedCounter;
21961
- var init_AnimatedCounter = __esm({
21962
- "components/core/molecules/AnimatedCounter.tsx"() {
21963
- "use client";
21964
- init_cn();
21965
- init_Box();
21966
- init_Typography();
21967
- AnimatedCounter = ({
21968
- value,
21969
- label,
21970
- duration = 1500,
21971
- className
21972
- }) => {
21973
- const ref = React105.useRef(null);
21974
- const [displayValue, setDisplayValue] = React105.useState("0");
21975
- const [hasAnimated, setHasAnimated] = React105.useState(false);
21976
- const animate = React105.useCallback(() => {
21977
- const { num: num2, prefix, suffix, decimals } = parseValue(value);
21978
- if (num2 === 0) {
21979
- setDisplayValue(String(value));
21980
- return;
21981
- }
21982
- const startTime = performance.now();
21983
- const tick = (now2) => {
21984
- const elapsed = now2 - startTime;
21985
- const progress = Math.min(elapsed / duration, 1);
21986
- const eased = 1 - Math.pow(1 - progress, 3);
21987
- const current = eased * num2;
21988
- setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
21989
- if (progress < 1) {
21990
- requestAnimationFrame(tick);
21991
- } else {
21992
- setDisplayValue(String(value));
21993
- }
21994
- };
21995
- requestAnimationFrame(tick);
21996
- }, [value, duration]);
21997
- React105.useEffect(() => {
21998
- if (hasAnimated) return;
21999
- const el = ref.current;
22000
- if (!el) return;
22001
- const observer2 = new IntersectionObserver(
22002
- (entries) => {
22003
- if (entries[0].isIntersecting) {
22004
- setHasAnimated(true);
22005
- animate();
22006
- observer2.disconnect();
22007
- }
22008
- },
22009
- { threshold: 0.3 }
22010
- );
22011
- observer2.observe(el);
22012
- return () => observer2.disconnect();
22013
- }, [hasAnimated, animate]);
22014
- return /* @__PURE__ */ jsxRuntime.jsxs(Box, { ref, className: cn("flex flex-col items-center gap-1 p-4", className), children: [
22015
- /* @__PURE__ */ jsxRuntime.jsx(
22016
- Typography,
22017
- {
22018
- variant: "h2",
22019
- className: "text-primary font-bold tabular-nums",
22020
- children: hasAnimated ? displayValue : "0"
22021
- }
22022
- ),
22023
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", color: "muted", className: "text-center", children: label })
22024
- ] });
22025
- };
22026
- AnimatedCounter.displayName = "AnimatedCounter";
22027
- }
22028
- });
22029
22036
  var AuthLayout;
22030
22037
  var init_AuthLayout = __esm({
22031
22038
  "components/marketing/templates/AuthLayout.tsx"() {
@@ -22994,10 +23001,107 @@ var init_BehaviorView = __esm({
22994
23001
  BehaviorView.displayName = "BehaviorView";
22995
23002
  }
22996
23003
  });
23004
+ var BiologyCanvas;
23005
+ var init_BiologyCanvas = __esm({
23006
+ "components/learning/molecules/BiologyCanvas.tsx"() {
23007
+ "use client";
23008
+ init_atoms();
23009
+ init_Stack();
23010
+ init_LearningCanvas();
23011
+ BiologyCanvas = ({
23012
+ className,
23013
+ width = 600,
23014
+ height = 400,
23015
+ title,
23016
+ backgroundColor,
23017
+ nodes = [],
23018
+ edges = [],
23019
+ shapes = [],
23020
+ interactive = false,
23021
+ animate = false,
23022
+ onShapeClick,
23023
+ isLoading,
23024
+ error
23025
+ }) => {
23026
+ const derivedShapes = React105.useMemo(() => {
23027
+ const out = [];
23028
+ const nodeById = /* @__PURE__ */ new Map();
23029
+ for (const n of nodes) {
23030
+ if (n.id) nodeById.set(n.id, n);
23031
+ }
23032
+ for (const e of edges) {
23033
+ const a = nodeById.get(e.from);
23034
+ const b = nodeById.get(e.to);
23035
+ if (!a || !b) continue;
23036
+ out.push({
23037
+ type: "line",
23038
+ x1: a.x,
23039
+ y1: a.y,
23040
+ x2: b.x,
23041
+ y2: b.y,
23042
+ color: e.color ?? "#9ca3af",
23043
+ lineWidth: 2
23044
+ });
23045
+ if (e.label) {
23046
+ out.push({
23047
+ type: "text",
23048
+ x: (a.x + b.x) / 2 + 4,
23049
+ y: (a.y + b.y) / 2 - 4,
23050
+ text: e.label,
23051
+ color: "#374151",
23052
+ fontSize: 11
23053
+ });
23054
+ }
23055
+ }
23056
+ for (const n of nodes) {
23057
+ out.push({
23058
+ type: "circle",
23059
+ x: n.x,
23060
+ y: n.y,
23061
+ radius: n.radius ?? 16,
23062
+ color: n.color ?? "#16a34a",
23063
+ fill: `${n.color ?? "#16a34a"}33`,
23064
+ id: n.id
23065
+ });
23066
+ if (n.label) {
23067
+ out.push({
23068
+ type: "text",
23069
+ x: n.x,
23070
+ y: n.y + (n.radius ?? 16) + 14,
23071
+ text: n.label,
23072
+ color: "#111827",
23073
+ fontSize: 12,
23074
+ align: "center"
23075
+ });
23076
+ }
23077
+ }
23078
+ out.push(...shapes);
23079
+ return out;
23080
+ }, [nodes, edges, shapes]);
23081
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
23082
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
23083
+ /* @__PURE__ */ jsxRuntime.jsx(
23084
+ LearningCanvas,
23085
+ {
23086
+ width,
23087
+ height,
23088
+ backgroundColor,
23089
+ shapes: derivedShapes,
23090
+ interactive,
23091
+ animate,
23092
+ onShapeClick,
23093
+ isLoading,
23094
+ error
23095
+ }
23096
+ )
23097
+ ] }) });
23098
+ };
23099
+ }
23100
+ });
22997
23101
 
22998
- // node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css
23102
+ // node_modules/katex/dist/katex.min.css
22999
23103
  var init_katex_min = __esm({
23000
- "node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css"() {
23104
+ "node_modules/katex/dist/katex.min.css"() {
23001
23105
  }
23002
23106
  });
23003
23107
  var Tabs;
@@ -27340,129 +27444,6 @@ var init_Breadcrumb = __esm({
27340
27444
  Breadcrumb.displayName = "Breadcrumb";
27341
27445
  }
27342
27446
  });
27343
- function useSafeEventBus2() {
27344
- try {
27345
- return useEventBus();
27346
- } catch {
27347
- return { emit: () => {
27348
- }, on: () => () => {
27349
- }, once: () => {
27350
- } };
27351
- }
27352
- }
27353
- var log6, lookStyles4, ButtonGroup;
27354
- var init_ButtonGroup = __esm({
27355
- "components/core/molecules/ButtonGroup.tsx"() {
27356
- "use client";
27357
- init_cn();
27358
- init_atoms();
27359
- init_useEventBus();
27360
- log6 = logger.createLogger("almadar:ui:button-group");
27361
- lookStyles4 = {
27362
- "right-aligned-buttons": "",
27363
- "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
27364
- "inline-row": "gap-2 inline-flex",
27365
- "dropdown-menu": "[&>button:not(:first-child)]:hidden",
27366
- "command-palette-trigger": "[&>button:not(:first-child)]:hidden"
27367
- };
27368
- ButtonGroup = ({
27369
- children,
27370
- primary,
27371
- secondary,
27372
- variant = "default",
27373
- orientation = "horizontal",
27374
- className,
27375
- // Filter-group pattern props (entity and filters are used for schema-driven filtering)
27376
- entity: _entity,
27377
- filters,
27378
- look = "right-aligned-buttons"
27379
- }) => {
27380
- const eventBus = useSafeEventBus2();
27381
- const variantClasses2 = {
27382
- default: "gap-0",
27383
- 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",
27384
- 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"
27385
- };
27386
- const orientationClasses = {
27387
- horizontal: "flex-row",
27388
- 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"
27389
- };
27390
- const handleActionClick = (action) => {
27391
- if (action.event) {
27392
- eventBus.emit("UI:DISPATCH", { event: action.event });
27393
- }
27394
- if (action.navigatesTo) {
27395
- eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
27396
- }
27397
- };
27398
- const renderFormActions = () => {
27399
- const buttons = [];
27400
- if (secondary) {
27401
- secondary.forEach((action, index) => {
27402
- buttons.push(
27403
- /* @__PURE__ */ jsxRuntime.jsx(
27404
- Button,
27405
- {
27406
- type: action.actionType === "submit" ? "submit" : "button",
27407
- variant: action.variant || "ghost",
27408
- onClick: () => handleActionClick(action),
27409
- children: action.label
27410
- },
27411
- `secondary-${index}`
27412
- )
27413
- );
27414
- });
27415
- }
27416
- if (primary) {
27417
- const isSubmit = primary.actionType === "submit";
27418
- buttons.push(
27419
- /* @__PURE__ */ jsxRuntime.jsx(
27420
- Button,
27421
- {
27422
- type: isSubmit ? "submit" : "button",
27423
- variant: primary.variant || "primary",
27424
- onClick: () => handleActionClick(primary),
27425
- "data-testid": isSubmit ? "form-submit" : void 0,
27426
- children: primary.label
27427
- },
27428
- "primary"
27429
- )
27430
- );
27431
- }
27432
- return buttons;
27433
- };
27434
- const renderFilters = () => {
27435
- if (!filters || filters.length === 0) return null;
27436
- return filters.map((filter, index) => /* @__PURE__ */ jsxRuntime.jsx(
27437
- Button,
27438
- {
27439
- variant: "ghost",
27440
- onClick: () => {
27441
- log6.debug("Filter clicked", { field: filter.field });
27442
- },
27443
- children: filter.label
27444
- },
27445
- `filter-${filter.field}-${index}`
27446
- ));
27447
- };
27448
- return /* @__PURE__ */ jsxRuntime.jsx(
27449
- "div",
27450
- {
27451
- className: cn(
27452
- "inline-flex gap-2",
27453
- variantClasses2[variant],
27454
- orientationClasses[orientation],
27455
- lookStyles4[look],
27456
- className
27457
- ),
27458
- role: "group",
27459
- children: children || renderFilters() || renderFormActions()
27460
- }
27461
- );
27462
- };
27463
- ButtonGroup.displayName = "ButtonGroup";
27464
- }
27465
- });
27466
27447
  function dayWindowForViewport(width) {
27467
27448
  if (width <= 640) return 1;
27468
27449
  if (width <= 1024) return 3;
@@ -28205,7 +28186,7 @@ var init_CardGrid = __esm({
28205
28186
  CardGrid.displayName = "CardGrid";
28206
28187
  }
28207
28188
  });
28208
- function useSafeEventBus3() {
28189
+ function useSafeEventBus2() {
28209
28190
  try {
28210
28191
  return useEventBus();
28211
28192
  } catch {
@@ -28241,7 +28222,7 @@ var init_Carousel = __esm({
28241
28222
  const [activeIndex, setActiveIndex] = React105.useState(0);
28242
28223
  const scrollRef = React105.useRef(null);
28243
28224
  const autoPlayRef = React105.useRef(null);
28244
- const eventBus = useSafeEventBus3();
28225
+ const eventBus = useSafeEventBus2();
28245
28226
  const { t } = hooks.useTranslate();
28246
28227
  const safeItems = items ?? [];
28247
28228
  const totalSlides = safeItems.length;
@@ -29489,6 +29470,122 @@ var init_ChatBar = __esm({
29489
29470
  ChatBar.displayName = "ChatBar";
29490
29471
  }
29491
29472
  });
29473
+ var ChemistryCanvas;
29474
+ var init_ChemistryCanvas = __esm({
29475
+ "components/learning/molecules/ChemistryCanvas.tsx"() {
29476
+ "use client";
29477
+ init_atoms();
29478
+ init_Stack();
29479
+ init_LearningCanvas();
29480
+ ChemistryCanvas = ({
29481
+ className,
29482
+ width = 600,
29483
+ height = 400,
29484
+ title,
29485
+ backgroundColor,
29486
+ atoms = [],
29487
+ bonds = [],
29488
+ arrows = [],
29489
+ shapes = [],
29490
+ interactive = false,
29491
+ animate = false,
29492
+ onShapeClick,
29493
+ isLoading,
29494
+ error
29495
+ }) => {
29496
+ const derivedShapes = React105.useMemo(() => {
29497
+ const out = [];
29498
+ const atomById = /* @__PURE__ */ new Map();
29499
+ for (const a of atoms) {
29500
+ if (a.id) atomById.set(a.id, a);
29501
+ }
29502
+ for (const b of bonds) {
29503
+ const a = atomById.get(b.from);
29504
+ const c = atomById.get(b.to);
29505
+ if (!a || !c) continue;
29506
+ const color = b.color ?? "#6b7280";
29507
+ const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
29508
+ out.push({
29509
+ type: "line",
29510
+ x1: a.x,
29511
+ y1: a.y,
29512
+ x2: c.x,
29513
+ y2: c.y,
29514
+ color,
29515
+ lineWidth: strokeWidth
29516
+ });
29517
+ }
29518
+ for (const a of arrows) {
29519
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
29520
+ const len = a.length ?? 60;
29521
+ const x2 = a.x + Math.cos(angle) * len;
29522
+ const y2 = a.y + Math.sin(angle) * len;
29523
+ out.push({
29524
+ type: "arrow",
29525
+ x1: a.x,
29526
+ y1: a.y,
29527
+ x2,
29528
+ y2,
29529
+ color: a.color ?? "#dc2626",
29530
+ lineWidth: 2
29531
+ });
29532
+ if (a.label) {
29533
+ out.push({
29534
+ type: "text",
29535
+ x: (a.x + x2) / 2,
29536
+ y: (a.y + y2) / 2 - 10,
29537
+ text: a.label,
29538
+ color: "#111827",
29539
+ fontSize: 12,
29540
+ align: "center"
29541
+ });
29542
+ }
29543
+ }
29544
+ for (const a of atoms) {
29545
+ out.push({
29546
+ type: "circle",
29547
+ x: a.x,
29548
+ y: a.y,
29549
+ radius: a.radius ?? 14,
29550
+ color: a.color ?? "#2563eb",
29551
+ fill: a.color ?? "#2563eb",
29552
+ id: a.id
29553
+ });
29554
+ if (a.element) {
29555
+ out.push({
29556
+ type: "text",
29557
+ x: a.x,
29558
+ y: a.y,
29559
+ text: a.element,
29560
+ color: "#ffffff",
29561
+ fontSize: 12,
29562
+ align: "center"
29563
+ });
29564
+ }
29565
+ }
29566
+ out.push(...shapes);
29567
+ return out;
29568
+ }, [atoms, bonds, arrows, shapes]);
29569
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
29570
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
29571
+ /* @__PURE__ */ jsxRuntime.jsx(
29572
+ LearningCanvas,
29573
+ {
29574
+ width,
29575
+ height,
29576
+ backgroundColor,
29577
+ shapes: derivedShapes,
29578
+ interactive,
29579
+ animate,
29580
+ onShapeClick,
29581
+ isLoading,
29582
+ error
29583
+ }
29584
+ )
29585
+ ] }) });
29586
+ };
29587
+ }
29588
+ });
29492
29589
  var CodeRunnerPanel;
29493
29590
  var init_CodeRunnerPanel = __esm({
29494
29591
  "components/core/organisms/CodeRunnerPanel.tsx"() {
@@ -31563,7 +31660,7 @@ function DataGrid({
31563
31660
  /* @__PURE__ */ jsxRuntime.jsx(
31564
31661
  Box,
31565
31662
  {
31566
- className: cn("grid", gapStyles5[gap], scrollX ? "grid-flow-col overflow-x-auto" : colsClass, lookStyles5[look], className),
31663
+ className: cn("grid", gapStyles5[gap], scrollX ? "grid-flow-col overflow-x-auto" : colsClass, lookStyles4[look], className),
31567
31664
  style: scrollX ? { gridAutoFlow: "column", gridAutoColumns: `minmax(${minCardWidth}px, 1fr)` } : gridTemplateColumns ? { gridTemplateColumns } : void 0,
31568
31665
  children: data.map((item, index) => {
31569
31666
  const itemData = item;
@@ -31758,7 +31855,7 @@ function DataGrid({
31758
31855
  ] })
31759
31856
  );
31760
31857
  }
31761
- var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles5;
31858
+ var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles4;
31762
31859
  var init_DataGrid = __esm({
31763
31860
  "components/core/molecules/DataGrid.tsx"() {
31764
31861
  "use client";
@@ -31793,7 +31890,7 @@ var init_DataGrid = __esm({
31793
31890
  lg: "gap-6",
31794
31891
  xl: "gap-8"
31795
31892
  };
31796
- lookStyles5 = {
31893
+ lookStyles4 = {
31797
31894
  dense: "gap-2 [&>*]:p-card-sm",
31798
31895
  spacious: "gap-8 [&>*]:p-card-lg",
31799
31896
  striped: "[&>*:nth-child(even)]:bg-muted/30",
@@ -32220,6 +32317,133 @@ var init_DataList = __esm({
32220
32317
  DataList.displayName = "DataList";
32221
32318
  }
32222
32319
  });
32320
+ var FormSection, FormLayout, FormActions;
32321
+ var init_FormSection = __esm({
32322
+ "components/core/molecules/FormSection.tsx"() {
32323
+ "use client";
32324
+ init_cn();
32325
+ init_atoms();
32326
+ init_Box();
32327
+ init_Typography();
32328
+ init_Button();
32329
+ init_Stack();
32330
+ init_Icon();
32331
+ init_useEventBus();
32332
+ FormSection = ({
32333
+ title,
32334
+ description,
32335
+ children,
32336
+ collapsible = false,
32337
+ defaultCollapsed = false,
32338
+ card = false,
32339
+ columns = 1,
32340
+ className
32341
+ }) => {
32342
+ const [collapsed, setCollapsed] = React105__namespace.default.useState(defaultCollapsed);
32343
+ const { t } = hooks.useTranslate();
32344
+ const eventBus = useEventBus();
32345
+ const gridClass = {
32346
+ 1: "grid-cols-1",
32347
+ 2: "grid-cols-1 md:grid-cols-2",
32348
+ 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
32349
+ }[columns];
32350
+ React105__namespace.default.useCallback(() => {
32351
+ if (collapsible) {
32352
+ setCollapsed((prev) => !prev);
32353
+ eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
32354
+ }
32355
+ }, [collapsible, collapsed, eventBus]);
32356
+ const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
32357
+ (title || description) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "mb-4", children: [
32358
+ title && /* @__PURE__ */ jsxRuntime.jsxs(
32359
+ HStack,
32360
+ {
32361
+ justify: "between",
32362
+ align: "center",
32363
+ className: cn(collapsible && "cursor-pointer"),
32364
+ action: collapsible ? "TOGGLE_COLLAPSE" : void 0,
32365
+ children: [
32366
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h3", weight: "semibold", children: title }),
32367
+ collapsible && /* @__PURE__ */ jsxRuntime.jsx(
32368
+ Button,
32369
+ {
32370
+ variant: "ghost",
32371
+ size: "sm",
32372
+ action: "TOGGLE_COLLAPSE",
32373
+ children: /* @__PURE__ */ jsxRuntime.jsx(
32374
+ Icon,
32375
+ {
32376
+ icon: LucideIcons2.ChevronDown,
32377
+ size: "sm",
32378
+ className: cn(
32379
+ "text-muted-foreground transition-transform",
32380
+ collapsed && "rotate-180"
32381
+ )
32382
+ }
32383
+ )
32384
+ }
32385
+ )
32386
+ ]
32387
+ }
32388
+ ),
32389
+ description && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: description })
32390
+ ] }),
32391
+ (!collapsible || !collapsed) && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: cn("grid gap-4", gridClass), children })
32392
+ ] });
32393
+ if (card) {
32394
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: cn("p-6", className), children: content });
32395
+ }
32396
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className, children: content });
32397
+ };
32398
+ FormSection.displayName = "FormSection";
32399
+ FormLayout = ({
32400
+ children,
32401
+ dividers = true,
32402
+ className
32403
+ }) => {
32404
+ return /* @__PURE__ */ jsxRuntime.jsx(
32405
+ VStack,
32406
+ {
32407
+ gap: "lg",
32408
+ className: cn(
32409
+ dividers && "[&>*+*]:pt-8 [&>*+*]:border-t [&>*+*]:border-border",
32410
+ className
32411
+ ),
32412
+ children
32413
+ }
32414
+ );
32415
+ };
32416
+ FormLayout.displayName = "FormLayout";
32417
+ FormActions = ({
32418
+ children,
32419
+ sticky = false,
32420
+ align = "right",
32421
+ className
32422
+ }) => {
32423
+ const alignClass2 = {
32424
+ left: "justify-start",
32425
+ right: "justify-end",
32426
+ between: "justify-between",
32427
+ center: "justify-center"
32428
+ }[align];
32429
+ return /* @__PURE__ */ jsxRuntime.jsx(
32430
+ HStack,
32431
+ {
32432
+ gap: "sm",
32433
+ align: "center",
32434
+ className: cn(
32435
+ "pt-6 border-t border-border",
32436
+ alignClass2,
32437
+ sticky && "sticky bottom-0 bg-card py-4 -mx-6 px-6 shadow-[0_-4px_6px_-1px_rgb(0,0,0,0.05)]",
32438
+ className
32439
+ ),
32440
+ children
32441
+ }
32442
+ );
32443
+ };
32444
+ FormActions.displayName = "FormActions";
32445
+ }
32446
+ });
32223
32447
  function fileIcon(name) {
32224
32448
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
32225
32449
  switch (ext) {
@@ -32381,6 +32605,129 @@ var init_FormField = __esm({
32381
32605
  FormField.displayName = "FormField";
32382
32606
  }
32383
32607
  });
32608
+ function useSafeEventBus3() {
32609
+ try {
32610
+ return useEventBus();
32611
+ } catch {
32612
+ return { emit: () => {
32613
+ }, on: () => () => {
32614
+ }, once: () => {
32615
+ } };
32616
+ }
32617
+ }
32618
+ var log6, lookStyles5, ButtonGroup;
32619
+ var init_ButtonGroup = __esm({
32620
+ "components/core/molecules/ButtonGroup.tsx"() {
32621
+ "use client";
32622
+ init_cn();
32623
+ init_atoms();
32624
+ init_useEventBus();
32625
+ log6 = logger.createLogger("almadar:ui:button-group");
32626
+ lookStyles5 = {
32627
+ "right-aligned-buttons": "",
32628
+ "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
32629
+ "inline-row": "gap-2 inline-flex",
32630
+ "dropdown-menu": "[&>button:not(:first-child)]:hidden",
32631
+ "command-palette-trigger": "[&>button:not(:first-child)]:hidden"
32632
+ };
32633
+ ButtonGroup = ({
32634
+ children,
32635
+ primary,
32636
+ secondary,
32637
+ variant = "default",
32638
+ orientation = "horizontal",
32639
+ className,
32640
+ // Filter-group pattern props (entity and filters are used for schema-driven filtering)
32641
+ entity: _entity,
32642
+ filters,
32643
+ look = "right-aligned-buttons"
32644
+ }) => {
32645
+ const eventBus = useSafeEventBus3();
32646
+ const variantClasses2 = {
32647
+ default: "gap-0",
32648
+ 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",
32649
+ 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"
32650
+ };
32651
+ const orientationClasses = {
32652
+ horizontal: "flex-row",
32653
+ 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"
32654
+ };
32655
+ const handleActionClick = (action) => {
32656
+ if (action.event) {
32657
+ eventBus.emit("UI:DISPATCH", { event: action.event });
32658
+ }
32659
+ if (action.navigatesTo) {
32660
+ eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
32661
+ }
32662
+ };
32663
+ const renderFormActions = () => {
32664
+ const buttons = [];
32665
+ if (secondary) {
32666
+ secondary.forEach((action, index) => {
32667
+ buttons.push(
32668
+ /* @__PURE__ */ jsxRuntime.jsx(
32669
+ Button,
32670
+ {
32671
+ type: action.actionType === "submit" ? "submit" : "button",
32672
+ variant: action.variant || "ghost",
32673
+ onClick: () => handleActionClick(action),
32674
+ children: action.label
32675
+ },
32676
+ `secondary-${index}`
32677
+ )
32678
+ );
32679
+ });
32680
+ }
32681
+ if (primary) {
32682
+ const isSubmit = primary.actionType === "submit";
32683
+ buttons.push(
32684
+ /* @__PURE__ */ jsxRuntime.jsx(
32685
+ Button,
32686
+ {
32687
+ type: isSubmit ? "submit" : "button",
32688
+ variant: primary.variant || "primary",
32689
+ onClick: () => handleActionClick(primary),
32690
+ "data-testid": isSubmit ? "form-submit" : void 0,
32691
+ children: primary.label
32692
+ },
32693
+ "primary"
32694
+ )
32695
+ );
32696
+ }
32697
+ return buttons;
32698
+ };
32699
+ const renderFilters = () => {
32700
+ if (!filters || filters.length === 0) return null;
32701
+ return filters.map((filter, index) => /* @__PURE__ */ jsxRuntime.jsx(
32702
+ Button,
32703
+ {
32704
+ variant: "ghost",
32705
+ onClick: () => {
32706
+ log6.debug("Filter clicked", { field: filter.field });
32707
+ },
32708
+ children: filter.label
32709
+ },
32710
+ `filter-${filter.field}-${index}`
32711
+ ));
32712
+ };
32713
+ return /* @__PURE__ */ jsxRuntime.jsx(
32714
+ "div",
32715
+ {
32716
+ className: cn(
32717
+ "inline-flex gap-2",
32718
+ variantClasses2[variant],
32719
+ orientationClasses[orientation],
32720
+ lookStyles5[look],
32721
+ className
32722
+ ),
32723
+ role: "group",
32724
+ children: children || renderFilters() || renderFormActions()
32725
+ }
32726
+ );
32727
+ };
32728
+ ButtonGroup.displayName = "ButtonGroup";
32729
+ }
32730
+ });
32384
32731
  var resolveFilterType, lookStyles6, FilterGroup;
32385
32732
  var init_FilterGroup = __esm({
32386
32733
  "components/core/molecules/FilterGroup.tsx"() {
@@ -34858,6 +35205,234 @@ var init_ProgressDots = __esm({
34858
35205
  ProgressDots.displayName = "ProgressDots";
34859
35206
  }
34860
35207
  });
35208
+ var MathCanvas;
35209
+ var init_MathCanvas = __esm({
35210
+ "components/learning/molecules/MathCanvas.tsx"() {
35211
+ "use client";
35212
+ init_atoms();
35213
+ init_Stack();
35214
+ init_LearningCanvas();
35215
+ MathCanvas = ({
35216
+ className,
35217
+ width = 600,
35218
+ height = 400,
35219
+ title,
35220
+ xMin = -10,
35221
+ xMax = 10,
35222
+ yMin = -10,
35223
+ yMax = 10,
35224
+ showAxes = true,
35225
+ showGrid = true,
35226
+ gridStep = 1,
35227
+ curves = [],
35228
+ points = [],
35229
+ vectors = [],
35230
+ shapes = [],
35231
+ interactive = false,
35232
+ animate = false,
35233
+ onShapeClick,
35234
+ isLoading,
35235
+ error
35236
+ }) => {
35237
+ const derivedShapes = React105.useMemo(() => {
35238
+ const out = [];
35239
+ const margin = 24;
35240
+ const plotW = width - margin * 2;
35241
+ const plotH = height - margin * 2;
35242
+ const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
35243
+ const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
35244
+ if (showGrid) {
35245
+ for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
35246
+ const px = mapX(x);
35247
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#e5e7eb", lineWidth: 1 });
35248
+ }
35249
+ for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
35250
+ const py = mapY(y);
35251
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
35252
+ }
35253
+ }
35254
+ if (showAxes) {
35255
+ const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
35256
+ const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
35257
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
35258
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
35259
+ }
35260
+ for (const curve of curves) {
35261
+ if (!curve.samples || curve.samples.length < 2) continue;
35262
+ for (let i = 1; i < curve.samples.length; i++) {
35263
+ const a = curve.samples[i - 1];
35264
+ const b = curve.samples[i];
35265
+ if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
35266
+ out.push({
35267
+ type: "line",
35268
+ x1: mapX(a.x),
35269
+ y1: mapY(a.y),
35270
+ x2: mapX(b.x),
35271
+ y2: mapY(b.y),
35272
+ color: curve.color ?? "#2563eb",
35273
+ lineWidth: 2
35274
+ });
35275
+ }
35276
+ }
35277
+ for (const p2 of points) {
35278
+ if (p2.x < xMin || p2.x > xMax || p2.y < yMin || p2.y > yMax) continue;
35279
+ out.push({
35280
+ type: "circle",
35281
+ x: mapX(p2.x),
35282
+ y: mapY(p2.y),
35283
+ radius: p2.radius ?? 4,
35284
+ color: p2.color ?? "#dc2626",
35285
+ fill: p2.color ?? "#dc2626"
35286
+ });
35287
+ if (p2.label) {
35288
+ out.push({ type: "text", x: mapX(p2.x) + 8, y: mapY(p2.y) - 8, text: p2.label, color: "#111827", fontSize: 12 });
35289
+ }
35290
+ }
35291
+ for (const v of vectors) {
35292
+ if (v.x < xMin || v.x > xMax || v.y < yMin || v.y > yMax) continue;
35293
+ const x1 = mapX(v.x);
35294
+ const y1 = mapY(v.y);
35295
+ const x2 = mapX(v.x + v.vx);
35296
+ const y2 = mapY(v.y + v.vy);
35297
+ out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
35298
+ if (v.label) {
35299
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
35300
+ }
35301
+ }
35302
+ out.push(...shapes);
35303
+ return out;
35304
+ }, [width, height, xMin, xMax, yMin, yMax, showAxes, showGrid, gridStep, curves, points, vectors, shapes]);
35305
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
35306
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
35307
+ /* @__PURE__ */ jsxRuntime.jsx(
35308
+ LearningCanvas,
35309
+ {
35310
+ width,
35311
+ height,
35312
+ shapes: derivedShapes,
35313
+ interactive,
35314
+ animate,
35315
+ onShapeClick,
35316
+ isLoading,
35317
+ error
35318
+ }
35319
+ )
35320
+ ] }) });
35321
+ };
35322
+ }
35323
+ });
35324
+ var PhysicsCanvas;
35325
+ var init_PhysicsCanvas = __esm({
35326
+ "components/learning/molecules/PhysicsCanvas.tsx"() {
35327
+ "use client";
35328
+ init_atoms();
35329
+ init_Stack();
35330
+ init_LearningCanvas();
35331
+ PhysicsCanvas = ({
35332
+ className,
35333
+ width = 600,
35334
+ height = 400,
35335
+ title,
35336
+ backgroundColor,
35337
+ bodies = [],
35338
+ constraints = [],
35339
+ showVelocity = true,
35340
+ showForces = false,
35341
+ velocityScale = 20,
35342
+ forceScale = 20,
35343
+ shapes = [],
35344
+ interactive = false,
35345
+ animate = false,
35346
+ onShapeClick,
35347
+ isLoading,
35348
+ error
35349
+ }) => {
35350
+ const derivedShapes = React105.useMemo(() => {
35351
+ const out = [];
35352
+ const bodyById = /* @__PURE__ */ new Map();
35353
+ for (const b of bodies) {
35354
+ if (b.id) bodyById.set(b.id, b);
35355
+ }
35356
+ for (const c of constraints) {
35357
+ const a = bodyById.get(c.from);
35358
+ const b = bodyById.get(c.to);
35359
+ if (!a || !b) continue;
35360
+ out.push({
35361
+ type: "line",
35362
+ x1: a.x,
35363
+ y1: a.y,
35364
+ x2: b.x,
35365
+ y2: b.y,
35366
+ color: c.color ?? "#9ca3af",
35367
+ lineWidth: 2
35368
+ });
35369
+ }
35370
+ for (const b of bodies) {
35371
+ out.push({
35372
+ type: "circle",
35373
+ x: b.x,
35374
+ y: b.y,
35375
+ radius: b.radius ?? 12,
35376
+ color: b.color ?? "#2563eb",
35377
+ fill: b.color ?? "#2563eb",
35378
+ id: b.id
35379
+ });
35380
+ if (b.label) {
35381
+ out.push({
35382
+ type: "text",
35383
+ x: b.x + (b.radius ?? 12) + 6,
35384
+ y: b.y - (b.radius ?? 12) - 6,
35385
+ text: b.label,
35386
+ color: "#111827",
35387
+ fontSize: 12
35388
+ });
35389
+ }
35390
+ if (showVelocity && b.vx != null && b.vy != null && (b.vx !== 0 || b.vy !== 0)) {
35391
+ out.push({
35392
+ type: "arrow",
35393
+ x1: b.x,
35394
+ y1: b.y,
35395
+ x2: b.x + b.vx * velocityScale,
35396
+ y2: b.y + b.vy * velocityScale,
35397
+ color: "#16a34a",
35398
+ lineWidth: 2
35399
+ });
35400
+ }
35401
+ if (showForces && b.fx != null && b.fy != null && (b.fx !== 0 || b.fy !== 0)) {
35402
+ out.push({
35403
+ type: "arrow",
35404
+ x1: b.x,
35405
+ y1: b.y,
35406
+ x2: b.x + b.fx * forceScale,
35407
+ y2: b.y + b.fy * forceScale,
35408
+ color: "#dc2626",
35409
+ lineWidth: 2
35410
+ });
35411
+ }
35412
+ }
35413
+ out.push(...shapes);
35414
+ return out;
35415
+ }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
35416
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
35417
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
35418
+ /* @__PURE__ */ jsxRuntime.jsx(
35419
+ LearningCanvas,
35420
+ {
35421
+ width,
35422
+ height,
35423
+ backgroundColor,
35424
+ shapes: derivedShapes,
35425
+ interactive,
35426
+ animate,
35427
+ onShapeClick,
35428
+ isLoading,
35429
+ error
35430
+ }
35431
+ )
35432
+ ] }) });
35433
+ };
35434
+ }
35435
+ });
34861
35436
  function resolveNodeColor(node, groups) {
34862
35437
  if (node.color) return node.color;
34863
35438
  if (node.group) {
@@ -35229,13 +35804,13 @@ var init_MapView = __esm({
35229
35804
  shadowSize: [41, 41]
35230
35805
  });
35231
35806
  L.Marker.prototype.options.icon = defaultIcon;
35232
- const { useEffect: useEffect74, useRef: useRef76, useCallback: useCallback112, useState: useState107 } = React105__namespace.default;
35807
+ const { useEffect: useEffect75, useRef: useRef77, useCallback: useCallback112, useState: useState107 } = React105__namespace.default;
35233
35808
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
35234
35809
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
35235
35810
  function MapUpdater({ centerLat, centerLng, zoom }) {
35236
35811
  const map = useMap();
35237
- const prevRef = useRef76({ centerLat, centerLng, zoom });
35238
- useEffect74(() => {
35812
+ const prevRef = useRef77({ centerLat, centerLng, zoom });
35813
+ useEffect75(() => {
35239
35814
  const prev = prevRef.current;
35240
35815
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
35241
35816
  map.setView([centerLat, centerLng], zoom);
@@ -35246,7 +35821,7 @@ var init_MapView = __esm({
35246
35821
  }
35247
35822
  function MapClickHandler({ onMapClick }) {
35248
35823
  const map = useMap();
35249
- useEffect74(() => {
35824
+ useEffect75(() => {
35250
35825
  if (!onMapClick) return;
35251
35826
  const handler = (e) => {
35252
35827
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -43549,7 +44124,7 @@ function getGroupColor(group, groups) {
43549
44124
  const idx = groups.indexOf(group);
43550
44125
  return GROUP_COLORS2[idx % GROUP_COLORS2.length];
43551
44126
  }
43552
- function resolveColor2(color, el) {
44127
+ function resolveColor3(color, el) {
43553
44128
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
43554
44129
  if (!m) return color;
43555
44130
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
@@ -43810,7 +44385,7 @@ var init_GraphCanvas = __esm({
43810
44385
  const w = logicalW;
43811
44386
  const h = height;
43812
44387
  const nodes = nodesRef.current;
43813
- const accentColor = resolveColor2("var(--color-accent)", canvas);
44388
+ const accentColor = resolveColor3("var(--color-accent)", canvas);
43814
44389
  const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
43815
44390
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
43816
44391
  ctx.clearRect(0, 0, w, h);
@@ -43849,7 +44424,7 @@ var init_GraphCanvas = __esm({
43849
44424
  ctx.globalAlpha = 1;
43850
44425
  for (const node of nodes) {
43851
44426
  const size = node.size || 8;
43852
- const color = resolveColor2(node.color || getGroupColor(node.group, groups), canvas);
44427
+ const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
43853
44428
  const isHovered = hoveredNode === node.id;
43854
44429
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
43855
44430
  ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
@@ -50747,6 +51322,7 @@ var init_component_registry_generated = __esm({
50747
51322
  init_BattleBoard();
50748
51323
  init_BattleTemplate();
50749
51324
  init_BehaviorView();
51325
+ init_BiologyCanvas();
50750
51326
  init_BloomQuizBlock();
50751
51327
  init_BoardgameBoard();
50752
51328
  init_BookChapterView();
@@ -50758,7 +51334,6 @@ var init_component_registry_generated = __esm({
50758
51334
  init_BranchingLogicBuilder();
50759
51335
  init_Breadcrumb();
50760
51336
  init_BuilderBoard();
50761
- init_ButtonGroup();
50762
51337
  init_CTABanner();
50763
51338
  init_CalendarGrid();
50764
51339
  init_Canvas2D();
@@ -50777,6 +51352,7 @@ var init_component_registry_generated = __esm({
50777
51352
  init_ChartLegend();
50778
51353
  init_ChatBar();
50779
51354
  init_Checkbox();
51355
+ init_ChemistryCanvas();
50780
51356
  init_ChoiceButton();
50781
51357
  init_CityBuilderBoard();
50782
51358
  init_CityBuilderTemplate();
@@ -50835,9 +51411,9 @@ var init_component_registry_generated = __esm({
50835
51411
  init_FlipContainer();
50836
51412
  init_FloatingActionButton();
50837
51413
  init_Form();
51414
+ init_FormSection();
50838
51415
  init_FormField();
50839
51416
  init_FormSectionHeader();
50840
- init_GameAudioProvider();
50841
51417
  init_GameAudioToggle();
50842
51418
  init_GameCard();
50843
51419
  init_GameHud();
@@ -50868,6 +51444,7 @@ var init_component_registry_generated = __esm({
50868
51444
  init_JazariStateMachine();
50869
51445
  init_LandingPageTemplate();
50870
51446
  init_LawReferenceTooltip();
51447
+ init_LearningCanvas();
50871
51448
  init_Lightbox();
50872
51449
  init_LikertScale();
50873
51450
  init_LineChart();
@@ -50876,9 +51453,11 @@ var init_component_registry_generated = __esm({
50876
51453
  init_MapView();
50877
51454
  init_MarkdownContent();
50878
51455
  init_MarketingFooter();
51456
+ init_MarketingStatCard();
50879
51457
  init_MasterDetail();
50880
51458
  init_MasterDetailLayout();
50881
51459
  init_MatchPuzzleBoard();
51460
+ init_MathCanvas();
50882
51461
  init_MatrixQuestion();
50883
51462
  init_MediaGallery();
50884
51463
  init_Menu();
@@ -50897,6 +51476,7 @@ var init_component_registry_generated = __esm({
50897
51476
  init_PageHeader();
50898
51477
  init_Pagination();
50899
51478
  init_PatternTile();
51479
+ init_PhysicsCanvas();
50900
51480
  init_PinballBoard();
50901
51481
  init_PirateBoard();
50902
51482
  init_PlatformerBoard();
@@ -51073,6 +51653,7 @@ var init_component_registry_generated = __esm({
51073
51653
  "BattleBoard": BattleBoard,
51074
51654
  "BattleTemplate": BattleTemplate,
51075
51655
  "BehaviorView": BehaviorView,
51656
+ "BiologyCanvas": BiologyCanvas,
51076
51657
  "BloomQuizBlock": BloomQuizBlock,
51077
51658
  "BoardgameBoard": BoardgameBoard,
51078
51659
  "BookChapterView": BookChapterView,
@@ -51086,7 +51667,6 @@ var init_component_registry_generated = __esm({
51086
51667
  "Breadcrumb": Breadcrumb,
51087
51668
  "BuilderBoard": BuilderBoard,
51088
51669
  "Button": ButtonPattern,
51089
- "ButtonGroup": ButtonGroup,
51090
51670
  "ButtonPattern": ButtonPattern,
51091
51671
  "CTABanner": CTABanner,
51092
51672
  "CalendarGrid": CalendarGrid,
@@ -51106,6 +51686,7 @@ var init_component_registry_generated = __esm({
51106
51686
  "ChartLegend": ChartLegend,
51107
51687
  "ChatBar": ChatBar,
51108
51688
  "Checkbox": Checkbox,
51689
+ "ChemistryCanvas": ChemistryCanvas,
51109
51690
  "ChoiceButton": ChoiceButton,
51110
51691
  "CityBuilderBoard": CityBuilderBoard,
51111
51692
  "CityBuilderTemplate": CityBuilderTemplate,
@@ -51166,9 +51747,10 @@ var init_component_registry_generated = __esm({
51166
51747
  "FlipContainer": FlipContainer,
51167
51748
  "FloatingActionButton": FloatingActionButton,
51168
51749
  "Form": Form,
51750
+ "FormActions": FormActions,
51169
51751
  "FormField": FormField,
51752
+ "FormLayout": FormLayout,
51170
51753
  "FormSectionHeader": FormSectionHeader,
51171
- "GameAudioProvider": GameAudioProvider,
51172
51754
  "GameAudioToggle": GameAudioToggle,
51173
51755
  "GameBoard3D": GameBoard3D,
51174
51756
  "GameCanvas3D": GameCanvas3D,
@@ -51206,6 +51788,7 @@ var init_component_registry_generated = __esm({
51206
51788
  "LabelPattern": LabelPattern,
51207
51789
  "LandingPageTemplate": LandingPageTemplate,
51208
51790
  "LawReferenceTooltip": LawReferenceTooltip,
51791
+ "LearningCanvas": LearningCanvas,
51209
51792
  "Lightbox": Lightbox,
51210
51793
  "LikertScale": LikertScale,
51211
51794
  "LineChart": LineChart2,
@@ -51214,9 +51797,11 @@ var init_component_registry_generated = __esm({
51214
51797
  "MapView": MapView,
51215
51798
  "MarkdownContent": MarkdownContent,
51216
51799
  "MarketingFooter": MarketingFooter,
51800
+ "MarketingStatCard": MarketingStatCard,
51217
51801
  "MasterDetail": MasterDetail,
51218
51802
  "MasterDetailLayout": MasterDetailLayout,
51219
51803
  "MatchPuzzleBoard": MatchPuzzleBoard,
51804
+ "MathCanvas": MathCanvas,
51220
51805
  "MatrixQuestion": MatrixQuestion,
51221
51806
  "MediaGallery": MediaGallery,
51222
51807
  "Menu": Menu,
@@ -51235,6 +51820,7 @@ var init_component_registry_generated = __esm({
51235
51820
  "PageHeader": PageHeader,
51236
51821
  "Pagination": Pagination,
51237
51822
  "PatternTile": PatternTile,
51823
+ "PhysicsCanvas": PhysicsCanvas,
51238
51824
  "PinballBoard": PinballBoard,
51239
51825
  "PirateBoard": PirateBoard,
51240
51826
  "PlatformerBoard": PlatformerBoard,