@almadar/ui 5.77.0 → 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.
package/dist/avl/index.js CHANGED
@@ -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"() {
@@ -23513,6 +23601,315 @@ var init_molecules = __esm({
23513
23601
  init_puzzleObject();
23514
23602
  }
23515
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
+ });
23516
23913
 
23517
23914
  // components/core/atoms/index.ts
23518
23915
  var init_atoms = __esm({
@@ -24998,91 +25395,6 @@ var init_ComponentPatterns = __esm({
24998
25395
  AlertPattern.displayName = "AlertPattern";
24999
25396
  }
25000
25397
  });
25001
- function parseValue(value) {
25002
- if (value === "" || value == null) return { num: 0, prefix: "", suffix: "", decimals: 0 };
25003
- const match = String(value).match(/^([^0-9]*)([0-9]+(?:\.[0-9]+)?)(.*)$/);
25004
- if (!match) {
25005
- return { num: 0, prefix: "", suffix: String(value), decimals: 0 };
25006
- }
25007
- const numStr = match[2];
25008
- const decimalIdx = numStr.indexOf(".");
25009
- const decimals = decimalIdx >= 0 ? numStr.length - decimalIdx - 1 : 0;
25010
- return {
25011
- prefix: match[1],
25012
- num: parseFloat(numStr),
25013
- suffix: match[3],
25014
- decimals
25015
- };
25016
- }
25017
- var AnimatedCounter;
25018
- var init_AnimatedCounter = __esm({
25019
- "components/core/molecules/AnimatedCounter.tsx"() {
25020
- "use client";
25021
- init_cn();
25022
- init_Box();
25023
- init_Typography();
25024
- AnimatedCounter = ({
25025
- value,
25026
- label,
25027
- duration = 1500,
25028
- className
25029
- }) => {
25030
- const ref = useRef(null);
25031
- const [displayValue, setDisplayValue] = useState("0");
25032
- const [hasAnimated, setHasAnimated] = useState(false);
25033
- const animate = useCallback(() => {
25034
- const { num: num2, prefix, suffix, decimals } = parseValue(value);
25035
- if (num2 === 0) {
25036
- setDisplayValue(String(value));
25037
- return;
25038
- }
25039
- const startTime = performance.now();
25040
- const tick = (now2) => {
25041
- const elapsed = now2 - startTime;
25042
- const progress = Math.min(elapsed / duration, 1);
25043
- const eased = 1 - Math.pow(1 - progress, 3);
25044
- const current = eased * num2;
25045
- setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
25046
- if (progress < 1) {
25047
- requestAnimationFrame(tick);
25048
- } else {
25049
- setDisplayValue(String(value));
25050
- }
25051
- };
25052
- requestAnimationFrame(tick);
25053
- }, [value, duration]);
25054
- useEffect(() => {
25055
- if (hasAnimated) return;
25056
- const el = ref.current;
25057
- if (!el) return;
25058
- const observer2 = new IntersectionObserver(
25059
- (entries) => {
25060
- if (entries[0].isIntersecting) {
25061
- setHasAnimated(true);
25062
- animate();
25063
- observer2.disconnect();
25064
- }
25065
- },
25066
- { threshold: 0.3 }
25067
- );
25068
- observer2.observe(el);
25069
- return () => observer2.disconnect();
25070
- }, [hasAnimated, animate]);
25071
- return /* @__PURE__ */ jsxs(Box, { ref, className: cn("flex flex-col items-center gap-1 p-4", className), children: [
25072
- /* @__PURE__ */ jsx(
25073
- Typography,
25074
- {
25075
- variant: "h2",
25076
- className: "text-primary font-bold tabular-nums",
25077
- children: hasAnimated ? displayValue : "0"
25078
- }
25079
- ),
25080
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "muted", className: "text-center", children: label })
25081
- ] });
25082
- };
25083
- AnimatedCounter.displayName = "AnimatedCounter";
25084
- }
25085
- });
25086
25398
  var AuthLayout;
