@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
@@ -630,7 +630,7 @@ var init_AvlFieldType = __esm({
630
630
  }
631
631
  });
632
632
 
633
- // node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
633
+ // node_modules/clsx/dist/clsx.mjs
634
634
  function r(e) {
635
635
  var t, f3, n = "";
636
636
  if ("string" == typeof e || "number" == typeof e) n += e;
@@ -645,11 +645,11 @@ function clsx() {
645
645
  return n;
646
646
  }
647
647
  var init_clsx = __esm({
648
- "node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs"() {
648
+ "node_modules/clsx/dist/clsx.mjs"() {
649
649
  }
650
650
  });
651
651
 
652
- // node_modules/.pnpm/tailwind-merge@2.6.1/node_modules/tailwind-merge/dist/bundle-mjs.mjs
652
+ // node_modules/tailwind-merge/dist/bundle-mjs.mjs
653
653
  function twJoin() {
654
654
  let index = 0;
655
655
  let argument;
@@ -693,7 +693,7 @@ function createTailwindMerge(createConfigFirst, ...createConfigRest) {
693
693
  }
694
694
  var CLASS_PART_SEPARATOR, createClassGroupUtils, getGroupRecursive, arbitraryPropertyRegex, getGroupIdForArbitraryProperty, createClassMap, processClassesRecursively, getPart, isThemeGetter, getPrefixedClassGroupEntries, createLruCache, IMPORTANT_MODIFIER, createParseClassName, sortModifiers, createConfigUtils, SPLIT_CLASSES_REGEX, mergeClassList, toValue, fromTheme, arbitraryValueRegex, fractionRegex, stringLengths, tshirtUnitRegex, lengthUnitRegex, colorFunctionRegex, shadowRegex, imageRegex, isLength, isArbitraryLength, isNumber, isArbitraryNumber, isInteger, isPercent, isArbitraryValue, isTshirtSize, sizeLabels, isArbitrarySize, isArbitraryPosition, imageLabels, isArbitraryImage, isArbitraryShadow, isAny, getIsArbitraryValue, isLengthOnly, isNever, isShadow, isImage, getDefaultConfig, twMerge;
695
695
  var init_bundle_mjs = __esm({
696
- "node_modules/.pnpm/tailwind-merge@2.6.1/node_modules/tailwind-merge/dist/bundle-mjs.mjs"() {
696
+ "node_modules/tailwind-merge/dist/bundle-mjs.mjs"() {
697
697
  CLASS_PART_SEPARATOR = "-";
698
698
  createClassGroupUtils = (config) => {
699
699
  const classMap = createClassMap(config);
@@ -9419,6 +9419,66 @@ var init_RangeSlider = __esm({
9419
9419
  RangeSlider.displayName = "RangeSlider";
9420
9420
  }
9421
9421
  });
9422
+ function easeOut(t) {
9423
+ return t * (2 - t);
9424
+ }
9425
+ var AnimatedCounter;
9426
+ var init_AnimatedCounter = __esm({
9427
+ "components/marketing/atoms/AnimatedCounter.tsx"() {
9428
+ "use client";
9429
+ init_cn();
9430
+ init_Typography();
9431
+ AnimatedCounter = ({
9432
+ value: rawValue,
9433
+ duration = 600,
9434
+ prefix,
9435
+ suffix,
9436
+ className
9437
+ }) => {
9438
+ const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
9439
+ const value = !Number.isNaN(numericRaw) ? numericRaw : 0;
9440
+ const [displayValue, setDisplayValue] = React114.useState(value);
9441
+ const previousValueRef = React114.useRef(value);
9442
+ const animationFrameRef = React114.useRef(null);
9443
+ React114.useEffect(() => {
9444
+ const from = previousValueRef.current;
9445
+ const to = value;
9446
+ previousValueRef.current = value;
9447
+ if (from === to) {
9448
+ setDisplayValue(to);
9449
+ return;
9450
+ }
9451
+ const startTime = performance.now();
9452
+ const diff = to - from;
9453
+ function animate(currentTime) {
9454
+ const elapsed = currentTime - startTime;
9455
+ const progress = Math.min(elapsed / duration, 1);
9456
+ const easedProgress = easeOut(progress);
9457
+ setDisplayValue(from + diff * easedProgress);
9458
+ if (progress < 1) {
9459
+ animationFrameRef.current = requestAnimationFrame(animate);
9460
+ } else {
9461
+ setDisplayValue(to);
9462
+ }
9463
+ }
9464
+ animationFrameRef.current = requestAnimationFrame(animate);
9465
+ return () => {
9466
+ if (animationFrameRef.current !== null) {
9467
+ cancelAnimationFrame(animationFrameRef.current);
9468
+ }
9469
+ };
9470
+ }, [value, duration]);
9471
+ const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
9472
+ const formattedValue = displayValue.toFixed(decimalPlaces);
9473
+ return /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
9474
+ prefix,
9475
+ formattedValue,
9476
+ suffix
9477
+ ] });
9478
+ };
9479
+ AnimatedCounter.displayName = "AnimatedCounter";
9480
+ }
9481
+ });
9422
9482
  function useInfiniteScroll(onLoadMore, options = {}) {
9423
9483
  const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
9424
9484
  const observerRef = React114.useRef(null);
@@ -9715,6 +9775,34 @@ var init_SectionHeader = __esm({
9715
9775
  SectionHeader.displayName = "SectionHeader";
9716
9776
  }
9717
9777
  });
9778
+ var sizeClasses6, MarketingStatCard;
9779
+ var init_MarketingStatCard = __esm({
9780
+ "components/marketing/atoms/MarketingStatCard.tsx"() {
9781
+ init_cn();
9782
+ init_Stack();
9783
+ init_Typography();
9784
+ sizeClasses6 = {
9785
+ sm: "text-2xl",
9786
+ md: "text-4xl",
9787
+ lg: "text-5xl"
9788
+ };
9789
+ MarketingStatCard = ({ value, label, size = "md", className }) => {
9790
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", align: "center", className: cn(className), children: [
9791
+ /* @__PURE__ */ jsxRuntime.jsx(
9792
+ Typography,
9793
+ {
9794
+ weight: "bold",
9795
+ color: "primary",
9796
+ className: cn(sizeClasses6[size]),
9797
+ children: value
9798
+ }
9799
+ ),
9800
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", children: label })
9801
+ ] });
9802
+ };
9803
+ MarketingStatCard.displayName = "MarketingStatCard";
9804
+ }
9805
+ });
9718
9806
  var backgroundClasses, paddingClasses, ContentSection;
9719
9807
  var init_ContentSection = __esm({
9720
9808
  "components/marketing/atoms/ContentSection.tsx"() {
@@ -15908,318 +15996,14 @@ var init_useCanvasEffects = __esm({
15908
15996
  "use client";
15909
15997
  }
15910
15998
  });
15911
- function pickPath(entry) {
15912
- if (Array.isArray(entry.path)) {
15913
- return entry.path[Math.floor(Math.random() * entry.path.length)];
15914
- }
15915
- return entry.path;
15916
- }
15917
- function useGameAudio({
15918
- manifest,
15919
- baseUrl = "",
15920
- initialMuted = false,
15921
- initialVolume = 1
15922
- }) {
15923
- const [muted, setMutedState] = React114.useState(initialMuted);
15924
- const [masterVolume, setMasterVolumeState] = React114.useState(initialVolume);
15925
- const mutedRef = React114.useRef(muted);
15926
- const volumeRef = React114.useRef(masterVolume);
15927
- const manifestRef = React114.useRef(manifest);
15928
- mutedRef.current = muted;
15929
- volumeRef.current = masterVolume;
15930
- manifestRef.current = manifest;
15931
- const poolsRef = React114.useRef(/* @__PURE__ */ new Map());
15932
- const getOrCreateElement = React114.useCallback((key) => {
15933
- const entry = manifestRef.current[key];
15934
- if (!entry) return null;
15935
- let pool = poolsRef.current.get(key);
15936
- if (!pool) {
15937
- pool = [];
15938
- poolsRef.current.set(key, pool);
15939
- }
15940
- const maxSize = entry.poolSize ?? 1;
15941
- for (const audio of pool) {
15942
- if (audio.paused && (audio.ended || audio.currentTime === 0)) {
15943
- return audio;
15944
- }
15945
- }
15946
- if (pool.length < maxSize) {
15947
- const src = baseUrl + pickPath(entry);
15948
- const audio = new Audio(src);
15949
- audio.loop = entry.loop ?? false;
15950
- pool.push(audio);
15951
- return audio;
15952
- }
15953
- if (!entry.loop) {
15954
- let oldest = pool[0];
15955
- for (const audio of pool) {
15956
- if (audio.currentTime > oldest.currentTime) {
15957
- oldest = audio;
15958
- }
15959
- }
15960
- oldest.pause();
15961
- oldest.currentTime = 0;
15962
- return oldest;
15963
- }
15964
- return null;
15965
- }, [baseUrl]);
15966
- const play = React114.useCallback((key) => {
15967
- if (mutedRef.current) return;
15968
- const entry = manifestRef.current[key];
15969
- if (!entry) return;
15970
- const audio = getOrCreateElement(key);
15971
- if (!audio) return;
15972
- audio.volume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
15973
- if (!entry.loop) {
15974
- audio.currentTime = 0;
15975
- }
15976
- const promise = audio.play();
15977
- if (promise) {
15978
- promise.catch(() => {
15979
- });
15980
- }
15981
- }, [getOrCreateElement]);
15982
- const stop = React114.useCallback((key) => {
15983
- const pool = poolsRef.current.get(key);
15984
- if (!pool) return;
15985
- for (const audio of pool) {
15986
- audio.pause();
15987
- audio.currentTime = 0;
15988
- }
15989
- }, []);
15990
- const currentMusicKeyRef = React114.useRef(null);
15991
- const currentMusicElRef = React114.useRef(null);
15992
- const musicFadeRef = React114.useRef(null);
15993
- const pendingMusicKeyRef = React114.useRef(null);
15994
- const clearMusicFade = React114.useCallback(() => {
15995
- if (musicFadeRef.current) {
15996
- clearInterval(musicFadeRef.current);
15997
- musicFadeRef.current = null;
15998
- }
15999
- }, []);
16000
- const playMusic = React114.useCallback((key) => {
16001
- if (key === currentMusicKeyRef.current) return;
16002
- pendingMusicKeyRef.current = key;
16003
- const entry = manifestRef.current[key];
16004
- if (!entry) return;
16005
- const fadeDurationMs = entry.crossfadeDurationMs ?? 1500;
16006
- const stepMs = 50;
16007
- const totalSteps = Math.max(1, fadeDurationMs / stepMs);
16008
- const targetVolume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
16009
- clearMusicFade();
16010
- const src = baseUrl + (Array.isArray(entry.path) ? entry.path[0] : entry.path);
16011
- const incoming = new Audio(src);
16012
- incoming.loop = true;
16013
- incoming.volume = 0;
16014
- const outgoing = currentMusicElRef.current;
16015
- const outgoingStartVol = outgoing?.volume ?? 0;
16016
- currentMusicKeyRef.current = key;
16017
- currentMusicElRef.current = incoming;
16018
- if (!mutedRef.current) {
16019
- incoming.play().catch(() => {
16020
- currentMusicKeyRef.current = null;
16021
- currentMusicElRef.current = outgoing;
16022
- });
16023
- }
16024
- let step = 0;
16025
- musicFadeRef.current = setInterval(() => {
16026
- step++;
16027
- const progress = Math.min(step / totalSteps, 1);
16028
- incoming.volume = Math.min(1, targetVolume * progress);
16029
- if (outgoing) {
16030
- outgoing.volume = Math.max(0, outgoingStartVol * (1 - progress));
16031
- }
16032
- if (progress >= 1) {
16033
- clearMusicFade();
16034
- if (outgoing) {
16035
- outgoing.pause();
16036
- outgoing.src = "";
16037
- }
16038
- }
16039
- }, stepMs);
16040
- }, [baseUrl, clearMusicFade]);
16041
- const stopMusic = React114.useCallback((fadeDurationMs = 1e3) => {
16042
- const outgoing = currentMusicElRef.current;
16043
- if (!outgoing) return;
16044
- currentMusicKeyRef.current = null;
16045
- currentMusicElRef.current = null;
16046
- pendingMusicKeyRef.current = null;
16047
- clearMusicFade();
16048
- const startVolume = outgoing.volume;
16049
- const stepMs = 50;
16050
- const totalSteps = Math.max(1, fadeDurationMs / stepMs);
16051
- let step = 0;
16052
- musicFadeRef.current = setInterval(() => {
16053
- step++;
16054
- const progress = step / totalSteps;
16055
- outgoing.volume = Math.max(0, startVolume * (1 - progress));
16056
- if (progress >= 1) {
16057
- clearMusicFade();
16058
- outgoing.pause();
16059
- outgoing.src = "";
16060
- }
16061
- }, stepMs);
16062
- }, [clearMusicFade]);
16063
- const stopAll = React114.useCallback(() => {
16064
- for (const pool of poolsRef.current.values()) {
16065
- for (const audio of pool) {
16066
- audio.pause();
16067
- audio.currentTime = 0;
16068
- }
16069
- }
16070
- stopMusic(0);
16071
- }, [stopMusic]);
16072
- const setMuted = React114.useCallback((value) => {
16073
- setMutedState(value);
16074
- if (value) {
16075
- for (const [key, pool] of poolsRef.current.entries()) {
16076
- if (manifestRef.current[key]?.loop) {
16077
- for (const audio of pool) {
16078
- if (!audio.paused) audio.pause();
16079
- }
16080
- }
16081
- }
16082
- currentMusicElRef.current?.pause();
16083
- } else {
16084
- for (const [key, pool] of poolsRef.current.entries()) {
16085
- const entry = manifestRef.current[key];
16086
- if (entry?.loop && entry?.autostart) {
16087
- for (const audio of pool) {
16088
- if (audio.paused) audio.play().catch(() => {
16089
- });
16090
- }
16091
- }
16092
- }
16093
- const musicEl = currentMusicElRef.current;
16094
- if (musicEl) {
16095
- musicEl.play().catch(() => {
16096
- });
16097
- }
16098
- }
16099
- }, []);
16100
- const setMasterVolume = React114.useCallback((volume) => {
16101
- const clamped = Math.max(0, Math.min(1, volume));
16102
- setMasterVolumeState(clamped);
16103
- for (const [key, pool] of poolsRef.current.entries()) {
16104
- const entryVol = manifestRef.current[key]?.volume ?? 1;
16105
- for (const audio of pool) {
16106
- audio.volume = Math.min(1, entryVol * clamped);
16107
- }
16108
- }
16109
- if (!musicFadeRef.current && currentMusicElRef.current) {
16110
- const key = currentMusicKeyRef.current;
16111
- const entryVol = key ? manifestRef.current[key]?.volume ?? 1 : 1;
16112
- currentMusicElRef.current.volume = Math.min(1, entryVol * clamped);
16113
- }
16114
- }, []);
16115
- const unlockedRef = React114.useRef(false);
16116
- React114.useEffect(() => {
16117
- const autoKeys = Object.keys(manifest).filter((k) => manifest[k].autostart);
16118
- const hasPendingMusic = () => pendingMusicKeyRef.current !== null;
16119
- const hasAutoStart = autoKeys.length > 0;
16120
- if (!hasAutoStart && !hasPendingMusic()) return;
16121
- const unlock = () => {
16122
- if (unlockedRef.current) return;
16123
- unlockedRef.current = true;
16124
- if (!mutedRef.current) {
16125
- for (const key of autoKeys) {
16126
- play(key);
16127
- }
16128
- const pending = pendingMusicKeyRef.current;
16129
- if (pending && pending !== currentMusicKeyRef.current) {
16130
- playMusic(pending);
16131
- }
16132
- }
16133
- };
16134
- document.addEventListener("click", unlock, { once: true });
16135
- document.addEventListener("keydown", unlock, { once: true });
16136
- document.addEventListener("touchstart", unlock, { once: true });
16137
- return () => {
16138
- document.removeEventListener("click", unlock);
16139
- document.removeEventListener("keydown", unlock);
16140
- document.removeEventListener("touchstart", unlock);
16141
- };
16142
- }, [manifest, play, playMusic]);
16143
- React114.useEffect(() => {
16144
- return () => {
16145
- clearMusicFade();
16146
- for (const pool of poolsRef.current.values()) {
16147
- for (const audio of pool) {
16148
- audio.pause();
16149
- audio.src = "";
16150
- }
16151
- }
16152
- poolsRef.current.clear();
16153
- if (currentMusicElRef.current) {
16154
- currentMusicElRef.current.pause();
16155
- currentMusicElRef.current.src = "";
16156
- currentMusicElRef.current = null;
16157
- }
16158
- };
16159
- }, [clearMusicFade]);
16160
- return {
16161
- play,
16162
- stop,
16163
- stopAll,
16164
- playMusic,
16165
- stopMusic,
16166
- muted,
16167
- setMuted,
16168
- masterVolume,
16169
- setMasterVolume
16170
- };
16171
- }
16172
15999
  var init_useGameAudio = __esm({
16173
16000
  "components/game/shared/hooks/useGameAudio.ts"() {
16174
16001
  "use client";
16175
- useGameAudio.displayName = "useGameAudio";
16176
16002
  }
16177
16003
  });
16178
16004
  function useGameAudioContextOptional() {
16179
16005
  return React114.useContext(GameAudioContext);
16180
16006
  }
16181
- function GameAudioProvider({
16182
- manifest,
16183
- baseUrl = "",
16184
- children,
16185
- initialMuted = false
16186
- }) {
16187
- const eventBus = useEventBus();
16188
- const { play, stop, stopAll, playMusic, stopMusic, muted, setMuted, masterVolume, setMasterVolume } = useGameAudio({ manifest, baseUrl, initialMuted });
16189
- React114.useEffect(() => {
16190
- const unsubPlay = eventBus.on("UI:PLAY_SOUND", (event) => {
16191
- const key = event.payload?.key;
16192
- if (key) play(key);
16193
- });
16194
- const unsubStop = eventBus.on("UI:STOP_SOUND", (event) => {
16195
- const key = event.payload?.key;
16196
- if (key) {
16197
- stop(key);
16198
- } else {
16199
- stopAll();
16200
- }
16201
- });
16202
- const unsubChangeMusic = eventBus.on("UI:CHANGE_MUSIC", (event) => {
16203
- const key = event.payload?.key;
16204
- if (key) {
16205
- playMusic(key);
16206
- } else {
16207
- stopMusic();
16208
- }
16209
- });
16210
- const unsubStopMusic = eventBus.on("UI:STOP_MUSIC", () => {
16211
- stopMusic();
16212
- });
16213
- return () => {
16214
- unsubPlay();
16215
- unsubStop();
16216
- unsubChangeMusic();
16217
- unsubStopMusic();
16218
- };
16219
- }, [eventBus, play, stop, stopAll, playMusic, stopMusic]);
16220
- const value = { muted, setMuted, masterVolume, setMasterVolume, play, playMusic, stopMusic };
16221
- return /* @__PURE__ */ jsxRuntime.jsx(GameAudioContext.Provider, { value, children });
16222
- }
16223
16007
  var GameAudioContext;
16224
16008
  var init_GameAudioProvider = __esm({
16225
16009
  "components/game/shared/providers/GameAudioProvider.tsx"() {
@@ -16228,7 +16012,6 @@ var init_GameAudioProvider = __esm({
16228
16012
  init_useGameAudio();
16229
16013
  GameAudioContext = React114.createContext(null);
16230
16014
  GameAudioContext.displayName = "GameAudioContext";
16231
- GameAudioProvider.displayName = "GameAudioProvider";
16232
16015
  }
16233
16016
  });
16234
16017
  function GameAudioToggle({
@@ -23864,6 +23647,315 @@ var init_molecules = __esm({
23864
23647
  init_puzzleObject();
23865
23648
  }
23866
23649
  });
23650
+ function resolveColor2(color, ctx, fallback) {
23651
+ if (!color) return fallback;
23652
+ if (color.startsWith("var(")) {
23653
+ ctx.canvas.style;
23654
+ const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color);
23655
+ if (m) {
23656
+ const computed = getComputedStyle(ctx.canvas).getPropertyValue(m[1]).trim();
23657
+ return computed || m[2]?.trim() || fallback;
23658
+ }
23659
+ }
23660
+ return color;
23661
+ }
23662
+ function shapeBounds(shape) {
23663
+ switch (shape.type) {
23664
+ case "line":
23665
+ case "arrow":
23666
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) return null;
23667
+ return {
23668
+ x: Math.min(shape.x1, shape.x2) - 6,
23669
+ y: Math.min(shape.y1, shape.y2) - 6,
23670
+ w: Math.abs(shape.x2 - shape.x1) + 12,
23671
+ h: Math.abs(shape.y2 - shape.y1) + 12
23672
+ };
23673
+ case "circle":
23674
+ if (shape.x == null || shape.y == null || shape.radius == null) return null;
23675
+ return {
23676
+ x: shape.x - shape.radius - 4,
23677
+ y: shape.y - shape.radius - 4,
23678
+ w: shape.radius * 2 + 8,
23679
+ h: shape.radius * 2 + 8
23680
+ };
23681
+ case "rect":
23682
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
23683
+ return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
23684
+ case "polygon":
23685
+ if (!shape.points || shape.points.length === 0) return null;
23686
+ {
23687
+ const xs = shape.points.map((p2) => p2.x);
23688
+ const ys = shape.points.map((p2) => p2.y);
23689
+ const minX = Math.min(...xs);
23690
+ const minY = Math.min(...ys);
23691
+ return {
23692
+ x: minX - 4,
23693
+ y: minY - 4,
23694
+ w: Math.max(...xs) - minX + 8,
23695
+ h: Math.max(...ys) - minY + 8
23696
+ };
23697
+ }
23698
+ case "text":
23699
+ if (shape.x == null || shape.y == null) return null;
23700
+ return { x: shape.x - 4, y: shape.y - (shape.fontSize ?? 14) - 4, w: 120, h: (shape.fontSize ?? 14) + 8 };
23701
+ default:
23702
+ return null;
23703
+ }
23704
+ }
23705
+ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
23706
+ const angle = Math.atan2(y2 - y1, x2 - x1);
23707
+ ctx.beginPath();
23708
+ ctx.moveTo(x2, y2);
23709
+ ctx.lineTo(x2 - size * Math.cos(angle - Math.PI / 6), y2 - size * Math.sin(angle - Math.PI / 6));
23710
+ ctx.lineTo(x2 - size * Math.cos(angle + Math.PI / 6), y2 - size * Math.sin(angle + Math.PI / 6));
23711
+ ctx.closePath();
23712
+ ctx.fill();
23713
+ }
23714
+ function drawShape(ctx, shape, width, height) {
23715
+ ctx.save();
23716
+ const opacity = shape.opacity ?? 1;
23717
+ ctx.globalAlpha = opacity;
23718
+ const stroke = resolveColor2(shape.color, ctx, "#333333");
23719
+ const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
23720
+ ctx.lineWidth = shape.lineWidth ?? 2;
23721
+ switch (shape.type) {
23722
+ case "grid": {
23723
+ const step = shape.step ?? 40;
23724
+ ctx.strokeStyle = stroke;
23725
+ ctx.globalAlpha = opacity * 0.25;
23726
+ ctx.lineWidth = 1;
23727
+ ctx.beginPath();
23728
+ for (let x = 0; x <= width; x += step) {
23729
+ ctx.moveTo(x, 0);
23730
+ ctx.lineTo(x, height);
23731
+ }
23732
+ for (let y = 0; y <= height; y += step) {
23733
+ ctx.moveTo(0, y);
23734
+ ctx.lineTo(width, y);
23735
+ }
23736
+ ctx.stroke();
23737
+ break;
23738
+ }
23739
+ case "axis": {
23740
+ const axis = shape.axis ?? "x";
23741
+ ctx.strokeStyle = stroke;
23742
+ ctx.lineWidth = shape.lineWidth ?? 2;
23743
+ ctx.beginPath();
23744
+ if (axis === "x") {
23745
+ ctx.moveTo(0, height / 2);
23746
+ ctx.lineTo(width, height / 2);
23747
+ } else {
23748
+ ctx.moveTo(width / 2, 0);
23749
+ ctx.lineTo(width / 2, height);
23750
+ }
23751
+ ctx.stroke();
23752
+ break;
23753
+ }
23754
+ case "line": {
23755
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
23756
+ ctx.strokeStyle = stroke;
23757
+ ctx.beginPath();
23758
+ ctx.moveTo(shape.x1, shape.y1);
23759
+ ctx.lineTo(shape.x2, shape.y2);
23760
+ ctx.stroke();
23761
+ break;
23762
+ }
23763
+ case "arrow": {
23764
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
23765
+ ctx.strokeStyle = stroke;
23766
+ ctx.fillStyle = stroke;
23767
+ ctx.beginPath();
23768
+ ctx.moveTo(shape.x1, shape.y1);
23769
+ ctx.lineTo(shape.x2, shape.y2);
23770
+ ctx.stroke();
23771
+ drawArrowHead(ctx, shape.x1, shape.y1, shape.x2, shape.y2, 10);
23772
+ break;
23773
+ }
23774
+ case "circle": {
23775
+ if (shape.x == null || shape.y == null || shape.radius == null) break;
23776
+ ctx.beginPath();
23777
+ ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
23778
+ if (fill) {
23779
+ ctx.fillStyle = fill;
23780
+ ctx.fill();
23781
+ }
23782
+ ctx.strokeStyle = stroke;
23783
+ ctx.stroke();
23784
+ break;
23785
+ }
23786
+ case "rect": {
23787
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
23788
+ if (fill) {
23789
+ ctx.fillStyle = fill;
23790
+ ctx.fillRect(shape.x, shape.y, shape.width, shape.height);
23791
+ }
23792
+ ctx.strokeStyle = stroke;
23793
+ ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
23794
+ break;
23795
+ }
23796
+ case "polygon": {
23797
+ if (!shape.points || shape.points.length < 2) break;
23798
+ ctx.beginPath();
23799
+ ctx.moveTo(shape.points[0].x, shape.points[0].y);
23800
+ for (let i = 1; i < shape.points.length; i++) {
23801
+ ctx.lineTo(shape.points[i].x, shape.points[i].y);
23802
+ }
23803
+ ctx.closePath();
23804
+ if (fill) {
23805
+ ctx.fillStyle = fill;
23806
+ ctx.fill();
23807
+ }
23808
+ ctx.strokeStyle = stroke;
23809
+ ctx.stroke();
23810
+ break;
23811
+ }
23812
+ case "path": {
23813
+ if (!shape.path) break;
23814
+ const p2 = new Path2D(shape.path);
23815
+ if (fill) {
23816
+ ctx.fillStyle = fill;
23817
+ ctx.fill(p2);
23818
+ }
23819
+ ctx.strokeStyle = stroke;
23820
+ ctx.stroke(p2);
23821
+ break;
23822
+ }
23823
+ case "text": {
23824
+ if (shape.x == null || shape.y == null || !shape.text) break;
23825
+ ctx.fillStyle = stroke;
23826
+ ctx.font = `${shape.fontSize ?? 14}px system-ui, sans-serif`;
23827
+ ctx.textAlign = shape.align ?? "left";
23828
+ ctx.textBaseline = "middle";
23829
+ ctx.fillText(shape.text, shape.x, shape.y);
23830
+ break;
23831
+ }
23832
+ }
23833
+ ctx.restore();
23834
+ }
23835
+ var LearningCanvas;
23836
+ var init_LearningCanvas = __esm({
23837
+ "components/learning/atoms/LearningCanvas.tsx"() {
23838
+ "use client";
23839
+ init_cn();
23840
+ init_useEventBus();
23841
+ LearningCanvas = ({
23842
+ className,
23843
+ width = 600,
23844
+ height = 400,
23845
+ backgroundColor,
23846
+ shapes = [],
23847
+ interactive = false,
23848
+ animate = false,
23849
+ onShapeClick,
23850
+ onShapeHover,
23851
+ isLoading,
23852
+ error
23853
+ }) => {
23854
+ const canvasRef = React114.useRef(null);
23855
+ const eventBus = useEventBus();
23856
+ const animRef = React114.useRef(0);
23857
+ const hoverIndexRef = React114.useRef(-1);
23858
+ const findShapeAt = React114.useCallback((clientX, clientY) => {
23859
+ const canvas = canvasRef.current;
23860
+ if (!canvas) return -1;
23861
+ const rect = canvas.getBoundingClientRect();
23862
+ const x = clientX - rect.left;
23863
+ const y = clientY - rect.top;
23864
+ for (let i = shapes.length - 1; i >= 0; i--) {
23865
+ const b = shapeBounds(shapes[i]);
23866
+ if (b && x >= b.x && x <= b.x + b.w && y >= b.y && y <= b.y + b.h) {
23867
+ return i;
23868
+ }
23869
+ }
23870
+ return -1;
23871
+ }, [shapes]);
23872
+ const draw = React114.useCallback(() => {
23873
+ const canvas = canvasRef.current;
23874
+ if (!canvas) return;
23875
+ const ctx = canvas.getContext("2d");
23876
+ if (!ctx) return;
23877
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
23878
+ canvas.width = Math.max(1, Math.floor(width * dpr));
23879
+ canvas.height = Math.max(1, Math.floor(height * dpr));
23880
+ canvas.style.width = `${width}px`;
23881
+ canvas.style.height = `${height}px`;
23882
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
23883
+ ctx.clearRect(0, 0, width, height);
23884
+ if (backgroundColor) {
23885
+ ctx.fillStyle = backgroundColor;
23886
+ ctx.fillRect(0, 0, width, height);
23887
+ }
23888
+ for (const shape of shapes) {
23889
+ drawShape(ctx, shape, width, height);
23890
+ }
23891
+ }, [width, height, backgroundColor, shapes]);
23892
+ React114.useEffect(() => {
23893
+ draw();
23894
+ }, [draw]);
23895
+ React114.useEffect(() => {
23896
+ if (!animate) return;
23897
+ const loop = () => {
23898
+ draw();
23899
+ animRef.current = requestAnimationFrame(loop);
23900
+ };
23901
+ animRef.current = requestAnimationFrame(loop);
23902
+ return () => cancelAnimationFrame(animRef.current);
23903
+ }, [animate, draw]);
23904
+ const handlePointerMove = React114.useCallback(
23905
+ (e) => {
23906
+ if (!interactive) return;
23907
+ const idx = findShapeAt(e.clientX, e.clientY);
23908
+ if (idx !== hoverIndexRef.current) {
23909
+ hoverIndexRef.current = idx;
23910
+ if (idx >= 0) {
23911
+ const shape = shapes[idx];
23912
+ const payload = { id: shape.id, type: shape.type, index: idx };
23913
+ if (onShapeHover) onShapeHover(payload);
23914
+ else if (eventBus) eventBus.emit(`UI:SHAPE_HOVER`, payload);
23915
+ }
23916
+ }
23917
+ },
23918
+ [interactive, onShapeHover, eventBus, findShapeAt, shapes]
23919
+ );
23920
+ const handleClick = React114.useCallback(
23921
+ (e) => {
23922
+ if (!interactive) return;
23923
+ const idx = findShapeAt(e.clientX, e.clientY);
23924
+ if (idx >= 0) {
23925
+ const shape = shapes[idx];
23926
+ const payload = { id: shape.id, type: shape.type, index: idx };
23927
+ if (onShapeClick) onShapeClick(payload);
23928
+ else if (eventBus) eventBus.emit(`UI:SHAPE_CLICK`, payload);
23929
+ }
23930
+ },
23931
+ [interactive, onShapeClick, eventBus, findShapeAt, shapes]
23932
+ );
23933
+ if (isLoading || error) {
23934
+ return /* @__PURE__ */ jsxRuntime.jsx(
23935
+ "div",
23936
+ {
23937
+ className: cn(
23938
+ "flex items-center justify-center rounded border border-border bg-surface",
23939
+ className
23940
+ ),
23941
+ style: { width, height },
23942
+ 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" })
23943
+ }
23944
+ );
23945
+ }
23946
+ return /* @__PURE__ */ jsxRuntime.jsx(
23947
+ "canvas",
23948
+ {
23949
+ ref: canvasRef,
23950
+ className: cn("block touch-none rounded border border-border", className),
23951
+ style: { width, height },
23952
+ onClick: handleClick,
23953
+ onPointerMove: handlePointerMove
23954
+ }
23955
+ );
23956
+ };
23957
+ }
23958
+ });
23867
23959
 
23868
23960
  // components/core/atoms/index.ts
23869
23961
  var init_atoms = __esm({
@@ -25349,91 +25441,6 @@ var init_ComponentPatterns = __esm({
25349
25441
  AlertPattern.displayName = "AlertPattern";
25350
25442
  }
25351
25443
  });
25352
- function parseValue(value) {
25353
- if (value === "" || value == null) return { num: 0, prefix: "", suffix: "", decimals: 0 };
25354
- const match = String(value).match(/^([^0-9]*)([0-9]+(?:\.[0-9]+)?)(.*)$/);
25355
- if (!match) {
25356
- return { num: 0, prefix: "", suffix: String(value), decimals: 0 };
25357
- }
25358
- const numStr = match[2];
25359
- const decimalIdx = numStr.indexOf(".");
25360
- const decimals = decimalIdx >= 0 ? numStr.length - decimalIdx - 1 : 0;
25361
- return {
25362
- prefix: match[1],
25363
- num: parseFloat(numStr),
25364
- suffix: match[3],
25365
- decimals
25366
- };
25367
- }
25368
- var AnimatedCounter;
25369
- var init_AnimatedCounter = __esm({
25370
- "components/core/molecules/AnimatedCounter.tsx"() {
25371
- "use client";
25372
- init_cn();
25373
- init_Box();
25374
- init_Typography();
25375
- AnimatedCounter = ({
25376
- value,
25377
- label,
25378
- duration = 1500,
25379
- className
25380
- }) => {
25381
- const ref = React114.useRef(null);
25382
- const [displayValue, setDisplayValue] = React114.useState("0");
25383
- const [hasAnimated, setHasAnimated] = React114.useState(false);
25384
- const animate = React114.useCallback(() => {
25385
- const { num: num2, prefix, suffix, decimals } = parseValue(value);
25386
- if (num2 === 0) {
25387
- setDisplayValue(String(value));
25388
- return;
25389
- }
25390
- const startTime = performance.now();
25391
- const tick = (now2) => {
25392
- const elapsed = now2 - startTime;
25393
- const progress = Math.min(elapsed / duration, 1);
25394
- const eased = 1 - Math.pow(1 - progress, 3);
25395
- const current = eased * num2;
25396
- setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
25397
- if (progress < 1) {
25398
- requestAnimationFrame(tick);
25399
- } else {
25400
- setDisplayValue(String(value));
25401
- }
25402
- };
25403
- requestAnimationFrame(tick);
25404
- }, [value, duration]);
25405
- React114.useEffect(() => {
25406
- if (hasAnimated) return;
25407
- const el = ref.current;
25408
- if (!el) return;
25409
- const observer2 = new IntersectionObserver(
25410
- (entries) => {
25411
- if (entries[0].isIntersecting) {
25412
- setHasAnimated(true);
25413
- animate();
25414
- observer2.disconnect();
25415
- }
25416
- },
25417
- { threshold: 0.3 }
25418
- );
25419
- observer2.observe(el);
25420
- return () => observer2.disconnect();
25421
- }, [hasAnimated, animate]);
25422
- return /* @__PURE__ */ jsxRuntime.jsxs(Box, { ref, className: cn("flex flex-col items-center gap-1 p-4", className), children: [
25423
- /* @__PURE__ */ jsxRuntime.jsx(
25424
- Typography,
25425
- {
25426
- variant: "h2",
25427
- className: "text-primary font-bold tabular-nums",
25428
- children: hasAnimated ? displayValue : "0"
25429
- }
25430
- ),
25431
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body2", color: "muted", className: "text-center", children: label })
25432
- ] });
25433
- };
25434
- AnimatedCounter.displayName = "AnimatedCounter";
25435
- }
25436
- });
25437
25444
  var AuthLayout;
25438
25445
  var init_AuthLayout = __esm({
25439
25446
  "components/marketing/templates/AuthLayout.tsx"() {
@@ -25575,10 +25582,107 @@ var init_AuthLayout = __esm({
25575
25582
  AuthLayout.displayName = "AuthLayout";
25576
25583
  }
25577
25584
  });
25585
+ var BiologyCanvas;
25586
+ var init_BiologyCanvas = __esm({
25587
+ "components/learning/molecules/BiologyCanvas.tsx"() {
25588
+ "use client";
25589
+ init_atoms();
25590
+ init_Stack();
25591
+ init_LearningCanvas();
25592
+ BiologyCanvas = ({
25593
+ className,
25594
+ width = 600,
25595
+ height = 400,
25596
+ title,
25597
+ backgroundColor,
25598
+ nodes = [],
25599
+ edges = [],
25600
+ shapes = [],
25601
+ interactive = false,
25602
+ animate = false,
25603
+ onShapeClick,
25604
+ isLoading,
25605
+ error
25606
+ }) => {
25607
+ const derivedShapes = React114.useMemo(() => {
25608
+ const out = [];
25609
+ const nodeById = /* @__PURE__ */ new Map();
25610
+ for (const n of nodes) {
25611
+ if (n.id) nodeById.set(n.id, n);
25612
+ }
25613
+ for (const e of edges) {
25614
+ const a = nodeById.get(e.from);
25615
+ const b = nodeById.get(e.to);
25616
+ if (!a || !b) continue;
25617
+ out.push({
25618
+ type: "line",
25619
+ x1: a.x,
25620
+ y1: a.y,
25621
+ x2: b.x,
25622
+ y2: b.y,
25623
+ color: e.color ?? "#9ca3af",
25624
+ lineWidth: 2
25625
+ });
25626
+ if (e.label) {
25627
+ out.push({
25628
+ type: "text",
25629
+ x: (a.x + b.x) / 2 + 4,
25630
+ y: (a.y + b.y) / 2 - 4,
25631
+ text: e.label,
25632
+ color: "#374151",
25633
+ fontSize: 11
25634
+ });
25635
+ }
25636
+ }
25637
+ for (const n of nodes) {
25638
+ out.push({
25639
+ type: "circle",
25640
+ x: n.x,
25641
+ y: n.y,
25642
+ radius: n.radius ?? 16,
25643
+ color: n.color ?? "#16a34a",
25644
+ fill: `${n.color ?? "#16a34a"}33`,
25645
+ id: n.id
25646
+ });
25647
+ if (n.label) {
25648
+ out.push({
25649
+ type: "text",
25650
+ x: n.x,
25651
+ y: n.y + (n.radius ?? 16) + 14,
25652
+ text: n.label,
25653
+ color: "#111827",
25654
+ fontSize: 12,
25655
+ align: "center"
25656
+ });
25657
+ }
25658
+ }
25659
+ out.push(...shapes);
25660
+ return out;
25661
+ }, [nodes, edges, shapes]);
25662
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
25663
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
25664
+ /* @__PURE__ */ jsxRuntime.jsx(
25665
+ LearningCanvas,
25666
+ {
25667
+ width,
25668
+ height,
25669
+ backgroundColor,
25670
+ shapes: derivedShapes,
25671
+ interactive,
25672
+ animate,
25673
+ onShapeClick,
25674
+ isLoading,
25675
+ error
25676
+ }
25677
+ )
25678
+ ] }) });
25679
+ };
25680
+ }
25681
+ });
25578
25682
 
25579
- // node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css
25683
+ // node_modules/katex/dist/katex.min.css
25580
25684
  var init_katex_min = __esm({
25581
- "node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css"() {
25685
+ "node_modules/katex/dist/katex.min.css"() {
25582
25686
  }
25583
25687
  });
25584
25688
  var Tabs;
@@ -29971,129 +30075,6 @@ var init_Breadcrumb = __esm({
29971
30075
  Breadcrumb.displayName = "Breadcrumb";
29972
30076
  }
29973
30077
  });
29974
- function useSafeEventBus2() {
29975
- try {
29976
- return useEventBus();
29977
- } catch {
29978
- return { emit: () => {
29979
- }, on: () => () => {
29980
- }, once: () => {
29981
- } };
29982
- }
29983
- }
29984
- var log5, lookStyles4, ButtonGroup;
29985
- var init_ButtonGroup = __esm({
29986
- "components/core/molecules/ButtonGroup.tsx"() {
29987
- "use client";
29988
- init_cn();
29989
- init_atoms();
29990
- init_useEventBus();
29991
- log5 = logger.createLogger("almadar:ui:button-group");
29992
- lookStyles4 = {
29993
- "right-aligned-buttons": "",
29994
- "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
29995
- "inline-row": "gap-2 inline-flex",
29996
- "dropdown-menu": "[&>button:not(:first-child)]:hidden",
29997
- "command-palette-trigger": "[&>button:not(:first-child)]:hidden"
29998
- };
29999
- ButtonGroup = ({
30000
- children,
30001
- primary,
30002
- secondary,
30003
- variant = "default",
30004
- orientation = "horizontal",
30005
- className,
30006
- // Filter-group pattern props (entity and filters are used for schema-driven filtering)
30007
- entity: _entity,
30008
- filters,
30009
- look = "right-aligned-buttons"
30010
- }) => {
30011
- const eventBus = useSafeEventBus2();
30012
- const variantClasses2 = {
30013
- default: "gap-0",
30014
- 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",
30015
- 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"
30016
- };
30017
- const orientationClasses = {
30018
- horizontal: "flex-row",
30019
- 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"
30020
- };
30021
- const handleActionClick = (action) => {
30022
- if (action.event) {
30023
- eventBus.emit("UI:DISPATCH", { event: action.event });
30024
- }
30025
- if (action.navigatesTo) {
30026
- eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
30027
- }
30028
- };
30029
- const renderFormActions = () => {
30030
- const buttons = [];
30031
- if (secondary) {
30032
- secondary.forEach((action, index) => {
30033
- buttons.push(
30034
- /* @__PURE__ */ jsxRuntime.jsx(
30035
- Button,
30036
- {
30037
- type: action.actionType === "submit" ? "submit" : "button",
30038
- variant: action.variant || "ghost",
30039
- onClick: () => handleActionClick(action),
30040
- children: action.label
30041
- },
30042
- `secondary-${index}`
30043
- )
30044
- );
30045
- });
30046
- }
30047
- if (primary) {
30048
- const isSubmit = primary.actionType === "submit";
30049
- buttons.push(
30050
- /* @__PURE__ */ jsxRuntime.jsx(
30051
- Button,
30052
- {
30053
- type: isSubmit ? "submit" : "button",
30054
- variant: primary.variant || "primary",
30055
- onClick: () => handleActionClick(primary),
30056
- "data-testid": isSubmit ? "form-submit" : void 0,
30057
- children: primary.label
30058
- },
30059
- "primary"
30060
- )
30061
- );
30062
- }
30063
- return buttons;
30064
- };
30065
- const renderFilters = () => {
30066
- if (!filters || filters.length === 0) return null;
30067
- return filters.map((filter, index) => /* @__PURE__ */ jsxRuntime.jsx(
30068
- Button,
30069
- {
30070
- variant: "ghost",
30071
- onClick: () => {
30072
- log5.debug("Filter clicked", { field: filter.field });
30073
- },
30074
- children: filter.label
30075
- },
30076
- `filter-${filter.field}-${index}`
30077
- ));
30078
- };
30079
- return /* @__PURE__ */ jsxRuntime.jsx(
30080
- "div",
30081
- {
30082
- className: cn(
30083
- "inline-flex gap-2",
30084
- variantClasses2[variant],
30085
- orientationClasses[orientation],
30086
- lookStyles4[look],
30087
- className
30088
- ),
30089
- role: "group",
30090
- children: children || renderFilters() || renderFormActions()
30091
- }
30092
- );
30093
- };
30094
- ButtonGroup.displayName = "ButtonGroup";
30095
- }
30096
- });
30097
30078
  function useSwipeGesture(callbacks, options = {}) {
30098
30079
  const { threshold = 50, velocityThreshold = 0.3, preventDefault = false } = options;
30099
30080
  const startX = React114.useRef(0);
@@ -30903,7 +30884,7 @@ var init_CardGrid = __esm({
30903
30884
  CardGrid.displayName = "CardGrid";
30904
30885
  }
30905
30886
  });
30906
- function useSafeEventBus3() {
30887
+ function useSafeEventBus2() {
30907
30888
  try {
30908
30889
  return useEventBus();
30909
30890
  } catch {
@@ -30939,7 +30920,7 @@ var init_Carousel = __esm({
30939
30920
  const [activeIndex, setActiveIndex] = React114.useState(0);
30940
30921
  const scrollRef = React114.useRef(null);
30941
30922
  const autoPlayRef = React114.useRef(null);
30942
- const eventBus = useSafeEventBus3();
30923
+ const eventBus = useSafeEventBus2();
30943
30924
  const { t } = hooks.useTranslate();
30944
30925
  const safeItems = items ?? [];
30945
30926
  const totalSlides = safeItems.length;
@@ -32187,6 +32168,122 @@ var init_ChatBar = __esm({
32187
32168
  ChatBar.displayName = "ChatBar";
32188
32169
  }
32189
32170
  });
32171
+ var ChemistryCanvas;
32172
+ var init_ChemistryCanvas = __esm({
32173
+ "components/learning/molecules/ChemistryCanvas.tsx"() {
32174
+ "use client";
32175
+ init_atoms();
32176
+ init_Stack();
32177
+ init_LearningCanvas();
32178
+ ChemistryCanvas = ({
32179
+ className,
32180
+ width = 600,
32181
+ height = 400,
32182
+ title,
32183
+ backgroundColor,
32184
+ atoms = [],
32185
+ bonds = [],
32186
+ arrows = [],
32187
+ shapes = [],
32188
+ interactive = false,
32189
+ animate = false,
32190
+ onShapeClick,
32191
+ isLoading,
32192
+ error
32193
+ }) => {
32194
+ const derivedShapes = React114.useMemo(() => {
32195
+ const out = [];
32196
+ const atomById = /* @__PURE__ */ new Map();
32197
+ for (const a of atoms) {
32198
+ if (a.id) atomById.set(a.id, a);
32199
+ }
32200
+ for (const b of bonds) {
32201
+ const a = atomById.get(b.from);
32202
+ const c = atomById.get(b.to);
32203
+ if (!a || !c) continue;
32204
+ const color = b.color ?? "#6b7280";
32205
+ const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
32206
+ out.push({
32207
+ type: "line",
32208
+ x1: a.x,
32209
+ y1: a.y,
32210
+ x2: c.x,
32211
+ y2: c.y,
32212
+ color,
32213
+ lineWidth: strokeWidth
32214
+ });
32215
+ }
32216
+ for (const a of arrows) {
32217
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
32218
+ const len = a.length ?? 60;
32219
+ const x2 = a.x + Math.cos(angle) * len;
32220
+ const y2 = a.y + Math.sin(angle) * len;
32221
+ out.push({
32222
+ type: "arrow",
32223
+ x1: a.x,
32224
+ y1: a.y,
32225
+ x2,
32226
+ y2,
32227
+ color: a.color ?? "#dc2626",
32228
+ lineWidth: 2
32229
+ });
32230
+ if (a.label) {
32231
+ out.push({
32232
+ type: "text",
32233
+ x: (a.x + x2) / 2,
32234
+ y: (a.y + y2) / 2 - 10,
32235
+ text: a.label,
32236
+ color: "#111827",
32237
+ fontSize: 12,
32238
+ align: "center"
32239
+ });
32240
+ }
32241
+ }
32242
+ for (const a of atoms) {
32243
+ out.push({
32244
+ type: "circle",
32245
+ x: a.x,
32246
+ y: a.y,
32247
+ radius: a.radius ?? 14,
32248
+ color: a.color ?? "#2563eb",
32249
+ fill: a.color ?? "#2563eb",
32250
+ id: a.id
32251
+ });
32252
+ if (a.element) {
32253
+ out.push({
32254
+ type: "text",
32255
+ x: a.x,
32256
+ y: a.y,
32257
+ text: a.element,
32258
+ color: "#ffffff",
32259
+ fontSize: 12,
32260
+ align: "center"
32261
+ });
32262
+ }
32263
+ }
32264
+ out.push(...shapes);
32265
+ return out;
32266
+ }, [atoms, bonds, arrows, shapes]);
32267
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
32268
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
32269
+ /* @__PURE__ */ jsxRuntime.jsx(
32270
+ LearningCanvas,
32271
+ {
32272
+ width,
32273
+ height,
32274
+ backgroundColor,
32275
+ shapes: derivedShapes,
32276
+ interactive,
32277
+ animate,
32278
+ onShapeClick,
32279
+ isLoading,
32280
+ error
32281
+ }
32282
+ )
32283
+ ] }) });
32284
+ };
32285
+ }
32286
+ });
32190
32287
  var CodeRunnerPanel;
