@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.
@@ -6180,6 +6180,66 @@ var init_RangeSlider = __esm({
6180
6180
  RangeSlider.displayName = "RangeSlider";
6181
6181
  }
6182
6182
  });
6183
+ function easeOut(t) {
6184
+ return t * (2 - t);
6185
+ }
6186
+ var AnimatedCounter;
6187
+ var init_AnimatedCounter = __esm({
6188
+ "components/marketing/atoms/AnimatedCounter.tsx"() {
6189
+ "use client";
6190
+ init_cn();
6191
+ init_Typography();
6192
+ AnimatedCounter = ({
6193
+ value: rawValue,
6194
+ duration = 600,
6195
+ prefix,
6196
+ suffix,
6197
+ className
6198
+ }) => {
6199
+ const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
6200
+ const value = !Number.isNaN(numericRaw) ? numericRaw : 0;
6201
+ const [displayValue, setDisplayValue] = useState(value);
6202
+ const previousValueRef = useRef(value);
6203
+ const animationFrameRef = useRef(null);
6204
+ useEffect(() => {
6205
+ const from = previousValueRef.current;
6206
+ const to = value;
6207
+ previousValueRef.current = value;
6208
+ if (from === to) {
6209
+ setDisplayValue(to);
6210
+ return;
6211
+ }
6212
+ const startTime = performance.now();
6213
+ const diff = to - from;
6214
+ function animate(currentTime) {
6215
+ const elapsed = currentTime - startTime;
6216
+ const progress = Math.min(elapsed / duration, 1);
6217
+ const easedProgress = easeOut(progress);
6218
+ setDisplayValue(from + diff * easedProgress);
6219
+ if (progress < 1) {
6220
+ animationFrameRef.current = requestAnimationFrame(animate);
6221
+ } else {
6222
+ setDisplayValue(to);
6223
+ }
6224
+ }
6225
+ animationFrameRef.current = requestAnimationFrame(animate);
6226
+ return () => {
6227
+ if (animationFrameRef.current !== null) {
6228
+ cancelAnimationFrame(animationFrameRef.current);
6229
+ }
6230
+ };
6231
+ }, [value, duration]);
6232
+ const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
6233
+ const formattedValue = displayValue.toFixed(decimalPlaces);
6234
+ return /* @__PURE__ */ jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
6235
+ prefix,
6236
+ formattedValue,
6237
+ suffix
6238
+ ] });
6239
+ };
6240
+ AnimatedCounter.displayName = "AnimatedCounter";
6241
+ }
6242
+ });
6183
6243
  var InfiniteScrollSentinel;
6184
6244
  var init_InfiniteScrollSentinel = __esm({
6185
6245
  "components/core/atoms/InfiniteScrollSentinel.tsx"() {
@@ -6441,6 +6501,34 @@ var init_SectionHeader = __esm({
6441
6501
  SectionHeader.displayName = "SectionHeader";
6442
6502
  }
6443
6503
  });
6504
+ var sizeClasses6, MarketingStatCard;
6505
+ var init_MarketingStatCard = __esm({
6506
+ "components/marketing/atoms/MarketingStatCard.tsx"() {
6507
+ init_cn();
6508
+ init_Stack();
6509
+ init_Typography();
6510
+ sizeClasses6 = {
6511
+ sm: "text-2xl",
6512
+ md: "text-4xl",
6513
+ lg: "text-5xl"
6514
+ };
6515
+ MarketingStatCard = ({ value, label, size = "md", className }) => {
6516
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", align: "center", className: cn(className), children: [
6517
+ /* @__PURE__ */ jsx(
6518
+ Typography,
6519
+ {
6520
+ weight: "bold",
6521
+ color: "primary",
6522
+ className: cn(sizeClasses6[size]),
6523
+ children: value
6524
+ }
6525
+ ),
6526
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: label })
6527
+ ] });
6528
+ };
6529
+ MarketingStatCard.displayName = "MarketingStatCard";
6530
+ }
6531
+ });
6444
6532
  var backgroundClasses, paddingClasses, ContentSection;
