@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.
@@ -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"() {
@@ -19759,6 +19847,315 @@ var init_molecules = __esm({
19759
19847
  init_puzzleObject();
19760
19848
  }
19761
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
+ });
19762
20159
 
19763
20160
  // components/core/atoms/index.ts
19764
20161
  var init_atoms = __esm({
@@ -21627,91 +22024,6 @@ var init_ComponentPatterns = __esm({
21627
22024
  AlertPattern.displayName = "AlertPattern";
21628
22025
  }
21629
22026
  });
21630
- function parseValue(value) {
21631
- if (value === "" || value == null) return { num: 0, prefix: "", suffix: "", decimals: 0 };
21632
- const match = String(value).match(/^([^0-9]*)([0-9]+(?:\.[0-9]+)?)(.*)$/);
21633
- if (!match) {
21634
- return { num: 0, prefix: "", suffix: String(value), decimals: 0 };
21635
- }
21636
- const numStr = match[2];
21637
- const decimalIdx = numStr.indexOf(".");
21638
- const decimals = decimalIdx >= 0 ? numStr.length - decimalIdx - 1 : 0;
21639
- return {
21640
- prefix: match[1],
21641
- num: parseFloat(numStr),
21642
- suffix: match[3],
21643
- decimals
21644
- };
21645
- }
21646
- var AnimatedCounter;
21647
- var init_AnimatedCounter = __esm({
21648
- "components/core/molecules/AnimatedCounter.tsx"() {
21649
- "use client";
21650
- init_cn();
21651
- init_Box();
21652
- init_Typography();
21653
- AnimatedCounter = ({
21654
- value,
21655
- label,
21656
- duration = 1500,
21657
- className
21658
- }) => {
21659
- const ref = useRef(null);
21660
- const [displayValue, setDisplayValue] = useState("0");
21661
- const [hasAnimated, setHasAnimated] = useState(false);
21662
- const animate = useCallback(() => {
21663
- const { num: num2, prefix, suffix, decimals } = parseValue(value);
21664
- if (num2 === 0) {
21665
- setDisplayValue(String(value));
21666
- return;
21667
- }
21668
- const startTime = performance.now();
21669
- const tick = (now) => {
21670
- const elapsed = now - startTime;
21671
- const progress = Math.min(elapsed / duration, 1);
21672
- const eased = 1 - Math.pow(1 - progress, 3);
21673
- const current = eased * num2;
21674
- setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
21675
- if (progress < 1) {
21676
- requestAnimationFrame(tick);
21677
- } else {
21678
- setDisplayValue(String(value));
21679
- }
21680
- };
21681
- requestAnimationFrame(tick);
21682
- }, [value, duration]);
21683
- useEffect(() => {
21684
- if (hasAnimated) return;
21685
- const el = ref.current;
21686
- if (!el) return;
21687
- const observer2 = new IntersectionObserver(
21688
- (entries) => {
21689
- if (entries[0].isIntersecting) {
21690
- setHasAnimated(true);
21691
- animate();
21692
- observer2.disconnect();
21693
- }
21694
- },
21695
- { threshold: 0.3 }
21696
- );
21697
- observer2.observe(el);
21698
- return () => observer2.disconnect();
21699
- }, [hasAnimated, animate]);
21700
- return /* @__PURE__ */ jsxs(Box, { ref, className: cn("flex flex-col items-center gap-1 p-4", className), children: [
21701
- /* @__PURE__ */ jsx(
21702
- Typography,
21703
- {
21704
- variant: "h2",
21705
- className: "text-primary font-bold tabular-nums",
21706
- children: hasAnimated ? displayValue : "0"
21707
- }
21708
- ),
21709
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "muted", className: "text-center", children: label })
21710
- ] });
21711
- };
21712
- AnimatedCounter.displayName = "AnimatedCounter";
21713
- }
21714
- });
21715
22027
  var AuthLayout;
