@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
@@ -6,7 +6,7 @@ import { createLogger, isLogLevelEnabled } from '@almadar/logger';
6
6
  import { clsx } from 'clsx';
7
7
  import { twMerge } from 'tailwind-merge';
8
8
  import * as LucideIcons2 from 'lucide-react';
9
- import { Loader2, X, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, Code, FileText, WrapText, Check, Copy, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ZoomOut, ZoomIn, Download, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, ArrowLeft, HelpCircle, PauseCircle, Search, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, Bug, Send, ChevronUp, ChevronDown, Wrench, Tag, User, DollarSign } from 'lucide-react';
9
+ import { Loader2, X, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, Code, FileText, WrapText, Check, Copy, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ZoomOut, ZoomIn, Download, ChevronDown, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, ArrowLeft, HelpCircle, PauseCircle, Search, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, Bug, Send, ChevronUp, Wrench, Tag, User, DollarSign } from 'lucide-react';
10
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
11
  import { createPortal } from 'react-dom';
12
12
  import { useTranslate } from '@almadar/ui/hooks';
@@ -6180,6 +6180,66 @@ var init_RangeSlider = __esm({
6180
6180
  RangeSlider.displayName = "RangeSlider";
6181
6181
  }
6182
6182
  });
6183
+ function easeOut(t) {
6184
+ return t * (2 - t);
6185
+ }
6186
+ var AnimatedCounter;
6187
+ var init_AnimatedCounter = __esm({
6188
+ "components/marketing/atoms/AnimatedCounter.tsx"() {
6189
+ "use client";
6190
+ init_cn();
6191
+ init_Typography();
6192
+ AnimatedCounter = ({
6193
+ value: rawValue,
6194
+ duration = 600,
6195
+ prefix,
6196
+ suffix,
6197
+ className
6198
+ }) => {
6199
+ const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
6200
+ const value = !Number.isNaN(numericRaw) ? numericRaw : 0;
6201
+ const [displayValue, setDisplayValue] = useState(value);
6202
+ const previousValueRef = useRef(value);
6203
+ const animationFrameRef = useRef(null);
6204
+ useEffect(() => {
6205
+ const from = previousValueRef.current;
6206
+ const to = value;
6207
+ previousValueRef.current = value;
6208
+ if (from === to) {
6209
+ setDisplayValue(to);
6210
+ return;
6211
+ }
6212
+ const startTime = performance.now();
6213
+ const diff = to - from;
6214
+ function animate(currentTime) {
6215
+ const elapsed = currentTime - startTime;
6216
+ const progress = Math.min(elapsed / duration, 1);
6217
+ const easedProgress = easeOut(progress);
6218
+ setDisplayValue(from + diff * easedProgress);
6219
+ if (progress < 1) {
6220
+ animationFrameRef.current = requestAnimationFrame(animate);
6221
+ } else {
6222
+ setDisplayValue(to);
6223
+ }
6224
+ }
6225
+ animationFrameRef.current = requestAnimationFrame(animate);
6226
+ return () => {
6227
+ if (animationFrameRef.current !== null) {
6228
+ cancelAnimationFrame(animationFrameRef.current);
6229
+ }
6230
+ };
6231
+ }, [value, duration]);
6232
+ const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
6233
+ const formattedValue = displayValue.toFixed(decimalPlaces);
6234
+ return /* @__PURE__ */ jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
6235
+ prefix,
6236
+ formattedValue,
6237
+ suffix
6238
+ ] });
6239
+ };
6240
+ AnimatedCounter.displayName = "AnimatedCounter";
6241
+ }
6242
+ });
6183
6243
  var InfiniteScrollSentinel;
6184
6244
  var init_InfiniteScrollSentinel = __esm({
6185
6245
  "components/core/atoms/InfiniteScrollSentinel.tsx"() {
@@ -6441,6 +6501,34 @@ var init_SectionHeader = __esm({
6441
6501
  SectionHeader.displayName = "SectionHeader";
6442
6502
  }
6443
6503
  });
6504
+ var sizeClasses6, MarketingStatCard;
6505
+ var init_MarketingStatCard = __esm({
6506
+ "components/marketing/atoms/MarketingStatCard.tsx"() {
6507
+ init_cn();
6508
+ init_Stack();
6509
+ init_Typography();
6510
+ sizeClasses6 = {
6511
+ sm: "text-2xl",
6512
+ md: "text-4xl",
6513
+ lg: "text-5xl"
6514
+ };
6515
+ MarketingStatCard = ({ value, label, size = "md", className }) => {
6516
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", align: "center", className: cn(className), children: [
6517
+ /* @__PURE__ */ jsx(
6518
+ Typography,
6519
+ {
6520
+ weight: "bold",
6521
+ color: "primary",
6522
+ className: cn(sizeClasses6[size]),
6523
+ children: value
6524
+ }
6525
+ ),
6526
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: label })
6527
+ ] });
6528
+ };
6529
+ MarketingStatCard.displayName = "MarketingStatCard";
6530
+ }
6531
+ });
6444
6532
  var backgroundClasses, paddingClasses, ContentSection;