6445
6533
  var init_ContentSection = __esm({
6446
6534
  "components/marketing/atoms/ContentSection.tsx"() {
@@ -20106,6 +20194,315 @@ var init_molecules = __esm({
20106
20194
  init_puzzleObject();
20107
20195
  }
20108
20196
  });
20197
+ function resolveColor2(color, ctx, fallback) {
20198
+ if (!color) return fallback;
20199
+ if (color.startsWith("var(")) {
20200
+ ctx.canvas.style;
20201
+ const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color);
20202
+ if (m) {
20203
+ const computed = getComputedStyle(ctx.canvas).getPropertyValue(m[1]).trim();
20204
+ return computed || m[2]?.trim() || fallback;
20205
+ }
20206
+ }
20207
+ return color;
20208
+ }
20209
+ function shapeBounds(shape) {
20210
+ switch (shape.type) {
20211
+ case "line":
20212
+ case "arrow":
20213
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) return null;
20214
+ return {
20215
+ x: Math.min(shape.x1, shape.x2) - 6,
20216
+ y: Math.min(shape.y1, shape.y2) - 6,
20217
+ w: Math.abs(shape.x2 - shape.x1) + 12,
20218
+ h: Math.abs(shape.y2 - shape.y1) + 12
20219
+ };
20220
+ case "circle":
20221
+ if (shape.x == null || shape.y == null || shape.radius == null) return null;
20222
+ return {
20223
+ x: shape.x - shape.radius - 4,
20224
+ y: shape.y - shape.radius - 4,
20225
+ w: shape.radius * 2 + 8,
20226
+ h: shape.radius * 2 + 8
20227
+ };
20228
+ case "rect":
20229
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
20230
+ return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
20231
+ case "polygon":
20232
+ if (!shape.points || shape.points.length === 0) return null;
20233
+ {
20234
+ const xs = shape.points.map((p2) => p2.x);
20235
+ const ys = shape.points.map((p2) => p2.y);
20236
+ const minX = Math.min(...xs);
20237
+ const minY = Math.min(...ys);
20238
+ return {
20239
+ x: minX - 4,
20240
+ y: minY - 4,
20241
+ w: Math.max(...xs) - minX + 8,
20242
+ h: Math.max(...ys) - minY + 8
20243
+ };
20244
+ }
20245
+ case "text":
20246
+ if (shape.x == null || shape.y == null) return null;
20247
+ return { x: shape.x - 4, y: shape.y - (shape.fontSize ?? 14) - 4, w: 120, h: (shape.fontSize ?? 14) + 8 };
20248
+ default:
20249
+ return null;
20250
+ }
20251
+ }
20252
+ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
20253
+ const angle = Math.atan2(y2 - y1, x2 - x1);
20254
+ ctx.beginPath();
20255
+ ctx.moveTo(x2, y2);
20256
+ ctx.lineTo(x2 - size * Math.cos(angle - Math.PI / 6), y2 - size * Math.sin(angle - Math.PI / 6));
20257
+ ctx.lineTo(x2 - size * Math.cos(angle + Math.PI / 6), y2 - size * Math.sin(angle + Math.PI / 6));
20258
+ ctx.closePath();
20259
+ ctx.fill();
20260
+ }
20261
+ function drawShape(ctx, shape, width, height) {
20262
+ ctx.save();
20263
+ const opacity = shape.opacity ?? 1;
20264
+ ctx.globalAlpha = opacity;
20265
+ const stroke = resolveColor2(shape.color, ctx, "#333333");
20266
+ const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
20267
+ ctx.lineWidth = shape.lineWidth ?? 2;
20268
+ switch (shape.type) {
20269
+ case "grid": {
20270
+ const step = shape.step ?? 40;
20271
+ ctx.strokeStyle = stroke;
20272
+ ctx.globalAlpha = opacity * 0.25;
20273
+ ctx.lineWidth = 1;
20274
+ ctx.beginPath();
20275
+ for (let x = 0; x <= width; x += step) {
20276
+ ctx.moveTo(x, 0);
20277
+ ctx.lineTo(x, height);
20278
+ }
20279
+ for (let y = 0; y <= height; y += step) {
20280
+ ctx.moveTo(0, y);
20281
+ ctx.lineTo(width, y);
20282
+ }
20283
+ ctx.stroke();
20284
+ break;
20285
+ }
20286
+ case "axis": {
20287
+ const axis = shape.axis ?? "x";
20288
+ ctx.strokeStyle = stroke;
20289
+ ctx.lineWidth = shape.lineWidth ?? 2;
20290
+ ctx.beginPath();
20291
+ if (axis === "x") {
20292
+ ctx.moveTo(0, height / 2);
20293
+ ctx.lineTo(width, height / 2);
20294
+ } else {
20295
+ ctx.moveTo(width / 2, 0);
20296
+ ctx.lineTo(width / 2, height);
20297
+ }
20298
+ ctx.stroke();
20299
+ break;
20300
+ }
20301
+ case "line": {
20302
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
20303
+ ctx.strokeStyle = stroke;
20304
+ ctx.beginPath();
20305
+ ctx.moveTo(shape.x1, shape.y1);
20306
+ ctx.lineTo(shape.x2, shape.y2);
20307
+ ctx.stroke();
20308
+ break;
20309
+ }
20310
+ case "arrow": {
20311
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
20312
+ ctx.strokeStyle = stroke;
20313
+ ctx.fillStyle = stroke;
20314
+ ctx.beginPath();
20315
+ ctx.moveTo(shape.x1, shape.y1);
20316
+ ctx.lineTo(shape.x2, shape.y2);
20317
+ ctx.stroke();
20318
+ drawArrowHead(ctx, shape.x1, shape.y1, shape.x2, shape.y2, 10);
20319
+ break;
20320
+ }
20321
+ case "circle": {
20322
+ if (shape.x == null || shape.y == null || shape.radius == null) break;
20323
+ ctx.beginPath();
20324
+ ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
20325
+ if (fill) {
20326
+ ctx.fillStyle = fill;
20327
+ ctx.fill();
20328
+ }
20329
+ ctx.strokeStyle = stroke;
20330
+ ctx.stroke();
20331
+ break;
20332
+ }
20333
+ case "rect": {
20334
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
20335
+ if (fill) {
20336
+ ctx.fillStyle = fill;
20337
+ ctx.fillRect(shape.x, shape.y, shape.width, shape.height);
20338
+ }
20339
+ ctx.strokeStyle = stroke;
20340
+ ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
20341
+ break;
20342
+ }
20343
+ case "polygon": {
20344
+ if (!shape.points || shape.points.length < 2) break;
20345
+ ctx.beginPath();
20346
+ ctx.moveTo(shape.points[0].x, shape.points[0].y);
20347
+ for (let i = 1; i < shape.points.length; i++) {
20348
+ ctx.lineTo(shape.points[i].x, shape.points[i].y);
20349
+ }
20350
+ ctx.closePath();
20351
+ if (fill) {
20352
+ ctx.fillStyle = fill;
20353
+ ctx.fill();
20354
+ }
20355
+ ctx.strokeStyle = stroke;
20356
+ ctx.stroke();
20357
+ break;
20358
+ }
20359
+ case "path": {
20360
+ if (!shape.path) break;
20361
+ const p2 = new Path2D(shape.path);
20362
+ if (fill) {
20363
+ ctx.fillStyle = fill;
20364
+ ctx.fill(p2);
20365
+ }
20366
+ ctx.strokeStyle = stroke;
20367
+ ctx.stroke(p2);
20368
+ break;
20369
+ }
20370
+ case "text": {
20371
+ if (shape.x == null || shape.y == null || !shape.text) break;
20372
+ ctx.fillStyle = stroke;
20373
+ ctx.font = `${shape.fontSize ?? 14}px system-ui, sans-serif`;
20374
+ ctx.textAlign = shape.align ?? "left";
20375
+ ctx.textBaseline = "middle";
20376
+ ctx.fillText(shape.text, shape.x, shape.y);
20377
+ break;
20378
+ }
20379
+ }
20380
+ ctx.restore();
20381
+ }
20382
+ var LearningCanvas;
20383
+ var init_LearningCanvas = __esm({
20384
+ "components/learning/atoms/LearningCanvas.tsx"() {
20385
+ "use client";
20386
+ init_cn();
20387
+ init_useEventBus();
20388
+ LearningCanvas = ({
20389
+ className,
20390
+ width = 600,
20391
+ height = 400,
20392
+ backgroundColor,
20393
+ shapes = [],
20394
+ interactive = false,
20395
+ animate = false,
20396
+ onShapeClick,
20397
+ onShapeHover,
20398
+ isLoading,
20399
+ error
20400
+ }) => {
20401
+ const canvasRef = useRef(null);
20402
+ const eventBus = useEventBus();
20403
+ const animRef = useRef(0);
20404
+ const hoverIndexRef = useRef(-1);
20405
+ const findShapeAt = useCallback((clientX, clientY) => {
20406
+ const canvas = canvasRef.current;
20407
+ if (!canvas) return -1;
20408
+ const rect = canvas.getBoundingClientRect();
20409
+ const x = clientX - rect.left;
20410
+ const y = clientY - rect.top;
20411
+ for (let i = shapes.length - 1; i >= 0; i--) {
20412
+ const b = shapeBounds(shapes[i]);
20413
+ if (b && x >= b.x && x <= b.x + b.w && y >= b.y && y <= b.y + b.h) {
20414
+ return i;
20415
+ }
20416
+ }
20417
+ return -1;
20418
+ }, [shapes]);
20419
+ const draw = useCallback(() => {
20420
+ const canvas = canvasRef.current;
20421
+ if (!canvas) return;
20422
+ const ctx = canvas.getContext("2d");
20423
+ if (!ctx) return;
20424
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
20425
+ canvas.width = Math.max(1, Math.floor(width * dpr));
20426
+ canvas.height = Math.max(1, Math.floor(height * dpr));
20427
+ canvas.style.width = `${width}px`;
20428
+ canvas.style.height = `${height}px`;
20429
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
20430
+ ctx.clearRect(0, 0, width, height);
20431
+ if (backgroundColor) {
20432
+ ctx.fillStyle = backgroundColor;
20433
+ ctx.fillRect(0, 0, width, height);
20434
+ }
20435
+ for (const shape of shapes) {
20436
+ drawShape(ctx, shape, width, height);
20437
+ }
20438
+ }, [width, height, backgroundColor, shapes]);
20439
+ useEffect(() => {
20440
+ draw();
20441
+ }, [draw]);
20442
+ useEffect(() => {
20443
+ if (!animate) return;
20444
+ const loop = () => {
20445
+ draw();
20446
+ animRef.current = requestAnimationFrame(loop);
20447
+ };
20448
+ animRef.current = requestAnimationFrame(loop);
20449
+ return () => cancelAnimationFrame(animRef.current);
20450
+ }, [animate, draw]);
20451
+ const handlePointerMove = useCallback(
20452
+ (e) => {
20453
+ if (!interactive) return;
20454
+ const idx = findShapeAt(e.clientX, e.clientY);
20455
+ if (idx !== hoverIndexRef.current) {
20456
+ hoverIndexRef.current = idx;
20457
+ if (idx >= 0) {
20458
+ const shape = shapes[idx];
20459
+ const payload = { id: shape.id, type: shape.type, index: idx };
20460
+ if (onShapeHover) onShapeHover(payload);
20461
+ else if (eventBus) eventBus.emit(`UI:SHAPE_HOVER`, payload);
20462
+ }
20463
+ }
20464
+ },
20465
+ [interactive, onShapeHover, eventBus, findShapeAt, shapes]
20466
+ );
20467
+ const handleClick = useCallback(
20468
+ (e) => {
20469
+ if (!interactive) return;
20470
+ const idx = findShapeAt(e.clientX, e.clientY);
20471
+ if (idx >= 0) {
20472
+ const shape = shapes[idx];
20473
+ const payload = { id: shape.id, type: shape.type, index: idx };
20474
+ if (onShapeClick) onShapeClick(payload);
20475
+ else if (eventBus) eventBus.emit(`UI:SHAPE_CLICK`, payload);
20476
+ }
20477
+ },
20478
+ [interactive, onShapeClick, eventBus, findShapeAt, shapes]
20479
+ );
20480
+ if (isLoading || error) {
20481
+ return /* @__PURE__ */ jsx(
20482
+ "div",
20483
+ {
20484
+ className: cn(
20485
+ "flex items-center justify-center rounded border border-border bg-surface",
20486
+ className
20487
+ ),
20488
+ style: { width, height },
20489
+ 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" })
20490
+ }
20491
+ );
20492
+ }
20493
+ return /* @__PURE__ */ jsx(
20494
+ "canvas",
20495
+ {
20496
+ ref: canvasRef,
20497
+ className: cn("block touch-none rounded border border-border", className),
20498
+ style: { width, height },
20499
+ onClick: handleClick,
20500
+ onPointerMove: handlePointerMove
20501
+ }
20502
+ );
20503
+ };
20504
+ }
20505
+ });
20109
20506
 
