@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
package/dist/avl/index.js CHANGED
@@ -7,7 +7,7 @@ import ELK from 'elkjs/lib/elk.bundled.js';
7
7
  import { MarkerType, useReactFlow, Handle, Position, getBezierPath, EdgeLabelRenderer, useNodeId, ReactFlowProvider, BaseEdge, useNodesState, useEdgesState, ReactFlow, Controls, Background, BackgroundVariant } from '@xyflow/react';
8
8
  import { useTranslate } from '@almadar/ui/hooks';
9
9
  import * as LucideIcons2 from 'lucide-react';
10
- import { Loader2, X, Code, FileText, WrapText, Check, Copy, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, 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';
10
+ import { Loader2, X, Code, FileText, WrapText, Check, Copy, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, 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';
11
11
  import { createPortal } from 'react-dom';
12
12
  import { UISlotProvider, useUISlots, useTheme } from '@almadar/ui/context';
13
13
  import { evaluateGuard, evaluate, createMinimalContext } from '@almadar/evaluator';
@@ -584,7 +584,7 @@ var init_AvlFieldType = __esm({
584
584
  }
585
585
  });
586
586
 
587
- // node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
587
+ // node_modules/clsx/dist/clsx.mjs
588
588
  function r(e) {
589
589
  var t, f3, n = "";
590
590
  if ("string" == typeof e || "number" == typeof e) n += e;
@@ -599,11 +599,11 @@ function clsx() {
599
599
  return n;
600
600
  }
601
601
  var init_clsx = __esm({
602
- "node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs"() {
602
+ "node_modules/clsx/dist/clsx.mjs"() {
603
603
  }
604
604
  });
605
605
 
606
- // node_modules/.pnpm/tailwind-merge@2.6.1/node_modules/tailwind-merge/dist/bundle-mjs.mjs
606
+ // node_modules/tailwind-merge/dist/bundle-mjs.mjs
607
607
  function twJoin() {
608
608
  let index = 0;
609
609
  let argument;
@@ -647,7 +647,7 @@ function createTailwindMerge(createConfigFirst, ...createConfigRest) {
647
647
  }
648
648
  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;
649
649
  var init_bundle_mjs = __esm({
650
- "node_modules/.pnpm/tailwind-merge@2.6.1/node_modules/tailwind-merge/dist/bundle-mjs.mjs"() {
650
+ "node_modules/tailwind-merge/dist/bundle-mjs.mjs"() {
651
651
  CLASS_PART_SEPARATOR = "-";
652
652
  createClassGroupUtils = (config) => {
653
653
  const classMap = createClassMap(config);
@@ -9373,6 +9373,66 @@ var init_RangeSlider = __esm({
9373
9373
  RangeSlider.displayName = "RangeSlider";
9374
9374
  }
9375
9375
  });
9376
+ function easeOut(t) {
9377
+ return t * (2 - t);
9378
+ }
9379
+ var AnimatedCounter;
9380
+ var init_AnimatedCounter = __esm({
9381
+ "components/marketing/atoms/AnimatedCounter.tsx"() {
9382
+ "use client";
9383
+ init_cn();
9384
+ init_Typography();
9385
+ AnimatedCounter = ({
9386
+ value: rawValue,
9387
+ duration = 600,
9388
+ prefix,
9389
+ suffix,
9390
+ className
9391
+ }) => {
9392
+ const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
9393
+ const value = !Number.isNaN(numericRaw) ? numericRaw : 0;
9394
+ const [displayValue, setDisplayValue] = useState(value);
9395
+ const previousValueRef = useRef(value);
9396
+ const animationFrameRef = useRef(null);
9397
+ useEffect(() => {
9398
+ const from = previousValueRef.current;
9399
+ const to = value;
9400
+ previousValueRef.current = value;
9401
+ if (from === to) {
9402
+ setDisplayValue(to);
9403
+ return;
9404
+ }
9405
+ const startTime = performance.now();
9406
+ const diff = to - from;
9407
+ function animate(currentTime) {
9408
+ const elapsed = currentTime - startTime;
9409
+ const progress = Math.min(elapsed / duration, 1);
9410
+ const easedProgress = easeOut(progress);
9411
+ setDisplayValue(from + diff * easedProgress);
9412
+ if (progress < 1) {
9413
+ animationFrameRef.current = requestAnimationFrame(animate);
9414
+ } else {
9415
+ setDisplayValue(to);
9416
+ }
9417
+ }
9418
+ animationFrameRef.current = requestAnimationFrame(animate);
9419
+ return () => {
9420
+ if (animationFrameRef.current !== null) {
9421
+ cancelAnimationFrame(animationFrameRef.current);
9422
+ }
9423
+ };
9424
+ }, [value, duration]);
9425
+ const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
9426
+ const formattedValue = displayValue.toFixed(decimalPlaces);
9427
+ return /* @__PURE__ */ jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
9428
+ prefix,
9429
+ formattedValue,
9430
+ suffix
9431
+ ] });
9432
+ };
9433
+ AnimatedCounter.displayName = "AnimatedCounter";
9434
+ }
9435
+ });
9376
9436
  function useInfiniteScroll(onLoadMore, options = {}) {
9377
9437
  const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
9378
9438
  const observerRef = useRef(null);
@@ -9669,6 +9729,34 @@ var init_SectionHeader = __esm({
9669
9729
  SectionHeader.displayName = "SectionHeader";
9670
9730
  }
9671
9731
  });
9732
+ var sizeClasses6, MarketingStatCard;
9733
+ var init_MarketingStatCard = __esm({
9734
+ "components/marketing/atoms/MarketingStatCard.tsx"() {
9735
+ init_cn();
9736
+ init_Stack();
9737
+ init_Typography();
9738
+ sizeClasses6 = {
9739
+ sm: "text-2xl",
9740
+ md: "text-4xl",
9741
+ lg: "text-5xl"
9742
+ };
9743
+ MarketingStatCard = ({ value, label, size = "md", className }) => {
9744
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", align: "center", className: cn(className), children: [
9745
+ /* @__PURE__ */ jsx(
9746
+ Typography,
9747
+ {
9748
+ weight: "bold",
9749
+ color: "primary",
9750
+ className: cn(sizeClasses6[size]),
9751
+ children: value
9752
+ }
9753
+ ),
9754
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: label })
9755
+ ] });
9756
+ };
9757
+ MarketingStatCard.displayName = "MarketingStatCard";
9758
+ }
9759
+ });
9672
9760
  var backgroundClasses, paddingClasses, ContentSection;
