@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
@@ -5,7 +5,7 @@ import { createLogger, isLogLevelEnabled } from '@almadar/logger';
5
5
  import { clsx } from 'clsx';
6
6
  import { twMerge } from 'tailwind-merge';
7
7
  import * as LucideIcons2 from 'lucide-react';
8
- import { Loader2, X, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, Code, FileText, WrapText, Check, Copy, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ZoomOut, ZoomIn, Download, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, ArrowLeft, HelpCircle, PauseCircle, Search, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, Bug, Send, ChevronUp, ChevronDown, Wrench, Tag, User, DollarSign } from 'lucide-react';
8
+ import { Loader2, X, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, Code, FileText, WrapText, Check, Copy, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ZoomOut, ZoomIn, Download, ChevronDown, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, ArrowLeft, HelpCircle, PauseCircle, Search, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, Bug, Send, ChevronUp, Wrench, Tag, User, DollarSign } from 'lucide-react';
9
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
10
  import { createPortal } from 'react-dom';
11
11
  import { useTranslate } from '@almadar/ui/hooks';
@@ -5631,6 +5631,66 @@ var init_RangeSlider = __esm({
5631
5631
  RangeSlider.displayName = "RangeSlider";
5632
5632
  }
5633
5633
  });
5634
+ function easeOut(t) {
5635
+ return t * (2 - t);
5636
+ }
5637
+ var AnimatedCounter;
5638
+ var init_AnimatedCounter = __esm({
5639
+ "components/marketing/atoms/AnimatedCounter.tsx"() {
5640
+ "use client";
5641
+ init_cn();
5642
+ init_Typography();
5643
+ AnimatedCounter = ({
5644
+ value: rawValue,
5645
+ duration = 600,
5646
+ prefix,
5647
+ suffix,
5648
+ className
5649
+ }) => {
5650
+ const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
5651
+ const value = !Number.isNaN(numericRaw) ? numericRaw : 0;
5652
+ const [displayValue, setDisplayValue] = useState(value);
5653
+ const previousValueRef = useRef(value);
5654
+ const animationFrameRef = useRef(null);
5655
+ useEffect(() => {
5656
+ const from = previousValueRef.current;
5657
+ const to = value;
5658
+ previousValueRef.current = value;
5659
+ if (from === to) {
5660
+ setDisplayValue(to);
5661
+ return;
5662
+ }
5663
+ const startTime = performance.now();
5664
+ const diff = to - from;
5665
+ function animate(currentTime) {
5666
+ const elapsed = currentTime - startTime;
5667
+ const progress = Math.min(elapsed / duration, 1);
5668
+ const easedProgress = easeOut(progress);
5669
+ setDisplayValue(from + diff * easedProgress);
5670
+ if (progress < 1) {
5671
+ animationFrameRef.current = requestAnimationFrame(animate);
5672
+ } else {
5673
+ setDisplayValue(to);
5674
+ }
5675
+ }
5676
+ animationFrameRef.current = requestAnimationFrame(animate);
5677
+ return () => {
5678
+ if (animationFrameRef.current !== null) {
5679
+ cancelAnimationFrame(animationFrameRef.current);
5680
+ }
5681
+ };
5682
+ }, [value, duration]);
5683
+ const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
5684
+ const formattedValue = displayValue.toFixed(decimalPlaces);
5685
+ return /* @__PURE__ */ jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
5686
+ prefix,
5687
+ formattedValue,
5688
+ suffix
5689
+ ] });
5690
+ };
5691
+ AnimatedCounter.displayName = "AnimatedCounter";
5692
+ }
5693
+ });
5634
5694
  function useInfiniteScroll(onLoadMore, options = {}) {
5635
5695
  const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
5636
5696
  const observerRef = useRef(null);
@@ -5927,6 +5987,34 @@ var init_SectionHeader = __esm({
5927
5987
  SectionHeader.displayName = "SectionHeader";
5928
5988
  }
5929
5989
  });
5990
+ var sizeClasses6, MarketingStatCard;
5991
+ var init_MarketingStatCard = __esm({
5992
+ "components/marketing/atoms/MarketingStatCard.tsx"() {
5993
+ init_cn();
5994
+ init_Stack();
5995
+ init_Typography();
5996
+ sizeClasses6 = {
5997
+ sm: "text-2xl",
5998
+ md: "text-4xl",
5999
+ lg: "text-5xl"
6000
+ };
6001
+ MarketingStatCard = ({ value, label, size = "md", className }) => {
6002
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", align: "center", className: cn(className), children: [
6003
+ /* @__PURE__ */ jsx(
6004
+ Typography,
6005
+ {
6006
+ weight: "bold",
6007
+ color: "primary",
6008
+ className: cn(sizeClasses6[size]),
6009
+ children: value
6010
+ }
6011
+ ),
6012
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: label })
6013
+ ] });
6014
+ };
6015
+ MarketingStatCard.displayName = "MarketingStatCard";
6016
+ }
6017
+ });
5930
6018
  var backgroundClasses, paddingClasses, ContentSection;