20110
20507
  // components/core/atoms/index.ts
20111
20508
  var init_atoms = __esm({
@@ -21591,91 +21988,6 @@ var init_ComponentPatterns = __esm({
21591
21988
  AlertPattern.displayName = "AlertPattern";
21592
21989
  }
21593
21990
  });
21594
- function parseValue(value) {
21595
- if (value === "" || value == null) return { num: 0, prefix: "", suffix: "", decimals: 0 };
21596
- const match = String(value).match(/^([^0-9]*)([0-9]+(?:\.[0-9]+)?)(.*)$/);
21597
- if (!match) {
21598
- return { num: 0, prefix: "", suffix: String(value), decimals: 0 };
21599
- }
21600
- const numStr = match[2];
21601
- const decimalIdx = numStr.indexOf(".");
21602
- const decimals = decimalIdx >= 0 ? numStr.length - decimalIdx - 1 : 0;
21603
- return {
21604
- prefix: match[1],
21605
- num: parseFloat(numStr),
21606
- suffix: match[3],
21607
- decimals
21608
- };
21609
- }
21610
- var AnimatedCounter;
21611
- var init_AnimatedCounter = __esm({
21612
- "components/core/molecules/AnimatedCounter.tsx"() {
21613
- "use client";
21614
- init_cn();
21615
- init_Box();
21616
- init_Typography();
21617
- AnimatedCounter = ({
21618
- value,
21619
- label,
21620
- duration = 1500,
21621
- className
21622
- }) => {
21623
- const ref = useRef(null);
21624
- const [displayValue, setDisplayValue] = useState("0");
21625
- const [hasAnimated, setHasAnimated] = useState(false);
21626
- const animate = useCallback(() => {
21627
- const { num: num2, prefix, suffix, decimals } = parseValue(value);
21628
- if (num2 === 0) {
21629
- setDisplayValue(String(value));
21630
- return;
21631
- }
21632
- const startTime = performance.now();
21633
- const tick = (now2) => {
21634
- const elapsed = now2 - startTime;
21635
- const progress = Math.min(elapsed / duration, 1);
21636
- const eased = 1 - Math.pow(1 - progress, 3);
21637
- const current = eased * num2;
21638
- setDisplayValue(`${prefix}${current.toFixed(decimals)}${suffix}`);
21639
- if (progress < 1) {
21640
- requestAnimationFrame(tick);
21641
- } else {
21642
- setDisplayValue(String(value));
21643
- }
21644
- };
21645
- requestAnimationFrame(tick);
21646
- }, [value, duration]);
21647
- useEffect(() => {
21648
- if (hasAnimated) return;
21649
- const el = ref.current;
21650
- if (!el) return;
21651
- const observer2 = new IntersectionObserver(
21652
- (entries) => {
21653
- if (entries[0].isIntersecting) {
21654
- setHasAnimated(true);
21655
- animate();
21656
- observer2.disconnect();
21657
- }
21658
- },
21659
- { threshold: 0.3 }
21660
- );
21661
- observer2.observe(el);
21662
- return () => observer2.disconnect();
21663
- }, [hasAnimated, animate]);
21664
- return /* @__PURE__ */ jsxs(Box, { ref, className: cn("flex flex-col items-center gap-1 p-4", className), children: [
21665
- /* @__PURE__ */ jsx(
21666
- Typography,
21667
- {
21668
- variant: "h2",
21669
- className: "text-primary font-bold tabular-nums",
21670
- children: hasAnimated ? displayValue : "0"
21671
- }
21672
- ),
21673
- /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "muted", className: "text-center", children: label })
21674
- ] });
21675
- };
21676
- AnimatedCounter.displayName = "AnimatedCounter";
21677
- }
21678
- });
21679
21991
  var AuthLayout;