9673
9761
  var init_ContentSection = __esm({
9674
9762
  "components/marketing/atoms/ContentSection.tsx"() {
@@ -15862,318 +15950,14 @@ var init_useCanvasEffects = __esm({
15862
15950
  "use client";
15863
15951
  }
15864
15952
  });
15865
- function pickPath(entry) {
15866
- if (Array.isArray(entry.path)) {
15867
- return entry.path[Math.floor(Math.random() * entry.path.length)];
15868
- }
15869
- return entry.path;
15870
- }
15871
- function useGameAudio({
15872
- manifest,
15873
- baseUrl = "",
15874
- initialMuted = false,
15875
- initialVolume = 1
15876
- }) {
15877
- const [muted, setMutedState] = useState(initialMuted);
15878
- const [masterVolume, setMasterVolumeState] = useState(initialVolume);
15879
- const mutedRef = useRef(muted);
15880
- const volumeRef = useRef(masterVolume);
15881
- const manifestRef = useRef(manifest);
15882
- mutedRef.current = muted;
15883
- volumeRef.current = masterVolume;
15884
- manifestRef.current = manifest;
15885
- const poolsRef = useRef(/* @__PURE__ */ new Map());
15886
- const getOrCreateElement = useCallback((key) => {
15887
- const entry = manifestRef.current[key];
15888
- if (!entry) return null;
15889
- let pool = poolsRef.current.get(key);
15890
- if (!pool) {
15891
- pool = [];
15892
- poolsRef.current.set(key, pool);
15893
- }
15894
- const maxSize = entry.poolSize ?? 1;
15895
- for (const audio of pool) {
15896
- if (audio.paused && (audio.ended || audio.currentTime === 0)) {
15897
- return audio;
15898
- }
15899
- }
15900
- if (pool.length < maxSize) {
15901
- const src = baseUrl + pickPath(entry);
15902
- const audio = new Audio(src);
15903
- audio.loop = entry.loop ?? false;
15904
- pool.push(audio);
15905
- return audio;
15906
- }
15907
- if (!entry.loop) {
15908
- let oldest = pool[0];
15909
- for (const audio of pool) {
15910
- if (audio.currentTime > oldest.currentTime) {
15911
- oldest = audio;
15912
- }
15913
- }
15914
- oldest.pause();
15915
- oldest.currentTime = 0;
15916
- return oldest;
15917
- }
15918
- return null;
15919
- }, [baseUrl]);
15920
- const play = useCallback((key) => {
15921
- if (mutedRef.current) return;
15922
- const entry = manifestRef.current[key];
15923
- if (!entry) return;
15924
- const audio = getOrCreateElement(key);
15925
- if (!audio) return;
15926
- audio.volume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
15927
- if (!entry.loop) {
15928
- audio.currentTime = 0;
15929
- }
15930
- const promise = audio.play();
15931
- if (promise) {
15932
- promise.catch(() => {
15933
- });
15934
- }
15935
- }, [getOrCreateElement]);
15936
- const stop = useCallback((key) => {
15937
- const pool = poolsRef.current.get(key);
15938
- if (!pool) return;
15939
- for (const audio of pool) {
15940
- audio.pause();
15941
- audio.currentTime = 0;
15942
- }
15943
- }, []);
15944
- const currentMusicKeyRef = useRef(null);
15945
- const currentMusicElRef = useRef(null);
15946
- const musicFadeRef = useRef(null);
15947
- const pendingMusicKeyRef = useRef(null);
15948
- const clearMusicFade = useCallback(() => {
15949
- if (musicFadeRef.current) {
15950
- clearInterval(musicFadeRef.current);
15951
- musicFadeRef.current = null;
15952
- }
15953
- }, []);
15954
- const playMusic = useCallback((key) => {
15955
- if (key === currentMusicKeyRef.current) return;
15956
- pendingMusicKeyRef.current = key;
15957
- const entry = manifestRef.current[key];
15958
- if (!entry) return;
15959
- const fadeDurationMs = entry.crossfadeDurationMs ?? 1500;
15960
- const stepMs = 50;
15961
- const totalSteps = Math.max(1, fadeDurationMs / stepMs);
15962
- const targetVolume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
15963
- clearMusicFade();
15964
- const src = baseUrl + (Array.isArray(entry.path) ? entry.path[0] : entry.path);
15965
- const incoming = new Audio(src);
15966
- incoming.loop = true;
15967
- incoming.volume = 0;
15968
- const outgoing = currentMusicElRef.current;
15969
- const outgoingStartVol = outgoing?.volume ?? 0;
15970
- currentMusicKeyRef.current = key;
15971
- currentMusicElRef.current = incoming;
15972
- if (!mutedRef.current) {
15973
- incoming.play().catch(() => {
15974
- currentMusicKeyRef.current = null;
15975
- currentMusicElRef.current = outgoing;
15976
- });
15977
- }
15978
- let step = 0;
15979
- musicFadeRef.current = setInterval(() => {
15980
- step++;
15981
- const progress = Math.min(step / totalSteps, 1);
15982
- incoming.volume = Math.min(1, targetVolume * progress);
15983
- if (outgoing) {
15984
- outgoing.volume = Math.max(0, outgoingStartVol * (1 - progress));
15985
- }
15986
- if (progress >= 1) {
15987
- clearMusicFade();
15988
- if (outgoing) {
15989
- outgoing.pause();
15990
- outgoing.src = "";
15991
- }
15992
- }
15993
- }, stepMs);
15994
- }, [baseUrl, clearMusicFade]);
15995
- const stopMusic = useCallback((fadeDurationMs = 1e3) => {
15996
- const outgoing = currentMusicElRef.current;
15997
- if (!outgoing) return;
15998
- currentMusicKeyRef.current = null;
15999
- currentMusicElRef.current = null;
16000
- pendingMusicKeyRef.current = null;
16001
- clearMusicFade();
16002
- const startVolume = outgoing.volume;
16003
- const stepMs = 50;
16004
- const totalSteps = Math.max(1, fadeDurationMs / stepMs);
16005
- let step = 0;
16006
- musicFadeRef.current = setInterval(() => {
16007
- step++;
16008
- const progress = step / totalSteps;
16009
- outgoing.volume = Math.max(0, startVolume * (1 - progress));
16010
- if (progress >= 1) {
16011
- clearMusicFade();
16012
- outgoing.pause();
16013
- outgoing.src = "";
16014
- }
16015
- }, stepMs);
16016
- }, [clearMusicFade]);
16017
- const stopAll = useCallback(() => {
16018
- for (const pool of poolsRef.current.values()) {
16019
- for (const audio of pool) {
16020
- audio.pause();
16021
- audio.currentTime = 0;
16022
- }
16023
- }
16024
- stopMusic(0);
16025
- }, [stopMusic]);
16026
- const setMuted = useCallback((value) => {
16027
- setMutedState(value);
16028
- if (value) {
16029
- for (const [key, pool] of poolsRef.current.entries()) {
16030
- if (manifestRef.current[key]?.loop) {
16031
- for (const audio of pool) {
16032
- if (!audio.paused) audio.pause();
16033
- }
16034
- }
16035
- }
16036
- currentMusicElRef.current?.pause();
16037
- } else {
16038
- for (const [key, pool] of poolsRef.current.entries()) {
16039
- const entry = manifestRef.current[key];
16040
- if (entry?.loop && entry?.autostart) {
16041
- for (const audio of pool) {
16042
- if (audio.paused) audio.play().catch(() => {
16043
- });
16044
- }
16045
- }
16046
- }
16047
- const musicEl = currentMusicElRef.current;
16048
- if (musicEl) {
16049
- musicEl.play().catch(() => {
16050
- });
16051
- }
16052
- }
16053
- }, []);
16054
- const setMasterVolume = useCallback((volume) => {
16055
- const clamped = Math.max(0, Math.min(1, volume));
16056
- setMasterVolumeState(clamped);
16057
- for (const [key, pool] of poolsRef.current.entries()) {
16058
- const entryVol = manifestRef.current[key]?.volume ?? 1;
16059
- for (const audio of pool) {
16060
- audio.volume = Math.min(1, entryVol * clamped);
16061
- }
16062
- }
16063
- if (!musicFadeRef.current && currentMusicElRef.current) {
16064
- const key = currentMusicKeyRef.current;
16065
- const entryVol = key ? manifestRef.current[key]?.volume ?? 1 : 1;
16066
- currentMusicElRef.current.volume = Math.min(1, entryVol * clamped);
16067
- }
16068
- }, []);
16069
- const unlockedRef = useRef(false);
16070
- useEffect(() => {
16071
- const autoKeys = Object.keys(manifest).filter((k) => manifest[k].autostart);
16072
- const hasPendingMusic = () => pendingMusicKeyRef.current !== null;
16073
- const hasAutoStart = autoKeys.length > 0;
16074
- if (!hasAutoStart && !hasPendingMusic()) return;
16075
- const unlock = () => {
16076
- if (unlockedRef.current) return;
16077
- unlockedRef.current = true;
16078
- if (!mutedRef.current) {
16079
- for (const key of autoKeys) {
16080
- play(key);
16081
- }
16082
- const pending = pendingMusicKeyRef.current;
16083
- if (pending && pending !== currentMusicKeyRef.current) {
16084
- playMusic(pending);
16085
- }
16086
- }
16087
- };
16088
- document.addEventListener("click", unlock, { once: true });
16089
- document.addEventListener("keydown", unlock, { once: true });
16090
- document.addEventListener("touchstart", unlock, { once: true });
16091
- return () => {
16092
- document.removeEventListener("click", unlock);
16093
- document.removeEventListener("keydown", unlock);
16094
- document.removeEventListener("touchstart", unlock);
16095
- };
16096
- }, [manifest, play, playMusic]);
16097
- useEffect(() => {
16098
- return () => {
16099
- clearMusicFade();
16100
- for (const pool of poolsRef.current.values()) {
16101
- for (const audio of pool) {
16102
- audio.pause();
16103
- audio.src = "";
16104
- }
16105
- }
16106
- poolsRef.current.clear();
16107
- if (currentMusicElRef.current) {
16108
- currentMusicElRef.current.pause();
16109
- currentMusicElRef.current.src = "";
16110
- currentMusicElRef.current = null;
16111
- }
16112
- };
16113
- }, [clearMusicFade]);
16114
- return {
16115
- play,
16116
- stop,
16117
- stopAll,
16118
- playMusic,
16119
- stopMusic,
16120
- muted,
16121
- setMuted,
16122
- masterVolume,
16123
- setMasterVolume
16124
- };
16125
- }
16126
15953
  var init_useGameAudio = __esm({
16127
15954
  "components/game/shared/hooks/useGameAudio.ts"() {
16128
15955
  "use client";
16129
- useGameAudio.displayName = "useGameAudio";
16130
15956
  }
16131
15957
  });
16132
15958
  function useGameAudioContextOptional() {
16133
15959
  return useContext(GameAudioContext);
16134
15960
  }
16135
- function GameAudioProvider({
16136
- manifest,
16137
- baseUrl = "",
16138
- children,
16139
- initialMuted = false
16140
- }) {
16141
- const eventBus = useEventBus();
16142
- const { play, stop, stopAll, playMusic, stopMusic, muted, setMuted, masterVolume, setMasterVolume } = useGameAudio({ manifest, baseUrl, initialMuted });
16143
- useEffect(() => {
16144
- const unsubPlay = eventBus.on("UI:PLAY_SOUND", (event) => {
16145
- const key = event.payload?.key;
16146
- if (key) play(key);
16147
- });
16148
- const unsubStop = eventBus.on("UI:STOP_SOUND", (event) => {
16149
- const key = event.payload?.key;
16150
- if (key) {
16151
- stop(key);
16152
- } else {
16153
- stopAll();
16154
- }
16155
- });
16156
- const unsubChangeMusic = eventBus.on("UI:CHANGE_MUSIC", (event) => {
16157
- const key = event.payload?.key;
16158
- if (key) {
16159
- playMusic(key);
16160
- } else {
16161
- stopMusic();
16162
- }
16163
- });
16164
- const unsubStopMusic = eventBus.on("UI:STOP_MUSIC", () => {
16165
- stopMusic();
16166
- });
16167
- return () => {
16168
- unsubPlay();
16169
- unsubStop();
16170
- unsubChangeMusic();
16171
- unsubStopMusic();
16172
- };
16173
- }, [eventBus, play, stop, stopAll, playMusic, stopMusic]);
16174
- const value = { muted, setMuted, masterVolume, setMasterVolume, play, playMusic, stopMusic };
16175
- return /* @__PURE__ */ jsx(GameAudioContext.Provider, { value, children });
16176
- }
16177
15961
  var GameAudioContext;
16178
15962
  var init_GameAudioProvider = __esm({
16179
15963
  "components/game/shared/providers/GameAudioProvider.tsx"() {
@@ -16182,7 +15966,6 @@ var init_GameAudioProvider = __esm({
16182
15966
  init_useGameAudio();
16183
15967
  GameAudioContext = createContext(null);
16184
15968
  GameAudioContext.displayName = "GameAudioContext";
16185
- GameAudioProvider.displayName = "GameAudioProvider";
16186
15969
  }
16187
15970
  });
16188
15971
  function GameAudioToggle({
@@ -23818,6 +23601,315 @@ var init_molecules = __esm({
23818
23601
  init_puzzleObject();
23819
23602
  }
23820
23603
  });
23604
+ function resolveColor2(color, ctx, fallback) {
23605
+ if (!color) return fallback;
23606
+ if (color.startsWith("var(")) {
23607
+ ctx.canvas.style;
23608
+ const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color);
23609
+ if (m) {
23610
+ const computed = getComputedStyle(ctx.canvas).getPropertyValue(m[1]).trim();
23611
+ return computed || m[2]?.trim() || fallback;
23612
+ }
23613
+ }
23614
+ return color;
23615
+ }
23616
+ function shapeBounds(shape) {
23617
+ switch (shape.type) {
23618
+ case "line":
23619
+ case "arrow":
23620
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) return null;
23621
+ return {
23622
+ x: Math.min(shape.x1, shape.x2) - 6,
23623
+ y: Math.min(shape.y1, shape.y2) - 6,
23624
+ w: Math.abs(shape.x2 - shape.x1) + 12,
23625
+ h: Math.abs(shape.y2 - shape.y1) + 12
23626
+ };
23627
+ case "circle":
23628
+ if (shape.x == null || shape.y == null || shape.radius == null) return null;
23629
+ return {
23630
+ x: shape.x - shape.radius - 4,
23631
+ y: shape.y - shape.radius - 4,
23632
+ w: shape.radius * 2 + 8,
23633
+ h: shape.radius * 2 + 8
23634
+ };
23635
+ case "rect":
23636
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
23637
+ return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
23638
+ case "polygon":
23639
+ if (!shape.points || shape.points.length === 0) return null;
23640
+ {
23641
+ const xs = shape.points.map((p2) => p2.x);
23642
+ const ys = shape.points.map((p2) => p2.y);
23643
+ const minX = Math.min(...xs);
23644
+ const minY = Math.min(...ys);
23645
+ return {
23646
+ x: minX - 4,
23647
+ y: minY - 4,
23648
+ w: Math.max(...xs) - minX + 8,
23649
+ h: Math.max(...ys) - minY + 8
23650
+ };
23651
+ }
23652
+ case "text":
23653
+ if (shape.x == null || shape.y == null) return null;
23654
+ return { x: shape.x - 4, y: shape.y - (shape.fontSize ?? 14) - 4, w: 120, h: (shape.fontSize ?? 14) + 8 };
23655
+ default:
23656
+ return null;
23657
+ }
23658
+ }
23659
+ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
23660
+ const angle = Math.atan2(y2 - y1, x2 - x1);
23661
+ ctx.beginPath();
23662
+ ctx.moveTo(x2, y2);
23663
+ ctx.lineTo(x2 - size * Math.cos(angle - Math.PI / 6), y2 - size * Math.sin(angle - Math.PI / 6));
23664
+ ctx.lineTo(x2 - size * Math.cos(angle + Math.PI / 6), y2 - size * Math.sin(angle + Math.PI / 6));
23665
+ ctx.closePath();
23666
+ ctx.fill();
23667
+ }
23668
+ function drawShape(ctx, shape, width, height) {
23669
+ ctx.save();
23670
+ const opacity = shape.opacity ?? 1;
23671
+ ctx.globalAlpha = opacity;
23672
+ const stroke = resolveColor2(shape.color, ctx, "#333333");
23673
+ const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
23674
+ ctx.lineWidth = shape.lineWidth ?? 2;
23675
+ switch (shape.type) {
23676
+ case "grid": {
23677
+ const step = shape.step ?? 40;
23678
+ ctx.strokeStyle = stroke;
23679
+ ctx.globalAlpha = opacity * 0.25;
23680
+ ctx.lineWidth = 1;
23681
+ ctx.beginPath();
23682
+ for (let x = 0; x <= width; x += step) {
23683
+ ctx.moveTo(x, 0);
23684
+ ctx.lineTo(x, height);
23685
+ }
23686
+ for (let y = 0; y <= height; y += step) {
23687
+ ctx.moveTo(0, y);
23688
+ ctx.lineTo(width, y);
23689
+ }
23690
+ ctx.stroke();
23691
+ break;
23692
+ }
23693
+ case "axis": {
23694
+ const axis = shape.axis ?? "x";
23695
+ ctx.strokeStyle = stroke;
23696
+ ctx.lineWidth = shape.lineWidth ?? 2;
23697
+ ctx.beginPath();
23698
+ if (axis === "x") {
23699
+ ctx.moveTo(0, height / 2);
23700
+ ctx.lineTo(width, height / 2);
23701
+ } else {
23702
+ ctx.moveTo(width / 2, 0);
23703
+ ctx.lineTo(width / 2, height);
23704
+ }
23705
+ ctx.stroke();
23706
+ break;
23707
+ }
23708
+ case "line": {
23709
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
23710
+ ctx.strokeStyle = stroke;
23711
+ ctx.beginPath();
23712
+ ctx.moveTo(shape.x1, shape.y1);
23713
+ ctx.lineTo(shape.x2, shape.y2);
23714
+ ctx.stroke();
23715
+ break;
23716
+ }
23717
+ case "arrow": {
23718
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
23719
+ ctx.strokeStyle = stroke;
23720
+ ctx.fillStyle = stroke;
23721
+ ctx.beginPath();
23722
+ ctx.moveTo(shape.x1, shape.y1);
23723
+ ctx.lineTo(shape.x2, shape.y2);
23724
+ ctx.stroke();
23725
+ drawArrowHead(ctx, shape.x1, shape.y1, shape.x2, shape.y2, 10);
23726
+ break;
23727
+ }
23728
+ case "circle": {
23729
+ if (shape.x == null || shape.y == null || shape.radius == null) break;
23730
+ ctx.beginPath();
23731
+ ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
23732
+ if (fill) {
23733
+ ctx.fillStyle = fill;
23734
+ ctx.fill();
23735
+ }
23736
+ ctx.strokeStyle = stroke;
23737
+ ctx.stroke();
23738
+ break;
23739
+ }
23740
+ case "rect": {
23741
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
23742
+ if (fill) {
23743
+ ctx.fillStyle = fill;
23744
+ ctx.fillRect(shape.x, shape.y, shape.width, shape.height);
23745
+ }
23746
+ ctx.strokeStyle = stroke;
23747
+ ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
23748
+ break;
23749
+ }
23750
+ case "polygon": {
23751
+ if (!shape.points || shape.points.length < 2) break;
23752
+ ctx.beginPath();
23753
+ ctx.moveTo(shape.points[0].x, shape.points[0].y);
23754
+ for (let i = 1; i < shape.points.length; i++) {
23755
+ ctx.lineTo(shape.points[i].x, shape.points[i].y);
23756
+ }
23757
+ ctx.closePath();
23758
+ if (fill) {
23759
+ ctx.fillStyle = fill;
23760
+ ctx.fill();
23761
+ }
23762
+ ctx.strokeStyle = stroke;
23763
+ ctx.stroke();
23764
+ break;
23765
+ }
23766
+ case "path": {
23767
+ if (!shape.path) break;
23768
+ const p2 = new Path2D(shape.path);
23769
+ if (fill) {
23770
+ ctx.fillStyle = fill;
23771
+ ctx.fill(p2);
23772
+ }
23773
+ ctx.strokeStyle = stroke;
23774
+ ctx.stroke(p2);
23775
+ break;
23776
+ }
23777
+ case "text": {
23778
+ if (shape.x == null || shape.y == null || !shape.text) break;
23779
+ ctx.fillStyle = stroke;
23780
+ ctx.font = `${shape.fontSize ?? 14}px system-ui, sans-serif`;
23781
+ ctx.textAlign = shape.align ?? "left";
23782
+ ctx.textBaseline = "middle";
23783
+ ctx.fillText(shape.text, shape.x, shape.y);
23784
+ break;
23785
+ }
23786
+ }
23787
+ ctx.restore();
23788
+ }
23789
+ var LearningCanvas;
23790
+ var init_LearningCanvas = __esm({
23791
+ "components/learning/atoms/LearningCanvas.tsx"() {
23792
+ "use client";
23793
+ init_cn();
23794
+ init_useEventBus();
23795
+ LearningCanvas = ({
23796
+ className,
23797
+ width = 600,
23798
+ height = 400,
23799
+ backgroundColor,
23800
+ shapes = [],
23801
+ interactive = false,
23802
+ animate = false,
23803
+ onShapeClick,
23804
+ onShapeHover,
23805
+ isLoading,
23806
+ error
23807
+ }) => {
23808
+ const canvasRef = useRef(null);
23809
+ const eventBus = useEventBus();
23810
+ const animRef = useRef(0);
23811
+ const hoverIndexRef = useRef(-1);
23812
+ const findShapeAt = useCallback((clientX, clientY) => {
23813
+ const canvas = canvasRef.current;
23814
+ if (!canvas) return -1;
23815
+ const rect = canvas.getBoundingClientRect();
23816
+ const x = clientX - rect.left;
23817
+ const y = clientY - rect.top;
23818
+ for (let i = shapes.length - 1; i >= 0; i--) {
23819
+ const b = shapeBounds(shapes[i]);
23820
+ if (b && x >= b.x && x <= b.x + b.w && y >= b.y && y <= b.y + b.h) {
23821
+ return i;
23822
+ }
23823
+ }
23824
+ return -1;
23825
+ }, [shapes]);
23826
+ const draw = useCallback(() => {
23827
+ const canvas = canvasRef.current;
23828
+ if (!canvas) return;
23829
+ const ctx = canvas.getContext("2d");
23830
+ if (!ctx) return;
23831
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
23832
+ canvas.width = Math.max(1, Math.floor(width * dpr));
23833
+ canvas.height = Math.max(1, Math.floor(height * dpr));
23834
+ canvas.style.width = `${width}px`;
23835
+ canvas.style.height = `${height}px`;
23836
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
23837
+ ctx.clearRect(0, 0, width, height);
23838
+ if (backgroundColor) {
23839
+ ctx.fillStyle = backgroundColor;
23840
+ ctx.fillRect(0, 0, width, height);
23841
+ }
23842
+ for (const shape of shapes) {
23843
+ drawShape(ctx, shape, width, height);
23844
+ }
23845
+ }, [width, height, backgroundColor, shapes]);
23846
+ useEffect(() => {
23847
+ draw();
23848
+ }, [draw]);
23849
+ useEffect(() => {
23850
+ if (!animate) return;
23851
+ const loop = () => {
23852
+ draw();
23853
+ animRef.current = requestAnimationFrame(loop);
23854
+ };
23855
+ animRef.current = requestAnimationFrame(loop);
23856
+ return () => cancelAnimationFrame(animRef.current);
23857
+ }, [animate, draw]);
23858
+ const handlePointerMove = useCallback(
23859
+ (e) => {
23860
+ if (!interactive) return;
23861
+ const idx = findShapeAt(e.clientX, e.clientY);
23862
+ if (idx !== hoverIndexRef.current) {
23863
+ hoverIndexRef.current = idx;
23864
+ if (idx >= 0) {
23865
+ const shape = shapes[idx];
23866
+ const payload = { id: shape.id, type: shape.type, index: idx };
23867
+ if (onShapeHover) onShapeHover(payload);
23868
+ else if (eventBus) eventBus.emit(`UI:SHAPE_HOVER`, payload);
23869
+ }
23870
+ }
23871
+ },
23872
+ [interactive, onShapeHover, eventBus, findShapeAt, shapes]
23873
+ );
23874
+ const handleClick = useCallback(
23875
+ (e) => {
23876
+ if (!interactive) return;
23877
+ const idx = findShapeAt(e.clientX, e.clientY);
23878
+ if (idx >= 0) {
23879
+ const shape = shapes[idx];
23880
+ const payload = { id: shape.id, type: shape.type, index: idx };
23881
+ if (onShapeClick) onShapeClick(payload);
23882
+ else if (eventBus) eventBus.emit(`UI:SHAPE_CLICK`, payload);
23883
+ }
23884
+ },
23885
+ [interactive, onShapeClick, eventBus, findShapeAt, shapes]
23886
+ );
23887
+ if (isLoading || error) {
23888
+ return /* @__PURE__ */ jsx(
23889
+ "div",
23890
+ {
23891
+ className: cn(
23892
+ "flex items-center justify-center rounded border border-border bg-surface",
23893
+ className
23894
+ ),
23895
+ style: { width, height },
23896
+ 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" })
23897
+ }
23898
+ );
23899
+ }
23900
+ return /* @__PURE__ */ jsx(
23901
+ "canvas",
23902
+ {
23903
+ ref: canvasRef,
23904
+ className: cn("block touch-none rounded border border-border", className),
23905
+ style: { width, height },
23906
+ onClick: handleClick,
23907
+ onPointerMove: handlePointerMove
23908
+ }
23909
+ );
23910
+ };
23911
+ }
23912
+ });
23821
23913
 
23822
23914
  // components/core/atoms/index.ts
23823
23915
  var init_atoms = __esm({
@@ -25303,91 +25395,6 @@ var init_ComponentPatterns = __esm({
25303
25395
  AlertPattern.displayName = "AlertPattern";
25304
25396
  }
25305
25397
  });
25306
- function parseValue(value) {
25307
- if (value === "" || value == null) return { num: 0, prefix: "", suffix: "", decimals: 0 };
25308
- const match = String(value).match(/^([^0-9]*)([0-9]+(?:\.[0-9]+)?)(.*)$/);
25309
- if (!match) {
25310
- return { num: 0, prefix: "", suffix: String(value), decimals: 0 };
25311
- }
25312
- const numStr = match[2];
25313
- const decimalIdx = numStr.indexOf(".");
25314
- const decimals = decimalIdx >= 0 ? numStr.length - decimalIdx - 1 : 0;
25315
- return {
25316
- prefix: match[1],
25317
- num: parseFloat(numStr),
25318
- suffix: match[3],
25319
- decimals
25320
- };
25321
- }
25322
- var AnimatedCounter;
25323
- var init_AnimatedCounter = __esm({
25324
- "components/core/molecules/AnimatedCounter.tsx"() {
25325
- "use client";
25326
- init_cn();
25327
- init_Box();
25328
- init_Typography();
25329
- AnimatedCounter = ({
25330
- value,
25331
- label,
25332
- duration = 1500,
25333
- className
25334
- }) => {
25335
- const ref = useRef(null);
25336
- const [displayValue, setDisplayValue] = useState("0");
25337
- const [hasAnimated, setHasAnimated] = useState(false);
25338
- const animate = useCallback(() => {
25339
- const { num: num2, prefix, suffix, decimals } = parseValue(value);
25340
- if (num2 === 0) {
25341
- setDisplayValue(String(value));
25342
- return;
25343
- }
25344
- const startTime = performance.now();
25345
- const tick = (now2) => {
25346
- const elapsed = now2 - startTime;
25347
- const progress = Math.min(elapsed / duration, 1);
25348
- const eased = 1 - Math.pow(1 - progress, 3);
25349
- const current = eased * num2;
25350
- setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
25351
- if (progress < 1) {
25352
- requestAnimationFrame(tick);
25353
- } else {
25354
- setDisplayValue(String(value));
25355
- }
25356
- };
25357
- requestAnimationFrame(tick);
25358
- }, [value, duration]);
25359
- useEffect(() => {
25360
- if (hasAnimated) return;
25361
- const el = ref.current;
25362
- if (!el) return;
25363
- const observer2 = new IntersectionObserver(
25364
- (entries) => {
25365
- if (entries[0].isIntersecting) {
25366
- setHasAnimated(true);
25367
- animate();
25368
- observer2.disconnect();
25369
- }
25370
- },
25371
- { threshold: 0.3 }
25372
- );
25373
- observer2.observe(el);
25374
- return () => observer2.disconnect();
25375
- }, [hasAnimated, animate]);
25376
- return /* @__PURE__ */ jsxs(Box, { ref, className: cn("flex flex-col items-center gap-1 p-4", className), children: [
25377
- /* @__PURE__ */ jsx(
25378
- Typography,
25379
- {
25380
- variant: "h2",
25381
- className: "text-primary font-bold tabular-nums",
25382
- children: hasAnimated ? displayValue : "0"
25383
- }
25384
- ),
25385
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "muted", className: "text-center", children: label })
25386
- ] });
25387
- };
25388
- AnimatedCounter.displayName = "AnimatedCounter";
25389
- }
25390
- });
25391
25398
  var AuthLayout;
25392
25399
  var init_AuthLayout = __esm({
25393
25400
  "components/marketing/templates/AuthLayout.tsx"() {
@@ -25529,10 +25536,107 @@ var init_AuthLayout = __esm({
25529
25536
  AuthLayout.displayName = "AuthLayout";
25530
25537
  }
25531
25538
  });
25539
+ var BiologyCanvas;
25540
+ var init_BiologyCanvas = __esm({
25541
+ "components/learning/molecules/BiologyCanvas.tsx"() {
25542
+ "use client";
25543
+ init_atoms();
25544
+ init_Stack();
25545
+ init_LearningCanvas();
25546
+ BiologyCanvas = ({
25547
+ className,
25548
+ width = 600,
25549
+ height = 400,
25550
+ title,
25551
+ backgroundColor,
25552
+ nodes = [],
25553
+ edges = [],
25554
+ shapes = [],
25555
+ interactive = false,
25556
+ animate = false,
25557
+ onShapeClick,
25558
+ isLoading,
25559
+ error
25560
+ }) => {
25561
+ const derivedShapes = useMemo(() => {
25562
+ const out = [];
25563
+ const nodeById = /* @__PURE__ */ new Map();
25564
+ for (const n of nodes) {
25565
+ if (n.id) nodeById.set(n.id, n);
25566
+ }
25567
+ for (const e of edges) {
25568
+ const a = nodeById.get(e.from);
25569
+ const b = nodeById.get(e.to);
25570
+ if (!a || !b) continue;
25571
+ out.push({
25572
+ type: "line",
25573
+ x1: a.x,
25574
+ y1: a.y,
25575
+ x2: b.x,
25576
+ y2: b.y,
25577
+ color: e.color ?? "#9ca3af",
25578
+ lineWidth: 2
25579
+ });
25580
+ if (e.label) {
25581
+ out.push({
25582
+ type: "text",
25583
+ x: (a.x + b.x) / 2 + 4,
25584
+ y: (a.y + b.y) / 2 - 4,
25585
+ text: e.label,
25586
+ color: "#374151",
25587
+ fontSize: 11
25588
+ });
25589
+ }
25590
+ }
25591
+ for (const n of nodes) {
25592
+ out.push({
25593
+ type: "circle",
25594
+ x: n.x,
25595
+ y: n.y,
25596
+ radius: n.radius ?? 16,
25597
+ color: n.color ?? "#16a34a",
25598
+ fill: `${n.color ?? "#16a34a"}33`,
25599
+ id: n.id
25600
+ });
25601
+ if (n.label) {
25602
+ out.push({
25603
+ type: "text",
25604
+ x: n.x,
25605
+ y: n.y + (n.radius ?? 16) + 14,
25606
+ text: n.label,
25607
+ color: "#111827",
25608
+ fontSize: 12,
25609
+ align: "center"
25610
+ });
25611
+ }
25612
+ }
25613
+ out.push(...shapes);
25614
+ return out;
25615
+ }, [nodes, edges, shapes]);
25616
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
25617
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
25618
+ /* @__PURE__ */ jsx(
25619
+ LearningCanvas,
25620
+ {
25621
+ width,
25622
+ height,
25623
+ backgroundColor,
25624
+ shapes: derivedShapes,
25625
+ interactive,
25626
+ animate,
25627
+ onShapeClick,
25628
+ isLoading,
25629
+ error
25630
+ }
25631
+ )
25632
+ ] }) });
25633
+ };
25634
+ }
25635
+ });
25532
25636
 