5931
6019
  var init_ContentSection = __esm({
5932
6020
  "components/marketing/atoms/ContentSection.tsx"() {
@@ -12108,318 +12196,14 @@ var init_useCanvasEffects = __esm({
12108
12196
  "use client";
12109
12197
  }
12110
12198
  });
12111
- function pickPath(entry) {
12112
- if (Array.isArray(entry.path)) {
12113
- return entry.path[Math.floor(Math.random() * entry.path.length)];
12114
- }
12115
- return entry.path;
12116
- }
12117
- function useGameAudio({
12118
- manifest,
12119
- baseUrl = "",
12120
- initialMuted = false,
12121
- initialVolume = 1
12122
- }) {
12123
- const [muted, setMutedState] = useState(initialMuted);
12124
- const [masterVolume, setMasterVolumeState] = useState(initialVolume);
12125
- const mutedRef = useRef(muted);
12126
- const volumeRef = useRef(masterVolume);
12127
- const manifestRef = useRef(manifest);
12128
- mutedRef.current = muted;
12129
- volumeRef.current = masterVolume;
12130
- manifestRef.current = manifest;
12131
- const poolsRef = useRef(/* @__PURE__ */ new Map());
12132
- const getOrCreateElement = useCallback((key) => {
12133
- const entry = manifestRef.current[key];
12134
- if (!entry) return null;
12135
- let pool = poolsRef.current.get(key);
12136
- if (!pool) {
12137
- pool = [];
12138
- poolsRef.current.set(key, pool);
12139
- }
12140
- const maxSize = entry.poolSize ?? 1;
12141
- for (const audio of pool) {
12142
- if (audio.paused && (audio.ended || audio.currentTime === 0)) {
12143
- return audio;
12144
- }
12145
- }
12146
- if (pool.length < maxSize) {
12147
- const src = baseUrl + pickPath(entry);
12148
- const audio = new Audio(src);
12149
- audio.loop = entry.loop ?? false;
12150
- pool.push(audio);
12151
- return audio;
12152
- }
12153
- if (!entry.loop) {
12154
- let oldest = pool[0];
12155
- for (const audio of pool) {
12156
- if (audio.currentTime > oldest.currentTime) {
12157
- oldest = audio;
12158
- }
12159
- }
12160
- oldest.pause();
12161
- oldest.currentTime = 0;
12162
- return oldest;
12163
- }
12164
- return null;
12165
- }, [baseUrl]);
12166
- const play = useCallback((key) => {
12167
- if (mutedRef.current) return;
12168
- const entry = manifestRef.current[key];
12169
- if (!entry) return;
12170
- const audio = getOrCreateElement(key);
12171
- if (!audio) return;
12172
- audio.volume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
12173
- if (!entry.loop) {
12174
- audio.currentTime = 0;
12175
- }
12176
- const promise = audio.play();
12177
- if (promise) {
12178
- promise.catch(() => {
12179
- });
12180
- }
12181
- }, [getOrCreateElement]);
12182
- const stop = useCallback((key) => {
12183
- const pool = poolsRef.current.get(key);
12184
- if (!pool) return;
12185
- for (const audio of pool) {
12186
- audio.pause();
12187
- audio.currentTime = 0;
12188
- }
12189
- }, []);
12190
- const currentMusicKeyRef = useRef(null);
12191
- const currentMusicElRef = useRef(null);
12192
- const musicFadeRef = useRef(null);
12193
- const pendingMusicKeyRef = useRef(null);
12194
- const clearMusicFade = useCallback(() => {
12195
- if (musicFadeRef.current) {
12196
- clearInterval(musicFadeRef.current);
12197
- musicFadeRef.current = null;
12198
- }
12199
- }, []);
12200
- const playMusic = useCallback((key) => {
12201
- if (key === currentMusicKeyRef.current) return;
12202
- pendingMusicKeyRef.current = key;
12203
- const entry = manifestRef.current[key];
12204
- if (!entry) return;
12205
- const fadeDurationMs = entry.crossfadeDurationMs ?? 1500;
12206
- const stepMs = 50;
12207
- const totalSteps = Math.max(1, fadeDurationMs / stepMs);
12208
- const targetVolume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
12209
- clearMusicFade();
12210
- const src = baseUrl + (Array.isArray(entry.path) ? entry.path[0] : entry.path);
12211
- const incoming = new Audio(src);
12212
- incoming.loop = true;
12213
- incoming.volume = 0;
12214
- const outgoing = currentMusicElRef.current;
12215
- const outgoingStartVol = outgoing?.volume ?? 0;
12216
- currentMusicKeyRef.current = key;
12217
- currentMusicElRef.current = incoming;
12218
- if (!mutedRef.current) {
12219
- incoming.play().catch(() => {
12220
- currentMusicKeyRef.current = null;
12221
- currentMusicElRef.current = outgoing;
12222
- });
12223
- }
12224
- let step = 0;
12225
- musicFadeRef.current = setInterval(() => {
12226
- step++;
12227
- const progress = Math.min(step / totalSteps, 1);
12228
- incoming.volume = Math.min(1, targetVolume * progress);
12229
- if (outgoing) {
12230
- outgoing.volume = Math.max(0, outgoingStartVol * (1 - progress));
12231
- }
12232
- if (progress >= 1) {
12233
- clearMusicFade();
12234
- if (outgoing) {
12235
- outgoing.pause();
12236
- outgoing.src = "";
12237
- }
12238
- }
12239
- }, stepMs);
12240
- }, [baseUrl, clearMusicFade]);
12241
- const stopMusic = useCallback((fadeDurationMs = 1e3) => {
12242
- const outgoing = currentMusicElRef.current;
12243
- if (!outgoing) return;
12244
- currentMusicKeyRef.current = null;
12245
- currentMusicElRef.current = null;
12246
- pendingMusicKeyRef.current = null;
12247
- clearMusicFade();
12248
- const startVolume = outgoing.volume;
12249
- const stepMs = 50;
12250
- const totalSteps = Math.max(1, fadeDurationMs / stepMs);
12251
- let step = 0;
12252
- musicFadeRef.current = setInterval(() => {
12253
- step++;
12254
- const progress = step / totalSteps;
12255
- outgoing.volume = Math.max(0, startVolume * (1 - progress));
12256
- if (progress >= 1) {
12257
- clearMusicFade();
12258
- outgoing.pause();
12259
- outgoing.src = "";
12260
- }
12261
- }, stepMs);
12262
- }, [clearMusicFade]);
12263
- const stopAll = useCallback(() => {
12264
- for (const pool of poolsRef.current.values()) {
12265
- for (const audio of pool) {
12266
- audio.pause();
12267
- audio.currentTime = 0;
12268
- }
12269
- }
12270
- stopMusic(0);
12271
- }, [stopMusic]);
12272
- const setMuted = useCallback((value) => {
12273
- setMutedState(value);
12274
- if (value) {
12275
- for (const [key, pool] of poolsRef.current.entries()) {
12276
- if (manifestRef.current[key]?.loop) {
12277
- for (const audio of pool) {
12278
- if (!audio.paused) audio.pause();
12279
- }
12280
- }
12281
- }
12282
- currentMusicElRef.current?.pause();
12283
- } else {
12284
- for (const [key, pool] of poolsRef.current.entries()) {
12285
- const entry = manifestRef.current[key];
12286
- if (entry?.loop && entry?.autostart) {
12287
- for (const audio of pool) {
12288
- if (audio.paused) audio.play().catch(() => {
12289
- });
12290
- }
12291
- }
12292
- }
12293
- const musicEl = currentMusicElRef.current;
12294
- if (musicEl) {
12295
- musicEl.play().catch(() => {
12296
- });
12297
- }
12298
- }
12299
- }, []);
12300
- const setMasterVolume = useCallback((volume) => {
12301
- const clamped = Math.max(0, Math.min(1, volume));
12302
- setMasterVolumeState(clamped);
12303
- for (const [key, pool] of poolsRef.current.entries()) {
12304
- const entryVol = manifestRef.current[key]?.volume ?? 1;
12305
- for (const audio of pool) {
12306
- audio.volume = Math.min(1, entryVol * clamped);
12307
- }
12308
- }
12309
- if (!musicFadeRef.current && currentMusicElRef.current) {
12310
- const key = currentMusicKeyRef.current;
12311
- const entryVol = key ? manifestRef.current[key]?.volume ?? 1 : 1;
12312
- currentMusicElRef.current.volume = Math.min(1, entryVol * clamped);
12313
- }
12314
- }, []);
12315
- const unlockedRef = useRef(false);
12316
- useEffect(() => {
12317
- const autoKeys = Object.keys(manifest).filter((k) => manifest[k].autostart);
12318
- const hasPendingMusic = () => pendingMusicKeyRef.current !== null;
12319
- const hasAutoStart = autoKeys.length > 0;
12320
- if (!hasAutoStart && !hasPendingMusic()) return;
12321
- const unlock = () => {
12322
- if (unlockedRef.current) return;
12323
- unlockedRef.current = true;
12324
- if (!mutedRef.current) {
12325
- for (const key of autoKeys) {
12326
- play(key);
12327
- }
12328
- const pending = pendingMusicKeyRef.current;
12329
- if (pending && pending !== currentMusicKeyRef.current) {
12330
- playMusic(pending);
12331
- }
12332
- }
12333
- };
12334
- document.addEventListener("click", unlock, { once: true });
12335
- document.addEventListener("keydown", unlock, { once: true });
12336
- document.addEventListener("touchstart", unlock, { once: true });
12337
- return () => {
12338
- document.removeEventListener("click", unlock);
12339
- document.removeEventListener("keydown", unlock);
12340
- document.removeEventListener("touchstart", unlock);
12341
- };
12342
- }, [manifest, play, playMusic]);
12343
- useEffect(() => {
12344
- return () => {
12345
- clearMusicFade();
12346
- for (const pool of poolsRef.current.values()) {
12347
- for (const audio of pool) {
12348
- audio.pause();
12349
- audio.src = "";
12350
- }
12351
- }
12352
- poolsRef.current.clear();
12353
- if (currentMusicElRef.current) {
12354
- currentMusicElRef.current.pause();
12355
- currentMusicElRef.current.src = "";
12356
- currentMusicElRef.current = null;
12357
- }
12358
- };
12359
- }, [clearMusicFade]);
12360
- return {
12361
- play,
12362
- stop,
12363
- stopAll,
12364
- playMusic,
12365
- stopMusic,
12366
- muted,
12367
- setMuted,
12368
- masterVolume,
12369
- setMasterVolume
12370
- };
12371
- }
12372
12199
  var init_useGameAudio = __esm({
12373
12200
  "components/game/shared/hooks/useGameAudio.ts"() {
12374
12201
  "use client";
12375
- useGameAudio.displayName = "useGameAudio";
12376
12202
  }
12377
12203
  });
12378
12204
  function useGameAudioContextOptional() {
12379
12205
  return useContext(GameAudioContext);
12380
12206
  }
12381
- function GameAudioProvider({
12382
- manifest,
12383
- baseUrl = "",
12384
- children,
12385
- initialMuted = false
12386
- }) {
12387
- const eventBus = useEventBus();
12388
- const { play, stop, stopAll, playMusic, stopMusic, muted, setMuted, masterVolume, setMasterVolume } = useGameAudio({ manifest, baseUrl, initialMuted });
12389
- useEffect(() => {
12390
- const unsubPlay = eventBus.on("UI:PLAY_SOUND", (event) => {
12391
- const key = event.payload?.key;
12392
- if (key) play(key);
12393
- });
12394
- const unsubStop = eventBus.on("UI:STOP_SOUND", (event) => {
12395
- const key = event.payload?.key;
12396
- if (key) {
12397
- stop(key);
12398
- } else {
12399
- stopAll();
12400
- }
12401
- });
12402
- const unsubChangeMusic = eventBus.on("UI:CHANGE_MUSIC", (event) => {
12403
- const key = event.payload?.key;
12404
- if (key) {
12405
- playMusic(key);
12406
- } else {
12407
- stopMusic();
12408
- }
12409
- });
12410
- const unsubStopMusic = eventBus.on("UI:STOP_MUSIC", () => {
12411
- stopMusic();
12412
- });
12413
- return () => {
12414
- unsubPlay();
12415
- unsubStop();
12416
- unsubChangeMusic();
12417
- unsubStopMusic();
12418
- };
12419
- }, [eventBus, play, stop, stopAll, playMusic, stopMusic]);
12420
- const value = { muted, setMuted, masterVolume, setMasterVolume, play, playMusic, stopMusic };
12421
- return /* @__PURE__ */ jsx(GameAudioContext.Provider, { value, children });
12422
- }
12423
12207
  var GameAudioContext;
12424
12208
  var init_GameAudioProvider = __esm({
12425
12209
  "components/game/shared/providers/GameAudioProvider.tsx"() {
@@ -12428,7 +12212,6 @@ var init_GameAudioProvider = __esm({
12428
12212
  init_useGameAudio();
12429
12213
  GameAudioContext = createContext(null);
12430
12214
  GameAudioContext.displayName = "GameAudioContext";
12431
- GameAudioProvider.displayName = "GameAudioProvider";
12432
12215
  }
12433
12216
  });
12434
12217
  function GameAudioToggle({
@@ -20064,6 +19847,315 @@ var init_molecules = __esm({
20064
19847
  init_puzzleObject();
20065
19848
  }
20066
19849
  });
19850
+ function resolveColor2(color, ctx, fallback) {
19851
+ if (!color) return fallback;
19852
+ if (color.startsWith("var(")) {
19853
+ ctx.canvas.style;
19854
+ const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color);
19855
+ if (m) {
19856
+ const computed = getComputedStyle(ctx.canvas).getPropertyValue(m[1]).trim();
19857
+ return computed || m[2]?.trim() || fallback;
19858
+ }
19859
+ }
19860
+ return color;
19861
+ }
19862
+ function shapeBounds(shape) {
19863
+ switch (shape.type) {
19864
+ case "line":
19865
+ case "arrow":
19866
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) return null;
19867
+ return {
19868
+ x: Math.min(shape.x1, shape.x2) - 6,
19869
+ y: Math.min(shape.y1, shape.y2) - 6,
19870
+ w: Math.abs(shape.x2 - shape.x1) + 12,
19871
+ h: Math.abs(shape.y2 - shape.y1) + 12
19872
+ };
19873
+ case "circle":
19874
+ if (shape.x == null || shape.y == null || shape.radius == null) return null;
19875
+ return {
19876
+ x: shape.x - shape.radius - 4,
19877
+ y: shape.y - shape.radius - 4,
19878
+ w: shape.radius * 2 + 8,
19879
+ h: shape.radius * 2 + 8
19880
+ };
19881
+ case "rect":
19882
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
19883
+ return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
19884
+ case "polygon":
19885
+ if (!shape.points || shape.points.length === 0) return null;
19886
+ {
19887
+ const xs = shape.points.map((p2) => p2.x);
19888
+ const ys = shape.points.map((p2) => p2.y);
19889
+ const minX = Math.min(...xs);
19890
+ const minY = Math.min(...ys);
19891
+ return {
19892
+ x: minX - 4,
19893
+ y: minY - 4,
19894
+ w: Math.max(...xs) - minX + 8,
19895
+ h: Math.max(...ys) - minY + 8
19896
+ };
19897
+ }
19898
+ case "text":
19899
+ if (shape.x == null || shape.y == null) return null;
19900
+ return { x: shape.x - 4, y: shape.y - (shape.fontSize ?? 14) - 4, w: 120, h: (shape.fontSize ?? 14) + 8 };
19901
+ default:
19902
+ return null;
19903
+ }
19904
+ }
19905
+ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
19906
+ const angle = Math.atan2(y2 - y1, x2 - x1);
19907
+ ctx.beginPath();
19908
+ ctx.moveTo(x2, y2);
19909
+ ctx.lineTo(x2 - size * Math.cos(angle - Math.PI / 6), y2 - size * Math.sin(angle - Math.PI / 6));
19910
+ ctx.lineTo(x2 - size * Math.cos(angle + Math.PI / 6), y2 - size * Math.sin(angle + Math.PI / 6));
19911
+ ctx.closePath();
19912
+ ctx.fill();
19913
+ }
19914
+ function drawShape(ctx, shape, width, height) {
19915
+ ctx.save();
19916
+ const opacity = shape.opacity ?? 1;
19917
+ ctx.globalAlpha = opacity;
19918
+ const stroke = resolveColor2(shape.color, ctx, "#333333");
19919
+ const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
19920
+ ctx.lineWidth = shape.lineWidth ?? 2;
19921
+ switch (shape.type) {
19922
+ case "grid": {
19923
+ const step = shape.step ?? 40;
19924
+ ctx.strokeStyle = stroke;
19925
+ ctx.globalAlpha = opacity * 0.25;
19926
+ ctx.lineWidth = 1;
19927
+ ctx.beginPath();
19928
+ for (let x = 0; x <= width; x += step) {
19929
+ ctx.moveTo(x, 0);
19930
+ ctx.lineTo(x, height);
19931
+ }
19932
+ for (let y = 0; y <= height; y += step) {
19933
+ ctx.moveTo(0, y);
19934
+ ctx.lineTo(width, y);
19935
+ }
19936
+ ctx.stroke();
19937
+ break;
19938
+ }
19939
+ case "axis": {
19940
+ const axis = shape.axis ?? "x";
19941
+ ctx.strokeStyle = stroke;
19942
+ ctx.lineWidth = shape.lineWidth ?? 2;
19943
+ ctx.beginPath();
19944
+ if (axis === "x") {
19945
+ ctx.moveTo(0, height / 2);
19946
+ ctx.lineTo(width, height / 2);
19947
+ } else {
19948
+ ctx.moveTo(width / 2, 0);
19949
+ ctx.lineTo(width / 2, height);
19950
+ }
19951
+ ctx.stroke();
19952
+ break;
19953
+ }
19954
+ case "line": {
19955
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
19956
+ ctx.strokeStyle = stroke;
19957
+ ctx.beginPath();
19958
+ ctx.moveTo(shape.x1, shape.y1);
19959
+ ctx.lineTo(shape.x2, shape.y2);
19960
+ ctx.stroke();
19961
+ break;
19962
+ }
19963
+ case "arrow": {
19964
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
19965
+ ctx.strokeStyle = stroke;
19966
+ ctx.fillStyle = stroke;
19967
+ ctx.beginPath();
19968
+ ctx.moveTo(shape.x1, shape.y1);
19969
+ ctx.lineTo(shape.x2, shape.y2);
19970
+ ctx.stroke();
19971
+ drawArrowHead(ctx, shape.x1, shape.y1, shape.x2, shape.y2, 10);
19972
+ break;
19973
+ }
19974
+ case "circle": {
19975
+ if (shape.x == null || shape.y == null || shape.radius == null) break;
19976
+ ctx.beginPath();
19977
+ ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
19978
+ if (fill) {
19979
+ ctx.fillStyle = fill;
19980
+ ctx.fill();
19981
+ }
19982
+ ctx.strokeStyle = stroke;
19983
+ ctx.stroke();
19984
+ break;
19985
+ }
19986
+ case "rect": {
19987
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
19988
+ if (fill) {
19989
+ ctx.fillStyle = fill;
19990
+ ctx.fillRect(shape.x, shape.y, shape.width, shape.height);
19991
+ }
19992
+ ctx.strokeStyle = stroke;
19993
+ ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
19994
+ break;
19995
+ }
19996
+ case "polygon": {
19997
+ if (!shape.points || shape.points.length < 2) break;
19998
+ ctx.beginPath();
19999
+ ctx.moveTo(shape.points[0].x, shape.points[0].y);
20000
+ for (let i = 1; i < shape.points.length; i++) {
20001
+ ctx.lineTo(shape.points[i].x, shape.points[i].y);
20002
+ }
20003
+ ctx.closePath();
20004
+ if (fill) {
20005
+ ctx.fillStyle = fill;
20006
+ ctx.fill();
20007
+ }
20008
+ ctx.strokeStyle = stroke;
20009
+ ctx.stroke();
20010
+ break;
20011
+ }
20012
+ case "path": {
20013
+ if (!shape.path) break;
20014
+ const p2 = new Path2D(shape.path);
20015
+ if (fill) {
20016
+ ctx.fillStyle = fill;
20017
+ ctx.fill(p2);
20018
+ }
20019
+ ctx.strokeStyle = stroke;
20020
+ ctx.stroke(p2);
20021
+ break;
20022
+ }
20023
+ case "text": {
20024
+ if (shape.x == null || shape.y == null || !shape.text) break;
20025
+ ctx.fillStyle = stroke;
20026
+ ctx.font = `${shape.fontSize ?? 14}px system-ui, sans-serif`;
20027
+ ctx.textAlign = shape.align ?? "left";
20028
+ ctx.textBaseline = "middle";
20029
+ ctx.fillText(shape.text, shape.x, shape.y);
20030
+ break;
20031
+ }
20032
+ }
20033
+ ctx.restore();
20034
+ }
20035
+ var LearningCanvas;
20036
+ var init_LearningCanvas = __esm({
20037
+ "components/learning/atoms/LearningCanvas.tsx"() {
20038
+ "use client";
20039
+ init_cn();
20040
+ init_useEventBus();
20041
+ LearningCanvas = ({
20042
+ className,
20043
+ width = 600,
20044
+ height = 400,
20045
+ backgroundColor,
20046
+ shapes = [],
20047
+ interactive = false,
20048
+ animate = false,
20049
+ onShapeClick,
20050
+ onShapeHover,
20051
+ isLoading,
20052
+ error
20053
+ }) => {
20054
+ const canvasRef = useRef(null);
20055
+ const eventBus = useEventBus();
20056
+ const animRef = useRef(0);
20057
+ const hoverIndexRef = useRef(-1);
20058
+ const findShapeAt = useCallback((clientX, clientY) => {
20059
+ const canvas = canvasRef.current;
20060
+ if (!canvas) return -1;
20061
+ const rect = canvas.getBoundingClientRect();
20062
+ const x = clientX - rect.left;
20063
+ const y = clientY - rect.top;
20064
+ for (let i = shapes.length - 1; i >= 0; i--) {
20065
+ const b = shapeBounds(shapes[i]);
20066
+ if (b && x >= b.x && x <= b.x + b.w && y >= b.y && y <= b.y + b.h) {
20067
+ return i;
20068
+ }
20069
+ }
20070
+ return -1;
20071
+ }, [shapes]);
20072
+ const draw = useCallback(() => {
20073
+ const canvas = canvasRef.current;
20074
+ if (!canvas) return;
20075
+ const ctx = canvas.getContext("2d");
20076
+ if (!ctx) return;
20077
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
20078
+ canvas.width = Math.max(1, Math.floor(width * dpr));
20079
+ canvas.height = Math.max(1, Math.floor(height * dpr));
20080
+ canvas.style.width = `${width}px`;
20081
+ canvas.style.height = `${height}px`;
20082
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
20083
+ ctx.clearRect(0, 0, width, height);
20084
+ if (backgroundColor) {
20085
+ ctx.fillStyle = backgroundColor;
20086
+ ctx.fillRect(0, 0, width, height);
20087
+ }
20088
+ for (const shape of shapes) {
20089
+ drawShape(ctx, shape, width, height);
20090
+ }
20091
+ }, [width, height, backgroundColor, shapes]);
20092
+ useEffect(() => {
20093
+ draw();
20094
+ }, [draw]);
20095
+ useEffect(() => {
20096
+ if (!animate) return;
20097
+ const loop = () => {
20098
+ draw();
20099
+ animRef.current = requestAnimationFrame(loop);
20100
+ };
20101
+ animRef.current = requestAnimationFrame(loop);
20102
+ return () => cancelAnimationFrame(animRef.current);
20103
+ }, [animate, draw]);
20104
+ const handlePointerMove = useCallback(
20105
+ (e) => {
20106
+ if (!interactive) return;
20107
+ const idx = findShapeAt(e.clientX, e.clientY);
20108
+ if (idx !== hoverIndexRef.current) {
20109
+ hoverIndexRef.current = idx;
20110
+ if (idx >= 0) {
20111
+ const shape = shapes[idx];
20112
+ const payload = { id: shape.id, type: shape.type, index: idx };
20113
+ if (onShapeHover) onShapeHover(payload);
20114
+ else if (eventBus) eventBus.emit(`UI:SHAPE_HOVER`, payload);
20115
+ }
20116
+ }
20117
+ },
20118
+ [interactive, onShapeHover, eventBus, findShapeAt, shapes]
20119
+ );
20120
+ const handleClick = useCallback(
20121
+ (e) => {
20122
+ if (!interactive) return;
20123
+ const idx = findShapeAt(e.clientX, e.clientY);
20124
+ if (idx >= 0) {
20125
+ const shape = shapes[idx];
20126
+ const payload = { id: shape.id, type: shape.type, index: idx };
20127
+ if (onShapeClick) onShapeClick(payload);
20128
+ else if (eventBus) eventBus.emit(`UI:SHAPE_CLICK`, payload);
20129
+ }
20130
+ },
20131
+ [interactive, onShapeClick, eventBus, findShapeAt, shapes]
20132
+ );
20133
+ if (isLoading || error) {
20134
+ return /* @__PURE__ */ jsx(
20135
+ "div",
20136
+ {
20137
+ className: cn(
20138
+ "flex items-center justify-center rounded border border-border bg-surface",
20139
+ className
20140
+ ),
20141
+ style: { width, height },
20142
+ 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" })
20143
+ }
20144
+ );
20145
+ }
20146
+ return /* @__PURE__ */ jsx(
20147
+ "canvas",
20148
+ {
20149
+ ref: canvasRef,
20150
+ className: cn("block touch-none rounded border border-border", className),
20151
+ style: { width, height },
20152
+ onClick: handleClick,
20153
+ onPointerMove: handlePointerMove
20154
+ }
20155
+ );
20156
+ };
20157
+ }
20158
+ });
20067
20159
 
20068
20160
  // components/core/atoms/index.ts
20069
20161
  var init_atoms = __esm({
@@ -21932,91 +22024,6 @@ var init_ComponentPatterns = __esm({
21932
22024
  AlertPattern.displayName = "AlertPattern";
21933
22025
  }
21934
22026
  });
21935
- function parseValue(value) {
21936
- if (value === "" || value == null) return { num: 0, prefix: "", suffix: "", decimals: 0 };
21937
- const match = String(value).match(/^([^0-9]*)([0-9]+(?:\.[0-9]+)?)(.*)$/);
21938
- if (!match) {
21939
- return { num: 0, prefix: "", suffix: String(value), decimals: 0 };
21940
- }
21941
- const numStr = match[2];
21942
- const decimalIdx = numStr.indexOf(".");
21943
- const decimals = decimalIdx >= 0 ? numStr.length - decimalIdx - 1 : 0;
21944
- return {
21945
- prefix: match[1],
21946
- num: parseFloat(numStr),
21947
- suffix: match[3],
21948
- decimals
21949
- };
21950
- }
21951
- var AnimatedCounter;
21952
- var init_AnimatedCounter = __esm({
21953
- "components/core/molecules/AnimatedCounter.tsx"() {
21954
- "use client";
21955
- init_cn();
21956
- init_Box();
21957
- init_Typography();
21958
- AnimatedCounter = ({
21959
- value,
21960
- label,
21961
- duration = 1500,
21962
- className
21963
- }) => {
21964
- const ref = useRef(null);
21965
- const [displayValue, setDisplayValue] = useState("0");
21966
- const [hasAnimated, setHasAnimated] = useState(false);
21967
- const animate = useCallback(() => {
21968
- const { num: num2, prefix, suffix, decimals } = parseValue(value);
21969
- if (num2 === 0) {
21970
- setDisplayValue(String(value));
21971
- return;
21972
- }
21973
- const startTime = performance.now();
21974
- const tick = (now) => {
21975
- const elapsed = now - startTime;
21976
- const progress = Math.min(elapsed / duration, 1);
21977
- const eased = 1 - Math.pow(1 - progress, 3);
21978
- const current = eased * num2;
21979
- setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
21980
- if (progress < 1) {
21981
- requestAnimationFrame(tick);
21982
- } else {
21983
- setDisplayValue(String(value));
21984
- }
21985
- };
21986
- requestAnimationFrame(tick);
21987
- }, [value, duration]);
21988
- useEffect(() => {
21989
- if (hasAnimated) return;
21990
- const el = ref.current;
21991
- if (!el) return;
21992
- const observer2 = new IntersectionObserver(
21993
- (entries) => {
21994
- if (entries[0].isIntersecting) {
21995
- setHasAnimated(true);
21996
- animate();
21997
- observer2.disconnect();
21998
- }
21999
- },
22000
- { threshold: 0.3 }
22001
- );
22002
- observer2.observe(el);
22003
- return () => observer2.disconnect();
22004
- }, [hasAnimated, animate]);
22005
- return /* @__PURE__ */ jsxs(Box, { ref, className: cn("flex flex-col items-center gap-1 p-4", className), children: [
22006
- /* @__PURE__ */ jsx(
22007
- Typography,
22008
- {
22009
- variant: "h2",
22010
- className: "text-primary font-bold tabular-nums",
22011
- children: hasAnimated ? displayValue : "0"
22012
- }
22013
- ),
22014
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "muted", className: "text-center", children: label })
22015
- ] });
22016
- };
22017
- AnimatedCounter.displayName = "AnimatedCounter";
22018
- }
22019
- });
22020
22027
  var AuthLayout;
22021
22028
  var init_AuthLayout = __esm({
22022
22029
  "components/marketing/templates/AuthLayout.tsx"() {
@@ -22985,10 +22992,107 @@ var init_BehaviorView = __esm({
22985
22992
  BehaviorView.displayName = "BehaviorView";
22986
22993
  }
22987
22994
  });
22995
+ var BiologyCanvas;
22996
+ var init_BiologyCanvas = __esm({
22997
+ "components/learning/molecules/BiologyCanvas.tsx"() {
22998
+ "use client";
22999
+ init_atoms();
23000
+ init_Stack();
23001
+ init_LearningCanvas();
23002
+ BiologyCanvas = ({
23003
+ className,
23004
+ width = 600,
23005
+ height = 400,
23006
+ title,
23007
+ backgroundColor,
23008
+ nodes = [],
23009
+ edges = [],
23010
+ shapes = [],
23011
+ interactive = false,
23012
+ animate = false,
23013
+ onShapeClick,
23014
+ isLoading,
23015
+ error
23016
+ }) => {
23017
+ const derivedShapes = useMemo(() => {
23018
+ const out = [];
23019
+ const nodeById = /* @__PURE__ */ new Map();
23020
+ for (const n of nodes) {
23021
+ if (n.id) nodeById.set(n.id, n);
23022
+ }
23023
+ for (const e of edges) {
23024
+ const a = nodeById.get(e.from);
23025
+ const b = nodeById.get(e.to);
23026
+ if (!a || !b) continue;
23027
+ out.push({
23028
+ type: "line",
23029
+ x1: a.x,
23030
+ y1: a.y,
23031
+ x2: b.x,
23032
+ y2: b.y,
23033
+ color: e.color ?? "#9ca3af",
23034
+ lineWidth: 2
23035
+ });
23036
+ if (e.label) {
23037
+ out.push({
23038
+ type: "text",
23039
+ x: (a.x + b.x) / 2 + 4,
23040
+ y: (a.y + b.y) / 2 - 4,
23041
+ text: e.label,
23042
+ color: "#374151",
23043
+ fontSize: 11
23044
+ });
23045
+ }
23046
+ }
23047
+ for (const n of nodes) {
23048
+ out.push({
23049
+ type: "circle",
23050
+ x: n.x,
23051
+ y: n.y,
23052
+ radius: n.radius ?? 16,
23053
+ color: n.color ?? "#16a34a",
23054
+ fill: `${n.color ?? "#16a34a"}33`,
23055
+ id: n.id
23056
+ });
23057
+ if (n.label) {
23058
+ out.push({
23059
+ type: "text",
23060
+ x: n.x,
23061
+ y: n.y + (n.radius ?? 16) + 14,
23062
+ text: n.label,
23063
+ color: "#111827",
23064
+ fontSize: 12,
23065
+ align: "center"
23066
+ });
23067
+ }
23068
+ }
23069
+ out.push(...shapes);
23070
+ return out;
23071
+ }, [nodes, edges, shapes]);
23072
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
23073
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
23074
+ /* @__PURE__ */ jsx(
23075
+ LearningCanvas,
23076
+ {
23077
+ width,
23078
+ height,
23079
+ backgroundColor,
23080
+ shapes: derivedShapes,
23081
+ interactive,
23082
+ animate,
23083
+ onShapeClick,
23084
+ isLoading,
23085
+ error
23086
+ }
23087
+ )
23088
+ ] }) });
23089
+ };
23090
+ }
23091
+ });
22988
23092
 