6445
6533
  var init_ContentSection = __esm({
6446
6534
  "components/marketing/atoms/ContentSection.tsx"() {
@@ -12455,318 +12543,14 @@ var init_useCanvasEffects = __esm({
12455
12543
  "use client";
12456
12544
  }
12457
12545
  });
12458
- function pickPath(entry) {
12459
- if (Array.isArray(entry.path)) {
12460
- return entry.path[Math.floor(Math.random() * entry.path.length)];
12461
- }
12462
- return entry.path;
12463
- }
12464
- function useGameAudio({
12465
- manifest,
12466
- baseUrl = "",
12467
- initialMuted = false,
12468
- initialVolume = 1
12469
- }) {
12470
- const [muted, setMutedState] = useState(initialMuted);
12471
- const [masterVolume, setMasterVolumeState] = useState(initialVolume);
12472
- const mutedRef = useRef(muted);
12473
- const volumeRef = useRef(masterVolume);
12474
- const manifestRef = useRef(manifest);
12475
- mutedRef.current = muted;
12476
- volumeRef.current = masterVolume;
12477
- manifestRef.current = manifest;
12478
- const poolsRef = useRef(/* @__PURE__ */ new Map());
12479
- const getOrCreateElement = useCallback((key) => {
12480
- const entry = manifestRef.current[key];
12481
- if (!entry) return null;
12482
- let pool = poolsRef.current.get(key);
12483
- if (!pool) {
12484
- pool = [];
12485
- poolsRef.current.set(key, pool);
12486
- }
12487
- const maxSize = entry.poolSize ?? 1;
12488
- for (const audio of pool) {
12489
- if (audio.paused && (audio.ended || audio.currentTime === 0)) {
12490
- return audio;
12491
- }
12492
- }
12493
- if (pool.length < maxSize) {
12494
- const src = baseUrl + pickPath(entry);
12495
- const audio = new Audio(src);
12496
- audio.loop = entry.loop ?? false;
12497
- pool.push(audio);
12498
- return audio;
12499
- }
12500
- if (!entry.loop) {
12501
- let oldest = pool[0];
12502
- for (const audio of pool) {
12503
- if (audio.currentTime > oldest.currentTime) {
12504
- oldest = audio;
12505
- }
12506
- }
12507
- oldest.pause();
12508
- oldest.currentTime = 0;
12509
- return oldest;
12510
- }
12511
- return null;
12512
- }, [baseUrl]);
12513
- const play = useCallback((key) => {
12514
- if (mutedRef.current) return;
12515
- const entry = manifestRef.current[key];
12516
- if (!entry) return;
12517
- const audio = getOrCreateElement(key);
12518
- if (!audio) return;
12519
- audio.volume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
12520
- if (!entry.loop) {
12521
- audio.currentTime = 0;
12522
- }
12523
- const promise = audio.play();
12524
- if (promise) {
12525
- promise.catch(() => {
12526
- });
12527
- }
12528
- }, [getOrCreateElement]);
12529
- const stop = useCallback((key) => {
12530
- const pool = poolsRef.current.get(key);
12531
- if (!pool) return;
12532
- for (const audio of pool) {
12533
- audio.pause();
12534
- audio.currentTime = 0;
12535
- }
12536
- }, []);
12537
- const currentMusicKeyRef = useRef(null);
12538
- const currentMusicElRef = useRef(null);
12539
- const musicFadeRef = useRef(null);
12540
- const pendingMusicKeyRef = useRef(null);
12541
- const clearMusicFade = useCallback(() => {
12542
- if (musicFadeRef.current) {
12543
- clearInterval(musicFadeRef.current);
12544
- musicFadeRef.current = null;
12545
- }
12546
- }, []);
12547
- const playMusic = useCallback((key) => {
12548
- if (key === currentMusicKeyRef.current) return;
12549
- pendingMusicKeyRef.current = key;
12550
- const entry = manifestRef.current[key];
12551
- if (!entry) return;
12552
- const fadeDurationMs = entry.crossfadeDurationMs ?? 1500;
12553
- const stepMs = 50;
12554
- const totalSteps = Math.max(1, fadeDurationMs / stepMs);
12555
- const targetVolume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
12556
- clearMusicFade();
12557
- const src = baseUrl + (Array.isArray(entry.path) ? entry.path[0] : entry.path);
12558
- const incoming = new Audio(src);
12559
- incoming.loop = true;
12560
- incoming.volume = 0;
12561
- const outgoing = currentMusicElRef.current;
12562
- const outgoingStartVol = outgoing?.volume ?? 0;
12563
- currentMusicKeyRef.current = key;
12564
- currentMusicElRef.current = incoming;
12565
- if (!mutedRef.current) {
12566
- incoming.play().catch(() => {
12567
- currentMusicKeyRef.current = null;
12568
- currentMusicElRef.current = outgoing;
12569
- });
12570
- }
12571
- let step = 0;
12572
- musicFadeRef.current = setInterval(() => {
12573
- step++;
12574
- const progress = Math.min(step / totalSteps, 1);
12575
- incoming.volume = Math.min(1, targetVolume * progress);
12576
- if (outgoing) {
12577
- outgoing.volume = Math.max(0, outgoingStartVol * (1 - progress));
12578
- }
12579
- if (progress >= 1) {
12580
- clearMusicFade();
12581
- if (outgoing) {
12582
- outgoing.pause();
12583
- outgoing.src = "";
12584
- }
12585
- }
12586
- }, stepMs);
12587
- }, [baseUrl, clearMusicFade]);
12588
- const stopMusic = useCallback((fadeDurationMs = 1e3) => {
12589
- const outgoing = currentMusicElRef.current;
12590
- if (!outgoing) return;
12591
- currentMusicKeyRef.current = null;
12592
- currentMusicElRef.current = null;
12593
- pendingMusicKeyRef.current = null;
12594
- clearMusicFade();
12595
- const startVolume = outgoing.volume;
12596
- const stepMs = 50;
12597
- const totalSteps = Math.max(1, fadeDurationMs / stepMs);
12598
- let step = 0;
12599
- musicFadeRef.current = setInterval(() => {
12600
- step++;
12601
- const progress = step / totalSteps;
12602
- outgoing.volume = Math.max(0, startVolume * (1 - progress));
12603
- if (progress >= 1) {
12604
- clearMusicFade();
12605
- outgoing.pause();
12606
- outgoing.src = "";
12607
- }
12608
- }, stepMs);
12609
- }, [clearMusicFade]);
12610
- const stopAll = useCallback(() => {
12611
- for (const pool of poolsRef.current.values()) {
12612
- for (const audio of pool) {
12613
- audio.pause();
12614
- audio.currentTime = 0;
12615
- }
12616
- }
12617
- stopMusic(0);
12618
- }, [stopMusic]);
12619
- const setMuted = useCallback((value) => {
12620
- setMutedState(value);
12621
- if (value) {
12622
- for (const [key, pool] of poolsRef.current.entries()) {
12623
- if (manifestRef.current[key]?.loop) {
12624
- for (const audio of pool) {
12625
- if (!audio.paused) audio.pause();
12626
- }
12627
- }
12628
- }
12629
- currentMusicElRef.current?.pause();
12630
- } else {
12631
- for (const [key, pool] of poolsRef.current.entries()) {
12632
- const entry = manifestRef.current[key];
12633
- if (entry?.loop && entry?.autostart) {
12634
- for (const audio of pool) {
12635
- if (audio.paused) audio.play().catch(() => {
12636
- });
12637
- }
12638
- }
12639
- }
12640
- const musicEl = currentMusicElRef.current;
12641
- if (musicEl) {
12642
- musicEl.play().catch(() => {
12643
- });
12644
- }
12645
- }
12646
- }, []);
12647
- const setMasterVolume = useCallback((volume) => {
12648
- const clamped = Math.max(0, Math.min(1, volume));
12649
- setMasterVolumeState(clamped);
12650
- for (const [key, pool] of poolsRef.current.entries()) {
12651
- const entryVol = manifestRef.current[key]?.volume ?? 1;
12652
- for (const audio of pool) {
12653
- audio.volume = Math.min(1, entryVol * clamped);
12654
- }
12655
- }
12656
- if (!musicFadeRef.current && currentMusicElRef.current) {
12657
- const key = currentMusicKeyRef.current;
12658
- const entryVol = key ? manifestRef.current[key]?.volume ?? 1 : 1;
12659
- currentMusicElRef.current.volume = Math.min(1, entryVol * clamped);
12660
- }
12661
- }, []);
12662
- const unlockedRef = useRef(false);
12663
- useEffect(() => {
12664
- const autoKeys = Object.keys(manifest).filter((k) => manifest[k].autostart);
12665
- const hasPendingMusic = () => pendingMusicKeyRef.current !== null;
12666
- const hasAutoStart = autoKeys.length > 0;
12667
- if (!hasAutoStart && !hasPendingMusic()) return;
12668
- const unlock = () => {
12669
- if (unlockedRef.current) return;
12670
- unlockedRef.current = true;
12671
- if (!mutedRef.current) {
12672
- for (const key of autoKeys) {
12673
- play(key);
12674
- }
12675
- const pending = pendingMusicKeyRef.current;
12676
- if (pending && pending !== currentMusicKeyRef.current) {
12677
- playMusic(pending);
12678
- }
12679
- }
12680
- };
12681
- document.addEventListener("click", unlock, { once: true });
12682
- document.addEventListener("keydown", unlock, { once: true });
12683
- document.addEventListener("touchstart", unlock, { once: true });
12684
- return () => {
12685
- document.removeEventListener("click", unlock);
12686
- document.removeEventListener("keydown", unlock);
12687
- document.removeEventListener("touchstart", unlock);
12688
- };
12689
- }, [manifest, play, playMusic]);
12690
- useEffect(() => {
12691
- return () => {
12692
- clearMusicFade();
12693
- for (const pool of poolsRef.current.values()) {
12694
- for (const audio of pool) {
12695
- audio.pause();
12696
- audio.src = "";
12697
- }
12698
- }
12699
- poolsRef.current.clear();
12700
- if (currentMusicElRef.current) {
12701
- currentMusicElRef.current.pause();
12702
- currentMusicElRef.current.src = "";
12703
- currentMusicElRef.current = null;
12704
- }
12705
- };
12706
- }, [clearMusicFade]);
12707
- return {
12708
- play,
12709
- stop,
12710
- stopAll,
12711
- playMusic,
12712
- stopMusic,
12713
- muted,
12714
- setMuted,
12715
- masterVolume,
12716
- setMasterVolume
12717
- };
12718
- }
12719
12546
  var init_useGameAudio = __esm({
12720
12547
  "components/game/shared/hooks/useGameAudio.ts"() {
12721
12548
  "use client";
12722
- useGameAudio.displayName = "useGameAudio";
12723
12549
  }
12724
12550
  });
12725
12551
  function useGameAudioContextOptional() {
12726
12552
  return useContext(GameAudioContext);
12727
12553
  }
12728
- function GameAudioProvider({
12729
- manifest,
12730
- baseUrl = "",
12731
- children,
12732
- initialMuted = false
12733
- }) {
12734
- const eventBus = useEventBus();
12735
- const { play, stop, stopAll, playMusic, stopMusic, muted, setMuted, masterVolume, setMasterVolume } = useGameAudio({ manifest, baseUrl, initialMuted });
12736
- useEffect(() => {
12737
- const unsubPlay = eventBus.on("UI:PLAY_SOUND", (event) => {
12738
- const key = event.payload?.key;
12739
- if (key) play(key);
12740
- });
12741
- const unsubStop = eventBus.on("UI:STOP_SOUND", (event) => {
12742
- const key = event.payload?.key;
12743
- if (key) {
12744
- stop(key);
12745
- } else {
12746
- stopAll();
12747
- }
12748
- });
12749
- const unsubChangeMusic = eventBus.on("UI:CHANGE_MUSIC", (event) => {
12750
- const key = event.payload?.key;
12751
- if (key) {
12752
- playMusic(key);
12753
- } else {
12754
- stopMusic();
12755
- }
12756
- });
12757
- const unsubStopMusic = eventBus.on("UI:STOP_MUSIC", () => {
12758
- stopMusic();
12759
- });
12760
- return () => {
12761
- unsubPlay();
12762
- unsubStop();
12763
- unsubChangeMusic();
12764
- unsubStopMusic();
12765
- };
12766
- }, [eventBus, play, stop, stopAll, playMusic, stopMusic]);
12767
- const value = { muted, setMuted, masterVolume, setMasterVolume, play, playMusic, stopMusic };
12768
- return /* @__PURE__ */ jsx(GameAudioContext.Provider, { value, children });
12769
- }
12770
12554
  var GameAudioContext;
12771
12555
  var init_GameAudioProvider = __esm({
12772
12556
  "components/game/shared/providers/GameAudioProvider.tsx"() {
@@ -12775,7 +12559,6 @@ var init_GameAudioProvider = __esm({
12775
12559
  init_useGameAudio();
12776
12560
  GameAudioContext = createContext(null);
12777
12561
  GameAudioContext.displayName = "GameAudioContext";
12778
- GameAudioProvider.displayName = "GameAudioProvider";
12779
12562
  }
12780
12563
  });
12781
12564
  function GameAudioToggle({
@@ -20411,6 +20194,315 @@ var init_molecules = __esm({
20411
20194
  init_puzzleObject();
20412
20195
  }
20413
20196
  });
20197
+ function resolveColor2(color, ctx, fallback) {
20198
+ if (!color) return fallback;
20199
+ if (color.startsWith("var(")) {
20200
+ ctx.canvas.style;
20201
+ const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color);
20202
+ if (m) {
20203
+ const computed = getComputedStyle(ctx.canvas).getPropertyValue(m[1]).trim();
20204
+ return computed || m[2]?.trim() || fallback;
20205
+ }
20206
+ }
20207
+ return color;
20208
+ }
20209
+ function shapeBounds(shape) {
20210
+ switch (shape.type) {
20211
+ case "line":
20212
+ case "arrow":
20213
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) return null;
20214
+ return {
20215
+ x: Math.min(shape.x1, shape.x2) - 6,
20216
+ y: Math.min(shape.y1, shape.y2) - 6,
20217
+ w: Math.abs(shape.x2 - shape.x1) + 12,
20218
+ h: Math.abs(shape.y2 - shape.y1) + 12
20219
+ };
20220
+ case "circle":
20221
+ if (shape.x == null || shape.y == null || shape.radius == null) return null;
20222
+ return {
20223
+ x: shape.x - shape.radius - 4,
20224
+ y: shape.y - shape.radius - 4,
20225
+ w: shape.radius * 2 + 8,
20226
+ h: shape.radius * 2 + 8
20227
+ };
20228
+ case "rect":
20229
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
20230
+ return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
20231
+ case "polygon":
20232
+ if (!shape.points || shape.points.length === 0) return null;
20233
+ {
20234
+ const xs = shape.points.map((p2) => p2.x);
20235
+ const ys = shape.points.map((p2) => p2.y);
20236
+ const minX = Math.min(...xs);
20237
+ const minY = Math.min(...ys);
20238
+ return {
20239
+ x: minX - 4,
20240
+ y: minY - 4,
20241
+ w: Math.max(...xs) - minX + 8,
20242
+ h: Math.max(...ys) - minY + 8
20243
+ };
20244
+ }
20245
+ case "text":
20246
+ if (shape.x == null || shape.y == null) return null;
20247
+ return { x: shape.x - 4, y: shape.y - (shape.fontSize ?? 14) - 4, w: 120, h: (shape.fontSize ?? 14) + 8 };
20248
+ default:
20249
+ return null;
20250
+ }
20251
+ }
20252
+ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
20253
+ const angle = Math.atan2(y2 - y1, x2 - x1);
20254
+ ctx.beginPath();
20255
+ ctx.moveTo(x2, y2);
20256
+ ctx.lineTo(x2 - size * Math.cos(angle - Math.PI / 6), y2 - size * Math.sin(angle - Math.PI / 6));
20257
+ ctx.lineTo(x2 - size * Math.cos(angle + Math.PI / 6), y2 - size * Math.sin(angle + Math.PI / 6));
20258
+ ctx.closePath();
20259
+ ctx.fill();
20260
+ }
20261
+ function drawShape(ctx, shape, width, height) {
20262
+ ctx.save();
20263
+ const opacity = shape.opacity ?? 1;
20264
+ ctx.globalAlpha = opacity;
20265
+ const stroke = resolveColor2(shape.color, ctx, "#333333");
20266
+ const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
20267
+ ctx.lineWidth = shape.lineWidth ?? 2;
20268
+ switch (shape.type) {
20269
+ case "grid": {
20270
+ const step = shape.step ?? 40;
20271
+ ctx.strokeStyle = stroke;
20272
+ ctx.globalAlpha = opacity * 0.25;
20273
+ ctx.lineWidth = 1;
20274
+ ctx.beginPath();
20275
+ for (let x = 0; x <= width; x += step) {
20276
+ ctx.moveTo(x, 0);
20277
+ ctx.lineTo(x, height);
20278
+ }
20279
+ for (let y = 0; y <= height; y += step) {
20280
+ ctx.moveTo(0, y);
20281
+ ctx.lineTo(width, y);
20282
+ }
20283
+ ctx.stroke();
20284
+ break;
20285
+ }
20286
+ case "axis": {
20287
+ const axis = shape.axis ?? "x";
20288
+ ctx.strokeStyle = stroke;
20289
+ ctx.lineWidth = shape.lineWidth ?? 2;
20290
+ ctx.beginPath();
20291
+ if (axis === "x") {
20292
+ ctx.moveTo(0, height / 2);
20293
+ ctx.lineTo(width, height / 2);
20294
+ } else {
20295
+ ctx.moveTo(width / 2, 0);
20296
+ ctx.lineTo(width / 2, height);
20297
+ }
20298
+ ctx.stroke();
20299
+ break;
20300
+ }
20301
+ case "line": {
20302
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
20303
+ ctx.strokeStyle = stroke;
20304
+ ctx.beginPath();
20305
+ ctx.moveTo(shape.x1, shape.y1);
20306
+ ctx.lineTo(shape.x2, shape.y2);
20307
+ ctx.stroke();
20308
+ break;
20309
+ }
20310
+ case "arrow": {
20311
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
20312
+ ctx.strokeStyle = stroke;
20313
+ ctx.fillStyle = stroke;
20314
+ ctx.beginPath();
20315
+ ctx.moveTo(shape.x1, shape.y1);
20316
+ ctx.lineTo(shape.x2, shape.y2);
20317
+ ctx.stroke();
20318
+ drawArrowHead(ctx, shape.x1, shape.y1, shape.x2, shape.y2, 10);
20319
+ break;
20320
+ }
20321
+ case "circle": {
20322
+ if (shape.x == null || shape.y == null || shape.radius == null) break;
20323
+ ctx.beginPath();
20324
+ ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
20325
+ if (fill) {
20326
+ ctx.fillStyle = fill;
20327
+ ctx.fill();
20328
+ }
20329
+ ctx.strokeStyle = stroke;
20330
+ ctx.stroke();
20331
+ break;
20332
+ }
20333
+ case "rect": {
20334
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
20335
+ if (fill) {
20336
+ ctx.fillStyle = fill;
20337
+ ctx.fillRect(shape.x, shape.y, shape.width, shape.height);
20338
+ }
20339
+ ctx.strokeStyle = stroke;
20340
+ ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
20341
+ break;
20342
+ }
20343
+ case "polygon": {
20344
+ if (!shape.points || shape.points.length < 2) break;
20345
+ ctx.beginPath();
20346
+ ctx.moveTo(shape.points[0].x, shape.points[0].y);
20347
+ for (let i = 1; i < shape.points.length; i++) {
20348
+ ctx.lineTo(shape.points[i].x, shape.points[i].y);
20349
+ }
20350
+ ctx.closePath();
20351
+ if (fill) {
20352
+ ctx.fillStyle = fill;
20353
+ ctx.fill();
20354
+ }
20355
+ ctx.strokeStyle = stroke;
20356
+ ctx.stroke();
20357
+ break;
20358
+ }
20359
+ case "path": {
20360
+ if (!shape.path) break;
20361
+ const p2 = new Path2D(shape.path);
20362
+ if (fill) {
20363
+ ctx.fillStyle = fill;
20364
+ ctx.fill(p2);
20365
+ }
20366
+ ctx.strokeStyle = stroke;
20367
+ ctx.stroke(p2);
20368
+ break;
20369
+ }
20370
+ case "text": {
20371
+ if (shape.x == null || shape.y == null || !shape.text) break;
20372
+ ctx.fillStyle = stroke;
20373
+ ctx.font = `${shape.fontSize ?? 14}px system-ui, sans-serif`;
20374
+ ctx.textAlign = shape.align ?? "left";
20375
+ ctx.textBaseline = "middle";
20376
+ ctx.fillText(shape.text, shape.x, shape.y);
20377
+ break;
20378
+ }
20379
+ }
20380
+ ctx.restore();
20381
+ }
20382
+ var LearningCanvas;
20383
+ var init_LearningCanvas = __esm({
20384
+ "components/learning/atoms/LearningCanvas.tsx"() {
20385
+ "use client";
20386
+ init_cn();
20387
+ init_useEventBus();
20388
+ LearningCanvas = ({
20389
+ className,
20390
+ width = 600,
20391
+ height = 400,
20392
+ backgroundColor,
20393
+ shapes = [],
20394
+ interactive = false,
20395
+ animate = false,
20396
+ onShapeClick,
20397
+ onShapeHover,
20398
+ isLoading,
20399
+ error
20400
+ }) => {
20401
+ const canvasRef = useRef(null);
20402
+ const eventBus = useEventBus();
20403
+ const animRef = useRef(0);
20404
+ const hoverIndexRef = useRef(-1);
20405
+ const findShapeAt = useCallback((clientX, clientY) => {
20406
+ const canvas = canvasRef.current;
20407
+ if (!canvas) return -1;
20408
+ const rect = canvas.getBoundingClientRect();
20409
+ const x = clientX - rect.left;
20410
+ const y = clientY - rect.top;
20411
+ for (let i = shapes.length - 1; i >= 0; i--) {
20412
+ const b = shapeBounds(shapes[i]);
20413
+ if (b && x >= b.x && x <= b.x + b.w && y >= b.y && y <= b.y + b.h) {
20414
+ return i;
20415
+ }
20416
+ }
20417
+ return -1;
20418
+ }, [shapes]);
20419
+ const draw = useCallback(() => {
20420
+ const canvas = canvasRef.current;
20421
+ if (!canvas) return;
20422
+ const ctx = canvas.getContext("2d");
20423
+ if (!ctx) return;
20424
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
20425
+ canvas.width = Math.max(1, Math.floor(width * dpr));
20426
+ canvas.height = Math.max(1, Math.floor(height * dpr));
20427
+ canvas.style.width = `${width}px`;
20428
+ canvas.style.height = `${height}px`;
20429
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
20430
+ ctx.clearRect(0, 0, width, height);
20431
+ if (backgroundColor) {
20432
+ ctx.fillStyle = backgroundColor;
20433
+ ctx.fillRect(0, 0, width, height);
20434
+ }
20435
+ for (const shape of shapes) {
20436
+ drawShape(ctx, shape, width, height);
20437
+ }
20438
+ }, [width, height, backgroundColor, shapes]);
20439
+ useEffect(() => {
20440
+ draw();
20441
+ }, [draw]);
20442
+ useEffect(() => {
20443
+ if (!animate) return;
20444
+ const loop = () => {
20445
+ draw();
20446
+ animRef.current = requestAnimationFrame(loop);
20447
+ };
20448
+ animRef.current = requestAnimationFrame(loop);
20449
+ return () => cancelAnimationFrame(animRef.current);
20450
+ }, [animate, draw]);
20451
+ const handlePointerMove = useCallback(
20452
+ (e) => {
20453
+ if (!interactive) return;
20454
+ const idx = findShapeAt(e.clientX, e.clientY);
20455
+ if (idx !== hoverIndexRef.current) {
20456
+ hoverIndexRef.current = idx;
20457
+ if (idx >= 0) {
20458
+ const shape = shapes[idx];
20459
+ const payload = { id: shape.id, type: shape.type, index: idx };
20460
+ if (onShapeHover) onShapeHover(payload);
20461
+ else if (eventBus) eventBus.emit(`UI:SHAPE_HOVER`, payload);
20462
+ }
20463
+ }
20464
+ },
20465
+ [interactive, onShapeHover, eventBus, findShapeAt, shapes]
20466
+ );
20467
+ const handleClick = useCallback(
20468
+ (e) => {
20469
+ if (!interactive) return;
20470
+ const idx = findShapeAt(e.clientX, e.clientY);
20471
+ if (idx >= 0) {
20472
+ const shape = shapes[idx];
20473
+ const payload = { id: shape.id, type: shape.type, index: idx };
20474
+ if (onShapeClick) onShapeClick(payload);
20475
+ else if (eventBus) eventBus.emit(`UI:SHAPE_CLICK`, payload);
20476
+ }
20477
+ },
20478
+ [interactive, onShapeClick, eventBus, findShapeAt, shapes]
20479
+ );
20480
+ if (isLoading || error) {
20481
+ return /* @__PURE__ */ jsx(
20482
+ "div",
20483
+ {
20484
+ className: cn(
20485
+ "flex items-center justify-center rounded border border-border bg-surface",
20486
+ className
20487
+ ),
20488
+ style: { width, height },
20489
+ children: error ? /* @__PURE__ */ jsx("span", { className: "text-sm text-destructive", children: error.message }) : /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Loading canvas\u2026" })
20490
+ }
20491
+ );
20492
+ }
20493
+ return /* @__PURE__ */ jsx(
20494
+ "canvas",
20495
+ {
20496
+ ref: canvasRef,
20497
+ className: cn("block touch-none rounded border border-border", className),
20498
+ style: { width, height },
20499
+ onClick: handleClick,
20500
+ onPointerMove: handlePointerMove
20501
+ }
20502
+ );
20503
+ };
20504
+ }
20505
+ });
20414
20506
 
20415
20507
  // components/core/atoms/index.ts
20416
20508
  var init_atoms = __esm({
@@ -21896,91 +21988,6 @@ var init_ComponentPatterns = __esm({
21896
21988
  AlertPattern.displayName = "AlertPattern";
21897
21989
  }
21898
21990
  });
21899
- function parseValue(value) {
21900
- if (value === "" || value == null) return { num: 0, prefix: "", suffix: "", decimals: 0 };
21901
- const match = String(value).match(/^([^0-9]*)([0-9]+(?:\.[0-9]+)?)(.*)$/);
21902
- if (!match) {
21903
- return { num: 0, prefix: "", suffix: String(value), decimals: 0 };
21904
- }
21905
- const numStr = match[2];
21906
- const decimalIdx = numStr.indexOf(".");
21907
- const decimals = decimalIdx >= 0 ? numStr.length - decimalIdx - 1 : 0;
21908
- return {
21909
- prefix: match[1],
21910
- num: parseFloat(numStr),
21911
- suffix: match[3],
21912
- decimals
21913
- };
21914
- }
21915
- var AnimatedCounter;
21916
- var init_AnimatedCounter = __esm({
21917
- "components/core/molecules/AnimatedCounter.tsx"() {
21918
- "use client";
21919
- init_cn();
21920
- init_Box();
21921
- init_Typography();
21922
- AnimatedCounter = ({
21923
- value,
21924
- label,
21925
- duration = 1500,
21926
- className
21927
- }) => {
21928
- const ref = useRef(null);
21929
- const [displayValue, setDisplayValue] = useState("0");
21930
- const [hasAnimated, setHasAnimated] = useState(false);
21931
- const animate = useCallback(() => {
21932
- const { num: num2, prefix, suffix, decimals } = parseValue(value);
21933
- if (num2 === 0) {
21934
- setDisplayValue(String(value));
21935
- return;
21936
- }
21937
- const startTime = performance.now();
21938
- const tick = (now2) => {
21939
- const elapsed = now2 - startTime;
21940
- const progress = Math.min(elapsed / duration, 1);
21941
- const eased = 1 - Math.pow(1 - progress, 3);
21942
- const current = eased * num2;
21943
- setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
21944
- if (progress < 1) {
21945
- requestAnimationFrame(tick);
21946
- } else {
21947
- setDisplayValue(String(value));
21948
- }
21949
- };
21950
- requestAnimationFrame(tick);
21951
- }, [value, duration]);
21952
- useEffect(() => {
21953
- if (hasAnimated) return;
21954
- const el = ref.current;
21955
- if (!el) return;
21956
- const observer2 = new IntersectionObserver(
21957
- (entries) => {
21958
- if (entries[0].isIntersecting) {
21959
- setHasAnimated(true);
21960
- animate();
21961
- observer2.disconnect();
21962
- }
21963
- },
21964
- { threshold: 0.3 }
21965
- );
21966
- observer2.observe(el);
21967
- return () => observer2.disconnect();
21968
- }, [hasAnimated, animate]);
21969
- return /* @__PURE__ */ jsxs(Box, { ref, className: cn("flex flex-col items-center gap-1 p-4", className), children: [
21970
- /* @__PURE__ */ jsx(
21971
- Typography,
21972
- {
21973
- variant: "h2",
21974
- className: "text-primary font-bold tabular-nums",
21975
- children: hasAnimated ? displayValue : "0"
21976
- }
21977
- ),
21978
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "muted", className: "text-center", children: label })
21979
- ] });
21980
- };
21981
- AnimatedCounter.displayName = "AnimatedCounter";
21982
- }
21983
- });
21984
21991
  var AuthLayout;
21985
21992
  var init_AuthLayout = __esm({
21986
21993
  "components/marketing/templates/AuthLayout.tsx"() {
@@ -22949,10 +22956,107 @@ var init_BehaviorView = __esm({
22949
22956
  BehaviorView.displayName = "BehaviorView";
22950
22957
  }
22951
22958
  });
22959
+ var BiologyCanvas;
22960
+ var init_BiologyCanvas = __esm({
22961
+ "components/learning/molecules/BiologyCanvas.tsx"() {
22962
+ "use client";
22963
+ init_atoms();
22964
+ init_Stack();
22965
+ init_LearningCanvas();
22966
+ BiologyCanvas = ({
22967
+ className,
22968
+ width = 600,
22969
+ height = 400,
22970
+ title,
22971
+ backgroundColor,
22972
+ nodes = [],
22973
+ edges = [],
22974
+ shapes = [],
22975
+ interactive = false,
22976
+ animate = false,
22977
+ onShapeClick,
22978
+ isLoading,
22979
+ error
22980
+ }) => {
22981
+ const derivedShapes = useMemo(() => {
22982
+ const out = [];
22983
+ const nodeById = /* @__PURE__ */ new Map();
22984
+ for (const n of nodes) {
22985
+ if (n.id) nodeById.set(n.id, n);
22986
+ }
22987
+ for (const e of edges) {
22988
+ const a = nodeById.get(e.from);
22989
+ const b = nodeById.get(e.to);
22990
+ if (!a || !b) continue;
22991
+ out.push({
22992
+ type: "line",
22993
+ x1: a.x,
22994
+ y1: a.y,
22995
+ x2: b.x,
22996
+ y2: b.y,
22997
+ color: e.color ?? "#9ca3af",
22998
+ lineWidth: 2
22999
+ });
23000
+ if (e.label) {
23001
+ out.push({
23002
+ type: "text",
23003
+ x: (a.x + b.x) / 2 + 4,
23004
+ y: (a.y + b.y) / 2 - 4,
23005
+ text: e.label,
23006
+ color: "#374151",
23007
+ fontSize: 11
23008
+ });
23009
+ }
23010
+ }
23011
+ for (const n of nodes) {
23012
+ out.push({
23013
+ type: "circle",
23014
+ x: n.x,
23015
+ y: n.y,
23016
+ radius: n.radius ?? 16,
23017
+ color: n.color ?? "#16a34a",
23018
+ fill: `${n.color ?? "#16a34a"}33`,
23019
+ id: n.id
23020
+ });
23021
+ if (n.label) {
23022
+ out.push({
23023
+ type: "text",
23024
+ x: n.x,
23025
+ y: n.y + (n.radius ?? 16) + 14,
23026
+ text: n.label,
23027
+ color: "#111827",
23028
+ fontSize: 12,
23029
+ align: "center"
23030
+ });
23031
+ }
23032
+ }
23033
+ out.push(...shapes);
23034
+ return out;
23035
+ }, [nodes, edges, shapes]);
23036
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
23037
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
23038
+ /* @__PURE__ */ jsx(
23039
+ LearningCanvas,
23040
+ {
23041
+ width,
23042
+ height,
23043
+ backgroundColor,
23044
+ shapes: derivedShapes,
23045
+ interactive,
23046
+ animate,
23047
+ onShapeClick,
23048
+ isLoading,
23049
+ error
23050
+ }
23051
+ )
23052
+ ] }) });
23053
+ };
23054
+ }
23055
+ });
22952
23056
 