32191
32288
  var init_CodeRunnerPanel = __esm({
32192
32289
  "components/core/organisms/CodeRunnerPanel.tsx"() {
@@ -34275,7 +34372,7 @@ function DataGrid({
34275
34372
  /* @__PURE__ */ jsxRuntime.jsx(
34276
34373
  Box,
34277
34374
  {
34278
- className: cn("grid", gapStyles5[gap], scrollX ? "grid-flow-col overflow-x-auto" : colsClass, lookStyles5[look], className),
34375
+ className: cn("grid", gapStyles5[gap], scrollX ? "grid-flow-col overflow-x-auto" : colsClass, lookStyles4[look], className),
34279
34376
  style: scrollX ? { gridAutoFlow: "column", gridAutoColumns: `minmax(${minCardWidth}px, 1fr)` } : gridTemplateColumns ? { gridTemplateColumns } : void 0,
34280
34377
  children: data.map((item, index) => {
34281
34378
  const itemData = item;
@@ -34470,7 +34567,7 @@ function DataGrid({
34470
34567
  ] })
34471
34568
  );
34472
34569
  }
34473
- var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles5;
34570
+ var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles4;
34474
34571
  var init_DataGrid = __esm({
34475
34572
  "components/core/molecules/DataGrid.tsx"() {
34476
34573
  "use client";
@@ -34505,7 +34602,7 @@ var init_DataGrid = __esm({
34505
34602
  lg: "gap-6",
34506
34603
  xl: "gap-8"
34507
34604
  };
34508
- lookStyles5 = {
34605
+ lookStyles4 = {
34509
34606
  dense: "gap-2 [&>*]:p-card-sm",
34510
34607
  spacious: "gap-8 [&>*]:p-card-lg",
34511
34608
  striped: "[&>*:nth-child(even)]:bg-muted/30",
@@ -34932,6 +35029,133 @@ var init_DataList = __esm({
34932
35029
  DataList.displayName = "DataList";
34933
35030
  }
34934
35031
  });
35032
+ var FormSection, FormLayout, FormActions;
35033
+ var init_FormSection = __esm({
35034
+ "components/core/molecules/FormSection.tsx"() {
35035
+ "use client";
35036
+ init_cn();
35037
+ init_atoms();
35038
+ init_Box();
35039
+ init_Typography();
35040
+ init_Button();
35041
+ init_Stack();
35042
+ init_Icon();
35043
+ init_useEventBus();
35044
+ FormSection = ({
35045
+ title,
35046
+ description,
35047
+ children,
35048
+ collapsible = false,
35049
+ defaultCollapsed = false,
35050
+ card = false,
35051
+ columns = 1,
35052
+ className
35053
+ }) => {
35054
+ const [collapsed, setCollapsed] = React114__namespace.default.useState(defaultCollapsed);
35055
+ const { t } = hooks.useTranslate();
35056
+ const eventBus = useEventBus();
35057
+ const gridClass = {
35058
+ 1: "grid-cols-1",
35059
+ 2: "grid-cols-1 md:grid-cols-2",
35060
+ 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
35061
+ }[columns];
35062
+ React114__namespace.default.useCallback(() => {
35063
+ if (collapsible) {
35064
+ setCollapsed((prev) => !prev);
35065
+ eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
35066
+ }
35067
+ }, [collapsible, collapsed, eventBus]);
35068
+ const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
35069
+ (title || description) && /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "mb-4", children: [
35070
+ title && /* @__PURE__ */ jsxRuntime.jsxs(
35071
+ HStack,
35072
+ {
35073
+ justify: "between",
35074
+ align: "center",
35075
+ className: cn(collapsible && "cursor-pointer"),
35076
+ action: collapsible ? "TOGGLE_COLLAPSE" : void 0,
35077
+ children: [
35078
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h3", weight: "semibold", children: title }),
35079
+ collapsible && /* @__PURE__ */ jsxRuntime.jsx(
35080
+ Button,
35081
+ {
35082
+ variant: "ghost",
35083
+ size: "sm",
35084
+ action: "TOGGLE_COLLAPSE",
35085
+ children: /* @__PURE__ */ jsxRuntime.jsx(
35086
+ Icon,
35087
+ {
35088
+ icon: LucideIcons2.ChevronDown,
35089
+ size: "sm",
35090
+ className: cn(
35091
+ "text-muted-foreground transition-transform",
35092
+ collapsed && "rotate-180"
35093
+ )
35094
+ }
35095
+ )
35096
+ }
35097
+ )
35098
+ ]
35099
+ }
35100
+ ),
35101
+ description && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: description })
35102
+ ] }),
35103
+ (!collapsible || !collapsed) && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: cn("grid gap-4", gridClass), children })
35104
+ ] });
35105
+ if (card) {
35106
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: cn("p-6", className), children: content });
35107
+ }
35108
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className, children: content });
35109
+ };
35110
+ FormSection.displayName = "FormSection";
35111
+ FormLayout = ({
35112
+ children,
35113
+ dividers = true,
35114
+ className
35115
+ }) => {
35116
+ return /* @__PURE__ */ jsxRuntime.jsx(
35117
+ VStack,
35118
+ {
35119
+ gap: "lg",
35120
+ className: cn(
35121
+ dividers && "[&>*+*]:pt-8 [&>*+*]:border-t [&>*+*]:border-border",
35122
+ className
35123
+ ),
35124
+ children
35125
+ }
35126
+ );
35127
+ };
35128
+ FormLayout.displayName = "FormLayout";
35129
+ FormActions = ({
35130
+ children,
35131
+ sticky = false,
35132
+ align = "right",
35133
+ className
35134
+ }) => {
35135
+ const alignClass2 = {
35136
+ left: "justify-start",
35137
+ right: "justify-end",
35138
+ between: "justify-between",
35139
+ center: "justify-center"
35140
+ }[align];
35141
+ return /* @__PURE__ */ jsxRuntime.jsx(
35142
+ HStack,
35143
+ {
35144
+ gap: "sm",
35145
+ align: "center",
35146
+ className: cn(
35147
+ "pt-6 border-t border-border",
35148
+ alignClass2,
35149
+ sticky && "sticky bottom-0 bg-card py-4 -mx-6 px-6 shadow-[0_-4px_6px_-1px_rgb(0,0,0,0.05)]",
35150
+ className
35151
+ ),
35152
+ children
35153
+ }
35154
+ );
35155
+ };
35156
+ FormActions.displayName = "FormActions";
35157
+ }
35158
+ });
34935
35159
  function fileIcon(name) {
34936
35160
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
34937
35161
  switch (ext) {
@@ -35093,6 +35317,129 @@ var init_FormField = __esm({
35093
35317
  FormField.displayName = "FormField";
35094
35318
  }
35095
35319
  });
35320
+ function useSafeEventBus3() {
35321
+ try {
35322
+ return useEventBus();
35323
+ } catch {
35324
+ return { emit: () => {
35325
+ }, on: () => () => {
35326
+ }, once: () => {
35327
+ } };
35328
+ }
35329
+ }
35330
+ var log5, lookStyles5, ButtonGroup;
35331
+ var init_ButtonGroup = __esm({
35332
+ "components/core/molecules/ButtonGroup.tsx"() {
35333
+ "use client";
35334
+ init_cn();
35335
+ init_atoms();
35336
+ init_useEventBus();
35337
+ log5 = logger.createLogger("almadar:ui:button-group");
35338
+ lookStyles5 = {
35339
+ "right-aligned-buttons": "",
35340
+ "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
35341
+ "inline-row": "gap-2 inline-flex",
35342
+ "dropdown-menu": "[&>button:not(:first-child)]:hidden",
35343
+ "command-palette-trigger": "[&>button:not(:first-child)]:hidden"
35344
+ };
35345
+ ButtonGroup = ({
35346
+ children,
35347
+ primary,
35348
+ secondary,
35349
+ variant = "default",
35350
+ orientation = "horizontal",
35351
+ className,
35352
+ // Filter-group pattern props (entity and filters are used for schema-driven filtering)
35353
+ entity: _entity,
35354
+ filters,
35355
+ look = "right-aligned-buttons"
35356
+ }) => {
35357
+ const eventBus = useSafeEventBus3();
35358
+ const variantClasses2 = {
35359
+ default: "gap-0",
35360
+ 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",
35361
+ 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"
35362
+ };
35363
+ const orientationClasses = {
35364
+ horizontal: "flex-row",
35365
+ 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"
35366
+ };
35367
+ const handleActionClick = (action) => {
35368
+ if (action.event) {
35369
+ eventBus.emit("UI:DISPATCH", { event: action.event });
35370
+ }
35371
+ if (action.navigatesTo) {
35372
+ eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
35373
+ }
35374
+ };
35375
+ const renderFormActions = () => {
35376
+ const buttons = [];
35377
+ if (secondary) {
35378
+ secondary.forEach((action, index) => {
35379
+ buttons.push(
35380
+ /* @__PURE__ */ jsxRuntime.jsx(
35381
+ Button,
35382
+ {
35383
+ type: action.actionType === "submit" ? "submit" : "button",
35384
+ variant: action.variant || "ghost",
35385
+ onClick: () => handleActionClick(action),
35386
+ children: action.label
35387
+ },
35388
+ `secondary-${index}`
35389
+ )
35390
+ );
35391
+ });
35392
+ }
35393
+ if (primary) {
35394
+ const isSubmit = primary.actionType === "submit";
35395
+ buttons.push(
35396
+ /* @__PURE__ */ jsxRuntime.jsx(
35397
+ Button,
35398
+ {
35399
+ type: isSubmit ? "submit" : "button",
35400
+ variant: primary.variant || "primary",
35401
+ onClick: () => handleActionClick(primary),
35402
+ "data-testid": isSubmit ? "form-submit" : void 0,
35403
+ children: primary.label
35404
+ },
35405
+ "primary"
35406
+ )
35407
+ );
35408
+ }
35409
+ return buttons;
35410
+ };
35411
+ const renderFilters = () => {
35412
+ if (!filters || filters.length === 0) return null;
35413
+ return filters.map((filter, index) => /* @__PURE__ */ jsxRuntime.jsx(
35414
+ Button,
35415
+ {
35416
+ variant: "ghost",
35417
+ onClick: () => {
35418
+ log5.debug("Filter clicked", { field: filter.field });
35419
+ },
35420
+ children: filter.label
35421
+ },
35422
+ `filter-${filter.field}-${index}`
35423
+ ));
35424
+ };
35425
+ return /* @__PURE__ */ jsxRuntime.jsx(
35426
+ "div",
35427
+ {
35428
+ className: cn(
35429
+ "inline-flex gap-2",
35430
+ variantClasses2[variant],
35431
+ orientationClasses[orientation],
35432
+ lookStyles5[look],
35433
+ className
35434
+ ),
35435
+ role: "group",
35436
+ children: children || renderFilters() || renderFormActions()
35437
+ }
35438
+ );
35439
+ };
35440
+ ButtonGroup.displayName = "ButtonGroup";
35441
+ }
35442
+ });
35096
35443
  function getOrCreateStore(query) {
35097
35444
  if (!queryStores.has(query)) {
35098
35445
  queryStores.set(query, {
@@ -37634,6 +37981,234 @@ var init_ProgressDots = __esm({
37634
37981
  ProgressDots.displayName = "ProgressDots";
37635
37982
  }
37636
37983
  });
37984
+ var MathCanvas;
37985
+ var init_MathCanvas = __esm({
37986
+ "components/learning/molecules/MathCanvas.tsx"() {
37987
+ "use client";
37988
+ init_atoms();
37989
+ init_Stack();
37990
+ init_LearningCanvas();
37991
+ MathCanvas = ({
37992
+ className,
37993
+ width = 600,
37994
+ height = 400,
37995
+ title,
37996
+ xMin = -10,
37997
+ xMax = 10,
37998
+ yMin = -10,
37999
+ yMax = 10,
38000
+ showAxes = true,
38001
+ showGrid = true,
38002
+ gridStep = 1,
38003
+ curves = [],
38004
+ points = [],
38005
+ vectors = [],
38006
+ shapes = [],
38007
+ interactive = false,
38008
+ animate = false,
38009
+ onShapeClick,
38010
+ isLoading,
38011
+ error
38012
+ }) => {
38013
+ const derivedShapes = React114.useMemo(() => {
38014
+ const out = [];
38015
+ const margin = 24;
38016
+ const plotW = width - margin * 2;
38017
+ const plotH = height - margin * 2;
38018
+ const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
38019
+ const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
38020
+ if (showGrid) {
38021
+ for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
38022
+ const px = mapX(x);
38023
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#e5e7eb", lineWidth: 1 });
38024
+ }
38025
+ for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
38026
+ const py = mapY(y);
38027
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
38028
+ }
38029
+ }
38030
+ if (showAxes) {
38031
+ const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
38032
+ const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
38033
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
38034
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
38035
+ }
38036
+ for (const curve of curves) {
38037
+ if (!curve.samples || curve.samples.length < 2) continue;
38038
+ for (let i = 1; i < curve.samples.length; i++) {
38039
+ const a = curve.samples[i - 1];
38040
+ const b = curve.samples[i];
38041
+ if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
38042
+ out.push({
38043
+ type: "line",
38044
+ x1: mapX(a.x),
38045
+ y1: mapY(a.y),
38046
+ x2: mapX(b.x),
38047
+ y2: mapY(b.y),
38048
+ color: curve.color ?? "#2563eb",
38049
+ lineWidth: 2
38050
+ });
38051
+ }
38052
+ }
38053
+ for (const p2 of points) {
38054
+ if (p2.x < xMin || p2.x > xMax || p2.y < yMin || p2.y > yMax) continue;
38055
+ out.push({
38056
+ type: "circle",
38057
+ x: mapX(p2.x),
38058
+ y: mapY(p2.y),
38059
+ radius: p2.radius ?? 4,
38060
+ color: p2.color ?? "#dc2626",
38061
+ fill: p2.color ?? "#dc2626"
38062
+ });
38063
+ if (p2.label) {
38064
+ out.push({ type: "text", x: mapX(p2.x) + 8, y: mapY(p2.y) - 8, text: p2.label, color: "#111827", fontSize: 12 });
38065
+ }
38066
+ }
38067
+ for (const v of vectors) {
38068
+ if (v.x < xMin || v.x > xMax || v.y < yMin || v.y > yMax) continue;
38069
+ const x1 = mapX(v.x);
38070
+ const y1 = mapY(v.y);
38071
+ const x2 = mapX(v.x + v.vx);
38072
+ const y2 = mapY(v.y + v.vy);
38073
+ out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
38074
+ if (v.label) {
38075
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
38076
+ }
38077
+ }
38078
+ out.push(...shapes);
38079
+ return out;
38080
+ }, [width, height, xMin, xMax, yMin, yMax, showAxes, showGrid, gridStep, curves, points, vectors, shapes]);
38081
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
38082
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
38083
+ /* @__PURE__ */ jsxRuntime.jsx(
38084
+ LearningCanvas,
38085
+ {
38086
+ width,
38087
+ height,
38088
+ shapes: derivedShapes,
38089
+ interactive,
38090
+ animate,
38091
+ onShapeClick,
38092
+ isLoading,
38093
+ error
38094
+ }
38095
+ )
38096
+ ] }) });
38097
+ };
38098
+ }
38099
+ });
38100
+ var PhysicsCanvas;
38101
+ var init_PhysicsCanvas = __esm({
38102
+ "components/learning/molecules/PhysicsCanvas.tsx"() {
38103
+ "use client";
38104
+ init_atoms();
38105
+ init_Stack();
38106
+ init_LearningCanvas();
38107
+ PhysicsCanvas = ({
38108
+ className,
38109
+ width = 600,
38110
+ height = 400,
38111
+ title,
38112
+ backgroundColor,
38113
+ bodies = [],
38114
+ constraints = [],
38115
+ showVelocity = true,
38116
+ showForces = false,
38117
+ velocityScale = 20,
38118
+ forceScale = 20,
38119
+ shapes = [],
38120
+ interactive = false,
38121
+ animate = false,
38122
+ onShapeClick,
38123
+ isLoading,
38124
+ error
38125
+ }) => {
38126
+ const derivedShapes = React114.useMemo(() => {
38127
+ const out = [];
38128
+ const bodyById = /* @__PURE__ */ new Map();
38129
+ for (const b of bodies) {
38130
+ if (b.id) bodyById.set(b.id, b);
38131
+ }
38132
+ for (const c of constraints) {
38133
+ const a = bodyById.get(c.from);
38134
+ const b = bodyById.get(c.to);
38135
+ if (!a || !b) continue;
38136
+ out.push({
38137
+ type: "line",
38138
+ x1: a.x,
38139
+ y1: a.y,
38140
+ x2: b.x,
38141
+ y2: b.y,
38142
+ color: c.color ?? "#9ca3af",
38143
+ lineWidth: 2
38144
+ });
38145
+ }
38146
+ for (const b of bodies) {
38147
+ out.push({
38148
+ type: "circle",
38149
+ x: b.x,
38150
+ y: b.y,
38151
+ radius: b.radius ?? 12,
38152
+ color: b.color ?? "#2563eb",
38153
+ fill: b.color ?? "#2563eb",
38154
+ id: b.id
38155
+ });
38156
+ if (b.label) {
38157
+ out.push({
38158
+ type: "text",
38159
+ x: b.x + (b.radius ?? 12) + 6,
38160
+ y: b.y - (b.radius ?? 12) - 6,
38161
+ text: b.label,
38162
+ color: "#111827",
38163
+ fontSize: 12
38164
+ });
38165
+ }
38166
+ if (showVelocity && b.vx != null && b.vy != null && (b.vx !== 0 || b.vy !== 0)) {
38167
+ out.push({
38168
+ type: "arrow",
38169
+ x1: b.x,
38170
+ y1: b.y,
38171
+ x2: b.x + b.vx * velocityScale,
38172
+ y2: b.y + b.vy * velocityScale,
38173
+ color: "#16a34a",
38174
+ lineWidth: 2
38175
+ });
38176
+ }
38177
+ if (showForces && b.fx != null && b.fy != null && (b.fx !== 0 || b.fy !== 0)) {
38178
+ out.push({
38179
+ type: "arrow",
38180
+ x1: b.x,
38181
+ y1: b.y,
38182
+ x2: b.x + b.fx * forceScale,
38183
+ y2: b.y + b.fy * forceScale,
38184
+ color: "#dc2626",
38185
+ lineWidth: 2
38186
+ });
38187
+ }
38188
+ }
38189
+ out.push(...shapes);
38190
+ return out;
38191
+ }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
38192
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
38193
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
38194
+ /* @__PURE__ */ jsxRuntime.jsx(
38195
+ LearningCanvas,
38196
+ {
38197
+ width,
38198
+ height,
38199
+ backgroundColor,
38200
+ shapes: derivedShapes,
38201
+ interactive,
38202
+ animate,
38203
+ onShapeClick,
38204
+ isLoading,
38205
+ error
38206
+ }
38207
+ )
38208
+ ] }) });
38209
+ };
38210
+ }
38211
+ });
37637
38212
  function resolveNodeColor(node, groups) {
37638
38213
  if (node.color) return node.color;
37639
38214
  if (node.group) {
@@ -38005,13 +38580,13 @@ var init_MapView = __esm({
38005
38580
  shadowSize: [41, 41]
38006
38581
  });
38007
38582
  L.Marker.prototype.options.icon = defaultIcon;
38008
- const { useEffect: useEffect78, useRef: useRef78, useCallback: useCallback118, useState: useState115 } = React114__namespace.default;
38583
+ const { useEffect: useEffect79, useRef: useRef79, useCallback: useCallback118, useState: useState115 } = React114__namespace.default;
38009
38584
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
38010
38585
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
38011
38586
  function MapUpdater({ centerLat, centerLng, zoom }) {
38012
38587
  const map = useMap();
38013
- const prevRef = useRef78({ centerLat, centerLng, zoom });
38014
- useEffect78(() => {
38588
+ const prevRef = useRef79({ centerLat, centerLng, zoom });
38589
+ useEffect79(() => {
38015
38590
  const prev = prevRef.current;
38016
38591
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
38017
38592
  map.setView([centerLat, centerLng], zoom);
@@ -38022,7 +38597,7 @@ var init_MapView = __esm({
38022
38597
  }
38023
38598
  function MapClickHandler({ onMapClick }) {
38024
38599
  const map = useMap();
38025
- useEffect78(() => {
38600
+ useEffect79(() => {
38026
38601
  if (!onMapClick) return;
38027
38602
  const handler = (e) => {
38028
38603
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -46054,7 +46629,7 @@ function getGroupColor(group, groups) {
46054
46629
  const idx = groups.indexOf(group);
46055
46630
  return GROUP_COLORS2[idx % GROUP_COLORS2.length];
46056
46631
  }
46057
- function resolveColor2(color, el) {
46632
+ function resolveColor3(color, el) {
46058
46633
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
46059
46634
  if (!m) return color;
46060
46635
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
@@ -46315,7 +46890,7 @@ var init_GraphCanvas = __esm({
46315
46890
  const w = logicalW;
46316
46891
  const h = height;
46317
46892
  const nodes = nodesRef.current;
46318
- const accentColor = resolveColor2("var(--color-accent)", canvas);
46893
+ const accentColor = resolveColor3("var(--color-accent)", canvas);
46319
46894
  const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
46320
46895
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
46321
46896
  ctx.clearRect(0, 0, w, h);
@@ -46354,7 +46929,7 @@ var init_GraphCanvas = __esm({
46354
46929
  ctx.globalAlpha = 1;
46355
46930
  for (const node of nodes) {
46356
46931
  const size = node.size || 8;
46357
- const color = resolveColor2(node.color || getGroupColor(node.group, groups), canvas);
46932
+ const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
46358
46933
  const isHovered = hoveredNode === node.id;
46359
46934
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
46360
46935
  ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
@@ -53252,6 +53827,7 @@ var init_component_registry_generated = __esm({
53252
53827
  init_BattleBoard();
53253
53828
  init_BattleTemplate();
53254
53829
  init_BehaviorView();
53830
+ init_BiologyCanvas();
53255
53831
  init_BloomQuizBlock();
53256
53832
  init_BoardgameBoard();
53257
53833
  init_BookChapterView();
@@ -53263,7 +53839,6 @@ var init_component_registry_generated = __esm({
53263
53839
  init_BranchingLogicBuilder();
53264
53840
  init_Breadcrumb();
53265
53841
  init_BuilderBoard();
53266
- init_ButtonGroup();
53267
53842
  init_CTABanner();
53268
53843
  init_CalendarGrid();
53269
53844
  init_Canvas2D();
@@ -53282,6 +53857,7 @@ var init_component_registry_generated = __esm({
53282
53857
  init_ChartLegend();
53283
53858
  init_ChatBar();
53284
53859
  init_Checkbox();
53860
+ init_ChemistryCanvas();
53285
53861
  init_ChoiceButton();
53286
53862
  init_CityBuilderBoard();
53287
53863
  init_CityBuilderTemplate();
@@ -53340,9 +53916,9 @@ var init_component_registry_generated = __esm({
53340
53916
  init_FlipContainer();
53341
53917
  init_FloatingActionButton();
53342
53918
  init_Form();
53919
+ init_FormSection();
53343
53920
  init_FormField();
53344
53921
  init_FormSectionHeader();
53345
- init_GameAudioProvider();
53346
53922
  init_GameAudioToggle();
53347
53923
  init_GameCard();
53348
53924
  init_GameHud();
@@ -53373,6 +53949,7 @@ var init_component_registry_generated = __esm({
53373
53949
  init_JazariStateMachine();
53374
53950
  init_LandingPageTemplate();
53375
53951
  init_LawReferenceTooltip();
53952
+ init_LearningCanvas();
53376
53953
  init_Lightbox();
53377
53954
  init_LikertScale();
53378
53955
  init_LineChart();
@@ -53381,9 +53958,11 @@ var init_component_registry_generated = __esm({
53381
53958
  init_MapView();
53382
53959
  init_MarkdownContent();
53383
53960
  init_MarketingFooter();
53961
+ init_MarketingStatCard();
53384
53962
  init_MasterDetail();
53385
53963
  init_MasterDetailLayout();
53386
53964
  init_MatchPuzzleBoard();
53965
+ init_MathCanvas();
53387
53966
  init_MatrixQuestion();
53388
53967
  init_MediaGallery();
53389
53968
  init_Menu();
@@ -53402,6 +53981,7 @@ var init_component_registry_generated = __esm({
53402
53981
  init_PageHeader();
53403
53982
  init_Pagination();
53404
53983
  init_PatternTile();
53984
+ init_PhysicsCanvas();
53405
53985
  init_PinballBoard();
53406
53986
  init_PirateBoard();
53407
53987
  init_PlatformerBoard();
@@ -53578,6 +54158,7 @@ var init_component_registry_generated = __esm({
53578
54158
  "BattleBoard": BattleBoard,
53579
54159
  "BattleTemplate": BattleTemplate,
53580
54160
  "BehaviorView": exports.BehaviorView,
54161
+ "BiologyCanvas": BiologyCanvas,
53581
54162
  "BloomQuizBlock": BloomQuizBlock,
53582
54163
  "BoardgameBoard": BoardgameBoard,
53583
54164
  "BookChapterView": BookChapterView,
@@ -53591,7 +54172,6 @@ var init_component_registry_generated = __esm({
53591
54172
  "Breadcrumb": Breadcrumb,
53592
54173
  "BuilderBoard": BuilderBoard,
53593
54174
  "Button": ButtonPattern,
53594
- "ButtonGroup": ButtonGroup,
53595
54175
  "ButtonPattern": ButtonPattern,
53596
54176
  "CTABanner": CTABanner,
53597
54177
  "CalendarGrid": CalendarGrid,
@@ -53611,6 +54191,7 @@ var init_component_registry_generated = __esm({
53611
54191
  "ChartLegend": ChartLegend,
53612
54192
  "ChatBar": ChatBar,
53613
54193
  "Checkbox": Checkbox,
54194
+ "ChemistryCanvas": ChemistryCanvas,
53614
54195
  "ChoiceButton": ChoiceButton,
53615
54196
  "CityBuilderBoard": CityBuilderBoard,
53616
54197
  "CityBuilderTemplate": CityBuilderTemplate,
@@ -53671,9 +54252,10 @@ var init_component_registry_generated = __esm({
53671
54252
  "FlipContainer": FlipContainer,
53672
54253
  "FloatingActionButton": FloatingActionButton,
53673
54254
  "Form": Form,
54255
+ "FormActions": FormActions,
53674
54256
  "FormField": FormField,
54257
+ "FormLayout": FormLayout,
53675
54258
  "FormSectionHeader": FormSectionHeader,
53676
- "GameAudioProvider": GameAudioProvider,
53677
54259
  "GameAudioToggle": GameAudioToggle,
53678
54260
  "GameBoard3D": GameBoard3D,
53679
54261
  "GameCanvas3D": GameCanvas3D,
@@ -53711,6 +54293,7 @@ var init_component_registry_generated = __esm({
53711
54293
  "LabelPattern": LabelPattern,
53712
54294
  "LandingPageTemplate": LandingPageTemplate,
53713
54295
  "LawReferenceTooltip": LawReferenceTooltip,
54296
+ "LearningCanvas": LearningCanvas,
53714
54297
  "Lightbox": Lightbox,
53715
54298
  "LikertScale": LikertScale,
53716
54299
  "LineChart": LineChart2,
@@ -53719,9 +54302,11 @@ var init_component_registry_generated = __esm({
53719
54302
  "MapView": MapView,
53720
54303
  "MarkdownContent": MarkdownContent,
53721
54304
  "MarketingFooter": MarketingFooter,
54305
+ "MarketingStatCard": MarketingStatCard,
53722
54306
  "MasterDetail": MasterDetail,
53723
54307
  "MasterDetailLayout": MasterDetailLayout,
53724
54308
  "MatchPuzzleBoard": MatchPuzzleBoard,
54309
+ "MathCanvas": MathCanvas,
53725
54310
  "MatrixQuestion": MatrixQuestion,
53726
54311
  "MediaGallery": MediaGallery,
53727
54312
  "Menu": Menu,
@@ -53740,6 +54325,7 @@ var init_component_registry_generated = __esm({
53740
54325
  "PageHeader": PageHeader,
53741
54326
  "Pagination": Pagination,
53742
54327
  "PatternTile": PatternTile,
54328
+ "PhysicsCanvas": PhysicsCanvas,
53743
54329
  "PinballBoard": PinballBoard,
53744
54330
  "PirateBoard": PirateBoard,
53745
54331
  "PlatformerBoard": PlatformerBoard,