25533
- // node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css
25637
+ // node_modules/katex/dist/katex.min.css
25534
25638
  var init_katex_min = __esm({
25535
- "node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css"() {
25639
+ "node_modules/katex/dist/katex.min.css"() {
25536
25640
  }
25537
25641
  });
25538
25642
  var Tabs;
@@ -29925,129 +30029,6 @@ var init_Breadcrumb = __esm({
29925
30029
  Breadcrumb.displayName = "Breadcrumb";
29926
30030
  }
29927
30031
  });
29928
- function useSafeEventBus2() {
29929
- try {
29930
- return useEventBus();
29931
- } catch {
29932
- return { emit: () => {
29933
- }, on: () => () => {
29934
- }, once: () => {
29935
- } };
29936
- }
29937
- }
29938
- var log5, lookStyles4, ButtonGroup;
29939
- var init_ButtonGroup = __esm({
29940
- "components/core/molecules/ButtonGroup.tsx"() {
29941
- "use client";
29942
- init_cn();
29943
- init_atoms();
29944
- init_useEventBus();
29945
- log5 = createLogger("almadar:ui:button-group");
29946
- lookStyles4 = {
29947
- "right-aligned-buttons": "",
29948
- "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
29949
- "inline-row": "gap-2 inline-flex",
29950
- "dropdown-menu": "[&>button:not(:first-child)]:hidden",
29951
- "command-palette-trigger": "[&>button:not(:first-child)]:hidden"
29952
- };
29953
- ButtonGroup = ({
29954
- children,
29955
- primary,
29956
- secondary,
29957
- variant = "default",
29958
- orientation = "horizontal",
29959
- className,
29960
- // Filter-group pattern props (entity and filters are used for schema-driven filtering)
29961
- entity: _entity,
29962
- filters,
29963
- look = "right-aligned-buttons"
29964
- }) => {
29965
- const eventBus = useSafeEventBus2();
29966
- const variantClasses2 = {
29967
- default: "gap-0",
29968
- 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",
29969
- 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"
29970
- };
29971
- const orientationClasses = {
29972
- horizontal: "flex-row",
29973
- 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"
29974
- };
29975
- const handleActionClick = (action) => {
29976
- if (action.event) {
29977
- eventBus.emit("UI:DISPATCH", { event: action.event });
29978
- }
29979
- if (action.navigatesTo) {
29980
- eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
29981
- }
29982
- };
29983
- const renderFormActions = () => {
29984
- const buttons = [];
29985
- if (secondary) {
29986
- secondary.forEach((action, index) => {
29987
- buttons.push(
29988
- /* @__PURE__ */ jsx(
29989
- Button,
29990
- {
29991
- type: action.actionType === "submit" ? "submit" : "button",
29992
- variant: action.variant || "ghost",
29993
- onClick: () => handleActionClick(action),
29994
- children: action.label
29995
- },
29996
- `secondary-${index}`
29997
- )
29998
- );
29999
- });
30000
- }
30001
- if (primary) {
30002
- const isSubmit = primary.actionType === "submit";
30003
- buttons.push(
30004
- /* @__PURE__ */ jsx(
30005
- Button,
30006
- {
30007
- type: isSubmit ? "submit" : "button",
30008
- variant: primary.variant || "primary",
30009
- onClick: () => handleActionClick(primary),
30010
- "data-testid": isSubmit ? "form-submit" : void 0,
30011
- children: primary.label
30012
- },
30013
- "primary"
30014
- )
30015
- );
30016
- }
30017
- return buttons;
30018
- };
30019
- const renderFilters = () => {
30020
- if (!filters || filters.length === 0) return null;
30021
- return filters.map((filter, index) => /* @__PURE__ */ jsx(
30022
- Button,
30023
- {
30024
- variant: "ghost",
30025
- onClick: () => {
30026
- log5.debug("Filter clicked", { field: filter.field });
30027
- },
30028
- children: filter.label
30029
- },
30030
- `filter-${filter.field}-${index}`
30031
- ));
30032
- };
30033
- return /* @__PURE__ */ jsx(
30034
- "div",
30035
- {
30036
- className: cn(
30037
- "inline-flex gap-2",
30038
- variantClasses2[variant],
30039
- orientationClasses[orientation],
30040
- lookStyles4[look],
30041
- className
30042
- ),
30043
- role: "group",
30044
- children: children || renderFilters() || renderFormActions()
30045
- }
30046
- );
30047
- };
30048
- ButtonGroup.displayName = "ButtonGroup";
30049
- }
30050
- });
30051
30032
  function useSwipeGesture(callbacks, options = {}) {
30052
30033
  const { threshold = 50, velocityThreshold = 0.3, preventDefault = false } = options;
30053
30034
  const startX = useRef(0);
@@ -30857,7 +30838,7 @@ var init_CardGrid = __esm({
30857
30838
  CardGrid.displayName = "CardGrid";
30858
30839
  }
30859
30840
  });