22989
- // node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css
23093
+ // node_modules/katex/dist/katex.min.css
22990
23094
  var init_katex_min = __esm({
22991
- "node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css"() {
23095
+ "node_modules/katex/dist/katex.min.css"() {
22992
23096
  }
22993
23097
  });
22994
23098
  var Tabs;
@@ -27381,129 +27485,6 @@ var init_Breadcrumb = __esm({
27381
27485
  Breadcrumb.displayName = "Breadcrumb";
27382
27486
  }
27383
27487
  });
27384
- function useSafeEventBus2() {
27385
- try {
27386
- return useEventBus();
27387
- } catch {
27388
- return { emit: () => {
27389
- }, on: () => () => {
27390
- }, once: () => {
27391
- } };
27392
- }
27393
- }
27394
- var log8, lookStyles4, ButtonGroup;
27395
- var init_ButtonGroup = __esm({
27396
- "components/core/molecules/ButtonGroup.tsx"() {
27397
- "use client";
27398
- init_cn();
27399
- init_atoms();
27400
- init_useEventBus();
27401
- log8 = createLogger("almadar:ui:button-group");
27402
- lookStyles4 = {
27403
- "right-aligned-buttons": "",
27404
- "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
27405
- "inline-row": "gap-2 inline-flex",
27406
- "dropdown-menu": "[&>button:not(:first-child)]:hidden",
27407
- "command-palette-trigger": "[&>button:not(:first-child)]:hidden"
27408
- };
27409
- ButtonGroup = ({
27410
- children,
27411
- primary,
27412
- secondary,
27413
- variant = "default",
27414
- orientation = "horizontal",
27415
- className,
27416
- // Filter-group pattern props (entity and filters are used for schema-driven filtering)
27417
- entity: _entity,
27418
- filters,
27419
- look = "right-aligned-buttons"
27420
- }) => {
27421
- const eventBus = useSafeEventBus2();
27422
- const variantClasses2 = {
27423
- default: "gap-0",
27424
- 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",
27425
- 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"
27426
- };
27427
- const orientationClasses = {
27428
- horizontal: "flex-row",
27429
- 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"
27430
- };
27431
- const handleActionClick = (action) => {
27432
- if (action.event) {
27433
- eventBus.emit("UI:DISPATCH", { event: action.event });
27434
- }
27435
- if (action.navigatesTo) {
27436
- eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
27437
- }
27438
- };
27439
- const renderFormActions = () => {
27440
- const buttons = [];
27441
- if (secondary) {
27442
- secondary.forEach((action, index) => {
27443
- buttons.push(
27444
- /* @__PURE__ */ jsx(
27445
- Button,
27446
- {
27447
- type: action.actionType === "submit" ? "submit" : "button",
27448
- variant: action.variant || "ghost",
27449
- onClick: () => handleActionClick(action),
27450
- children: action.label
27451
- },
27452
- `secondary-${index}`
27453
- )
27454
- );
27455
- });
27456
- }
27457
- if (primary) {
27458
- const isSubmit = primary.actionType === "submit";
27459
- buttons.push(
27460
- /* @__PURE__ */ jsx(
27461
- Button,
27462
- {
27463
- type: isSubmit ? "submit" : "button",
27464
- variant: primary.variant || "primary",
27465
- onClick: () => handleActionClick(primary),
27466
- "data-testid": isSubmit ? "form-submit" : void 0,
27467
- children: primary.label
27468
- },
27469
- "primary"
27470
- )
27471
- );
27472
- }
27473
- return buttons;
27474
- };
27475
- const renderFilters = () => {
27476
- if (!filters || filters.length === 0) return null;
27477
- return filters.map((filter, index) => /* @__PURE__ */ jsx(
27478
- Button,
27479
- {
27480
- variant: "ghost",
27481
- onClick: () => {
27482
- log8.debug("Filter clicked", { field: filter.field });
27483
- },
27484
- children: filter.label
27485
- },
27486
- `filter-${filter.field}-${index}`
27487
- ));
27488
- };
27489
- return /* @__PURE__ */ jsx(
27490
- "div",
27491
- {
27492
- className: cn(
27493
- "inline-flex gap-2",
27494
- variantClasses2[variant],
27495
- orientationClasses[orientation],
27496
- lookStyles4[look],
27497
- className
27498
- ),
27499
- role: "group",
27500
- children: children || renderFilters() || renderFormActions()
27501
- }
27502
- );
27503
- };
27504
- ButtonGroup.displayName = "ButtonGroup";
27505
- }
27506
- });
27507
27488
  function useSwipeGesture(callbacks, options = {}) {
27508
27489
  const { threshold = 50, velocityThreshold = 0.3, preventDefault = false } = options;
27509
27490
  const startX = useRef(0);
@@ -28313,7 +28294,7 @@ var init_CardGrid = __esm({
28313
28294
  CardGrid.displayName = "CardGrid";
28314
28295
  }
28315
28296
  });