21680
21992
  var init_AuthLayout = __esm({
21681
21993
  "components/marketing/templates/AuthLayout.tsx"() {
@@ -22644,6 +22956,103 @@ var init_BehaviorView = __esm({
22644
22956
  BehaviorView.displayName = "BehaviorView";
22645
22957
  }
22646
22958
  });
22959
+ var BiologyCanvas;
22960
+ var init_BiologyCanvas = __esm({
22961
+ "components/learning/molecules/BiologyCanvas.tsx"() {
22962
+ "use client";
22963
+ init_atoms();
22964
+ init_Stack();
22965
+ init_LearningCanvas();
22966
+ BiologyCanvas = ({
22967
+ className,
22968
+ width = 600,
22969
+ height = 400,
22970
+ title,
22971
+ backgroundColor,
22972
+ nodes = [],
22973
+ edges = [],
22974
+ shapes = [],
22975
+ interactive = false,
22976
+ animate = false,
22977
+ onShapeClick,
22978
+ isLoading,
22979
+ error
22980
+ }) => {
22981
+ const derivedShapes = useMemo(() => {
22982
+ const out = [];
22983
+ const nodeById = /* @__PURE__ */ new Map();
22984
+ for (const n of nodes) {
22985
+ if (n.id) nodeById.set(n.id, n);
22986
+ }
22987
+ for (const e of edges) {
22988
+ const a = nodeById.get(e.from);
22989
+ const b = nodeById.get(e.to);
22990
+ if (!a || !b) continue;
22991
+ out.push({
22992
+ type: "line",
22993
+ x1: a.x,
22994
+ y1: a.y,
22995
+ x2: b.x,
22996
+ y2: b.y,
22997
+ color: e.color ?? "#9ca3af",
22998
+ lineWidth: 2
22999
+ });
23000
+ if (e.label) {
23001
+ out.push({
23002
+ type: "text",
23003
+ x: (a.x + b.x) / 2 + 4,
23004
+ y: (a.y + b.y) / 2 - 4,
23005
+ text: e.label,
23006
+ color: "#374151",
23007
+ fontSize: 11
23008
+ });
23009
+ }
23010
+ }
23011
+ for (const n of nodes) {
23012
+ out.push({
23013
+ type: "circle",
23014
+ x: n.x,
23015
+ y: n.y,
23016
+ radius: n.radius ?? 16,
23017
+ color: n.color ?? "#16a34a",
23018
+ fill: `${n.color ?? "#16a34a"}33`,
23019
+ id: n.id
23020
+ });
23021
+ if (n.label) {
23022
+ out.push({
23023
+ type: "text",
23024
+ x: n.x,
23025
+ y: n.y + (n.radius ?? 16) + 14,
23026
+ text: n.label,
23027
+ color: "#111827",
23028
+ fontSize: 12,
23029
+ align: "center"
23030
+ });
23031
+ }
23032
+ }
23033
+ out.push(...shapes);
23034
+ return out;
23035
+ }, [nodes, edges, shapes]);
23036
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
23037
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
23038
+ /* @__PURE__ */ jsx(
23039
+ LearningCanvas,
23040
+ {
23041
+ width,
23042
+ height,
23043
+ backgroundColor,
23044
+ shapes: derivedShapes,
23045
+ interactive,
23046
+ animate,
23047
+ onShapeClick,
23048
+ isLoading,
23049
+ error
23050
+ }
23051
+ )
23052
+ ] }) });
23053
+ };
23054
+ }
23055
+ });
22647
23056
 