21716
22028
  var init_AuthLayout = __esm({
21717
22029
  "components/marketing/templates/AuthLayout.tsx"() {
@@ -22680,6 +22992,103 @@ var init_BehaviorView = __esm({
22680
22992
  BehaviorView.displayName = "BehaviorView";
22681
22993
  }
22682
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
+ });
22683
23092
 
22684
23093
  // node_modules/katex/dist/katex.min.css
22685
23094
  var init_katex_min = __esm({
@@ -29169,6 +29578,122 @@ var init_ChatBar = __esm({
29169
29578
  ChatBar.displayName = "ChatBar";
29170
29579
  }
29171
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
+ });
29172
29697
  var CodeRunnerPanel;
29173
29698
  var init_CodeRunnerPanel = __esm({
29174
29699
  "components/core/organisms/CodeRunnerPanel.tsx"() {
@@ -34866,6 +35391,234 @@ var init_ProgressDots = __esm({
34866
35391
  ProgressDots.displayName = "ProgressDots";
34867
35392
  }
34868
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
+ });
34869
35622
  function resolveNodeColor(node, groups) {
34870
35623
  if (node.color) return node.color;
34871
35624
  if (node.group) {
@@ -35237,13 +35990,13 @@ var init_MapView = __esm({
35237
35990
  shadowSize: [41, 41]
35238
35991
  });
35239
35992
  L.Marker.prototype.options.icon = defaultIcon;
35240
- const { useEffect: useEffect75, useRef: useRef76, useCallback: useCallback117, useState: useState108 } = React107__default;
35993
+ const { useEffect: useEffect76, useRef: useRef77, useCallback: useCallback117, useState: useState108 } = React107__default;
35241
35994
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
35242
35995
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
35243
35996
  function MapUpdater({ centerLat, centerLng, zoom }) {
35244
35997
  const map = useMap();
35245
- const prevRef = useRef76({ centerLat, centerLng, zoom });
35246
- useEffect75(() => {
35998
+ const prevRef = useRef77({ centerLat, centerLng, zoom });
35999
+ useEffect76(() => {
35247
36000
  const prev = prevRef.current;
35248
36001
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
35249
36002
  map.setView([centerLat, centerLng], zoom);
@@ -35254,7 +36007,7 @@ var init_MapView = __esm({
35254
36007
  }
35255
36008
  function MapClickHandler({ onMapClick }) {
35256
36009
  const map = useMap();
35257
- useEffect75(() => {
36010
+ useEffect76(() => {
35258
36011
  if (!onMapClick) return;
35259
36012
  const handler = (e) => {
35260
36013
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -43695,7 +44448,7 @@ function getGroupColor(group, groups) {
43695
44448
  const idx = groups.indexOf(group);
43696
44449
  return GROUP_COLORS2[idx % GROUP_COLORS2.length];
43697
44450
  }
43698
- function resolveColor2(color, el) {
44451
+ function resolveColor3(color, el) {
43699
44452
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
43700
44453
  if (!m) return color;
43701
44454
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
@@ -43956,7 +44709,7 @@ var init_GraphCanvas = __esm({
43956
44709
  const w = logicalW;
43957
44710
  const h = height;
43958
44711
  const nodes = nodesRef.current;
43959
- const accentColor = resolveColor2("var(--color-accent)", canvas);
44712
+ const accentColor = resolveColor3("var(--color-accent)", canvas);
43960
44713
  const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
43961
44714
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
43962
44715
  ctx.clearRect(0, 0, w, h);
@@ -43995,7 +44748,7 @@ var init_GraphCanvas = __esm({
43995
44748
  ctx.globalAlpha = 1;
43996
44749
  for (const node of nodes) {
43997
44750
  const size = node.size || 8;
43998
- const color = resolveColor2(node.color || getGroupColor(node.group, groups), canvas);
44751
+ const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
43999
44752
  const isHovered = hoveredNode === node.id;
44000
44753
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
44001
44754
  ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
@@ -50874,6 +51627,7 @@ var init_component_registry_generated = __esm({
50874
51627
  init_BattleBoard();
50875
51628
  init_BattleTemplate();
50876
51629
  init_BehaviorView();
51630
+ init_BiologyCanvas();
50877
51631
  init_BloomQuizBlock();
50878
51632
  init_BoardgameBoard();
50879
51633
  init_BookChapterView();
@@ -50903,6 +51657,7 @@ var init_component_registry_generated = __esm({
50903
51657
  init_ChartLegend();
50904
51658
  init_ChatBar();
50905
51659
  init_Checkbox();
51660
+ init_ChemistryCanvas();
50906
51661
  init_ChoiceButton();
50907
51662
  init_CityBuilderBoard();
50908
51663
  init_CityBuilderTemplate();
@@ -50994,6 +51749,7 @@ var init_component_registry_generated = __esm({
50994
51749
  init_JazariStateMachine();
50995
51750
  init_LandingPageTemplate();
50996
51751
  init_LawReferenceTooltip();
51752
+ init_LearningCanvas();
50997
51753
  init_Lightbox();
50998
51754
  init_LikertScale();
50999
51755
  init_LineChart();
@@ -51002,9 +51758,11 @@ var init_component_registry_generated = __esm({
51002
51758
  init_MapView();
51003
51759
  init_MarkdownContent();
51004
51760
  init_MarketingFooter();
51761
+ init_MarketingStatCard();
51005
51762
  init_MasterDetail();
51006
51763
  init_MasterDetailLayout();
51007
51764
  init_MatchPuzzleBoard();
51765
+ init_MathCanvas();
51008
51766
  init_MatrixQuestion();
51009
51767
  init_MediaGallery();
51010
51768
  init_Menu();
@@ -51023,6 +51781,7 @@ var init_component_registry_generated = __esm({
51023
51781
  init_PageHeader();
51024
51782
  init_Pagination();
51025
51783
  init_PatternTile();
51784
+ init_PhysicsCanvas();
51026
51785
  init_PinballBoard();
51027
51786
  init_PirateBoard();
51028
51787
  init_PlatformerBoard();
@@ -51199,6 +51958,7 @@ var init_component_registry_generated = __esm({
51199
51958
  "BattleBoard": BattleBoard,
51200
51959
  "BattleTemplate": BattleTemplate,
51201
51960
  "BehaviorView": BehaviorView,
51961
+ "BiologyCanvas": BiologyCanvas,
51202
51962
  "BloomQuizBlock": BloomQuizBlock,
51203
51963
  "BoardgameBoard": BoardgameBoard,
51204
51964
  "BookChapterView": BookChapterView,
@@ -51231,6 +51991,7 @@ var init_component_registry_generated = __esm({
51231
51991
  "ChartLegend": ChartLegend,
51232
51992
  "ChatBar": ChatBar,
51233
51993
  "Checkbox": Checkbox,
51994
+ "ChemistryCanvas": ChemistryCanvas,
51234
51995
  "ChoiceButton": ChoiceButton,
51235
51996
  "CityBuilderBoard": CityBuilderBoard,
51236
51997
  "CityBuilderTemplate": CityBuilderTemplate,
@@ -51332,6 +52093,7 @@ var init_component_registry_generated = __esm({
51332
52093
  "LabelPattern": LabelPattern,
51333
52094
  "LandingPageTemplate": LandingPageTemplate,
51334
52095
  "LawReferenceTooltip": LawReferenceTooltip,
52096
+ "LearningCanvas": LearningCanvas,
51335
52097
  "Lightbox": Lightbox,
51336
52098
  "LikertScale": LikertScale,
51337
52099
  "LineChart": LineChart2,
@@ -51340,9 +52102,11 @@ var init_component_registry_generated = __esm({
51340
52102
  "MapView": MapView,
51341
52103
  "MarkdownContent": MarkdownContent,
51342
52104
  "MarketingFooter": MarketingFooter,
52105
+ "MarketingStatCard": MarketingStatCard,
51343
52106
  "MasterDetail": MasterDetail,
51344
52107
  "MasterDetailLayout": MasterDetailLayout,
51345
52108
  "MatchPuzzleBoard": MatchPuzzleBoard,
52109
+ "MathCanvas": MathCanvas,
51346
52110
  "MatrixQuestion": MatrixQuestion,
51347
52111
  "MediaGallery": MediaGallery,
51348
52112
  "Menu": Menu,
@@ -51361,6 +52125,7 @@ var init_component_registry_generated = __esm({
51361
52125
  "PageHeader": PageHeader,
51362
52126
  "Pagination": Pagination,
51363
52127
  "PatternTile": PatternTile,
52128
+ "PhysicsCanvas": PhysicsCanvas,
51364
52129
  "PinballBoard": PinballBoard,
51365
52130
  "PirateBoard": PirateBoard,
51366
52131
  "PlatformerBoard": PlatformerBoard,