28316
- function useSafeEventBus3() {
28297
+ function useSafeEventBus2() {
28317
28298
  try {
28318
28299
  return useEventBus();
28319
28300
  } catch {
@@ -28349,7 +28330,7 @@ var init_Carousel = __esm({
28349
28330
  const [activeIndex, setActiveIndex] = useState(0);
28350
28331
  const scrollRef = useRef(null);
28351
28332
  const autoPlayRef = useRef(null);
28352
- const eventBus = useSafeEventBus3();
28333
+ const eventBus = useSafeEventBus2();
28353
28334
  const { t } = useTranslate();
28354
28335
  const safeItems = items ?? [];
28355
28336
  const totalSlides = safeItems.length;
@@ -29597,6 +29578,122 @@ var init_ChatBar = __esm({
29597
29578
  ChatBar.displayName = "ChatBar";
29598
29579
  }
29599
29580
  });
29581
+ var ChemistryCanvas;
29582
+ var init_ChemistryCanvas = __esm({
29583
+ "components/learning/molecules/ChemistryCanvas.tsx"() {
29584
+ "use client";
29585
+ init_atoms();
29586
+ init_Stack();
29587
+ init_LearningCanvas();
29588
+ ChemistryCanvas = ({
29589
+ className,
29590
+ width = 600,
29591
+ height = 400,
29592
+ title,
29593
+ backgroundColor,
29594
+ atoms = [],
29595
+ bonds = [],
29596
+ arrows = [],
29597
+ shapes = [],
29598
+ interactive = false,
29599
+ animate = false,
29600
+ onShapeClick,
29601
+ isLoading,
29602
+ error
29603
+ }) => {
29604
+ const derivedShapes = useMemo(() => {
29605
+ const out = [];
29606
+ const atomById = /* @__PURE__ */ new Map();
29607
+ for (const a of atoms) {
29608
+ if (a.id) atomById.set(a.id, a);
29609
+ }
29610
+ for (const b of bonds) {
29611
+ const a = atomById.get(b.from);
29612
+ const c = atomById.get(b.to);
29613
+ if (!a || !c) continue;
29614
+ const color = b.color ?? "#6b7280";
29615
+ const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
29616
+ out.push({
29617
+ type: "line",
29618
+ x1: a.x,
29619
+ y1: a.y,
29620
+ x2: c.x,
29621
+ y2: c.y,
29622
+ color,
29623
+ lineWidth: strokeWidth
29624
+ });
29625
+ }
29626
+ for (const a of arrows) {
29627
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
29628
+ const len = a.length ?? 60;
29629
+ const x2 = a.x + Math.cos(angle) * len;
29630
+ const y2 = a.y + Math.sin(angle) * len;
29631
+ out.push({
29632
+ type: "arrow",
29633
+ x1: a.x,
29634
+ y1: a.y,
29635
+ x2,
29636
+ y2,
29637
+ color: a.color ?? "#dc2626",
29638
+ lineWidth: 2
29639
+ });
29640
+ if (a.label) {
29641
+ out.push({
29642
+ type: "text",
29643
+ x: (a.x + x2) / 2,
29644
+ y: (a.y + y2) / 2 - 10,
29645
+ text: a.label,
29646
+ color: "#111827",
29647
+ fontSize: 12,
29648
+ align: "center"
29649
+ });
29650
+ }
29651
+ }
29652
+ for (const a of atoms) {
29653
+ out.push({
29654
+ type: "circle",
29655
+ x: a.x,
29656
+ y: a.y,
29657
+ radius: a.radius ?? 14,
29658
+ color: a.color ?? "#2563eb",
29659
+ fill: a.color ?? "#2563eb",
29660
+ id: a.id
29661
+ });
29662
+ if (a.element) {
29663
+ out.push({
29664
+ type: "text",
29665
+ x: a.x,
29666
+ y: a.y,
29667
+ text: a.element,
29668
+ color: "#ffffff",
29669
+ fontSize: 12,
29670
+ align: "center"
29671
+ });
29672
+ }
29673
+ }
29674
+ out.push(...shapes);
29675
+ return out;
29676
+ }, [atoms, bonds, arrows, shapes]);
29677
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
29678
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
29679
+ /* @__PURE__ */ jsx(
29680
+ LearningCanvas,
29681
+ {
29682
+ width,
29683
+ height,
29684
+ backgroundColor,
29685
+ shapes: derivedShapes,
29686
+ interactive,
29687
+ animate,
29688
+ onShapeClick,
29689
+ isLoading,
29690
+ error
29691
+ }
29692
+ )
29693
+ ] }) });
29694
+ };
29695
+ }
29696
+ });
29600
29697
  var CodeRunnerPanel;