30860
- function useSafeEventBus3() {
30841
+ function useSafeEventBus2() {
30861
30842
  try {
30862
30843
  return useEventBus();
30863
30844
  } catch {
@@ -30893,7 +30874,7 @@ var init_Carousel = __esm({
30893
30874
  const [activeIndex, setActiveIndex] = useState(0);
30894
30875
  const scrollRef = useRef(null);
30895
30876
  const autoPlayRef = useRef(null);
30896
- const eventBus = useSafeEventBus3();
30877
+ const eventBus = useSafeEventBus2();
30897
30878
  const { t } = useTranslate();
30898
30879
  const safeItems = items ?? [];
30899
30880
  const totalSlides = safeItems.length;
@@ -32141,6 +32122,122 @@ var init_ChatBar = __esm({
32141
32122
  ChatBar.displayName = "ChatBar";
32142
32123
  }
32143
32124
  });
32125
+ var ChemistryCanvas;
32126
+ var init_ChemistryCanvas = __esm({
32127
+ "components/learning/molecules/ChemistryCanvas.tsx"() {
32128
+ "use client";
32129
+ init_atoms();
32130
+ init_Stack();
32131
+ init_LearningCanvas();
32132
+ ChemistryCanvas = ({
32133
+ className,
32134
+ width = 600,
32135
+ height = 400,
32136
+ title,
32137
+ backgroundColor,
32138
+ atoms = [],
32139
+ bonds = [],
32140
+ arrows = [],
32141
+ shapes = [],
32142
+ interactive = false,
32143
+ animate = false,
32144
+ onShapeClick,
32145
+ isLoading,
32146
+ error
32147
+ }) => {
32148
+ const derivedShapes = useMemo(() => {
32149
+ const out = [];
32150
+ const atomById = /* @__PURE__ */ new Map();
32151
+ for (const a of atoms) {
32152
+ if (a.id) atomById.set(a.id, a);
32153
+ }
32154
+ for (const b of bonds) {
32155
+ const a = atomById.get(b.from);
32156
+ const c = atomById.get(b.to);
32157
+ if (!a || !c) continue;
32158
+ const color = b.color ?? "#6b7280";
32159
+ const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
32160
+ out.push({
32161
+ type: "line",
32162
+ x1: a.x,
32163
+ y1: a.y,
32164
+ x2: c.x,
32165
+ y2: c.y,
32166
+ color,
32167
+ lineWidth: strokeWidth
32168
+ });
32169
+ }
32170
+ for (const a of arrows) {
32171
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
32172
+ const len = a.length ?? 60;
32173
+ const x2 = a.x + Math.cos(angle) * len;
32174
+ const y2 = a.y + Math.sin(angle) * len;
32175
+ out.push({
32176
+ type: "arrow",
32177
+ x1: a.x,
32178
+ y1: a.y,
32179
+ x2,
32180
+ y2,
32181
+ color: a.color ?? "#dc2626",
32182
+ lineWidth: 2
32183
+ });
32184
+ if (a.label) {
32185
+ out.push({
32186
+ type: "text",
32187
+ x: (a.x + x2) / 2,
32188
+ y: (a.y + y2) / 2 - 10,
32189
+ text: a.label,
32190
+ color: "#111827",
32191
+ fontSize: 12,
32192
+ align: "center"
32193
+ });
32194
+ }
32195
+ }
32196
+ for (const a of atoms) {
32197
+ out.push({
32198
+ type: "circle",
32199
+ x: a.x,
32200
+ y: a.y,
32201
+ radius: a.radius ?? 14,
32202
+ color: a.color ?? "#2563eb",
32203
+ fill: a.color ?? "#2563eb",
32204
+ id: a.id
32205
+ });
32206
+ if (a.element) {
32207
+ out.push({
32208
+ type: "text",
32209
+ x: a.x,
32210
+ y: a.y,
32211
+ text: a.element,
32212
+ color: "#ffffff",
32213
+ fontSize: 12,
32214
+ align: "center"
32215
+ });
32216
+ }
32217
+ }
32218
+ out.push(...shapes);
32219
+ return out;
32220
+ }, [atoms, bonds, arrows, shapes]);
32221
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
32222
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
32223
+ /* @__PURE__ */ jsx(
32224
+ LearningCanvas,
32225
+ {
32226
+ width,
32227
+ height,
32228
+ backgroundColor,
32229
+ shapes: derivedShapes,
32230
+ interactive,
32231
+ animate,
32232
+ onShapeClick,
32233
+ isLoading,
32234
+ error
32235
+ }
32236
+ )
32237
+ ] }) });
32238
+ };
32239
+ }
32240
+ });
32144
32241
  var CodeRunnerPanel;