22953
- // node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css
23057
+ // node_modules/katex/dist/katex.min.css
22954
23058
  var init_katex_min = __esm({
22955
- "node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css"() {
23059
+ "node_modules/katex/dist/katex.min.css"() {
22956
23060
  }
22957
23061
  });
22958
23062
  var Tabs;
@@ -27295,129 +27399,6 @@ var init_Breadcrumb = __esm({
27295
27399
  Breadcrumb.displayName = "Breadcrumb";
27296
27400
  }
27297
27401
  });
27298
- function useSafeEventBus2() {
27299
- try {
27300
- return useEventBus();
27301
- } catch {
27302
- return { emit: () => {
27303
- }, on: () => () => {
27304
- }, once: () => {
27305
- } };
27306
- }
27307
- }
27308
- var log6, lookStyles4, ButtonGroup;
27309
- var init_ButtonGroup = __esm({
27310
- "components/core/molecules/ButtonGroup.tsx"() {
27311
- "use client";
27312
- init_cn();
27313
- init_atoms();
27314
- init_useEventBus();
27315
- log6 = createLogger("almadar:ui:button-group");
27316
- lookStyles4 = {
27317
- "right-aligned-buttons": "",
27318
- "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
27319
- "inline-row": "gap-2 inline-flex",
27320
- "dropdown-menu": "[&>button:not(:first-child)]:hidden",
27321
- "command-palette-trigger": "[&>button:not(:first-child)]:hidden"
27322
- };
27323
- ButtonGroup = ({
27324
- children,
27325
- primary,
27326
- secondary,
27327
- variant = "default",
27328
- orientation = "horizontal",
27329
- className,
27330
- // Filter-group pattern props (entity and filters are used for schema-driven filtering)
27331
- entity: _entity,
27332
- filters,
27333
- look = "right-aligned-buttons"
27334
- }) => {
27335
- const eventBus = useSafeEventBus2();
27336
- const variantClasses2 = {
27337
- default: "gap-0",
27338
- 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",
27339
- 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"
27340
- };
27341
- const orientationClasses = {
27342
- horizontal: "flex-row",
27343
- 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"
27344
- };
27345
- const handleActionClick = (action) => {
27346
- if (action.event) {
27347
- eventBus.emit("UI:DISPATCH", { event: action.event });
27348
- }
27349
- if (action.navigatesTo) {
27350
- eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
27351
- }
27352
- };
27353
- const renderFormActions = () => {
27354
- const buttons = [];
27355
- if (secondary) {
27356
- secondary.forEach((action, index) => {
27357
- buttons.push(
27358
- /* @__PURE__ */ jsx(
27359
- Button,
27360
- {
27361
- type: action.actionType === "submit" ? "submit" : "button",
27362
- variant: action.variant || "ghost",
27363
- onClick: () => handleActionClick(action),
27364
- children: action.label
27365
- },
27366
- `secondary-${index}`
27367
- )
27368
- );
27369
- });
27370
- }
27371
- if (primary) {
27372
- const isSubmit = primary.actionType === "submit";
27373
- buttons.push(
27374
- /* @__PURE__ */ jsx(
27375
- Button,
27376
- {
27377
- type: isSubmit ? "submit" : "button",
27378
- variant: primary.variant || "primary",
27379
- onClick: () => handleActionClick(primary),
27380
- "data-testid": isSubmit ? "form-submit" : void 0,
27381
- children: primary.label
27382
- },
27383
- "primary"
27384
- )
27385
- );
27386
- }
27387
- return buttons;
27388
- };
27389
- const renderFilters = () => {
27390
- if (!filters || filters.length === 0) return null;
27391
- return filters.map((filter, index) => /* @__PURE__ */ jsx(
27392
- Button,
27393
- {
27394
- variant: "ghost",
27395
- onClick: () => {
27396
- log6.debug("Filter clicked", { field: filter.field });
27397
- },
27398
- children: filter.label
27399
- },
27400
- `filter-${filter.field}-${index}`
27401
- ));
27402
- };
27403
- return /* @__PURE__ */ jsx(
27404
- "div",
27405
- {
27406
- className: cn(
27407
- "inline-flex gap-2",
27408
- variantClasses2[variant],
27409
- orientationClasses[orientation],
27410
- lookStyles4[look],
27411
- className
27412
- ),
27413
- role: "group",
27414
- children: children || renderFilters() || renderFormActions()
27415
- }
27416
- );
27417
- };
27418
- ButtonGroup.displayName = "ButtonGroup";
27419
- }
27420
- });
27421
27402
  function dayWindowForViewport(width) {
27422
27403
  if (width <= 640) return 1;
27423
27404
  if (width <= 1024) return 3;
@@ -28160,7 +28141,7 @@ var init_CardGrid = __esm({
28160
28141
  CardGrid.displayName = "CardGrid";
28161
28142
  }
28162
28143
  });