25087
25399
  var init_AuthLayout = __esm({
25088
25400
  "components/marketing/templates/AuthLayout.tsx"() {
@@ -25224,6 +25536,103 @@ var init_AuthLayout = __esm({
25224
25536
  AuthLayout.displayName = "AuthLayout";
25225
25537
  }
25226
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
+ });
25227
25636
 
25228
25637
  // node_modules/katex/dist/katex.min.css
25229
25638
  var init_katex_min = __esm({
@@ -31713,6 +32122,122 @@ var init_ChatBar = __esm({
31713
32122
  ChatBar.displayName = "ChatBar";
31714
32123
  }
31715
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
+ });
31716
32241
  var CodeRunnerPanel;
31717
32242
  var init_CodeRunnerPanel = __esm({
31718
32243
  "components/core/organisms/CodeRunnerPanel.tsx"() {
@@ -37410,6 +37935,234 @@ var init_ProgressDots = __esm({
37410
37935
  ProgressDots.displayName = "ProgressDots";
37411
37936
  }
37412
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
+ });
37413
38166
  function resolveNodeColor(node, groups) {
37414
38167
  if (node.color) return node.color;
37415
38168
  if (node.group) {
@@ -37781,13 +38534,13 @@ var init_MapView = __esm({
37781
38534
  shadowSize: [41, 41]
37782
38535
  });
37783
38536
  L.Marker.prototype.options.icon = defaultIcon;
37784
- const { useEffect: useEffect78, useRef: useRef78, useCallback: useCallback118, useState: useState115 } = React114__default;
38537
+ const { useEffect: useEffect79, useRef: useRef79, useCallback: useCallback118, useState: useState115 } = React114__default;
37785
38538
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
37786
38539
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
37787
38540
  function MapUpdater({ centerLat, centerLng, zoom }) {
37788
38541
  const map = useMap();
37789
- const prevRef = useRef78({ centerLat, centerLng, zoom });
37790
- useEffect78(() => {
38542
+ const prevRef = useRef79({ centerLat, centerLng, zoom });
38543
+ useEffect79(() => {
37791
38544
  const prev = prevRef.current;
37792
38545
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
37793
38546
  map.setView([centerLat, centerLng], zoom);
@@ -37798,7 +38551,7 @@ var init_MapView = __esm({
37798
38551
  }
37799
38552
  function MapClickHandler({ onMapClick }) {
37800
38553
  const map = useMap();
37801
- useEffect78(() => {
38554
+ useEffect79(() => {
37802
38555
  if (!onMapClick) return;
37803
38556
  const handler = (e) => {
37804
38557
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -45830,7 +46583,7 @@ function getGroupColor(group, groups) {
45830
46583
  const idx = groups.indexOf(group);
45831
46584
  return GROUP_COLORS2[idx % GROUP_COLORS2.length];
45832
46585
  }
45833
- function resolveColor2(color, el) {
46586
+ function resolveColor3(color, el) {
45834
46587
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
45835
46588
  if (!m) return color;
45836
46589
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
@@ -46091,7 +46844,7 @@ var init_GraphCanvas = __esm({
46091
46844
  const w = logicalW;
46092
46845
  const h = height;
46093
46846
  const nodes = nodesRef.current;
46094
- const accentColor = resolveColor2("var(--color-accent)", canvas);
46847
+ const accentColor = resolveColor3("var(--color-accent)", canvas);
46095
46848
  const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
46096
46849
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
46097
46850
  ctx.clearRect(0, 0, w, h);
@@ -46130,7 +46883,7 @@ var init_GraphCanvas = __esm({
46130
46883
  ctx.globalAlpha = 1;
46131
46884
  for (const node of nodes) {
46132
46885
  const size = node.size || 8;
46133
- const color = resolveColor2(node.color || getGroupColor(node.group, groups), canvas);
46886
+ const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
46134
46887
  const isHovered = hoveredNode === node.id;
46135
46888
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
46136
46889
  ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
@@ -53028,6 +53781,7 @@ var init_component_registry_generated = __esm({
53028
53781
  init_BattleBoard();
53029
53782
  init_BattleTemplate();
53030
53783
  init_BehaviorView();
53784
+ init_BiologyCanvas();
53031
53785
  init_BloomQuizBlock();
53032
53786
  init_BoardgameBoard();
53033
53787
  init_BookChapterView();
@@ -53057,6 +53811,7 @@ var init_component_registry_generated = __esm({
53057
53811
  init_ChartLegend();
53058
53812
  init_ChatBar();
53059
53813
  init_Checkbox();
53814
+ init_ChemistryCanvas();
53060
53815
  init_ChoiceButton();
53061
53816
  init_CityBuilderBoard();
53062
53817
  init_CityBuilderTemplate();
@@ -53148,6 +53903,7 @@ var init_component_registry_generated = __esm({
53148
53903
  init_JazariStateMachine();
53149
53904
  init_LandingPageTemplate();
53150
53905
  init_LawReferenceTooltip();
53906
+ init_LearningCanvas();
53151
53907
  init_Lightbox();
53152
53908
  init_LikertScale();
53153
53909
  init_LineChart();
@@ -53156,9 +53912,11 @@ var init_component_registry_generated = __esm({
53156
53912
  init_MapView();
53157
53913
  init_MarkdownContent();
53158
53914
  init_MarketingFooter();
53915
+ init_MarketingStatCard();
53159
53916
  init_MasterDetail();
53160
53917
  init_MasterDetailLayout();
53161
53918
  init_MatchPuzzleBoard();
53919
+ init_MathCanvas();
53162
53920
  init_MatrixQuestion();
53163
53921
  init_MediaGallery();
53164
53922
  init_Menu();
@@ -53177,6 +53935,7 @@ var init_component_registry_generated = __esm({
53177
53935
  init_PageHeader();
53178
53936
  init_Pagination();
53179
53937
  init_PatternTile();
53938
+ init_PhysicsCanvas();
53180
53939
  init_PinballBoard();
53181
53940
  init_PirateBoard();
53182
53941
  init_PlatformerBoard();
@@ -53353,6 +54112,7 @@ var init_component_registry_generated = __esm({
53353
54112
  "BattleBoard": BattleBoard,
53354
54113
  "BattleTemplate": BattleTemplate,
53355
54114
  "BehaviorView": BehaviorView,
54115
+ "BiologyCanvas": BiologyCanvas,
53356
54116
  "BloomQuizBlock": BloomQuizBlock,
53357
54117
  "BoardgameBoard": BoardgameBoard,
53358
54118
  "BookChapterView": BookChapterView,
@@ -53385,6 +54145,7 @@ var init_component_registry_generated = __esm({
53385
54145
  "ChartLegend": ChartLegend,
53386
54146
  "ChatBar": ChatBar,
53387
54147
  "Checkbox": Checkbox,
54148
+ "ChemistryCanvas": ChemistryCanvas,
53388
54149
  "ChoiceButton": ChoiceButton,
53389
54150
  "CityBuilderBoard": CityBuilderBoard,
53390
54151
  "CityBuilderTemplate": CityBuilderTemplate,
@@ -53486,6 +54247,7 @@ var init_component_registry_generated = __esm({
53486
54247
  "LabelPattern": LabelPattern,
53487
54248
  "LandingPageTemplate": LandingPageTemplate,
53488
54249
  "LawReferenceTooltip": LawReferenceTooltip,
54250
+ "LearningCanvas": LearningCanvas,
53489
54251
  "Lightbox": Lightbox,
53490
54252
  "LikertScale": LikertScale,
53491
54253
  "LineChart": LineChart2,
@@ -53494,9 +54256,11 @@ var init_component_registry_generated = __esm({
53494
54256
  "MapView": MapView,
53495
54257
  "MarkdownContent": MarkdownContent,
53496
54258
  "MarketingFooter": MarketingFooter,
54259
+ "MarketingStatCard": MarketingStatCard,
53497
54260
  "MasterDetail": MasterDetail,
53498
54261
  "MasterDetailLayout": MasterDetailLayout,
53499
54262
  "MatchPuzzleBoard": MatchPuzzleBoard,
54263
+ "MathCanvas": MathCanvas,
53500
54264
  "MatrixQuestion": MatrixQuestion,
53501
54265
  "MediaGallery": MediaGallery,
53502
54266
  "Menu": Menu,
@@ -53515,6 +54279,7 @@ var init_component_registry_generated = __esm({
53515
54279
  "PageHeader": PageHeader,
53516
54280
  "Pagination": Pagination,
53517
54281
  "PatternTile": PatternTile,
54282
+ "PhysicsCanvas": PhysicsCanvas,
53518
54283
  "PinballBoard": PinballBoard,
53519
54284
  "PirateBoard": PirateBoard,
53520
54285
  "PlatformerBoard": PlatformerBoard,