32145
32242
  var init_CodeRunnerPanel = __esm({
32146
32243
  "components/core/organisms/CodeRunnerPanel.tsx"() {
@@ -34229,7 +34326,7 @@ function DataGrid({
34229
34326
  /* @__PURE__ */ jsx(
34230
34327
  Box,
34231
34328
  {
34232
- className: cn("grid", gapStyles5[gap], scrollX ? "grid-flow-col overflow-x-auto" : colsClass, lookStyles5[look], className),
34329
+ className: cn("grid", gapStyles5[gap], scrollX ? "grid-flow-col overflow-x-auto" : colsClass, lookStyles4[look], className),
34233
34330
  style: scrollX ? { gridAutoFlow: "column", gridAutoColumns: `minmax(${minCardWidth}px, 1fr)` } : gridTemplateColumns ? { gridTemplateColumns } : void 0,
34234
34331
  children: data.map((item, index) => {
34235
34332
  const itemData = item;
@@ -34424,7 +34521,7 @@ function DataGrid({
34424
34521
  ] })
34425
34522
  );
34426
34523
  }
34427
- var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles5;
34524
+ var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles4;
34428
34525
  var init_DataGrid = __esm({
34429
34526
  "components/core/molecules/DataGrid.tsx"() {
34430
34527
  "use client";
@@ -34459,7 +34556,7 @@ var init_DataGrid = __esm({
34459
34556
  lg: "gap-6",
34460
34557
  xl: "gap-8"
34461
34558
  };
34462
- lookStyles5 = {
34559
+ lookStyles4 = {
34463
34560
  dense: "gap-2 [&>*]:p-card-sm",
34464
34561
  spacious: "gap-8 [&>*]:p-card-lg",
34465
34562
  striped: "[&>*:nth-child(even)]:bg-muted/30",
@@ -34886,6 +34983,133 @@ var init_DataList = __esm({
34886
34983
  DataList.displayName = "DataList";
34887
34984
  }
34888
34985
  });
34986
+ var FormSection, FormLayout, FormActions;
34987
+ var init_FormSection = __esm({
34988
+ "components/core/molecules/FormSection.tsx"() {
34989
+ "use client";
34990
+ init_cn();
34991
+ init_atoms();
34992
+ init_Box();
34993
+ init_Typography();
34994
+ init_Button();
34995
+ init_Stack();
34996
+ init_Icon();
34997
+ init_useEventBus();
34998
+ FormSection = ({
34999
+ title,
35000
+ description,
35001
+ children,
35002
+ collapsible = false,
35003
+ defaultCollapsed = false,
35004
+ card = false,
35005
+ columns = 1,
35006
+ className
35007
+ }) => {
35008
+ const [collapsed, setCollapsed] = React114__default.useState(defaultCollapsed);
35009
+ const { t } = useTranslate();
35010
+ const eventBus = useEventBus();
35011
+ const gridClass = {
35012
+ 1: "grid-cols-1",
35013
+ 2: "grid-cols-1 md:grid-cols-2",
35014
+ 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
35015
+ }[columns];
35016
+ React114__default.useCallback(() => {
35017
+ if (collapsible) {
35018
+ setCollapsed((prev) => !prev);
35019
+ eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
35020
+ }
35021
+ }, [collapsible, collapsed, eventBus]);
35022
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
35023
+ (title || description) && /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "mb-4", children: [
35024
+ title && /* @__PURE__ */ jsxs(
35025
+ HStack,
35026
+ {
35027
+ justify: "between",
35028
+ align: "center",
35029
+ className: cn(collapsible && "cursor-pointer"),
35030
+ action: collapsible ? "TOGGLE_COLLAPSE" : void 0,
35031
+ children: [
35032
+ /* @__PURE__ */ jsx(Typography, { variant: "h3", weight: "semibold", children: title }),
35033
+ collapsible && /* @__PURE__ */ jsx(
35034
+ Button,
35035
+ {
35036
+ variant: "ghost",
35037
+ size: "sm",
35038
+ action: "TOGGLE_COLLAPSE",
35039
+ children: /* @__PURE__ */ jsx(
35040
+ Icon,
35041
+ {
35042
+ icon: ChevronDown,
35043
+ size: "sm",
35044
+ className: cn(
35045
+ "text-muted-foreground transition-transform",
35046
+ collapsed && "rotate-180"
35047
+ )
35048
+ }
35049
+ )
35050
+ }
35051
+ )
35052
+ ]
35053
+ }
35054
+ ),
35055
+ description && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: description })
35056
+ ] }),
35057
+ (!collapsible || !collapsed) && /* @__PURE__ */ jsx(Box, { className: cn("grid gap-4", gridClass), children })
35058
+ ] });
35059
+ if (card) {
35060
+ return /* @__PURE__ */ jsx(Card, { className: cn("p-6", className), children: content });
35061
+ }
35062
+ return /* @__PURE__ */ jsx(Box, { className, children: content });
35063
+ };
35064
+ FormSection.displayName = "FormSection";
35065
+ FormLayout = ({
35066
+ children,
35067
+ dividers = true,
35068
+ className
35069
+ }) => {
35070
+ return /* @__PURE__ */ jsx(
35071
+ VStack,
35072
+ {
35073
+ gap: "lg",
35074
+ className: cn(
35075
+ dividers && "[&>*+*]:pt-8 [&>*+*]:border-t [&>*+*]:border-border",
35076
+ className
35077
+ ),
35078
+ children
35079
+ }
35080
+ );
35081
+ };
35082
+ FormLayout.displayName = "FormLayout";
35083
+ FormActions = ({
35084
+ children,
35085
+ sticky = false,
35086
+ align = "right",
35087
+ className
35088
+ }) => {
35089
+ const alignClass2 = {
35090
+ left: "justify-start",
35091
+ right: "justify-end",
35092
+ between: "justify-between",
35093
+ center: "justify-center"
35094
+ }[align];
35095
+ return /* @__PURE__ */ jsx(
35096
+ HStack,
35097
+ {
35098
+ gap: "sm",
35099
+ align: "center",
35100
+ className: cn(
35101
+ "pt-6 border-t border-border",
35102
+ alignClass2,
35103
+ sticky && "sticky bottom-0 bg-card py-4 -mx-6 px-6 shadow-[0_-4px_6px_-1px_rgb(0,0,0,0.05)]",
35104
+ className
35105
+ ),
35106
+ children
35107
+ }
35108
+ );
35109
+ };
35110
+ FormActions.displayName = "FormActions";
35111
+ }
35112
+ });
34889
35113
  function fileIcon(name) {
34890
35114
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
34891
35115
  switch (ext) {
@@ -35047,6 +35271,129 @@ var init_FormField = __esm({
35047
35271
  FormField.displayName = "FormField";
35048
35272
  }
35049
35273
  });
35274
+ function useSafeEventBus3() {
35275
+ try {
35276
+ return useEventBus();
35277
+ } catch {
35278
+ return { emit: () => {
35279
+ }, on: () => () => {
35280
+ }, once: () => {
35281
+ } };
35282
+ }
35283
+ }
35284
+ var log5, lookStyles5, ButtonGroup;
35285
+ var init_ButtonGroup = __esm({
35286
+ "components/core/molecules/ButtonGroup.tsx"() {
35287
+ "use client";
35288
+ init_cn();
35289
+ init_atoms();
35290
+ init_useEventBus();
35291
+ log5 = createLogger("almadar:ui:button-group");
35292
+ lookStyles5 = {
35293
+ "right-aligned-buttons": "",
35294
+ "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
35295
+ "inline-row": "gap-2 inline-flex",
35296
+ "dropdown-menu": "[&>button:not(:first-child)]:hidden",
35297
+ "command-palette-trigger": "[&>button:not(:first-child)]:hidden"
35298
+ };
35299
+ ButtonGroup = ({
35300
+ children,
35301
+ primary,
35302
+ secondary,
35303
+ variant = "default",
35304
+ orientation = "horizontal",
35305
+ className,
35306
+ // Filter-group pattern props (entity and filters are used for schema-driven filtering)
35307
+ entity: _entity,
35308
+ filters,
35309
+ look = "right-aligned-buttons"
35310
+ }) => {
35311
+ const eventBus = useSafeEventBus3();
35312
+ const variantClasses2 = {
35313
+ default: "gap-0",
35314
+ 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",
35315
+ 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"
35316
+ };
35317
+ const orientationClasses = {
35318
+ horizontal: "flex-row",
35319
+ 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"
35320
+ };
35321
+ const handleActionClick = (action) => {
35322
+ if (action.event) {
35323
+ eventBus.emit("UI:DISPATCH", { event: action.event });
35324
+ }
35325
+ if (action.navigatesTo) {
35326
+ eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
35327
+ }
35328
+ };
35329
+ const renderFormActions = () => {
35330
+ const buttons = [];
35331
+ if (secondary) {
35332
+ secondary.forEach((action, index) => {
35333
+ buttons.push(
35334
+ /* @__PURE__ */ jsx(
35335
+ Button,
35336
+ {
35337
+ type: action.actionType === "submit" ? "submit" : "button",
35338
+ variant: action.variant || "ghost",
35339
+ onClick: () => handleActionClick(action),
35340
+ children: action.label
35341
+ },
35342
+ `secondary-${index}`
35343
+ )
35344
+ );
35345
+ });
35346
+ }
35347
+ if (primary) {
35348
+ const isSubmit = primary.actionType === "submit";
35349
+ buttons.push(
35350
+ /* @__PURE__ */ jsx(
35351
+ Button,
35352
+ {
35353
+ type: isSubmit ? "submit" : "button",
35354
+ variant: primary.variant || "primary",
35355
+ onClick: () => handleActionClick(primary),
35356
+ "data-testid": isSubmit ? "form-submit" : void 0,
35357
+ children: primary.label
35358
+ },
35359
+ "primary"
35360
+ )
35361
+ );
35362
+ }
35363
+ return buttons;
35364
+ };
35365
+ const renderFilters = () => {
35366
+ if (!filters || filters.length === 0) return null;
35367
+ return filters.map((filter, index) => /* @__PURE__ */ jsx(
35368
+ Button,
35369
+ {
35370
+ variant: "ghost",
35371
+ onClick: () => {
35372
+ log5.debug("Filter clicked", { field: filter.field });
35373
+ },
35374
+ children: filter.label
35375
+ },
35376
+ `filter-${filter.field}-${index}`
35377
+ ));
35378
+ };
35379
+ return /* @__PURE__ */ jsx(
35380
+ "div",
35381
+ {
35382
+ className: cn(
35383
+ "inline-flex gap-2",
35384
+ variantClasses2[variant],
35385
+ orientationClasses[orientation],
35386
+ lookStyles5[look],
35387
+ className
35388
+ ),
35389
+ role: "group",
35390
+ children: children || renderFilters() || renderFormActions()
35391
+ }
35392
+ );
35393
+ };
35394
+ ButtonGroup.displayName = "ButtonGroup";
35395
+ }
35396
+ });
35050
35397
  function getOrCreateStore(query) {
35051
35398
  if (!queryStores.has(query)) {
35052
35399
  queryStores.set(query, {
@@ -37588,6 +37935,234 @@ var init_ProgressDots = __esm({
37588
37935
  ProgressDots.displayName = "ProgressDots";
37589
37936
  }
37590
37937
  });
37938
+ var MathCanvas;
37939
+ var init_MathCanvas = __esm({
37940
+ "components/learning/molecules/MathCanvas.tsx"() {
37941
+ "use client";
37942
+ init_atoms();
37943
+ init_Stack();
37944
+ init_LearningCanvas();
37945
+ MathCanvas = ({
37946
+ className,
37947
+ width = 600,
37948
+ height = 400,
37949
+ title,
37950
+ xMin = -10,
37951
+ xMax = 10,
37952
+ yMin = -10,
37953
+ yMax = 10,
37954
+ showAxes = true,
37955
+ showGrid = true,
37956
+ gridStep = 1,
37957
+ curves = [],
37958
+ points = [],
37959
+ vectors = [],
37960
+ shapes = [],
37961
+ interactive = false,
37962
+ animate = false,
37963
+ onShapeClick,
37964
+ isLoading,
37965
+ error
37966
+ }) => {
37967
+ const derivedShapes = useMemo(() => {
37968
+ const out = [];
37969
+ const margin = 24;
37970
+ const plotW = width - margin * 2;
37971
+ const plotH = height - margin * 2;
37972
+ const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
37973
+ const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
37974
+ if (showGrid) {
37975
+ for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
37976
+ const px = mapX(x);
37977
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#e5e7eb", lineWidth: 1 });
37978
+ }
37979
+ for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
37980
+ const py = mapY(y);
37981
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
37982
+ }
37983
+ }
37984
+ if (showAxes) {
37985
+ const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
37986
+ const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
37987
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
37988
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
37989
+ }
37990
+ for (const curve of curves) {
37991
+ if (!curve.samples || curve.samples.length < 2) continue;
37992
+ for (let i = 1; i < curve.samples.length; i++) {
37993
+ const a = curve.samples[i - 1];
37994
+ const b = curve.samples[i];
37995
+ if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
37996
+ out.push({
37997
+ type: "line",
37998
+ x1: mapX(a.x),
37999
+ y1: mapY(a.y),
38000
+ x2: mapX(b.x),
38001
+ y2: mapY(b.y),
38002
+ color: curve.color ?? "#2563eb",
38003
+ lineWidth: 2
38004
+ });
38005
+ }
38006
+ }
38007
+ for (const p2 of points) {
38008
+ if (p2.x < xMin || p2.x > xMax || p2.y < yMin || p2.y > yMax) continue;
38009
+ out.push({
38010
+ type: "circle",
38011
+ x: mapX(p2.x),
38012
+ y: mapY(p2.y),
38013
+ radius: p2.radius ?? 4,
38014
+ color: p2.color ?? "#dc2626",
38015
+ fill: p2.color ?? "#dc2626"
38016
+ });
38017
+ if (p2.label) {
38018
+ out.push({ type: "text", x: mapX(p2.x) + 8, y: mapY(p2.y) - 8, text: p2.label, color: "#111827", fontSize: 12 });
38019
+ }
38020
+ }
38021
+ for (const v of vectors) {
38022
+ if (v.x < xMin || v.x > xMax || v.y < yMin || v.y > yMax) continue;
38023
+ const x1 = mapX(v.x);
38024
+ const y1 = mapY(v.y);
38025
+ const x2 = mapX(v.x + v.vx);
38026
+ const y2 = mapY(v.y + v.vy);
38027
+ out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
38028
+ if (v.label) {
38029
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
38030
+ }
38031
+ }
38032
+ out.push(...shapes);
38033
+ return out;
38034
+ }, [width, height, xMin, xMax, yMin, yMax, showAxes, showGrid, gridStep, curves, points, vectors, shapes]);
38035
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
38036
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
38037
+ /* @__PURE__ */ jsx(
38038
+ LearningCanvas,
38039
+ {
38040
+ width,
38041
+ height,
38042
+ shapes: derivedShapes,
38043
+ interactive,
38044
+ animate,
38045
+ onShapeClick,
38046
+ isLoading,
38047
+ error
38048
+ }
38049
+ )
38050
+ ] }) });
38051
+ };
38052
+ }
38053
+ });
38054
+ var PhysicsCanvas;
38055
+ var init_PhysicsCanvas = __esm({
38056
+ "components/learning/molecules/PhysicsCanvas.tsx"() {
38057
+ "use client";
38058
+ init_atoms();
38059
+ init_Stack();
38060
+ init_LearningCanvas();
38061
+ PhysicsCanvas = ({
38062
+ className,
38063
+ width = 600,
38064
+ height = 400,
38065
+ title,
38066
+ backgroundColor,
38067
+ bodies = [],
38068
+ constraints = [],
38069
+ showVelocity = true,
38070
+ showForces = false,
38071
+ velocityScale = 20,
38072
+ forceScale = 20,
38073
+ shapes = [],
38074
+ interactive = false,
38075
+ animate = false,
38076
+ onShapeClick,
38077
+ isLoading,
38078
+ error
38079
+ }) => {
38080
+ const derivedShapes = useMemo(() => {
38081
+ const out = [];
38082
+ const bodyById = /* @__PURE__ */ new Map();
38083
+ for (const b of bodies) {
38084
+ if (b.id) bodyById.set(b.id, b);
38085
+ }
38086
+ for (const c of constraints) {
38087
+ const a = bodyById.get(c.from);
38088
+ const b = bodyById.get(c.to);
38089
+ if (!a || !b) continue;
38090
+ out.push({
38091
+ type: "line",
38092
+ x1: a.x,
38093
+ y1: a.y,
38094
+ x2: b.x,
38095
+ y2: b.y,
38096
+ color: c.color ?? "#9ca3af",
38097
+ lineWidth: 2
38098
+ });
38099
+ }
38100
+ for (const b of bodies) {
38101
+ out.push({
38102
+ type: "circle",
38103
+ x: b.x,
38104
+ y: b.y,
38105
+ radius: b.radius ?? 12,
38106
+ color: b.color ?? "#2563eb",
38107
+ fill: b.color ?? "#2563eb",
38108
+ id: b.id
38109
+ });
38110
+ if (b.label) {
38111
+ out.push({
38112
+ type: "text",
38113
+ x: b.x + (b.radius ?? 12) + 6,
38114
+ y: b.y - (b.radius ?? 12) - 6,
38115
+ text: b.label,
38116
+ color: "#111827",
38117
+ fontSize: 12
38118
+ });
38119
+ }
38120
+ if (showVelocity && b.vx != null && b.vy != null && (b.vx !== 0 || b.vy !== 0)) {
38121
+ out.push({
38122
+ type: "arrow",
38123
+ x1: b.x,
38124
+ y1: b.y,
38125
+ x2: b.x + b.vx * velocityScale,
38126
+ y2: b.y + b.vy * velocityScale,
38127
+ color: "#16a34a",
38128
+ lineWidth: 2
38129
+ });
38130
+ }
38131
+ if (showForces && b.fx != null && b.fy != null && (b.fx !== 0 || b.fy !== 0)) {
38132
+ out.push({
38133
+ type: "arrow",
38134
+ x1: b.x,
38135
+ y1: b.y,
38136
+ x2: b.x + b.fx * forceScale,
38137
+ y2: b.y + b.fy * forceScale,
38138
+ color: "#dc2626",
38139
+ lineWidth: 2
38140
+ });
38141
+ }
38142
+ }
38143
+ out.push(...shapes);
38144
+ return out;
38145
+ }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
38146
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
38147
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
38148
+ /* @__PURE__ */ jsx(
38149
+ LearningCanvas,
38150
+ {
38151
+ width,
38152
+ height,
38153
+ backgroundColor,
38154
+ shapes: derivedShapes,
38155
+ interactive,
38156
+ animate,
38157
+ onShapeClick,
38158
+ isLoading,
38159
+ error
38160
+ }
38161
+ )
38162
+ ] }) });
38163
+ };
38164
+ }
38165
+ });
37591
38166
  function resolveNodeColor(node, groups) {
37592
38167
  if (node.color) return node.color;
37593
38168
  if (node.group) {
@@ -37959,13 +38534,13 @@ var init_MapView = __esm({
37959
38534
  shadowSize: [41, 41]
37960
38535
  });
37961
38536
  L.Marker.prototype.options.icon = defaultIcon;
37962
- const { useEffect: useEffect78, useRef: useRef78, useCallback: useCallback118, useState: useState115 } = React114__default;
38537
+ const { useEffect: useEffect79, useRef: useRef79, useCallback: useCallback118, useState: useState115 } = React114__default;
37963
38538
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
37964
38539
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
37965
38540
  function MapUpdater({ centerLat, centerLng, zoom }) {
37966
38541
  const map = useMap();
37967
- const prevRef = useRef78({ centerLat, centerLng, zoom });
37968
- useEffect78(() => {
38542
+ const prevRef = useRef79({ centerLat, centerLng, zoom });
38543
+ useEffect79(() => {
37969
38544
  const prev = prevRef.current;
37970
38545
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
37971
38546
  map.setView([centerLat, centerLng], zoom);
@@ -37976,7 +38551,7 @@ var init_MapView = __esm({
37976
38551
  }
37977
38552
  function MapClickHandler({ onMapClick }) {
37978
38553
  const map = useMap();
37979
- useEffect78(() => {
38554
+ useEffect79(() => {
37980
38555
  if (!onMapClick) return;
37981
38556
  const handler = (e) => {
37982
38557
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -46008,7 +46583,7 @@ function getGroupColor(group, groups) {
46008
46583
  const idx = groups.indexOf(group);
46009
46584
  return GROUP_COLORS2[idx % GROUP_COLORS2.length];
46010
46585
  }
46011
- function resolveColor2(color, el) {
46586
+ function resolveColor3(color, el) {
46012
46587
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
46013
46588
  if (!m) return color;
46014
46589
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
@@ -46269,7 +46844,7 @@ var init_GraphCanvas = __esm({
46269
46844
  const w = logicalW;
46270
46845
  const h = height;
46271
46846
  const nodes = nodesRef.current;
46272
- const accentColor = resolveColor2("var(--color-accent)", canvas);
46847
+ const accentColor = resolveColor3("var(--color-accent)", canvas);
46273
46848
  const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
46274
46849
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
46275
46850
  ctx.clearRect(0, 0, w, h);
@@ -46308,7 +46883,7 @@ var init_GraphCanvas = __esm({
46308
46883
  ctx.globalAlpha = 1;
46309
46884
  for (const node of nodes) {
46310
46885
  const size = node.size || 8;
46311
- const color = resolveColor2(node.color || getGroupColor(node.group, groups), canvas);
46886
+ const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
46312
46887
  const isHovered = hoveredNode === node.id;
46313
46888
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
46314
46889
  ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
@@ -53206,6 +53781,7 @@ var init_component_registry_generated = __esm({
53206
53781
  init_BattleBoard();
53207
53782
  init_BattleTemplate();
53208
53783
  init_BehaviorView();
53784
+ init_BiologyCanvas();
53209
53785
  init_BloomQuizBlock();
53210
53786
  init_BoardgameBoard();
53211
53787
  init_BookChapterView();
@@ -53217,7 +53793,6 @@ var init_component_registry_generated = __esm({
53217
53793
  init_BranchingLogicBuilder();
53218
53794
  init_Breadcrumb();
53219
53795
  init_BuilderBoard();
53220
- init_ButtonGroup();
53221
53796
  init_CTABanner();
53222
53797
  init_CalendarGrid();
53223
53798
  init_Canvas2D();
@@ -53236,6 +53811,7 @@ var init_component_registry_generated = __esm({
53236
53811
  init_ChartLegend();
53237
53812
  init_ChatBar();
53238
53813
  init_Checkbox();
53814
+ init_ChemistryCanvas();
53239
53815
  init_ChoiceButton();
53240
53816
  init_CityBuilderBoard();
53241
53817
  init_CityBuilderTemplate();
@@ -53294,9 +53870,9 @@ var init_component_registry_generated = __esm({
53294
53870
  init_FlipContainer();
53295
53871
  init_FloatingActionButton();
53296
53872
  init_Form();
53873
+ init_FormSection();
53297
53874
  init_FormField();
53298
53875
  init_FormSectionHeader();
53299
- init_GameAudioProvider();
53300
53876
  init_GameAudioToggle();
53301
53877
  init_GameCard();
53302
53878
  init_GameHud();
@@ -53327,6 +53903,7 @@ var init_component_registry_generated = __esm({
53327
53903
  init_JazariStateMachine();
53328
53904
  init_LandingPageTemplate();
53329
53905
  init_LawReferenceTooltip();
53906
+ init_LearningCanvas();
53330
53907
  init_Lightbox();
53331
53908
  init_LikertScale();
53332
53909
  init_LineChart();
@@ -53335,9 +53912,11 @@ var init_component_registry_generated = __esm({
53335
53912
  init_MapView();
53336
53913
  init_MarkdownContent();
53337
53914
  init_MarketingFooter();
53915
+ init_MarketingStatCard();
53338
53916
  init_MasterDetail();
53339
53917
  init_MasterDetailLayout();
53340
53918
  init_MatchPuzzleBoard();
53919
+ init_MathCanvas();
53341
53920
  init_MatrixQuestion();
53342
53921
  init_MediaGallery();
53343
53922
  init_Menu();
@@ -53356,6 +53935,7 @@ var init_component_registry_generated = __esm({
53356
53935
  init_PageHeader();
53357
53936
  init_Pagination();
53358
53937
  init_PatternTile();
53938
+ init_PhysicsCanvas();
53359
53939
  init_PinballBoard();
53360
53940
  init_PirateBoard();
53361
53941
  init_PlatformerBoard();
@@ -53532,6 +54112,7 @@ var init_component_registry_generated = __esm({
53532
54112
  "BattleBoard": BattleBoard,
53533
54113
  "BattleTemplate": BattleTemplate,
53534
54114
  "BehaviorView": BehaviorView,
54115
+ "BiologyCanvas": BiologyCanvas,
53535
54116
  "BloomQuizBlock": BloomQuizBlock,
53536
54117
  "BoardgameBoard": BoardgameBoard,
53537
54118
  "BookChapterView": BookChapterView,
@@ -53545,7 +54126,6 @@ var init_component_registry_generated = __esm({
53545
54126
  "Breadcrumb": Breadcrumb,
53546
54127
  "BuilderBoard": BuilderBoard,
53547
54128
  "Button": ButtonPattern,
53548
- "ButtonGroup": ButtonGroup,
53549
54129
  "ButtonPattern": ButtonPattern,
53550
54130
  "CTABanner": CTABanner,
53551
54131
  "CalendarGrid": CalendarGrid,
@@ -53565,6 +54145,7 @@ var init_component_registry_generated = __esm({
53565
54145
  "ChartLegend": ChartLegend,
53566
54146
  "ChatBar": ChatBar,
53567
54147
  "Checkbox": Checkbox,
54148
+ "ChemistryCanvas": ChemistryCanvas,
53568
54149
  "ChoiceButton": ChoiceButton,
53569
54150
  "CityBuilderBoard": CityBuilderBoard,
53570
54151
  "CityBuilderTemplate": CityBuilderTemplate,
@@ -53625,9 +54206,10 @@ var init_component_registry_generated = __esm({
53625
54206
  "FlipContainer": FlipContainer,
53626
54207
  "FloatingActionButton": FloatingActionButton,
53627
54208
  "Form": Form,
54209
+ "FormActions": FormActions,
53628
54210
  "FormField": FormField,
54211
+ "FormLayout": FormLayout,
53629
54212
  "FormSectionHeader": FormSectionHeader,
53630
- "GameAudioProvider": GameAudioProvider,
53631
54213
  "GameAudioToggle": GameAudioToggle,
53632
54214
  "GameBoard3D": GameBoard3D,
53633
54215
  "GameCanvas3D": GameCanvas3D,
@@ -53665,6 +54247,7 @@ var init_component_registry_generated = __esm({
53665
54247
  "LabelPattern": LabelPattern,
53666
54248
  "LandingPageTemplate": LandingPageTemplate,
53667
54249
  "LawReferenceTooltip": LawReferenceTooltip,
54250
+ "LearningCanvas": LearningCanvas,
53668
54251
  "Lightbox": Lightbox,
53669
54252
  "LikertScale": LikertScale,
53670
54253
  "LineChart": LineChart2,
@@ -53673,9 +54256,11 @@ var init_component_registry_generated = __esm({
53673
54256
  "MapView": MapView,
53674
54257
  "MarkdownContent": MarkdownContent,
53675
54258
  "MarketingFooter": MarketingFooter,
54259
+ "MarketingStatCard": MarketingStatCard,
53676
54260
  "MasterDetail": MasterDetail,
53677
54261
  "MasterDetailLayout": MasterDetailLayout,
53678
54262
  "MatchPuzzleBoard": MatchPuzzleBoard,
54263
+ "MathCanvas": MathCanvas,
53679
54264
  "MatrixQuestion": MatrixQuestion,
53680
54265
  "MediaGallery": MediaGallery,
53681
54266
  "Menu": Menu,
@@ -53694,6 +54279,7 @@ var init_component_registry_generated = __esm({
53694
54279
  "PageHeader": PageHeader,
53695
54280
  "Pagination": Pagination,
53696
54281
  "PatternTile": PatternTile,
54282
+ "PhysicsCanvas": PhysicsCanvas,
53697
54283
  "PinballBoard": PinballBoard,
53698
54284
  "PirateBoard": PirateBoard,
53699
54285
  "PlatformerBoard": PlatformerBoard,