28163
- function useSafeEventBus3() {
28144
+ function useSafeEventBus2() {
28164
28145
  try {
28165
28146
  return useEventBus();
28166
28147
  } catch {
@@ -28196,7 +28177,7 @@ var init_Carousel = __esm({
28196
28177
  const [activeIndex, setActiveIndex] = useState(0);
28197
28178
  const scrollRef = useRef(null);
28198
28179
  const autoPlayRef = useRef(null);
28199
- const eventBus = useSafeEventBus3();
28180
+ const eventBus = useSafeEventBus2();
28200
28181
  const { t } = useTranslate();
28201
28182
  const safeItems = items ?? [];
28202
28183
  const totalSlides = safeItems.length;
@@ -29444,6 +29425,122 @@ var init_ChatBar = __esm({
29444
29425
  ChatBar.displayName = "ChatBar";
29445
29426
  }
29446
29427
  });
29428
+ var ChemistryCanvas;
29429
+ var init_ChemistryCanvas = __esm({
29430
+ "components/learning/molecules/ChemistryCanvas.tsx"() {
29431
+ "use client";
29432
+ init_atoms();
29433
+ init_Stack();
29434
+ init_LearningCanvas();
29435
+ ChemistryCanvas = ({
29436
+ className,
29437
+ width = 600,
29438
+ height = 400,
29439
+ title,
29440
+ backgroundColor,
29441
+ atoms = [],
29442
+ bonds = [],
29443
+ arrows = [],
29444
+ shapes = [],
29445
+ interactive = false,
29446
+ animate = false,
29447
+ onShapeClick,
29448
+ isLoading,
29449
+ error
29450
+ }) => {
29451
+ const derivedShapes = useMemo(() => {
29452
+ const out = [];
29453
+ const atomById = /* @__PURE__ */ new Map();
29454
+ for (const a of atoms) {
29455
+ if (a.id) atomById.set(a.id, a);
29456
+ }
29457
+ for (const b of bonds) {
29458
+ const a = atomById.get(b.from);
29459
+ const c = atomById.get(b.to);
29460
+ if (!a || !c) continue;
29461
+ const color = b.color ?? "#6b7280";
29462
+ const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
29463
+ out.push({
29464
+ type: "line",
29465
+ x1: a.x,
29466
+ y1: a.y,
29467
+ x2: c.x,
29468
+ y2: c.y,
29469
+ color,
29470
+ lineWidth: strokeWidth
29471
+ });
29472
+ }
29473
+ for (const a of arrows) {
29474
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
29475
+ const len = a.length ?? 60;
29476
+ const x2 = a.x + Math.cos(angle) * len;
29477
+ const y2 = a.y + Math.sin(angle) * len;
29478
+ out.push({
29479
+ type: "arrow",
29480
+ x1: a.x,
29481
+ y1: a.y,
29482
+ x2,
29483
+ y2,
29484
+ color: a.color ?? "#dc2626",
29485
+ lineWidth: 2
29486
+ });
29487
+ if (a.label) {
29488
+ out.push({
29489
+ type: "text",
29490
+ x: (a.x + x2) / 2,
29491
+ y: (a.y + y2) / 2 - 10,
29492
+ text: a.label,
29493
+ color: "#111827",
29494
+ fontSize: 12,
29495
+ align: "center"
29496
+ });
29497
+ }
29498
+ }
29499
+ for (const a of atoms) {
29500
+ out.push({
29501
+ type: "circle",
29502
+ x: a.x,
29503
+ y: a.y,
29504
+ radius: a.radius ?? 14,
29505
+ color: a.color ?? "#2563eb",
29506
+ fill: a.color ?? "#2563eb",
29507
+ id: a.id
29508
+ });
29509
+ if (a.element) {
29510
+ out.push({
29511
+ type: "text",
29512
+ x: a.x,
29513
+ y: a.y,
29514
+ text: a.element,
29515
+ color: "#ffffff",
29516
+ fontSize: 12,
29517
+ align: "center"
29518
+ });
29519
+ }
29520
+ }
29521
+ out.push(...shapes);
29522
+ return out;
29523
+ }, [atoms, bonds, arrows, shapes]);
29524
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
29525
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
29526
+ /* @__PURE__ */ jsx(
29527
+ LearningCanvas,
29528
+ {
29529
+ width,
29530
+ height,
29531
+ backgroundColor,
29532
+ shapes: derivedShapes,
29533
+ interactive,
29534
+ animate,
29535
+ onShapeClick,
29536
+ isLoading,
29537
+ error
29538
+ }
29539
+ )
29540
+ ] }) });
29541
+ };
29542
+ }
29543
+ });
29447
29544
  var CodeRunnerPanel;