29601
29698
  var init_CodeRunnerPanel = __esm({
29602
29699
  "components/core/organisms/CodeRunnerPanel.tsx"() {
@@ -31685,7 +31782,7 @@ function DataGrid({
31685
31782
  /* @__PURE__ */ jsx(
31686
31783
  Box,
31687
31784
  {
31688
- className: cn("grid", gapStyles5[gap], scrollX ? "grid-flow-col overflow-x-auto" : colsClass, lookStyles5[look], className),
31785
+ className: cn("grid", gapStyles5[gap], scrollX ? "grid-flow-col overflow-x-auto" : colsClass, lookStyles4[look], className),
31689
31786
  style: scrollX ? { gridAutoFlow: "column", gridAutoColumns: `minmax(${minCardWidth}px, 1fr)` } : gridTemplateColumns ? { gridTemplateColumns } : void 0,
31690
31787
  children: data.map((item, index) => {
31691
31788
  const itemData = item;
@@ -31880,7 +31977,7 @@ function DataGrid({
31880
31977
  ] })
31881
31978
  );
31882
31979
  }
31883
- var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles5;
31980
+ var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles4;
31884
31981
  var init_DataGrid = __esm({
31885
31982
  "components/core/molecules/DataGrid.tsx"() {
31886
31983
  "use client";
@@ -31915,7 +32012,7 @@ var init_DataGrid = __esm({
31915
32012
  lg: "gap-6",
31916
32013
  xl: "gap-8"
31917
32014
  };
31918
- lookStyles5 = {
32015
+ lookStyles4 = {
31919
32016
  dense: "gap-2 [&>*]:p-card-sm",
31920
32017
  spacious: "gap-8 [&>*]:p-card-lg",
31921
32018
  striped: "[&>*:nth-child(even)]:bg-muted/30",
@@ -32342,6 +32439,133 @@ var init_DataList = __esm({
32342
32439
  DataList.displayName = "DataList";
32343
32440
  }
32344
32441
  });
32442
+ var FormSection, FormLayout, FormActions;
32443
+ var init_FormSection = __esm({
32444
+ "components/core/molecules/FormSection.tsx"() {
32445
+ "use client";
32446
+ init_cn();
32447
+ init_atoms();
32448
+ init_Box();
32449
+ init_Typography();
32450
+ init_Button();
32451
+ init_Stack();
32452
+ init_Icon();
32453
+ init_useEventBus();
32454
+ FormSection = ({
32455
+ title,
32456
+ description,
32457
+ children,
32458
+ collapsible = false,
32459
+ defaultCollapsed = false,
32460
+ card = false,
32461
+ columns = 1,
32462
+ className
32463
+ }) => {
32464
+ const [collapsed, setCollapsed] = React107__default.useState(defaultCollapsed);
32465
+ const { t } = useTranslate();
32466
+ const eventBus = useEventBus();
32467
+ const gridClass = {
32468
+ 1: "grid-cols-1",
32469
+ 2: "grid-cols-1 md:grid-cols-2",
32470
+ 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
32471
+ }[columns];
32472
+ React107__default.useCallback(() => {
32473
+ if (collapsible) {
32474
+ setCollapsed((prev) => !prev);
32475
+ eventBus.emit("UI:TOGGLE_COLLAPSE", { collapsed: !collapsed });
32476
+ }
32477
+ }, [collapsible, collapsed, eventBus]);
32478
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
32479
+ (title || description) && /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "mb-4", children: [
32480
+ title && /* @__PURE__ */ jsxs(
32481
+ HStack,
32482
+ {
32483
+ justify: "between",
32484
+ align: "center",
32485
+ className: cn(collapsible && "cursor-pointer"),
32486
+ action: collapsible ? "TOGGLE_COLLAPSE" : void 0,
32487
+ children: [
32488
+ /* @__PURE__ */ jsx(Typography, { variant: "h3", weight: "semibold", children: title }),
32489
+ collapsible && /* @__PURE__ */ jsx(
32490
+ Button,
32491
+ {
32492
+ variant: "ghost",
32493
+ size: "sm",
32494
+ action: "TOGGLE_COLLAPSE",
32495
+ children: /* @__PURE__ */ jsx(
32496
+ Icon,
32497
+ {
32498
+ icon: ChevronDown,
32499
+ size: "sm",
32500
+ className: cn(
32501
+ "text-muted-foreground transition-transform",
32502
+ collapsed && "rotate-180"
32503
+ )
32504
+ }
32505
+ )
32506
+ }
32507
+ )
32508
+ ]
32509
+ }
32510
+ ),
32511
+ description && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: description })
32512
+ ] }),
32513
+ (!collapsible || !collapsed) && /* @__PURE__ */ jsx(Box, { className: cn("grid gap-4", gridClass), children })
32514
+ ] });
32515
+ if (card) {
32516
+ return /* @__PURE__ */ jsx(Card, { className: cn("p-6", className), children: content });
32517
+ }
32518
+ return /* @__PURE__ */ jsx(Box, { className, children: content });
32519
+ };
32520
+ FormSection.displayName = "FormSection";
32521
+ FormLayout = ({
32522
+ children,
32523
+ dividers = true,
32524
+ className
32525
+ }) => {
32526
+ return /* @__PURE__ */ jsx(
32527
+ VStack,
32528
+ {
32529
+ gap: "lg",
32530
+ className: cn(
32531
+ dividers && "[&>*+*]:pt-8 [&>*+*]:border-t [&>*+*]:border-border",
32532
+ className
32533
+ ),
32534
+ children
32535
+ }
32536
+ );
32537
+ };
32538
+ FormLayout.displayName = "FormLayout";
32539
+ FormActions = ({
32540
+ children,
32541
+ sticky = false,
32542
+ align = "right",
32543
+ className
32544
+ }) => {
32545
+ const alignClass2 = {
32546
+ left: "justify-start",
32547
+ right: "justify-end",
32548
+ between: "justify-between",
32549
+ center: "justify-center"
32550
+ }[align];
32551
+ return /* @__PURE__ */ jsx(
32552
+ HStack,
32553
+ {
32554
+ gap: "sm",
32555
+ align: "center",
32556
+ className: cn(
32557
+ "pt-6 border-t border-border",
32558
+ alignClass2,
32559
+ sticky && "sticky bottom-0 bg-card py-4 -mx-6 px-6 shadow-[0_-4px_6px_-1px_rgb(0,0,0,0.05)]",
32560
+ className
32561
+ ),
32562
+ children
32563
+ }
32564
+ );
32565
+ };
32566
+ FormActions.displayName = "FormActions";
32567
+ }
32568
+ });
32345
32569
  function fileIcon(name) {
32346
32570
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
32347
32571
  switch (ext) {
@@ -32503,6 +32727,129 @@ var init_FormField = __esm({
32503
32727
  FormField.displayName = "FormField";
32504
32728
  }
32505
32729
  });
32730
+ function useSafeEventBus3() {
32731
+ try {
32732
+ return useEventBus();
32733
+ } catch {
32734
+ return { emit: () => {
32735
+ }, on: () => () => {
32736
+ }, once: () => {
32737
+ } };
32738
+ }
32739
+ }
32740
+ var log8, lookStyles5, ButtonGroup;
32741
+ var init_ButtonGroup = __esm({
32742
+ "components/core/molecules/ButtonGroup.tsx"() {
32743
+ "use client";
32744
+ init_cn();
32745
+ init_atoms();
32746
+ init_useEventBus();
32747
+ log8 = createLogger("almadar:ui:button-group");
32748
+ lookStyles5 = {
32749
+ "right-aligned-buttons": "",
32750
+ "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
32751
+ "inline-row": "gap-2 inline-flex",
32752
+ "dropdown-menu": "[&>button:not(:first-child)]:hidden",
32753
+ "command-palette-trigger": "[&>button:not(:first-child)]:hidden"
32754
+ };
32755
+ ButtonGroup = ({
32756
+ children,
32757
+ primary,
32758
+ secondary,
32759
+ variant = "default",
32760
+ orientation = "horizontal",
32761
+ className,
32762
+ // Filter-group pattern props (entity and filters are used for schema-driven filtering)
32763
+ entity: _entity,
32764
+ filters,
32765
+ look = "right-aligned-buttons"
32766
+ }) => {
32767
+ const eventBus = useSafeEventBus3();
32768
+ const variantClasses2 = {
32769
+ default: "gap-0",
32770
+ 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",
32771
+ 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"
32772
+ };
32773
+ const orientationClasses = {
32774
+ horizontal: "flex-row",
32775
+ 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"
32776
+ };
32777
+ const handleActionClick = (action) => {
32778
+ if (action.event) {
32779
+ eventBus.emit("UI:DISPATCH", { event: action.event });
32780
+ }
32781
+ if (action.navigatesTo) {
32782
+ eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
32783
+ }
32784
+ };
32785
+ const renderFormActions = () => {
32786
+ const buttons = [];
32787
+ if (secondary) {
32788
+ secondary.forEach((action, index) => {
32789
+ buttons.push(
32790
+ /* @__PURE__ */ jsx(
32791
+ Button,
32792
+ {
32793
+ type: action.actionType === "submit" ? "submit" : "button",
32794
+ variant: action.variant || "ghost",
32795
+ onClick: () => handleActionClick(action),
32796
+ children: action.label
32797
+ },
32798
+ `secondary-${index}`
32799
+ )
32800
+ );
32801
+ });
32802
+ }
32803
+ if (primary) {
32804
+ const isSubmit = primary.actionType === "submit";
32805
+ buttons.push(
32806
+ /* @__PURE__ */ jsx(
32807
+ Button,
32808
+ {
32809
+ type: isSubmit ? "submit" : "button",
32810
+ variant: primary.variant || "primary",
32811
+ onClick: () => handleActionClick(primary),
32812
+ "data-testid": isSubmit ? "form-submit" : void 0,
32813
+ children: primary.label
32814
+ },
32815
+ "primary"
32816
+ )
32817
+ );
32818
+ }
32819
+ return buttons;
32820
+ };
32821
+ const renderFilters = () => {
32822
+ if (!filters || filters.length === 0) return null;
32823
+ return filters.map((filter, index) => /* @__PURE__ */ jsx(
32824
+ Button,
32825
+ {
32826
+ variant: "ghost",
32827
+ onClick: () => {
32828
+ log8.debug("Filter clicked", { field: filter.field });
32829
+ },
32830
+ children: filter.label
32831
+ },
32832
+ `filter-${filter.field}-${index}`
32833
+ ));
32834
+ };
32835
+ return /* @__PURE__ */ jsx(
32836
+ "div",
32837
+ {
32838
+ className: cn(
32839
+ "inline-flex gap-2",
32840
+ variantClasses2[variant],
32841
+ orientationClasses[orientation],
32842
+ lookStyles5[look],
32843
+ className
32844
+ ),
32845
+ role: "group",
32846
+ children: children || renderFilters() || renderFormActions()
32847
+ }
32848
+ );
32849
+ };
32850
+ ButtonGroup.displayName = "ButtonGroup";
32851
+ }
32852
+ });
32506
32853
  function getOrCreateStore(query) {
32507
32854
  if (!queryStores.has(query)) {
32508
32855
  queryStores.set(query, {
@@ -35044,6 +35391,234 @@ var init_ProgressDots = __esm({
35044
35391
  ProgressDots.displayName = "ProgressDots";
35045
35392
  }
35046
35393
  });
35394
+ var MathCanvas;
35395
+ var init_MathCanvas = __esm({
35396
+ "components/learning/molecules/MathCanvas.tsx"() {
35397
+ "use client";
35398
+ init_atoms();
35399
+ init_Stack();
35400
+ init_LearningCanvas();
35401
+ MathCanvas = ({
35402
+ className,
35403
+ width = 600,
35404
+ height = 400,
35405
+ title,
35406
+ xMin = -10,
35407
+ xMax = 10,
35408
+ yMin = -10,
35409
+ yMax = 10,
35410
+ showAxes = true,
35411
+ showGrid = true,
35412
+ gridStep = 1,
35413
+ curves = [],
35414
+ points = [],
35415
+ vectors = [],
35416
+ shapes = [],
35417
+ interactive = false,
35418
+ animate = false,
35419
+ onShapeClick,
35420
+ isLoading,
35421
+ error
35422
+ }) => {
35423
+ const derivedShapes = useMemo(() => {
35424
+ const out = [];
35425
+ const margin = 24;
35426
+ const plotW = width - margin * 2;
35427
+ const plotH = height - margin * 2;
35428
+ const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
35429
+ const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
35430
+ if (showGrid) {
35431
+ for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
35432
+ const px = mapX(x);
35433
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#e5e7eb", lineWidth: 1 });
35434
+ }
35435
+ for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
35436
+ const py = mapY(y);
35437
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
35438
+ }
35439
+ }
35440
+ if (showAxes) {
35441
+ const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
35442
+ const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
35443
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
35444
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
35445
+ }
35446
+ for (const curve of curves) {
35447
+ if (!curve.samples || curve.samples.length < 2) continue;
35448
+ for (let i = 1; i < curve.samples.length; i++) {
35449
+ const a = curve.samples[i - 1];
35450
+ const b = curve.samples[i];
35451
+ if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
35452
+ out.push({
35453
+ type: "line",
35454
+ x1: mapX(a.x),
35455
+ y1: mapY(a.y),
35456
+ x2: mapX(b.x),
35457
+ y2: mapY(b.y),
35458
+ color: curve.color ?? "#2563eb",
35459
+ lineWidth: 2
35460
+ });
35461
+ }
35462
+ }
35463
+ for (const p2 of points) {
35464
+ if (p2.x < xMin || p2.x > xMax || p2.y < yMin || p2.y > yMax) continue;
35465
+ out.push({
35466
+ type: "circle",
35467
+ x: mapX(p2.x),
35468
+ y: mapY(p2.y),
35469
+ radius: p2.radius ?? 4,
35470
+ color: p2.color ?? "#dc2626",
35471
+ fill: p2.color ?? "#dc2626"
35472
+ });
35473
+ if (p2.label) {
35474
+ out.push({ type: "text", x: mapX(p2.x) + 8, y: mapY(p2.y) - 8, text: p2.label, color: "#111827", fontSize: 12 });
35475
+ }
35476
+ }
35477
+ for (const v of vectors) {
35478
+ if (v.x < xMin || v.x > xMax || v.y < yMin || v.y > yMax) continue;
35479
+ const x1 = mapX(v.x);
35480
+ const y1 = mapY(v.y);
35481
+ const x2 = mapX(v.x + v.vx);
35482
+ const y2 = mapY(v.y + v.vy);
35483
+ out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
35484
+ if (v.label) {
35485
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
35486
+ }
35487
+ }
35488
+ out.push(...shapes);
35489
+ return out;
35490
+ }, [width, height, xMin, xMax, yMin, yMax, showAxes, showGrid, gridStep, curves, points, vectors, shapes]);
35491
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
35492
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
35493
+ /* @__PURE__ */ jsx(
35494
+ LearningCanvas,
35495
+ {
35496
+ width,
35497
+ height,
35498
+ shapes: derivedShapes,
35499
+ interactive,
35500
+ animate,
35501
+ onShapeClick,
35502
+ isLoading,
35503
+ error
35504
+ }
35505
+ )
35506
+ ] }) });
35507
+ };
35508
+ }
35509
+ });
35510
+ var PhysicsCanvas;
35511
+ var init_PhysicsCanvas = __esm({
35512
+ "components/learning/molecules/PhysicsCanvas.tsx"() {
35513
+ "use client";
35514
+ init_atoms();
35515
+ init_Stack();
35516
+ init_LearningCanvas();
35517
+ PhysicsCanvas = ({
35518
+ className,
35519
+ width = 600,
35520
+ height = 400,
35521
+ title,
35522
+ backgroundColor,
35523
+ bodies = [],
35524
+ constraints = [],
35525
+ showVelocity = true,
35526
+ showForces = false,
35527
+ velocityScale = 20,
35528
+ forceScale = 20,
35529
+ shapes = [],
35530
+ interactive = false,
35531
+ animate = false,
35532
+ onShapeClick,
35533
+ isLoading,
35534
+ error
35535
+ }) => {
35536
+ const derivedShapes = useMemo(() => {
35537
+ const out = [];
35538
+ const bodyById = /* @__PURE__ */ new Map();
35539
+ for (const b of bodies) {
35540
+ if (b.id) bodyById.set(b.id, b);
35541
+ }
35542
+ for (const c of constraints) {
35543
+ const a = bodyById.get(c.from);
35544
+ const b = bodyById.get(c.to);
35545
+ if (!a || !b) continue;
35546
+ out.push({
35547
+ type: "line",
35548
+ x1: a.x,
35549
+ y1: a.y,
35550
+ x2: b.x,
35551
+ y2: b.y,
35552
+ color: c.color ?? "#9ca3af",
35553
+ lineWidth: 2
35554
+ });
35555
+ }
35556
+ for (const b of bodies) {
35557
+ out.push({
35558
+ type: "circle",
35559
+ x: b.x,
35560
+ y: b.y,
35561
+ radius: b.radius ?? 12,
35562
+ color: b.color ?? "#2563eb",
35563
+ fill: b.color ?? "#2563eb",
35564
+ id: b.id
35565
+ });
35566
+ if (b.label) {
35567
+ out.push({
35568
+ type: "text",
35569
+ x: b.x + (b.radius ?? 12) + 6,
35570
+ y: b.y - (b.radius ?? 12) - 6,
35571
+ text: b.label,
35572
+ color: "#111827",
35573
+ fontSize: 12
35574
+ });
35575
+ }
35576
+ if (showVelocity && b.vx != null && b.vy != null && (b.vx !== 0 || b.vy !== 0)) {
35577
+ out.push({
35578
+ type: "arrow",
35579
+ x1: b.x,
35580
+ y1: b.y,
35581
+ x2: b.x + b.vx * velocityScale,
35582
+ y2: b.y + b.vy * velocityScale,
35583
+ color: "#16a34a",
35584
+ lineWidth: 2
35585
+ });
35586
+ }
35587
+ if (showForces && b.fx != null && b.fy != null && (b.fx !== 0 || b.fy !== 0)) {
35588
+ out.push({
35589
+ type: "arrow",
35590
+ x1: b.x,
35591
+ y1: b.y,
35592
+ x2: b.x + b.fx * forceScale,
35593
+ y2: b.y + b.fy * forceScale,
35594
+ color: "#dc2626",
35595
+ lineWidth: 2
35596
+ });
35597
+ }
35598
+ }
35599
+ out.push(...shapes);
35600
+ return out;
35601
+ }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
35602
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
35603
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
35604
+ /* @__PURE__ */ jsx(
35605
+ LearningCanvas,
35606
+ {
35607
+ width,
35608
+ height,
35609
+ backgroundColor,
35610
+ shapes: derivedShapes,
35611
+ interactive,
35612
+ animate,
35613
+ onShapeClick,
35614
+ isLoading,
35615
+ error
35616
+ }
35617
+ )
35618
+ ] }) });
35619
+ };
35620
+ }
35621
+ });
35047
35622
  function resolveNodeColor(node, groups) {
35048
35623
  if (node.color) return node.color;
35049
35624
  if (node.group) {
@@ -35415,13 +35990,13 @@ var init_MapView = __esm({
35415
35990
  shadowSize: [41, 41]
35416
35991
  });
35417
35992
  L.Marker.prototype.options.icon = defaultIcon;
35418
- const { useEffect: useEffect75, useRef: useRef76, useCallback: useCallback117, useState: useState108 } = React107__default;
35993
+ const { useEffect: useEffect76, useRef: useRef77, useCallback: useCallback117, useState: useState108 } = React107__default;
35419
35994
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
35420
35995
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
35421
35996
  function MapUpdater({ centerLat, centerLng, zoom }) {
35422
35997
  const map = useMap();
35423
- const prevRef = useRef76({ centerLat, centerLng, zoom });
35424
- useEffect75(() => {
35998
+ const prevRef = useRef77({ centerLat, centerLng, zoom });
35999
+ useEffect76(() => {
35425
36000
  const prev = prevRef.current;
35426
36001
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
35427
36002
  map.setView([centerLat, centerLng], zoom);
@@ -35432,7 +36007,7 @@ var init_MapView = __esm({
35432
36007
  }
35433
36008
  function MapClickHandler({ onMapClick }) {
35434
36009
  const map = useMap();
35435
- useEffect75(() => {
36010
+ useEffect76(() => {
35436
36011
  if (!onMapClick) return;
35437
36012
  const handler = (e) => {
35438
36013
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -43873,7 +44448,7 @@ function getGroupColor(group, groups) {
43873
44448
  const idx = groups.indexOf(group);
43874
44449
  return GROUP_COLORS2[idx % GROUP_COLORS2.length];
43875
44450
  }
43876
- function resolveColor2(color, el) {
44451
+ function resolveColor3(color, el) {
43877
44452
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
43878
44453
  if (!m) return color;
43879
44454
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
@@ -44134,7 +44709,7 @@ var init_GraphCanvas = __esm({
44134
44709
  const w = logicalW;
44135
44710
  const h = height;
44136
44711
  const nodes = nodesRef.current;
44137
- const accentColor = resolveColor2("var(--color-accent)", canvas);
44712
+ const accentColor = resolveColor3("var(--color-accent)", canvas);
44138
44713
  const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
44139
44714
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
44140
44715
  ctx.clearRect(0, 0, w, h);
@@ -44173,7 +44748,7 @@ var init_GraphCanvas = __esm({
44173
44748
  ctx.globalAlpha = 1;
44174
44749
  for (const node of nodes) {
44175
44750
  const size = node.size || 8;
44176
- const color = resolveColor2(node.color || getGroupColor(node.group, groups), canvas);
44751
+ const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
44177
44752
  const isHovered = hoveredNode === node.id;
44178
44753
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
44179
44754
  ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
@@ -51052,6 +51627,7 @@ var init_component_registry_generated = __esm({
51052
51627
  init_BattleBoard();
51053
51628
  init_BattleTemplate();
51054
51629
  init_BehaviorView();
51630
+ init_BiologyCanvas();
51055
51631
  init_BloomQuizBlock();
51056
51632
  init_BoardgameBoard();
51057
51633
  init_BookChapterView();
@@ -51063,7 +51639,6 @@ var init_component_registry_generated = __esm({
51063
51639
  init_BranchingLogicBuilder();
51064
51640
  init_Breadcrumb();
51065
51641
  init_BuilderBoard();
51066
- init_ButtonGroup();
51067
51642
  init_CTABanner();
51068
51643
  init_CalendarGrid();
51069
51644
  init_Canvas2D();
@@ -51082,6 +51657,7 @@ var init_component_registry_generated = __esm({
51082
51657
  init_ChartLegend();
51083
51658
  init_ChatBar();
51084
51659
  init_Checkbox();
51660
+ init_ChemistryCanvas();
51085
51661
  init_ChoiceButton();
51086
51662
  init_CityBuilderBoard();
51087
51663
  init_CityBuilderTemplate();
@@ -51140,9 +51716,9 @@ var init_component_registry_generated = __esm({
51140
51716
  init_FlipContainer();
51141
51717
  init_FloatingActionButton();
51142
51718
  init_Form();
51719
+ init_FormSection();
51143
51720
  init_FormField();
51144
51721
  init_FormSectionHeader();
51145
- init_GameAudioProvider();
51146
51722
  init_GameAudioToggle();
51147
51723
  init_GameCard();
51148
51724
  init_GameHud();
@@ -51173,6 +51749,7 @@ var init_component_registry_generated = __esm({
51173
51749
  init_JazariStateMachine();
51174
51750
  init_LandingPageTemplate();
51175
51751
  init_LawReferenceTooltip();
51752
+ init_LearningCanvas();
51176
51753
  init_Lightbox();
51177
51754
  init_LikertScale();
51178
51755
  init_LineChart();
@@ -51181,9 +51758,11 @@ var init_component_registry_generated = __esm({
51181
51758
  init_MapView();
51182
51759
  init_MarkdownContent();
51183
51760
  init_MarketingFooter();
51761
+ init_MarketingStatCard();
51184
51762
  init_MasterDetail();
51185
51763
  init_MasterDetailLayout();
51186
51764
  init_MatchPuzzleBoard();
51765
+ init_MathCanvas();
51187
51766
  init_MatrixQuestion();
51188
51767
  init_MediaGallery();
51189
51768
  init_Menu();
@@ -51202,6 +51781,7 @@ var init_component_registry_generated = __esm({
51202
51781
  init_PageHeader();
51203
51782
  init_Pagination();
51204
51783
  init_PatternTile();
51784
+ init_PhysicsCanvas();
51205
51785
  init_PinballBoard();
51206
51786
  init_PirateBoard();
51207
51787
  init_PlatformerBoard();
@@ -51378,6 +51958,7 @@ var init_component_registry_generated = __esm({
51378
51958
  "BattleBoard": BattleBoard,
51379
51959
  "BattleTemplate": BattleTemplate,
51380
51960
  "BehaviorView": BehaviorView,
51961
+ "BiologyCanvas": BiologyCanvas,
51381
51962
  "BloomQuizBlock": BloomQuizBlock,
51382
51963
  "BoardgameBoard": BoardgameBoard,
51383
51964
  "BookChapterView": BookChapterView,
@@ -51391,7 +51972,6 @@ var init_component_registry_generated = __esm({
51391
51972
  "Breadcrumb": Breadcrumb,
51392
51973
  "BuilderBoard": BuilderBoard,
51393
51974
  "Button": ButtonPattern,
51394
- "ButtonGroup": ButtonGroup,
51395
51975
  "ButtonPattern": ButtonPattern,
51396
51976
  "CTABanner": CTABanner,
51397
51977
  "CalendarGrid": CalendarGrid,
@@ -51411,6 +51991,7 @@ var init_component_registry_generated = __esm({
51411
51991
  "ChartLegend": ChartLegend,
51412
51992
  "ChatBar": ChatBar,
51413
51993
  "Checkbox": Checkbox,
51994
+ "ChemistryCanvas": ChemistryCanvas,
51414
51995
  "ChoiceButton": ChoiceButton,
51415
51996
  "CityBuilderBoard": CityBuilderBoard,
51416
51997
  "CityBuilderTemplate": CityBuilderTemplate,
@@ -51471,9 +52052,10 @@ var init_component_registry_generated = __esm({
51471
52052
  "FlipContainer": FlipContainer,
51472
52053
  "FloatingActionButton": FloatingActionButton,
51473
52054
  "Form": Form,
52055
+ "FormActions": FormActions,
51474
52056
  "FormField": FormField,
52057
+ "FormLayout": FormLayout,
51475
52058
  "FormSectionHeader": FormSectionHeader,
51476
- "GameAudioProvider": GameAudioProvider,
51477
52059
  "GameAudioToggle": GameAudioToggle,
51478
52060
  "GameBoard3D": GameBoard3D,
51479
52061
  "GameCanvas3D": GameCanvas3D,
@@ -51511,6 +52093,7 @@ var init_component_registry_generated = __esm({
51511
52093
  "LabelPattern": LabelPattern,
51512
52094
  "LandingPageTemplate": LandingPageTemplate,
51513
52095
  "LawReferenceTooltip": LawReferenceTooltip,
52096
+ "LearningCanvas": LearningCanvas,
51514
52097
  "Lightbox": Lightbox,
51515
52098
  "LikertScale": LikertScale,
51516
52099
  "LineChart": LineChart2,
@@ -51519,9 +52102,11 @@ var init_component_registry_generated = __esm({
51519
52102
  "MapView": MapView,
51520
52103
  "MarkdownContent": MarkdownContent,
51521
52104
  "MarketingFooter": MarketingFooter,
52105
+ "MarketingStatCard": MarketingStatCard,
51522
52106
  "MasterDetail": MasterDetail,
51523
52107
  "MasterDetailLayout": MasterDetailLayout,
51524
52108
  "MatchPuzzleBoard": MatchPuzzleBoard,
52109
+ "MathCanvas": MathCanvas,
51525
52110
  "MatrixQuestion": MatrixQuestion,
51526
52111
  "MediaGallery": MediaGallery,
51527
52112
  "Menu": Menu,
@@ -51540,6 +52125,7 @@ var init_component_registry_generated = __esm({
51540
52125
  "PageHeader": PageHeader,
51541
52126
  "Pagination": Pagination,
51542
52127
  "PatternTile": PatternTile,
52128
+ "PhysicsCanvas": PhysicsCanvas,
51543
52129
  "PinballBoard": PinballBoard,
51544
52130
  "PirateBoard": PirateBoard,
51545
52131
  "PlatformerBoard": PlatformerBoard,