22648
23057
  // node_modules/katex/dist/katex.min.css
22649
23058
  var init_katex_min = __esm({
@@ -29016,6 +29425,122 @@ var init_ChatBar = __esm({
29016
29425
  ChatBar.displayName = "ChatBar";
29017
29426
  }
29018
29427
  });
29428
+ var ChemistryCanvas;
29429
+ var init_ChemistryCanvas = __esm({
29430
+ "components/learning/molecules/ChemistryCanvas.tsx"() {
29431
+ "use client";
29432
+ init_atoms();
29433
+ init_Stack();
29434
+ init_LearningCanvas();
29435
+ ChemistryCanvas = ({
29436
+ className,
29437
+ width = 600,
29438
+ height = 400,
29439
+ title,
29440
+ backgroundColor,
29441
+ atoms = [],
29442
+ bonds = [],
29443
+ arrows = [],
29444
+ shapes = [],
29445
+ interactive = false,
29446
+ animate = false,
29447
+ onShapeClick,
29448
+ isLoading,
29449
+ error
29450
+ }) => {
29451
+ const derivedShapes = useMemo(() => {
29452
+ const out = [];
29453
+ const atomById = /* @__PURE__ */ new Map();
29454
+ for (const a of atoms) {
29455
+ if (a.id) atomById.set(a.id, a);
29456
+ }
29457
+ for (const b of bonds) {
29458
+ const a = atomById.get(b.from);
29459
+ const c = atomById.get(b.to);
29460
+ if (!a || !c) continue;
29461
+ const color = b.color ?? "#6b7280";
29462
+ const strokeWidth = b.type === "double" ? 4 : b.type === "triple" ? 6 : 2;
29463
+ out.push({
29464
+ type: "line",
29465
+ x1: a.x,
29466
+ y1: a.y,
29467
+ x2: c.x,
29468
+ y2: c.y,
29469
+ color,
29470
+ lineWidth: strokeWidth
29471
+ });
29472
+ }
29473
+ for (const a of arrows) {
29474
+ const angle = (a.angle ?? 0) * (Math.PI / 180);
29475
+ const len = a.length ?? 60;
29476
+ const x2 = a.x + Math.cos(angle) * len;
29477
+ const y2 = a.y + Math.sin(angle) * len;
29478
+ out.push({
29479
+ type: "arrow",
29480
+ x1: a.x,
29481
+ y1: a.y,
29482
+ x2,
29483
+ y2,
29484
+ color: a.color ?? "#dc2626",
29485
+ lineWidth: 2
29486
+ });
29487
+ if (a.label) {
29488
+ out.push({
29489
+ type: "text",
29490
+ x: (a.x + x2) / 2,
29491
+ y: (a.y + y2) / 2 - 10,
29492
+ text: a.label,
29493
+ color: "#111827",
29494
+ fontSize: 12,
29495
+ align: "center"
29496
+ });
29497
+ }
29498
+ }
29499
+ for (const a of atoms) {
29500
+ out.push({
29501
+ type: "circle",
29502
+ x: a.x,
29503
+ y: a.y,
29504
+ radius: a.radius ?? 14,
29505
+ color: a.color ?? "#2563eb",
29506
+ fill: a.color ?? "#2563eb",
29507
+ id: a.id
29508
+ });
29509
+ if (a.element) {
29510
+ out.push({
29511
+ type: "text",
29512
+ x: a.x,
29513
+ y: a.y,
29514
+ text: a.element,
29515
+ color: "#ffffff",
29516
+ fontSize: 12,
29517
+ align: "center"
29518
+ });
29519
+ }
29520
+ }
29521
+ out.push(...shapes);
29522
+ return out;
29523
+ }, [atoms, bonds, arrows, shapes]);
29524
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
29525
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
29526
+ /* @__PURE__ */ jsx(
29527
+ LearningCanvas,
29528
+ {
29529
+ width,
29530
+ height,
29531
+ backgroundColor,
29532
+ shapes: derivedShapes,
29533
+ interactive,
29534
+ animate,
29535
+ onShapeClick,
29536
+ isLoading,
29537
+ error
29538
+ }
29539
+ )
29540
+ ] }) });
29541
+ };
29542
+ }
29543
+ });
29019
29544
  var CodeRunnerPanel;