29448
29545
  var init_CodeRunnerPanel = __esm({
29449
29546
  "components/core/organisms/CodeRunnerPanel.tsx"() {
@@ -31518,7 +31615,7 @@ function DataGrid({
31518
31615
  /* @__PURE__ */ jsx(
31519
31616
  Box,
31520
31617
  {
31521
- className: cn("grid", gapStyles5[gap], scrollX ? "grid-flow-col overflow-x-auto" : colsClass, lookStyles5[look], className),
31618
+ className: cn("grid", gapStyles5[gap], scrollX ? "grid-flow-col overflow-x-auto" : colsClass, lookStyles4[look], className),
31522
31619
  style: scrollX ? { gridAutoFlow: "column", gridAutoColumns: `minmax(${minCardWidth}px, 1fr)` } : gridTemplateColumns ? { gridTemplateColumns } : void 0,
31523
31620
  children: data.map((item, index) => {
31524
31621
  const itemData = item;
@@ -31713,7 +31810,7 @@ function DataGrid({
31713
31810
  ] })
31714
31811
  );
31715
31812
  }
31716
- var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles5;
31813
+ var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles4;
31717
31814
  var init_DataGrid = __esm({
31718
31815
  "components/core/molecules/DataGrid.tsx"() {
31719
31816
  "use client";
@@ -31748,7 +31845,7 @@ var init_DataGrid = __esm({
31748
31845
  lg: "gap-6",
31749
31846
  xl: "gap-8"
31750
31847
  };
31751
- lookStyles5 = {
31848
+ lookStyles4 = {
31752
31849
  dense: "gap-2 [&>*]:p-card-sm",
31753
31850
  spacious: "gap-8 [&>*]:p-card-lg",
31754
31851
  striped: "[&>*:nth-child(even)]:bg-muted/30",
@@ -32175,6 +32272,133 @@ var init_DataList = __esm({
32175
32272
  DataList.displayName = "DataList";
32176
32273
  }
32177
32274
  });
32275
+ var FormSection, FormLayout, FormActions;
32276
+ var init_FormSection = __esm({
32277
+ "components/core/molecules/FormSection.tsx"() {
32278
+ "use client";
32279
+ init_cn();
32280
+ init_atoms();
32281
+ init_Box();
32282
+ init_Typography();
32283
+ init_Button();
32284
+ init_Stack();
32285
+ init_Icon();
32286
+ init_useEventBus();
32287
+ FormSection = ({
32288
+ title,
32289
+ description,
32290
+ children,
32291
+ collapsible = false,
32292
+ defaultCollapsed = false,
32293
+ card = false,
32294
+ columns = 1,
32295
+ className
32296
+ }) => {
32297
+ const [collapsed, setCollapsed] = React105__default.useState(defaultCollapsed);
32298
+ const { t } = useTranslate();
32299
+ const eventBus = useEventBus();
32300
+ const gridClass = {
32301
+ 1: "grid-cols-1",
32302
+ 2: "grid-cols-1 md:grid-cols-2",
32303
+ 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
32304
+ }[columns];
32305
+ React105__default.useCallback(() => {
32306
+ if (collapsible) {
32307
+ setCollapsed((prev) => !prev);
32308
+ eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
32309
+ }
32310
+ }, [collapsible, collapsed, eventBus]);
32311
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
32312
+ (title || description) && /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "mb-4", children: [
32313
+ title && /* @__PURE__ */ jsxs(
32314
+ HStack,
32315
+ {
32316
+ justify: "between",
32317
+ align: "center",
32318
+ className: cn(collapsible && "cursor-pointer"),
32319
+ action: collapsible ? "TOGGLE_COLLAPSE" : void 0,
32320
+ children: [
32321
+ /* @__PURE__ */ jsx(Typography, { variant: "h3", weight: "semibold", children: title }),
32322
+ collapsible && /* @__PURE__ */ jsx(
32323
+ Button,
32324
+ {
32325
+ variant: "ghost",
32326
+ size: "sm",
32327
+ action: "TOGGLE_COLLAPSE",
32328
+ children: /* @__PURE__ */ jsx(
32329
+ Icon,
32330
+ {
32331
+ icon: ChevronDown,
32332
+ size: "sm",
32333
+ className: cn(
32334
+ "text-muted-foreground transition-transform",
32335
+ collapsed && "rotate-180"
32336
+ )
32337
+ }
32338
+ )
32339
+ }
32340
+ )
32341
+ ]
32342
+ }
32343
+ ),
32344
+ description && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: description })
32345
+ ] }),
32346
+ (!collapsible || !collapsed) && /* @__PURE__ */ jsx(Box, { className: cn("grid gap-4", gridClass), children })
32347
+ ] });
32348
+ if (card) {
32349
+ return /* @__PURE__ */ jsx(Card, { className: cn("p-6", className), children: content });
32350
+ }
32351
+ return /* @__PURE__ */ jsx(Box, { className, children: content });
32352
+ };
32353
+ FormSection.displayName = "FormSection";
32354
+ FormLayout = ({
32355
+ children,
32356
+ dividers = true,
32357
+ className
32358
+ }) => {
32359
+ return /* @__PURE__ */ jsx(
32360
+ VStack,
32361
+ {
32362
+ gap: "lg",
32363
+ className: cn(
32364
+ dividers && "[&>*+*]:pt-8 [&>*+*]:border-t [&>*+*]:border-border",
32365
+ className
32366
+ ),
32367
+ children
32368
+ }
32369
+ );
32370
+ };
32371
+ FormLayout.displayName = "FormLayout";
32372
+ FormActions = ({
32373
+ children,
32374
+ sticky = false,
32375
+ align = "right",
32376
+ className
32377
+ }) => {
32378
+ const alignClass2 = {
32379
+ left: "justify-start",
32380
+ right: "justify-end",
32381
+ between: "justify-between",
32382
+ center: "justify-center"
32383
+ }[align];
32384
+ return /* @__PURE__ */ jsx(
32385
+ HStack,
32386
+ {
32387
+ gap: "sm",
32388
+ align: "center",
32389
+ className: cn(
32390
+ "pt-6 border-t border-border",
32391
+ alignClass2,
32392
+ sticky && "sticky bottom-0 bg-card py-4 -mx-6 px-6 shadow-[0_-4px_6px_-1px_rgb(0,0,0,0.05)]",
32393
+ className
32394
+ ),
32395
+ children
32396
+ }
32397
+ );
32398
+ };
32399
+ FormActions.displayName = "FormActions";
32400
+ }
32401
+ });
32178
32402
  function fileIcon(name) {
32179
32403
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
32180
32404
  switch (ext) {
@@ -32336,6 +32560,129 @@ var init_FormField = __esm({
32336
32560
  FormField.displayName = "FormField";
32337
32561
  }
32338
32562
  });
32563
+ function useSafeEventBus3() {
32564
+ try {
32565
+ return useEventBus();
32566
+ } catch {
32567
+ return { emit: () => {
32568
+ }, on: () => () => {
32569
+ }, once: () => {
32570
+ } };
32571
+ }
32572
+ }
32573
+ var log6, lookStyles5, ButtonGroup;
32574
+ var init_ButtonGroup = __esm({
32575
+ "components/core/molecules/ButtonGroup.tsx"() {
32576
+ "use client";
32577
+ init_cn();
32578
+ init_atoms();
32579
+ init_useEventBus();
32580
+ log6 = createLogger("almadar:ui:button-group");
32581
+ lookStyles5 = {
32582
+ "right-aligned-buttons": "",
32583
+ "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
32584
+ "inline-row": "gap-2 inline-flex",
32585
+ "dropdown-menu": "[&>button:not(:first-child)]:hidden",
32586
+ "command-palette-trigger": "[&>button:not(:first-child)]:hidden"
32587
+ };
32588
+ ButtonGroup = ({
32589
+ children,
32590
+ primary,
32591
+ secondary,
32592
+ variant = "default",
32593
+ orientation = "horizontal",
32594
+ className,
32595
+ // Filter-group pattern props (entity and filters are used for schema-driven filtering)
32596
+ entity: _entity,
32597
+ filters,
32598
+ look = "right-aligned-buttons"
32599
+ }) => {
32600
+ const eventBus = useSafeEventBus3();
32601
+ const variantClasses2 = {
32602
+ default: "gap-0",
32603
+ 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",
32604
+ 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"
32605
+ };
32606
+ const orientationClasses = {
32607
+ horizontal: "flex-row",
32608
+ 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"
32609
+ };
32610
+ const handleActionClick = (action) => {
32611
+ if (action.event) {
32612
+ eventBus.emit("UI:DISPATCH", { event: action.event });
32613
+ }
32614
+ if (action.navigatesTo) {
32615
+ eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
32616
+ }
32617
+ };
32618
+ const renderFormActions = () => {
32619
+ const buttons = [];
32620
+ if (secondary) {
32621
+ secondary.forEach((action, index) => {
32622
+ buttons.push(
32623
+ /* @__PURE__ */ jsx(
32624
+ Button,
32625
+ {
32626
+ type: action.actionType === "submit" ? "submit" : "button",
32627
+ variant: action.variant || "ghost",
32628
+ onClick: () => handleActionClick(action),
32629
+ children: action.label
32630
+ },
32631
+ `secondary-${index}`
32632
+ )
32633
+ );
32634
+ });
32635
+ }
32636
+ if (primary) {
32637
+ const isSubmit = primary.actionType === "submit";
32638
+ buttons.push(
32639
+ /* @__PURE__ */ jsx(
32640
+ Button,
32641
+ {
32642
+ type: isSubmit ? "submit" : "button",
32643
+ variant: primary.variant || "primary",
32644
+ onClick: () => handleActionClick(primary),
32645
+ "data-testid": isSubmit ? "form-submit" : void 0,
32646
+ children: primary.label
32647
+ },
32648
+ "primary"
32649
+ )
32650
+ );
32651
+ }
32652
+ return buttons;
32653
+ };
32654
+ const renderFilters = () => {
32655
+ if (!filters || filters.length === 0) return null;
32656
+ return filters.map((filter, index) => /* @__PURE__ */ jsx(
32657
+ Button,
32658
+ {
32659
+ variant: "ghost",
32660
+ onClick: () => {
32661
+ log6.debug("Filter clicked", { field: filter.field });
32662
+ },
32663
+ children: filter.label
32664
+ },
32665
+ `filter-${filter.field}-${index}`
32666
+ ));
32667
+ };
32668
+ return /* @__PURE__ */ jsx(
32669
+ "div",
32670
+ {
32671
+ className: cn(
32672
+ "inline-flex gap-2",
32673
+ variantClasses2[variant],
32674
+ orientationClasses[orientation],
32675
+ lookStyles5[look],
32676
+ className
32677
+ ),
32678
+ role: "group",
32679
+ children: children || renderFilters() || renderFormActions()
32680
+ }
32681
+ );
32682
+ };
32683
+ ButtonGroup.displayName = "ButtonGroup";
32684
+ }
32685
+ });
32339
32686
  var resolveFilterType, lookStyles6, FilterGroup;
32340
32687
  var init_FilterGroup = __esm({
32341
32688
  "components/core/molecules/FilterGroup.tsx"() {
@@ -34813,6 +35160,234 @@ var init_ProgressDots = __esm({
34813
35160
  ProgressDots.displayName = "ProgressDots";
34814
35161
  }
34815
35162
  });
35163
+ var MathCanvas;
35164
+ var init_MathCanvas = __esm({
35165
+ "components/learning/molecules/MathCanvas.tsx"() {
35166
+ "use client";
35167
+ init_atoms();
35168
+ init_Stack();
35169
+ init_LearningCanvas();
35170
+ MathCanvas = ({
35171
+ className,
35172
+ width = 600,
35173
+ height = 400,
35174
+ title,
35175
+ xMin = -10,
35176
+ xMax = 10,
35177
+ yMin = -10,
35178
+ yMax = 10,
35179
+ showAxes = true,
35180
+ showGrid = true,
35181
+ gridStep = 1,
35182
+ curves = [],
35183
+ points = [],
35184
+ vectors = [],
35185
+ shapes = [],
35186
+ interactive = false,
35187
+ animate = false,
35188
+ onShapeClick,
35189
+ isLoading,
35190
+ error
35191
+ }) => {
35192
+ const derivedShapes = useMemo(() => {
35193
+ const out = [];
35194
+ const margin = 24;
35195
+ const plotW = width - margin * 2;
35196
+ const plotH = height - margin * 2;
35197
+ const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
35198
+ const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
35199
+ if (showGrid) {
35200
+ for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
35201
+ const px = mapX(x);
35202
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#e5e7eb", lineWidth: 1 });
35203
+ }
35204
+ for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
35205
+ const py = mapY(y);
35206
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
35207
+ }
35208
+ }
35209
+ if (showAxes) {
35210
+ const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
35211
+ const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
35212
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
35213
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
35214
+ }
35215
+ for (const curve of curves) {
35216
+ if (!curve.samples || curve.samples.length < 2) continue;
35217
+ for (let i = 1; i < curve.samples.length; i++) {
35218
+ const a = curve.samples[i - 1];
35219
+ const b = curve.samples[i];
35220
+ if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
35221
+ out.push({
35222
+ type: "line",
35223
+ x1: mapX(a.x),
35224
+ y1: mapY(a.y),
35225
+ x2: mapX(b.x),
35226
+ y2: mapY(b.y),
35227
+ color: curve.color ?? "#2563eb",
35228
+ lineWidth: 2
35229
+ });
35230
+ }
35231
+ }
35232
+ for (const p2 of points) {
35233
+ if (p2.x < xMin || p2.x > xMax || p2.y < yMin || p2.y > yMax) continue;
35234
+ out.push({
35235
+ type: "circle",
35236
+ x: mapX(p2.x),
35237
+ y: mapY(p2.y),
35238
+ radius: p2.radius ?? 4,
35239
+ color: p2.color ?? "#dc2626",
35240
+ fill: p2.color ?? "#dc2626"
35241
+ });
35242
+ if (p2.label) {
35243
+ out.push({ type: "text", x: mapX(p2.x) + 8, y: mapY(p2.y) - 8, text: p2.label, color: "#111827", fontSize: 12 });
35244
+ }
35245
+ }
35246
+ for (const v of vectors) {
35247
+ if (v.x < xMin || v.x > xMax || v.y < yMin || v.y > yMax) continue;
35248
+ const x1 = mapX(v.x);
35249
+ const y1 = mapY(v.y);
35250
+ const x2 = mapX(v.x + v.vx);
35251
+ const y2 = mapY(v.y + v.vy);
35252
+ out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
35253
+ if (v.label) {
35254
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
35255
+ }
35256
+ }
35257
+ out.push(...shapes);
35258
+ return out;
35259
+ }, [width, height, xMin, xMax, yMin, yMax, showAxes, showGrid, gridStep, curves, points, vectors, shapes]);
35260
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
35261
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
35262
+ /* @__PURE__ */ jsx(
35263
+ LearningCanvas,
35264
+ {
35265
+ width,
35266
+ height,
35267
+ shapes: derivedShapes,
35268
+ interactive,
35269
+ animate,
35270
+ onShapeClick,
35271
+ isLoading,
35272
+ error
35273
+ }
35274
+ )
35275
+ ] }) });
35276
+ };
35277
+ }
35278
+ });
35279
+ var PhysicsCanvas;
35280
+ var init_PhysicsCanvas = __esm({
35281
+ "components/learning/molecules/PhysicsCanvas.tsx"() {
35282
+ "use client";
35283
+ init_atoms();
35284
+ init_Stack();
35285
+ init_LearningCanvas();
35286
+ PhysicsCanvas = ({
35287
+ className,
35288
+ width = 600,
35289
+ height = 400,
35290
+ title,
35291
+ backgroundColor,
35292
+ bodies = [],
35293
+ constraints = [],
35294
+ showVelocity = true,
35295
+ showForces = false,
35296
+ velocityScale = 20,
35297
+ forceScale = 20,
35298
+ shapes = [],
35299
+ interactive = false,
35300
+ animate = false,
35301
+ onShapeClick,
35302
+ isLoading,
35303
+ error
35304
+ }) => {
35305
+ const derivedShapes = useMemo(() => {
35306
+ const out = [];
35307
+ const bodyById = /* @__PURE__ */ new Map();
35308
+ for (const b of bodies) {
35309
+ if (b.id) bodyById.set(b.id, b);
35310
+ }
35311
+ for (const c of constraints) {
35312
+ const a = bodyById.get(c.from);
35313
+ const b = bodyById.get(c.to);
35314
+ if (!a || !b) continue;
35315
+ out.push({
35316
+ type: "line",
35317
+ x1: a.x,
35318
+ y1: a.y,
35319
+ x2: b.x,
35320
+ y2: b.y,
35321
+ color: c.color ?? "#9ca3af",
35322
+ lineWidth: 2
35323
+ });
35324
+ }
35325
+ for (const b of bodies) {
35326
+ out.push({
35327
+ type: "circle",
35328
+ x: b.x,
35329
+ y: b.y,
35330
+ radius: b.radius ?? 12,
35331
+ color: b.color ?? "#2563eb",
35332
+ fill: b.color ?? "#2563eb",
35333
+ id: b.id
35334
+ });
35335
+ if (b.label) {
35336
+ out.push({
35337
+ type: "text",
35338
+ x: b.x + (b.radius ?? 12) + 6,
35339
+ y: b.y - (b.radius ?? 12) - 6,
35340
+ text: b.label,
35341
+ color: "#111827",
35342
+ fontSize: 12
35343
+ });
35344
+ }
35345
+ if (showVelocity && b.vx != null && b.vy != null && (b.vx !== 0 || b.vy !== 0)) {
35346
+ out.push({
35347
+ type: "arrow",
35348
+ x1: b.x,
35349
+ y1: b.y,
35350
+ x2: b.x + b.vx * velocityScale,
35351
+ y2: b.y + b.vy * velocityScale,
35352
+ color: "#16a34a",
35353
+ lineWidth: 2
35354
+ });
35355
+ }
35356
+ if (showForces && b.fx != null && b.fy != null && (b.fx !== 0 || b.fy !== 0)) {
35357
+ out.push({
35358
+ type: "arrow",
35359
+ x1: b.x,
35360
+ y1: b.y,
35361
+ x2: b.x + b.fx * forceScale,
35362
+ y2: b.y + b.fy * forceScale,
35363
+ color: "#dc2626",
35364
+ lineWidth: 2
35365
+ });
35366
+ }
35367
+ }
35368
+ out.push(...shapes);
35369
+ return out;
35370
+ }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
35371
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
35372
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
35373
+ /* @__PURE__ */ jsx(
35374
+ LearningCanvas,
35375
+ {
35376
+ width,
35377
+ height,
35378
+ backgroundColor,
35379
+ shapes: derivedShapes,
35380
+ interactive,
35381
+ animate,
35382
+ onShapeClick,
35383
+ isLoading,
35384
+ error
35385
+ }
35386
+ )
35387
+ ] }) });
35388
+ };
35389
+ }
35390
+ });
34816
35391
  function resolveNodeColor(node, groups) {
34817
35392
  if (node.color) return node.color;
34818
35393
  if (node.group) {
@@ -35184,13 +35759,13 @@ var init_MapView = __esm({
35184
35759
  shadowSize: [41, 41]
35185
35760
  });
35186
35761
  L.Marker.prototype.options.icon = defaultIcon;
35187
- const { useEffect: useEffect74, useRef: useRef76, useCallback: useCallback112, useState: useState107 } = React105__default;
35762
+ const { useEffect: useEffect75, useRef: useRef77, useCallback: useCallback112, useState: useState107 } = React105__default;
35188
35763
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
35189
35764
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
35190
35765
  function MapUpdater({ centerLat, centerLng, zoom }) {
35191
35766
  const map = useMap();
35192
- const prevRef = useRef76({ centerLat, centerLng, zoom });
35193
- useEffect74(() => {
35767
+ const prevRef = useRef77({ centerLat, centerLng, zoom });
35768
+ useEffect75(() => {
35194
35769
  const prev = prevRef.current;
35195
35770
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
35196
35771
  map.setView([centerLat, centerLng], zoom);
@@ -35201,7 +35776,7 @@ var init_MapView = __esm({
35201
35776
  }
35202
35777
  function MapClickHandler({ onMapClick }) {
35203
35778
  const map = useMap();
35204
- useEffect74(() => {
35779
+ useEffect75(() => {
35205
35780
  if (!onMapClick) return;
35206
35781
  const handler = (e) => {
35207
35782
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -43504,7 +44079,7 @@ function getGroupColor(group, groups) {
43504
44079
  const idx = groups.indexOf(group);
43505
44080
  return GROUP_COLORS2[idx % GROUP_COLORS2.length];
43506
44081
  }
43507
- function resolveColor2(color, el) {
44082
+ function resolveColor3(color, el) {
43508
44083
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
43509
44084
  if (!m) return color;
43510
44085
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
@@ -43765,7 +44340,7 @@ var init_GraphCanvas = __esm({
43765
44340
  const w = logicalW;
43766
44341
  const h = height;
43767
44342
  const nodes = nodesRef.current;
43768
- const accentColor = resolveColor2("var(--color-accent)", canvas);
44343
+ const accentColor = resolveColor3("var(--color-accent)", canvas);
43769
44344
  const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
43770
44345
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
43771
44346
  ctx.clearRect(0, 0, w, h);
@@ -43804,7 +44379,7 @@ var init_GraphCanvas = __esm({
43804
44379
  ctx.globalAlpha = 1;
43805
44380
  for (const node of nodes) {
43806
44381
  const size = node.size || 8;
43807
- const color = resolveColor2(node.color || getGroupColor(node.group, groups), canvas);
44382
+ const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
43808
44383
  const isHovered = hoveredNode === node.id;
43809
44384
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
43810
44385
  ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
@@ -50702,6 +51277,7 @@ var init_component_registry_generated = __esm({
50702
51277
  init_BattleBoard();
50703
51278
  init_BattleTemplate();
50704
51279
  init_BehaviorView();
51280
+ init_BiologyCanvas();
50705
51281
  init_BloomQuizBlock();
50706
51282
  init_BoardgameBoard();
50707
51283
  init_BookChapterView();
@@ -50713,7 +51289,6 @@ var init_component_registry_generated = __esm({
50713
51289
  init_BranchingLogicBuilder();
50714
51290
  init_Breadcrumb();
50715
51291
  init_BuilderBoard();
50716
- init_ButtonGroup();
50717
51292
  init_CTABanner();
50718
51293
  init_CalendarGrid();
50719
51294
  init_Canvas2D();
@@ -50732,6 +51307,7 @@ var init_component_registry_generated = __esm({
50732
51307
  init_ChartLegend();
50733
51308
  init_ChatBar();
50734
51309
  init_Checkbox();
51310
+ init_ChemistryCanvas();
50735
51311
  init_ChoiceButton();
50736
51312
  init_CityBuilderBoard();
50737
51313
  init_CityBuilderTemplate();
@@ -50790,9 +51366,9 @@ var init_component_registry_generated = __esm({
50790
51366
  init_FlipContainer();
50791
51367
  init_FloatingActionButton();
50792
51368
  init_Form();
51369
+ init_FormSection();
50793
51370
  init_FormField();
50794
51371
  init_FormSectionHeader();
50795
- init_GameAudioProvider();
50796
51372
  init_GameAudioToggle();
50797
51373
  init_GameCard();
50798
51374
  init_GameHud();
@@ -50823,6 +51399,7 @@ var init_component_registry_generated = __esm({
50823
51399
  init_JazariStateMachine();
50824
51400
  init_LandingPageTemplate();
50825
51401
  init_LawReferenceTooltip();
51402
+ init_LearningCanvas();
50826
51403
  init_Lightbox();
50827
51404
  init_LikertScale();
50828
51405
  init_LineChart();
@@ -50831,9 +51408,11 @@ var init_component_registry_generated = __esm({
50831
51408
  init_MapView();
50832
51409
  init_MarkdownContent();
50833
51410
  init_MarketingFooter();
51411
+ init_MarketingStatCard();
50834
51412
  init_MasterDetail();
50835
51413
  init_MasterDetailLayout();
50836
51414
  init_MatchPuzzleBoard();
51415
+ init_MathCanvas();
50837
51416
  init_MatrixQuestion();
50838
51417
  init_MediaGallery();
50839
51418
  init_Menu();
@@ -50852,6 +51431,7 @@ var init_component_registry_generated = __esm({
50852
51431
  init_PageHeader();
50853
51432
  init_Pagination();
50854
51433
  init_PatternTile();
51434
+ init_PhysicsCanvas();
50855
51435
  init_PinballBoard();
50856
51436
  init_PirateBoard();
50857
51437
  init_PlatformerBoard();
@@ -51028,6 +51608,7 @@ var init_component_registry_generated = __esm({
51028
51608
  "BattleBoard": BattleBoard,
51029
51609
  "BattleTemplate": BattleTemplate,
51030
51610
  "BehaviorView": BehaviorView,
51611
+ "BiologyCanvas": BiologyCanvas,
51031
51612
  "BloomQuizBlock": BloomQuizBlock,
51032
51613
  "BoardgameBoard": BoardgameBoard,
51033
51614
  "BookChapterView": BookChapterView,
@@ -51041,7 +51622,6 @@ var init_component_registry_generated = __esm({
51041
51622
  "Breadcrumb": Breadcrumb,
51042
51623
  "BuilderBoard": BuilderBoard,
51043
51624
  "Button": ButtonPattern,
51044
- "ButtonGroup": ButtonGroup,
51045
51625
  "ButtonPattern": ButtonPattern,
51046
51626
  "CTABanner": CTABanner,
51047
51627
  "CalendarGrid": CalendarGrid,
@@ -51061,6 +51641,7 @@ var init_component_registry_generated = __esm({
51061
51641
  "ChartLegend": ChartLegend,
51062
51642
  "ChatBar": ChatBar,
51063
51643
  "Checkbox": Checkbox,
51644
+ "ChemistryCanvas": ChemistryCanvas,
51064
51645
  "ChoiceButton": ChoiceButton,
51065
51646
  "CityBuilderBoard": CityBuilderBoard,
51066
51647
  "CityBuilderTemplate": CityBuilderTemplate,
@@ -51121,9 +51702,10 @@ var init_component_registry_generated = __esm({
51121
51702
  "FlipContainer": FlipContainer,
51122
51703
  "FloatingActionButton": FloatingActionButton,
51123
51704
  "Form": Form,
51705
+ "FormActions": FormActions,
51124
51706
  "FormField": FormField,
51707
+ "FormLayout": FormLayout,
51125
51708
  "FormSectionHeader": FormSectionHeader,
51126
- "GameAudioProvider": GameAudioProvider,
51127
51709
  "GameAudioToggle": GameAudioToggle,
51128
51710
  "GameBoard3D": GameBoard3D,
51129
51711
  "GameCanvas3D": GameCanvas3D,
@@ -51161,6 +51743,7 @@ var init_component_registry_generated = __esm({
51161
51743
  "LabelPattern": LabelPattern,
51162
51744
  "LandingPageTemplate": LandingPageTemplate,
51163
51745
  "LawReferenceTooltip": LawReferenceTooltip,
51746
+ "LearningCanvas": LearningCanvas,
51164
51747
  "Lightbox": Lightbox,
51165
51748
  "LikertScale": LikertScale,
51166
51749
  "LineChart": LineChart2,
@@ -51169,9 +51752,11 @@ var init_component_registry_generated = __esm({
51169
51752
  "MapView": MapView,
51170
51753
  "MarkdownContent": MarkdownContent,
51171
51754
  "MarketingFooter": MarketingFooter,
51755
+ "MarketingStatCard": MarketingStatCard,
51172
51756
  "MasterDetail": MasterDetail,
51173
51757
  "MasterDetailLayout": MasterDetailLayout,
51174
51758
  "MatchPuzzleBoard": MatchPuzzleBoard,
51759
+ "MathCanvas": MathCanvas,
51175
51760
  "MatrixQuestion": MatrixQuestion,
51176
51761
  "MediaGallery": MediaGallery,
51177
51762
  "Menu": Menu,
@@ -51190,6 +51775,7 @@ var init_component_registry_generated = __esm({
51190
51775
  "PageHeader": PageHeader,
51191
51776
  "Pagination": Pagination,
51192
51777
  "PatternTile": PatternTile,
51778
+ "PhysicsCanvas": PhysicsCanvas,
51193
51779
  "PinballBoard": PinballBoard,
51194
51780
  "PirateBoard": PirateBoard,
51195
51781
  "PlatformerBoard": PlatformerBoard,