29020
29545
  var init_CodeRunnerPanel = __esm({
29021
29546
  "components/core/organisms/CodeRunnerPanel.tsx"() {
@@ -34635,6 +35160,234 @@ var init_ProgressDots = __esm({
34635
35160
  ProgressDots.displayName = "ProgressDots";
34636
35161
  }
34637
35162
  });
35163
+ var MathCanvas;
35164
+ var init_MathCanvas = __esm({
35165
+ "components/learning/molecules/MathCanvas.tsx"() {
35166
+ "use client";
35167
+ init_atoms();
35168
+ init_Stack();
35169
+ init_LearningCanvas();
35170
+ MathCanvas = ({
35171
+ className,
35172
+ width = 600,
35173
+ height = 400,
35174
+ title,
35175
+ xMin = -10,
35176
+ xMax = 10,
35177
+ yMin = -10,
35178
+ yMax = 10,
35179
+ showAxes = true,
35180
+ showGrid = true,
35181
+ gridStep = 1,
35182
+ curves = [],
35183
+ points = [],
35184
+ vectors = [],
35185
+ shapes = [],
35186
+ interactive = false,
35187
+ animate = false,
35188
+ onShapeClick,
35189
+ isLoading,
35190
+ error
35191
+ }) => {
35192
+ const derivedShapes = useMemo(() => {
35193
+ const out = [];
35194
+ const margin = 24;
35195
+ const plotW = width - margin * 2;
35196
+ const plotH = height - margin * 2;
35197
+ const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
35198
+ const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
35199
+ if (showGrid) {
35200
+ for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
35201
+ const px = mapX(x);
35202
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#e5e7eb", lineWidth: 1 });
35203
+ }
35204
+ for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
35205
+ const py = mapY(y);
35206
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#e5e7eb", lineWidth: 1 });
35207
+ }
35208
+ }
35209
+ if (showAxes) {
35210
+ const xAxisY = Math.max(margin, Math.min(height - margin, mapY(0)));
35211
+ const yAxisX = Math.max(margin, Math.min(width - margin, mapX(0)));
35212
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
35213
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
35214
+ }
35215
+ for (const curve of curves) {
35216
+ if (!curve.samples || curve.samples.length < 2) continue;
35217
+ for (let i = 1; i < curve.samples.length; i++) {
35218
+ const a = curve.samples[i - 1];
35219
+ const b = curve.samples[i];
35220
+ if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
35221
+ out.push({
35222
+ type: "line",
35223
+ x1: mapX(a.x),
35224
+ y1: mapY(a.y),
35225
+ x2: mapX(b.x),
35226
+ y2: mapY(b.y),
35227
+ color: curve.color ?? "#2563eb",
35228
+ lineWidth: 2
35229
+ });
35230
+ }
35231
+ }
35232
+ for (const p2 of points) {
35233
+ if (p2.x < xMin || p2.x > xMax || p2.y < yMin || p2.y > yMax) continue;
35234
+ out.push({
35235
+ type: "circle",
35236
+ x: mapX(p2.x),
35237
+ y: mapY(p2.y),
35238
+ radius: p2.radius ?? 4,
35239
+ color: p2.color ?? "#dc2626",
35240
+ fill: p2.color ?? "#dc2626"
35241
+ });
35242
+ if (p2.label) {
35243
+ out.push({ type: "text", x: mapX(p2.x) + 8, y: mapY(p2.y) - 8, text: p2.label, color: "#111827", fontSize: 12 });
35244
+ }
35245
+ }
35246
+ for (const v of vectors) {
35247
+ if (v.x < xMin || v.x > xMax || v.y < yMin || v.y > yMax) continue;
35248
+ const x1 = mapX(v.x);
35249
+ const y1 = mapY(v.y);
35250
+ const x2 = mapX(v.x + v.vx);
35251
+ const y2 = mapY(v.y + v.vy);
35252
+ out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
35253
+ if (v.label) {
35254
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
35255
+ }
35256
+ }
35257
+ out.push(...shapes);
35258
+ return out;
35259
+ }, [width, height, xMin, xMax, yMin, yMax, showAxes, showGrid, gridStep, curves, points, vectors, shapes]);
35260
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
35261
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
35262
+ /* @__PURE__ */ jsx(
35263
+ LearningCanvas,
35264
+ {
35265
+ width,
35266
+ height,
35267
+ shapes: derivedShapes,
35268
+ interactive,
35269
+ animate,
35270
+ onShapeClick,
35271
+ isLoading,
35272
+ error
35273
+ }
35274
+ )
35275
+ ] }) });
35276
+ };
35277
+ }
35278
+ });
35279
+ var PhysicsCanvas;
35280
+ var init_PhysicsCanvas = __esm({
35281
+ "components/learning/molecules/PhysicsCanvas.tsx"() {
35282
+ "use client";
35283
+ init_atoms();
35284
+ init_Stack();
35285
+ init_LearningCanvas();
35286
+ PhysicsCanvas = ({
35287
+ className,
35288
+ width = 600,
35289
+ height = 400,
35290
+ title,
35291
+ backgroundColor,
35292
+ bodies = [],
35293
+ constraints = [],
35294
+ showVelocity = true,
35295
+ showForces = false,
35296
+ velocityScale = 20,
35297
+ forceScale = 20,
35298
+ shapes = [],
35299
+ interactive = false,
35300
+ animate = false,
35301
+ onShapeClick,
35302
+ isLoading,
35303
+ error
35304
+ }) => {
35305
+ const derivedShapes = useMemo(() => {
35306
+ const out = [];
35307
+ const bodyById = /* @__PURE__ */ new Map();
35308
+ for (const b of bodies) {
35309
+ if (b.id) bodyById.set(b.id, b);
35310
+ }
35311
+ for (const c of constraints) {
35312
+ const a = bodyById.get(c.from);
35313
+ const b = bodyById.get(c.to);
35314
+ if (!a || !b) continue;
35315
+ out.push({
35316
+ type: "line",
35317
+ x1: a.x,
35318
+ y1: a.y,
35319
+ x2: b.x,
35320
+ y2: b.y,
35321
+ color: c.color ?? "#9ca3af",
35322
+ lineWidth: 2
35323
+ });
35324
+ }
35325
+ for (const b of bodies) {
35326
+ out.push({
35327
+ type: "circle",
35328
+ x: b.x,
35329
+ y: b.y,
35330
+ radius: b.radius ?? 12,
35331
+ color: b.color ?? "#2563eb",
35332
+ fill: b.color ?? "#2563eb",
35333
+ id: b.id
35334
+ });
35335
+ if (b.label) {
35336
+ out.push({
35337
+ type: "text",
35338
+ x: b.x + (b.radius ?? 12) + 6,
35339
+ y: b.y - (b.radius ?? 12) - 6,
35340
+ text: b.label,
35341
+ color: "#111827",
35342
+ fontSize: 12
35343
+ });
35344
+ }
35345
+ if (showVelocity && b.vx != null && b.vy != null && (b.vx !== 0 || b.vy !== 0)) {
35346
+ out.push({
35347
+ type: "arrow",
35348
+ x1: b.x,
35349
+ y1: b.y,
35350
+ x2: b.x + b.vx * velocityScale,
35351
+ y2: b.y + b.vy * velocityScale,
35352
+ color: "#16a34a",
35353
+ lineWidth: 2
35354
+ });
35355
+ }
35356
+ if (showForces && b.fx != null && b.fy != null && (b.fx !== 0 || b.fy !== 0)) {
35357
+ out.push({
35358
+ type: "arrow",
35359
+ x1: b.x,
35360
+ y1: b.y,
35361
+ x2: b.x + b.fx * forceScale,
35362
+ y2: b.y + b.fy * forceScale,
35363
+ color: "#dc2626",
35364
+ lineWidth: 2
35365
+ });
35366
+ }
35367
+ }
35368
+ out.push(...shapes);
35369
+ return out;
35370
+ }, [bodies, constraints, showVelocity, showForces, velocityScale, forceScale, shapes]);
35371
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
35372
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
35373
+ /* @__PURE__ */ jsx(
35374
+ LearningCanvas,
35375
+ {
35376
+ width,
35377
+ height,
35378
+ backgroundColor,
35379
+ shapes: derivedShapes,
35380
+ interactive,
35381
+ animate,
35382
+ onShapeClick,
35383
+ isLoading,
35384
+ error
35385
+ }
35386
+ )
35387
+ ] }) });
35388
+ };
35389
+ }
35390
+ });
34638
35391
  function resolveNodeColor(node, groups) {
34639
35392
  if (node.color) return node.color;
34640
35393
  if (node.group) {
@@ -35006,13 +35759,13 @@ var init_MapView = __esm({
35006
35759
  shadowSize: [41, 41]
35007
35760
  });
35008
35761
  L.Marker.prototype.options.icon = defaultIcon;
35009
- const { useEffect: useEffect74, useRef: useRef76, useCallback: useCallback112, useState: useState107 } = React105__default;
35762
+ const { useEffect: useEffect75, useRef: useRef77, useCallback: useCallback112, useState: useState107 } = React105__default;
35010
35763
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
35011
35764
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
35012
35765
  function MapUpdater({ centerLat, centerLng, zoom }) {
35013
35766
  const map = useMap();
35014
- const prevRef = useRef76({ centerLat, centerLng, zoom });
35015
- useEffect74(() => {
35767
+ const prevRef = useRef77({ centerLat, centerLng, zoom });
35768
+ useEffect75(() => {
35016
35769
  const prev = prevRef.current;
35017
35770
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
35018
35771
  map.setView([centerLat, centerLng], zoom);
@@ -35023,7 +35776,7 @@ var init_MapView = __esm({
35023
35776
  }
35024
35777
  function MapClickHandler({ onMapClick }) {
35025
35778
  const map = useMap();
35026
- useEffect74(() => {
35779
+ useEffect75(() => {
35027
35780
  if (!onMapClick) return;
35028
35781
  const handler = (e) => {
35029
35782
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -43326,7 +44079,7 @@ function getGroupColor(group, groups) {
43326
44079
  const idx = groups.indexOf(group);
43327
44080
  return GROUP_COLORS2[idx % GROUP_COLORS2.length];
43328
44081
  }
43329
- function resolveColor2(color, el) {
44082
+ function resolveColor3(color, el) {
43330
44083
  const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color.trim());
43331
44084
  if (!m) return color;
43332
44085
  const resolved = getComputedStyle(el).getPropertyValue(m[1]).trim();
@@ -43587,7 +44340,7 @@ var init_GraphCanvas = __esm({
43587
44340
  const w = logicalW;
43588
44341
  const h = height;
43589
44342
  const nodes = nodesRef.current;
43590
- const accentColor = resolveColor2("var(--color-accent)", canvas);
44343
+ const accentColor = resolveColor3("var(--color-accent)", canvas);
43591
44344
  const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
43592
44345
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
43593
44346
  ctx.clearRect(0, 0, w, h);
@@ -43626,7 +44379,7 @@ var init_GraphCanvas = __esm({
43626
44379
  ctx.globalAlpha = 1;
43627
44380
  for (const node of nodes) {
43628
44381
  const size = node.size || 8;
43629
- const color = resolveColor2(node.color || getGroupColor(node.group, groups), canvas);
44382
+ const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
43630
44383
  const isHovered = hoveredNode === node.id;
43631
44384
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
43632
44385
  ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
@@ -50524,6 +51277,7 @@ var init_component_registry_generated = __esm({
50524
51277
  init_BattleBoard();
50525
51278
  init_BattleTemplate();
50526
51279
  init_BehaviorView();
51280
+ init_BiologyCanvas();
50527
51281
  init_BloomQuizBlock();
50528
51282
  init_BoardgameBoard();
50529
51283
  init_BookChapterView();
@@ -50553,6 +51307,7 @@ var init_component_registry_generated = __esm({
50553
51307
  init_ChartLegend();
50554
51308
  init_ChatBar();
50555
51309
  init_Checkbox();
51310
+ init_ChemistryCanvas();
50556
51311
  init_ChoiceButton();
50557
51312
  init_CityBuilderBoard();
50558
51313
  init_CityBuilderTemplate();
@@ -50644,6 +51399,7 @@ var init_component_registry_generated = __esm({
50644
51399
  init_JazariStateMachine();
50645
51400
  init_LandingPageTemplate();
50646
51401
  init_LawReferenceTooltip();
51402
+ init_LearningCanvas();
50647
51403
  init_Lightbox();
50648
51404
  init_LikertScale();
50649
51405
  init_LineChart();
@@ -50652,9 +51408,11 @@ var init_component_registry_generated = __esm({
50652
51408
  init_MapView();
50653
51409
  init_MarkdownContent();
50654
51410
  init_MarketingFooter();
51411
+ init_MarketingStatCard();
50655
51412
  init_MasterDetail();
50656
51413
  init_MasterDetailLayout();
50657
51414
  init_MatchPuzzleBoard();
51415
+ init_MathCanvas();
50658
51416
  init_MatrixQuestion();
50659
51417
  init_MediaGallery();
50660
51418
  init_Menu();
@@ -50673,6 +51431,7 @@ var init_component_registry_generated = __esm({
50673
51431
  init_PageHeader();
50674
51432
  init_Pagination();
50675
51433
  init_PatternTile();
51434
+ init_PhysicsCanvas();
50676
51435
  init_PinballBoard();
50677
51436
  init_PirateBoard();
50678
51437
  init_PlatformerBoard();
@@ -50849,6 +51608,7 @@ var init_component_registry_generated = __esm({
50849
51608
  "BattleBoard": BattleBoard,
50850
51609
  "BattleTemplate": BattleTemplate,
50851
51610
  "BehaviorView": BehaviorView,
51611
+ "BiologyCanvas": BiologyCanvas,
50852
51612
  "BloomQuizBlock": BloomQuizBlock,
50853
51613
  "BoardgameBoard": BoardgameBoard,
50854
51614
  "BookChapterView": BookChapterView,
@@ -50881,6 +51641,7 @@ var init_component_registry_generated = __esm({
50881
51641
  "ChartLegend": ChartLegend,
50882
51642
  "ChatBar": ChatBar,
50883
51643
  "Checkbox": Checkbox,
51644
+ "ChemistryCanvas": ChemistryCanvas,
50884
51645
  "ChoiceButton": ChoiceButton,
50885
51646
  "CityBuilderBoard": CityBuilderBoard,
50886
51647
  "CityBuilderTemplate": CityBuilderTemplate,
@@ -50982,6 +51743,7 @@ var init_component_registry_generated = __esm({
50982
51743
  "LabelPattern": LabelPattern,
50983
51744
  "LandingPageTemplate": LandingPageTemplate,
50984
51745
  "LawReferenceTooltip": LawReferenceTooltip,
51746
+ "LearningCanvas": LearningCanvas,
50985
51747
  "Lightbox": Lightbox,
50986
51748
  "LikertScale": LikertScale,
50987
51749
  "LineChart": LineChart2,
@@ -50990,9 +51752,11 @@ var init_component_registry_generated = __esm({
50990
51752
  "MapView": MapView,
50991
51753
  "MarkdownContent": MarkdownContent,
50992
51754
  "MarketingFooter": MarketingFooter,
51755
+ "MarketingStatCard": MarketingStatCard,
50993
51756
  "MasterDetail": MasterDetail,
50994
51757
  "MasterDetailLayout": MasterDetailLayout,
50995
51758
  "MatchPuzzleBoard": MatchPuzzleBoard,
51759
+ "MathCanvas": MathCanvas,
50996
51760
  "MatrixQuestion": MatrixQuestion,
50997
51761
  "MediaGallery": MediaGallery,
50998
51762
  "Menu": Menu,
@@ -51011,6 +51775,7 @@ var init_component_registry_generated = __esm({
51011
51775
  "PageHeader": PageHeader,
51012
51776
  "Pagination": Pagination,
51013
51777
  "PatternTile": PatternTile,
51778
+ "PhysicsCanvas": PhysicsCanvas,
51014
51779
  "PinballBoard": PinballBoard,
51015
51780
  "PirateBoard": PirateBoard,
51016
51781
  "PlatformerBoard": PlatformerBoard,