@almadar/ui 5.94.1 → 5.95.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.
@@ -8595,6 +8595,462 @@ var init_ComponentPatterns = __esm({
8595
8595
  AlertPattern.displayName = "AlertPattern";
8596
8596
  }
8597
8597
  });
8598
+ function resolveColor2(color, ctx, fallback) {
8599
+ if (!color) return fallback;
8600
+ if (color.startsWith("var(")) {
8601
+ ctx.canvas.style;
8602
+ const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color);
8603
+ if (m) {
8604
+ const computed = getComputedStyle(ctx.canvas).getPropertyValue(m[1]).trim();
8605
+ return computed || m[2]?.trim() || fallback;
8606
+ }
8607
+ }
8608
+ return color;
8609
+ }
8610
+ function shapeBounds(shape) {
8611
+ switch (shape.type) {
8612
+ case "line":
8613
+ case "arrow":
8614
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) return null;
8615
+ return {
8616
+ x: Math.min(shape.x1, shape.x2) - 6,
8617
+ y: Math.min(shape.y1, shape.y2) - 6,
8618
+ w: Math.abs(shape.x2 - shape.x1) + 12,
8619
+ h: Math.abs(shape.y2 - shape.y1) + 12
8620
+ };
8621
+ case "circle":
8622
+ if (shape.x == null || shape.y == null || shape.radius == null) return null;
8623
+ return {
8624
+ x: shape.x - shape.radius - 4,
8625
+ y: shape.y - shape.radius - 4,
8626
+ w: shape.radius * 2 + 8,
8627
+ h: shape.radius * 2 + 8
8628
+ };
8629
+ case "rect":
8630
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
8631
+ return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
8632
+ case "polygon":
8633
+ if (!shape.points || shape.points.length === 0) return null;
8634
+ {
8635
+ const xs = shape.points.map((p) => p.x);
8636
+ const ys = shape.points.map((p) => p.y);
8637
+ const minX = Math.min(...xs);
8638
+ const minY = Math.min(...ys);
8639
+ return {
8640
+ x: minX - 4,
8641
+ y: minY - 4,
8642
+ w: Math.max(...xs) - minX + 8,
8643
+ h: Math.max(...ys) - minY + 8
8644
+ };
8645
+ }
8646
+ case "text":
8647
+ if (shape.x == null || shape.y == null) return null;
8648
+ return { x: shape.x - 4, y: shape.y - (shape.fontSize ?? 14) - 4, w: 120, h: (shape.fontSize ?? 14) + 8 };
8649
+ default:
8650
+ return null;
8651
+ }
8652
+ }
8653
+ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
8654
+ const angle = Math.atan2(y2 - y1, x2 - x1);
8655
+ ctx.beginPath();
8656
+ ctx.moveTo(x2, y2);
8657
+ ctx.lineTo(x2 - size * Math.cos(angle - Math.PI / 6), y2 - size * Math.sin(angle - Math.PI / 6));
8658
+ ctx.lineTo(x2 - size * Math.cos(angle + Math.PI / 6), y2 - size * Math.sin(angle + Math.PI / 6));
8659
+ ctx.closePath();
8660
+ ctx.fill();
8661
+ }
8662
+ function drawShape(ctx, shape, width, height) {
8663
+ ctx.save();
8664
+ const opacity = shape.opacity ?? 1;
8665
+ ctx.globalAlpha = opacity;
8666
+ const stroke = resolveColor2(shape.color, ctx, "#333333");
8667
+ const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
8668
+ ctx.lineWidth = shape.lineWidth ?? 2;
8669
+ switch (shape.type) {
8670
+ case "grid": {
8671
+ const step = shape.step ?? 40;
8672
+ ctx.strokeStyle = stroke;
8673
+ ctx.globalAlpha = opacity * 0.25;
8674
+ ctx.lineWidth = 1;
8675
+ ctx.beginPath();
8676
+ for (let x = 0; x <= width; x += step) {
8677
+ ctx.moveTo(x, 0);
8678
+ ctx.lineTo(x, height);
8679
+ }
8680
+ for (let y = 0; y <= height; y += step) {
8681
+ ctx.moveTo(0, y);
8682
+ ctx.lineTo(width, y);
8683
+ }
8684
+ ctx.stroke();
8685
+ break;
8686
+ }
8687
+ case "axis": {
8688
+ const axis = shape.axis ?? "x";
8689
+ ctx.strokeStyle = stroke;
8690
+ ctx.lineWidth = shape.lineWidth ?? 2;
8691
+ ctx.beginPath();
8692
+ if (axis === "x") {
8693
+ ctx.moveTo(0, height / 2);
8694
+ ctx.lineTo(width, height / 2);
8695
+ } else {
8696
+ ctx.moveTo(width / 2, 0);
8697
+ ctx.lineTo(width / 2, height);
8698
+ }
8699
+ ctx.stroke();
8700
+ break;
8701
+ }
8702
+ case "line": {
8703
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
8704
+ ctx.strokeStyle = stroke;
8705
+ ctx.beginPath();
8706
+ ctx.moveTo(shape.x1, shape.y1);
8707
+ ctx.lineTo(shape.x2, shape.y2);
8708
+ ctx.stroke();
8709
+ break;
8710
+ }
8711
+ case "arrow": {
8712
+ if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
8713
+ ctx.strokeStyle = stroke;
8714
+ ctx.fillStyle = stroke;
8715
+ ctx.beginPath();
8716
+ ctx.moveTo(shape.x1, shape.y1);
8717
+ ctx.lineTo(shape.x2, shape.y2);
8718
+ ctx.stroke();
8719
+ drawArrowHead(ctx, shape.x1, shape.y1, shape.x2, shape.y2, 10);
8720
+ break;
8721
+ }
8722
+ case "circle": {
8723
+ if (shape.x == null || shape.y == null || shape.radius == null) break;
8724
+ ctx.beginPath();
8725
+ ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
8726
+ if (fill) {
8727
+ ctx.fillStyle = fill;
8728
+ ctx.fill();
8729
+ }
8730
+ ctx.strokeStyle = stroke;
8731
+ ctx.stroke();
8732
+ break;
8733
+ }
8734
+ case "rect": {
8735
+ if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
8736
+ if (fill) {
8737
+ ctx.fillStyle = fill;
8738
+ ctx.fillRect(shape.x, shape.y, shape.width, shape.height);
8739
+ }
8740
+ ctx.strokeStyle = stroke;
8741
+ ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
8742
+ break;
8743
+ }
8744
+ case "polygon": {
8745
+ if (!shape.points || shape.points.length < 2) break;
8746
+ ctx.beginPath();
8747
+ ctx.moveTo(shape.points[0].x, shape.points[0].y);
8748
+ for (let i = 1; i < shape.points.length; i++) {
8749
+ ctx.lineTo(shape.points[i].x, shape.points[i].y);
8750
+ }
8751
+ ctx.closePath();
8752
+ if (fill) {
8753
+ ctx.fillStyle = fill;
8754
+ ctx.fill();
8755
+ }
8756
+ ctx.strokeStyle = stroke;
8757
+ ctx.stroke();
8758
+ break;
8759
+ }
8760
+ case "path": {
8761
+ if (!shape.path) break;
8762
+ const p = new Path2D(shape.path);
8763
+ if (fill) {
8764
+ ctx.fillStyle = fill;
8765
+ ctx.fill(p);
8766
+ }
8767
+ ctx.strokeStyle = stroke;
8768
+ ctx.stroke(p);
8769
+ break;
8770
+ }
8771
+ case "text": {
8772
+ if (shape.x == null || shape.y == null || !shape.text) break;
8773
+ ctx.fillStyle = stroke;
8774
+ ctx.font = `${shape.fontSize ?? 14}px system-ui, sans-serif`;
8775
+ ctx.textAlign = shape.align ?? "left";
8776
+ ctx.textBaseline = "middle";
8777
+ ctx.fillText(shape.text, shape.x, shape.y);
8778
+ break;
8779
+ }
8780
+ }
8781
+ ctx.restore();
8782
+ }
8783
+ exports.LearningCanvas = void 0;
8784
+ var init_LearningCanvas = __esm({
8785
+ "components/learning/atoms/LearningCanvas.tsx"() {
8786
+ "use client";
8787
+ init_cn();
8788
+ init_useEventBus();
8789
+ exports.LearningCanvas = ({
8790
+ className,
8791
+ width = 600,
8792
+ height = 400,
8793
+ backgroundColor,
8794
+ shapes = [],
8795
+ interactive = false,
8796
+ animate = false,
8797
+ onShapeClick,
8798
+ onShapeHover,
8799
+ isLoading,
8800
+ error
8801
+ }) => {
8802
+ const canvasRef = React74.useRef(null);
8803
+ const eventBus = useEventBus();
8804
+ const animRef = React74.useRef(0);
8805
+ const hoverIndexRef = React74.useRef(-1);
8806
+ const findShapeAt = React74.useCallback((clientX, clientY) => {
8807
+ const canvas = canvasRef.current;
8808
+ if (!canvas) return -1;
8809
+ const rect = canvas.getBoundingClientRect();
8810
+ const x = clientX - rect.left;
8811
+ const y = clientY - rect.top;
8812
+ for (let i = shapes.length - 1; i >= 0; i--) {
8813
+ const b = shapeBounds(shapes[i]);
8814
+ if (b && x >= b.x && x <= b.x + b.w && y >= b.y && y <= b.y + b.h) {
8815
+ return i;
8816
+ }
8817
+ }
8818
+ return -1;
8819
+ }, [shapes]);
8820
+ const draw = React74.useCallback(() => {
8821
+ const canvas = canvasRef.current;
8822
+ if (!canvas) return;
8823
+ const ctx = canvas.getContext("2d");
8824
+ if (!ctx) return;
8825
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
8826
+ canvas.width = Math.max(1, Math.floor(width * dpr));
8827
+ canvas.height = Math.max(1, Math.floor(height * dpr));
8828
+ canvas.style.width = `${width}px`;
8829
+ canvas.style.height = `${height}px`;
8830
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
8831
+ ctx.clearRect(0, 0, width, height);
8832
+ if (backgroundColor) {
8833
+ ctx.fillStyle = backgroundColor;
8834
+ ctx.fillRect(0, 0, width, height);
8835
+ }
8836
+ for (const shape of shapes) {
8837
+ drawShape(ctx, shape, width, height);
8838
+ }
8839
+ }, [width, height, backgroundColor, shapes]);
8840
+ React74.useEffect(() => {
8841
+ draw();
8842
+ }, [draw]);
8843
+ React74.useEffect(() => {
8844
+ if (!animate) return;
8845
+ const loop = () => {
8846
+ draw();
8847
+ animRef.current = requestAnimationFrame(loop);
8848
+ };
8849
+ animRef.current = requestAnimationFrame(loop);
8850
+ return () => cancelAnimationFrame(animRef.current);
8851
+ }, [animate, draw]);
8852
+ const handlePointerMove = React74.useCallback(
8853
+ (e) => {
8854
+ if (!interactive) return;
8855
+ const idx = findShapeAt(e.clientX, e.clientY);
8856
+ if (idx !== hoverIndexRef.current) {
8857
+ hoverIndexRef.current = idx;
8858
+ if (idx >= 0) {
8859
+ const shape = shapes[idx];
8860
+ const payload = { id: shape.id, type: shape.type, index: idx };
8861
+ if (onShapeHover) onShapeHover(payload);
8862
+ else if (eventBus) eventBus.emit(`UI:SHAPE_HOVER`, payload);
8863
+ }
8864
+ }
8865
+ },
8866
+ [interactive, onShapeHover, eventBus, findShapeAt, shapes]
8867
+ );
8868
+ const handleClick = React74.useCallback(
8869
+ (e) => {
8870
+ if (!interactive) return;
8871
+ const idx = findShapeAt(e.clientX, e.clientY);
8872
+ if (idx >= 0) {
8873
+ const shape = shapes[idx];
8874
+ const payload = { id: shape.id, type: shape.type, index: idx };
8875
+ if (onShapeClick) onShapeClick(payload);
8876
+ else if (eventBus) eventBus.emit(`UI:SHAPE_CLICK`, payload);
8877
+ }
8878
+ },
8879
+ [interactive, onShapeClick, eventBus, findShapeAt, shapes]
8880
+ );
8881
+ if (isLoading || error) {
8882
+ return /* @__PURE__ */ jsxRuntime.jsx(
8883
+ "div",
8884
+ {
8885
+ className: cn(
8886
+ "flex items-center justify-center rounded border border-border bg-surface",
8887
+ className
8888
+ ),
8889
+ style: { width, height },
8890
+ children: error ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-destructive", children: error.message }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: "Loading canvas\u2026" })
8891
+ }
8892
+ );
8893
+ }
8894
+ return /* @__PURE__ */ jsxRuntime.jsx(
8895
+ "canvas",
8896
+ {
8897
+ ref: canvasRef,
8898
+ className: cn("block touch-none rounded border border-border", className),
8899
+ style: { width, height },
8900
+ onClick: handleClick,
8901
+ onPointerMove: handlePointerMove
8902
+ }
8903
+ );
8904
+ };
8905
+ }
8906
+ });
8907
+ var DEFAULT_BAR_COLOR, DEFAULT_CELL_COLOR, DEFAULT_POINTER_COLOR, POINTER_BAND, TOP_PAD; exports.AlgorithmCanvas = void 0;
8908
+ var init_AlgorithmCanvas = __esm({
8909
+ "components/learning/molecules/AlgorithmCanvas.tsx"() {
8910
+ "use client";
8911
+ init_atoms();
8912
+ init_Stack();
8913
+ init_LearningCanvas();
8914
+ DEFAULT_BAR_COLOR = "#3b82f6";
8915
+ DEFAULT_CELL_COLOR = "#e5e7eb";
8916
+ DEFAULT_POINTER_COLOR = "#dc2626";
8917
+ POINTER_BAND = 34;
8918
+ TOP_PAD = 12;
8919
+ exports.AlgorithmCanvas = ({
8920
+ className,
8921
+ width = 600,
8922
+ height = 400,
8923
+ title,
8924
+ backgroundColor,
8925
+ bars = [],
8926
+ cells = [],
8927
+ pointers = [],
8928
+ shapes = [],
8929
+ interactive = false,
8930
+ animate = false,
8931
+ onShapeClick,
8932
+ isLoading,
8933
+ error
8934
+ }) => {
8935
+ const derivedShapes = React74.useMemo(() => {
8936
+ const out = [];
8937
+ if (bars.length > 0) {
8938
+ const slot = width / bars.length;
8939
+ const barW = slot * 0.8;
8940
+ const gap = slot * 0.1;
8941
+ const baseline = height - POINTER_BAND;
8942
+ const usableH = baseline - TOP_PAD;
8943
+ const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
8944
+ bars.forEach((bar, i) => {
8945
+ const v = Number.isFinite(bar.value) ? bar.value : 0;
8946
+ const bh = Math.max(0, v / maxV * usableH);
8947
+ const x = i * slot + gap;
8948
+ const color = bar.color ?? DEFAULT_BAR_COLOR;
8949
+ out.push({
8950
+ type: "rect",
8951
+ id: `bar-${i}`,
8952
+ x,
8953
+ y: baseline - bh,
8954
+ width: barW,
8955
+ height: bh,
8956
+ color,
8957
+ fill: color
8958
+ });
8959
+ const label = bar.label ?? (bars.length <= 24 ? String(v) : void 0);
8960
+ if (label) {
8961
+ out.push({
8962
+ type: "text",
8963
+ x: x + barW / 2,
8964
+ y: baseline - bh - 8,
8965
+ text: label,
8966
+ color: "#374151",
8967
+ fontSize: 11,
8968
+ align: "center"
8969
+ });
8970
+ }
8971
+ });
8972
+ pointers.forEach((p) => {
8973
+ if (p.index < 0 || p.index >= bars.length) return;
8974
+ const cx = p.index * slot + slot / 2;
8975
+ const color = p.color ?? DEFAULT_POINTER_COLOR;
8976
+ out.push({
8977
+ type: "arrow",
8978
+ x1: cx,
8979
+ y1: height - 6,
8980
+ x2: cx,
8981
+ y2: baseline + 4,
8982
+ color,
8983
+ lineWidth: 2
8984
+ });
8985
+ if (p.label) {
8986
+ out.push({
8987
+ type: "text",
8988
+ x: cx,
8989
+ y: height - 22,
8990
+ text: p.label,
8991
+ color,
8992
+ fontSize: 11,
8993
+ align: "center"
8994
+ });
8995
+ }
8996
+ });
8997
+ }
8998
+ if (cells.length > 0) {
8999
+ const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
9000
+ const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
9001
+ const cw = width / maxCol;
9002
+ const ch = height / maxRow;
9003
+ cells.forEach((c, i) => {
9004
+ const x = c.col * cw;
9005
+ const y = c.row * ch;
9006
+ const color = c.color ?? DEFAULT_CELL_COLOR;
9007
+ out.push({
9008
+ type: "rect",
9009
+ id: `cell-${i}`,
9010
+ x: x + 1,
9011
+ y: y + 1,
9012
+ width: cw - 2,
9013
+ height: ch - 2,
9014
+ color: "#9ca3af",
9015
+ fill: color
9016
+ });
9017
+ const label = c.label ?? (c.value != null ? String(c.value) : void 0);
9018
+ if (label && cw >= 18 && ch >= 14) {
9019
+ out.push({
9020
+ type: "text",
9021
+ x: x + cw / 2,
9022
+ y: y + ch / 2,
9023
+ text: label,
9024
+ color: "#111827",
9025
+ fontSize: 12,
9026
+ align: "center"
9027
+ });
9028
+ }
9029
+ });
9030
+ }
9031
+ out.push(...shapes);
9032
+ return out;
9033
+ }, [bars, cells, pointers, shapes, width, height]);
9034
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
9035
+ title ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h4", children: title }) : null,
9036
+ /* @__PURE__ */ jsxRuntime.jsx(
9037
+ exports.LearningCanvas,
9038
+ {
9039
+ width,
9040
+ height,
9041
+ backgroundColor,
9042
+ shapes: derivedShapes,
9043
+ interactive,
9044
+ animate,
9045
+ onShapeClick,
9046
+ isLoading,
9047
+ error
9048
+ }
9049
+ )
9050
+ ] }) });
9051
+ };
9052
+ }
9053
+ });
8598
9054
  exports.AuthLayout = void 0;
8599
9055
  var init_AuthLayout = __esm({
8600
9056
  "components/marketing/templates/AuthLayout.tsx"() {
@@ -9563,315 +10019,6 @@ var init_BehaviorView = __esm({
9563
10019
  exports.BehaviorView.displayName = "BehaviorView";
9564
10020
  }
9565
10021
  });
9566
- function resolveColor2(color, ctx, fallback) {
9567
- if (!color) return fallback;
9568
- if (color.startsWith("var(")) {
9569
- ctx.canvas.style;
9570
- const m = /^var\((--[^,)]+)(?:,\s*([^)]+))?\)$/.exec(color);
9571
- if (m) {
9572
- const computed = getComputedStyle(ctx.canvas).getPropertyValue(m[1]).trim();
9573
- return computed || m[2]?.trim() || fallback;
9574
- }
9575
- }
9576
- return color;
9577
- }
9578
- function shapeBounds(shape) {
9579
- switch (shape.type) {
9580
- case "line":
9581
- case "arrow":
9582
- if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) return null;
9583
- return {
9584
- x: Math.min(shape.x1, shape.x2) - 6,
9585
- y: Math.min(shape.y1, shape.y2) - 6,
9586
- w: Math.abs(shape.x2 - shape.x1) + 12,
9587
- h: Math.abs(shape.y2 - shape.y1) + 12
9588
- };
9589
- case "circle":
9590
- if (shape.x == null || shape.y == null || shape.radius == null) return null;
9591
- return {
9592
- x: shape.x - shape.radius - 4,
9593
- y: shape.y - shape.radius - 4,
9594
- w: shape.radius * 2 + 8,
9595
- h: shape.radius * 2 + 8
9596
- };
9597
- case "rect":
9598
- if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) return null;
9599
- return { x: shape.x - 4, y: shape.y - 4, w: shape.width + 8, h: shape.height + 8 };
9600
- case "polygon":
9601
- if (!shape.points || shape.points.length === 0) return null;
9602
- {
9603
- const xs = shape.points.map((p) => p.x);
9604
- const ys = shape.points.map((p) => p.y);
9605
- const minX = Math.min(...xs);
9606
- const minY = Math.min(...ys);
9607
- return {
9608
- x: minX - 4,
9609
- y: minY - 4,
9610
- w: Math.max(...xs) - minX + 8,
9611
- h: Math.max(...ys) - minY + 8
9612
- };
9613
- }
9614
- case "text":
9615
- if (shape.x == null || shape.y == null) return null;
9616
- return { x: shape.x - 4, y: shape.y - (shape.fontSize ?? 14) - 4, w: 120, h: (shape.fontSize ?? 14) + 8 };
9617
- default:
9618
- return null;
9619
- }
9620
- }
9621
- function drawArrowHead(ctx, x1, y1, x2, y2, size) {
9622
- const angle = Math.atan2(y2 - y1, x2 - x1);
9623
- ctx.beginPath();
9624
- ctx.moveTo(x2, y2);
9625
- ctx.lineTo(x2 - size * Math.cos(angle - Math.PI / 6), y2 - size * Math.sin(angle - Math.PI / 6));
9626
- ctx.lineTo(x2 - size * Math.cos(angle + Math.PI / 6), y2 - size * Math.sin(angle + Math.PI / 6));
9627
- ctx.closePath();
9628
- ctx.fill();
9629
- }
9630
- function drawShape(ctx, shape, width, height) {
9631
- ctx.save();
9632
- const opacity = shape.opacity ?? 1;
9633
- ctx.globalAlpha = opacity;
9634
- const stroke = resolveColor2(shape.color, ctx, "#333333");
9635
- const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
9636
- ctx.lineWidth = shape.lineWidth ?? 2;
9637
- switch (shape.type) {
9638
- case "grid": {
9639
- const step = shape.step ?? 40;
9640
- ctx.strokeStyle = stroke;
9641
- ctx.globalAlpha = opacity * 0.25;
9642
- ctx.lineWidth = 1;
9643
- ctx.beginPath();
9644
- for (let x = 0; x <= width; x += step) {
9645
- ctx.moveTo(x, 0);
9646
- ctx.lineTo(x, height);
9647
- }
9648
- for (let y = 0; y <= height; y += step) {
9649
- ctx.moveTo(0, y);
9650
- ctx.lineTo(width, y);
9651
- }
9652
- ctx.stroke();
9653
- break;
9654
- }
9655
- case "axis": {
9656
- const axis = shape.axis ?? "x";
9657
- ctx.strokeStyle = stroke;
9658
- ctx.lineWidth = shape.lineWidth ?? 2;
9659
- ctx.beginPath();
9660
- if (axis === "x") {
9661
- ctx.moveTo(0, height / 2);
9662
- ctx.lineTo(width, height / 2);
9663
- } else {
9664
- ctx.moveTo(width / 2, 0);
9665
- ctx.lineTo(width / 2, height);
9666
- }
9667
- ctx.stroke();
9668
- break;
9669
- }
9670
- case "line": {
9671
- if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
9672
- ctx.strokeStyle = stroke;
9673
- ctx.beginPath();
9674
- ctx.moveTo(shape.x1, shape.y1);
9675
- ctx.lineTo(shape.x2, shape.y2);
9676
- ctx.stroke();
9677
- break;
9678
- }
9679
- case "arrow": {
9680
- if (shape.x1 == null || shape.y1 == null || shape.x2 == null || shape.y2 == null) break;
9681
- ctx.strokeStyle = stroke;
9682
- ctx.fillStyle = stroke;
9683
- ctx.beginPath();
9684
- ctx.moveTo(shape.x1, shape.y1);
9685
- ctx.lineTo(shape.x2, shape.y2);
9686
- ctx.stroke();
9687
- drawArrowHead(ctx, shape.x1, shape.y1, shape.x2, shape.y2, 10);
9688
- break;
9689
- }
9690
- case "circle": {
9691
- if (shape.x == null || shape.y == null || shape.radius == null) break;
9692
- ctx.beginPath();
9693
- ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2);
9694
- if (fill) {
9695
- ctx.fillStyle = fill;
9696
- ctx.fill();
9697
- }
9698
- ctx.strokeStyle = stroke;
9699
- ctx.stroke();
9700
- break;
9701
- }
9702
- case "rect": {
9703
- if (shape.x == null || shape.y == null || shape.width == null || shape.height == null) break;
9704
- if (fill) {
9705
- ctx.fillStyle = fill;
9706
- ctx.fillRect(shape.x, shape.y, shape.width, shape.height);
9707
- }
9708
- ctx.strokeStyle = stroke;
9709
- ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
9710
- break;
9711
- }
9712
- case "polygon": {
9713
- if (!shape.points || shape.points.length < 2) break;
9714
- ctx.beginPath();
9715
- ctx.moveTo(shape.points[0].x, shape.points[0].y);
9716
- for (let i = 1; i < shape.points.length; i++) {
9717
- ctx.lineTo(shape.points[i].x, shape.points[i].y);
9718
- }
9719
- ctx.closePath();
9720
- if (fill) {
9721
- ctx.fillStyle = fill;
9722
- ctx.fill();
9723
- }
9724
- ctx.strokeStyle = stroke;
9725
- ctx.stroke();
9726
- break;
9727
- }
9728
- case "path": {
9729
- if (!shape.path) break;
9730
- const p = new Path2D(shape.path);
9731
- if (fill) {
9732
- ctx.fillStyle = fill;
9733
- ctx.fill(p);
9734
- }
9735
- ctx.strokeStyle = stroke;
9736
- ctx.stroke(p);
9737
- break;
9738
- }
9739
- case "text": {
9740
- if (shape.x == null || shape.y == null || !shape.text) break;
9741
- ctx.fillStyle = stroke;
9742
- ctx.font = `${shape.fontSize ?? 14}px system-ui, sans-serif`;
9743
- ctx.textAlign = shape.align ?? "left";
9744
- ctx.textBaseline = "middle";
9745
- ctx.fillText(shape.text, shape.x, shape.y);
9746
- break;
9747
- }
9748
- }
9749
- ctx.restore();
9750
- }
9751
- exports.LearningCanvas = void 0;
9752
- var init_LearningCanvas = __esm({
9753
- "components/learning/atoms/LearningCanvas.tsx"() {
9754
- "use client";
9755
- init_cn();
9756
- init_useEventBus();
9757
- exports.LearningCanvas = ({
9758
- className,
9759
- width = 600,
9760
- height = 400,
9761
- backgroundColor,
9762
- shapes = [],
9763
- interactive = false,
9764
- animate = false,
9765
- onShapeClick,
9766
- onShapeHover,
9767
- isLoading,
9768
- error
9769
- }) => {
9770
- const canvasRef = React74.useRef(null);
9771
- const eventBus = useEventBus();
9772
- const animRef = React74.useRef(0);
9773
- const hoverIndexRef = React74.useRef(-1);
9774
- const findShapeAt = React74.useCallback((clientX, clientY) => {
9775
- const canvas = canvasRef.current;
9776
- if (!canvas) return -1;
9777
- const rect = canvas.getBoundingClientRect();
9778
- const x = clientX - rect.left;
9779
- const y = clientY - rect.top;
9780
- for (let i = shapes.length - 1; i >= 0; i--) {
9781
- const b = shapeBounds(shapes[i]);
9782
- if (b && x >= b.x && x <= b.x + b.w && y >= b.y && y <= b.y + b.h) {
9783
- return i;
9784
- }
9785
- }
9786
- return -1;
9787
- }, [shapes]);
9788
- const draw = React74.useCallback(() => {
9789
- const canvas = canvasRef.current;
9790
- if (!canvas) return;
9791
- const ctx = canvas.getContext("2d");
9792
- if (!ctx) return;
9793
- const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
9794
- canvas.width = Math.max(1, Math.floor(width * dpr));
9795
- canvas.height = Math.max(1, Math.floor(height * dpr));
9796
- canvas.style.width = `${width}px`;
9797
- canvas.style.height = `${height}px`;
9798
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
9799
- ctx.clearRect(0, 0, width, height);
9800
- if (backgroundColor) {
9801
- ctx.fillStyle = backgroundColor;
9802
- ctx.fillRect(0, 0, width, height);
9803
- }
9804
- for (const shape of shapes) {
9805
- drawShape(ctx, shape, width, height);
9806
- }
9807
- }, [width, height, backgroundColor, shapes]);
9808
- React74.useEffect(() => {
9809
- draw();
9810
- }, [draw]);
9811
- React74.useEffect(() => {
9812
- if (!animate) return;
9813
- const loop = () => {
9814
- draw();
9815
- animRef.current = requestAnimationFrame(loop);
9816
- };
9817
- animRef.current = requestAnimationFrame(loop);
9818
- return () => cancelAnimationFrame(animRef.current);
9819
- }, [animate, draw]);
9820
- const handlePointerMove = React74.useCallback(
9821
- (e) => {
9822
- if (!interactive) return;
9823
- const idx = findShapeAt(e.clientX, e.clientY);
9824
- if (idx !== hoverIndexRef.current) {
9825
- hoverIndexRef.current = idx;
9826
- if (idx >= 0) {
9827
- const shape = shapes[idx];
9828
- const payload = { id: shape.id, type: shape.type, index: idx };
9829
- if (onShapeHover) onShapeHover(payload);
9830
- else if (eventBus) eventBus.emit(`UI:SHAPE_HOVER`, payload);
9831
- }
9832
- }
9833
- },
9834
- [interactive, onShapeHover, eventBus, findShapeAt, shapes]
9835
- );
9836
- const handleClick = React74.useCallback(
9837
- (e) => {
9838
- if (!interactive) return;
9839
- const idx = findShapeAt(e.clientX, e.clientY);
9840
- if (idx >= 0) {
9841
- const shape = shapes[idx];
9842
- const payload = { id: shape.id, type: shape.type, index: idx };
9843
- if (onShapeClick) onShapeClick(payload);
9844
- else if (eventBus) eventBus.emit(`UI:SHAPE_CLICK`, payload);
9845
- }
9846
- },
9847
- [interactive, onShapeClick, eventBus, findShapeAt, shapes]
9848
- );
9849
- if (isLoading || error) {
9850
- return /* @__PURE__ */ jsxRuntime.jsx(
9851
- "div",
9852
- {
9853
- className: cn(
9854
- "flex items-center justify-center rounded border border-border bg-surface",
9855
- className
9856
- ),
9857
- style: { width, height },
9858
- children: error ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-destructive", children: error.message }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: "Loading canvas\u2026" })
9859
- }
9860
- );
9861
- }
9862
- return /* @__PURE__ */ jsxRuntime.jsx(
9863
- "canvas",
9864
- {
9865
- ref: canvasRef,
9866
- className: cn("block touch-none rounded border border-border", className),
9867
- style: { width, height },
9868
- onClick: handleClick,
9869
- onPointerMove: handlePointerMove
9870
- }
9871
- );
9872
- };
9873
- }
9874
- });
9875
10022
  exports.BiologyCanvas = void 0;
9876
10023
  var init_BiologyCanvas = __esm({
9877
10024
  "components/learning/molecules/BiologyCanvas.tsx"() {
@@ -15457,6 +15604,76 @@ var init_useImageCache = __esm({
15457
15604
  init_verificationRegistry();
15458
15605
  }
15459
15606
  });
15607
+
15608
+ // components/game/shared/atlasSlice.ts
15609
+ function isTilesheet(a) {
15610
+ return typeof a.tileWidth === "number";
15611
+ }
15612
+ function getAtlas(url, onReady) {
15613
+ if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
15614
+ atlasCache.set(url, void 0);
15615
+ fetch(url).then((r) => r.json()).then((json) => {
15616
+ atlasCache.set(url, json);
15617
+ onReady();
15618
+ }).catch(() => {
15619
+ atlasCache.set(url, null);
15620
+ });
15621
+ return void 0;
15622
+ }
15623
+ function subRectFor(atlas, sprite) {
15624
+ if (isTilesheet(atlas)) {
15625
+ let col;
15626
+ let row;
15627
+ if (sprite.includes(",")) {
15628
+ const [c, r] = sprite.split(",").map((n) => Number(n.trim()));
15629
+ col = c;
15630
+ row = r;
15631
+ } else {
15632
+ const i = Number(sprite);
15633
+ if (!Number.isFinite(i)) return null;
15634
+ col = i % atlas.columns;
15635
+ row = Math.floor(i / atlas.columns);
15636
+ }
15637
+ if (!Number.isFinite(col) || !Number.isFinite(row) || col < 0 || row < 0 || col >= atlas.columns || row >= atlas.rows) return null;
15638
+ const margin = atlas.margin ?? 0;
15639
+ const spacing = atlas.spacing ?? 0;
15640
+ return {
15641
+ sx: margin + col * (atlas.tileWidth + spacing),
15642
+ sy: margin + row * (atlas.tileHeight + spacing),
15643
+ sw: atlas.tileWidth,
15644
+ sh: atlas.tileHeight
15645
+ };
15646
+ }
15647
+ const st = atlas.subTextures[sprite];
15648
+ if (!st) return null;
15649
+ return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
15650
+ }
15651
+ function isAtlasAsset(asset) {
15652
+ return !!asset && typeof asset.atlas === "string" && typeof asset.sprite === "string";
15653
+ }
15654
+ function resolveAssetSource(img, asset, onReady) {
15655
+ if (isAtlasAsset(asset)) {
15656
+ const atlas = getAtlas(asset.atlas, onReady);
15657
+ if (!atlas) return null;
15658
+ const rect = subRectFor(atlas, asset.sprite);
15659
+ if (!rect) return null;
15660
+ return { img, rect, aspect: rect.sw / rect.sh };
15661
+ }
15662
+ const natW = img.naturalWidth || 1;
15663
+ const natH = img.naturalHeight || 1;
15664
+ return { img, rect: null, aspect: natW / natH };
15665
+ }
15666
+ function blit(ctx, src, dx, dy, dw, dh) {
15667
+ if (src.rect) ctx.drawImage(src.img, src.rect.sx, src.rect.sy, src.rect.sw, src.rect.sh, dx, dy, dw, dh);
15668
+ else ctx.drawImage(src.img, dx, dy, dw, dh);
15669
+ }
15670
+ var atlasCache;
15671
+ var init_atlasSlice = __esm({
15672
+ "components/game/shared/atlasSlice.ts"() {
15673
+ "use client";
15674
+ atlasCache = /* @__PURE__ */ new Map();
15675
+ }
15676
+ });
15460
15677
  function useCamera() {
15461
15678
  const cameraRef = React74.useRef({ x: 0, y: 0, zoom: 1 });
15462
15679
  const targetCameraRef = React74.useRef(null);
@@ -16212,15 +16429,18 @@ function SideView({
16212
16429
  const platType = plat.type ?? "ground";
16213
16430
  const spriteAsset = tSprites?.[platType];
16214
16431
  const tileImg = spriteAsset ? loadImage(spriteAsset.url) : null;
16215
- if (tileImg) {
16216
- const tileW = tileImg.naturalWidth;
16217
- const tileH = tileImg.naturalHeight;
16432
+ const tileSrc = tileImg ? resolveAssetSource(tileImg, spriteAsset, NOOP) : null;
16433
+ if (tileSrc) {
16434
+ const originX = tileSrc.rect ? tileSrc.rect.sx : 0;
16435
+ const originY = tileSrc.rect ? tileSrc.rect.sy : 0;
16436
+ const tileW = tileSrc.rect ? tileSrc.rect.sw : tileImg.naturalWidth;
16437
+ const tileH = tileSrc.rect ? tileSrc.rect.sh : tileImg.naturalHeight;
16218
16438
  const scaleH = plat.height / tileH;
16219
16439
  const scaledW = tileW * scaleH;
16220
16440
  for (let tx = 0; tx < plat.width; tx += scaledW) {
16221
16441
  const drawW = Math.min(scaledW, plat.width - tx);
16222
16442
  const srcW = drawW / scaleH;
16223
- ctx.drawImage(tileImg, 0, 0, srcW, tileH, platX + tx, platY, drawW, plat.height);
16443
+ ctx.drawImage(tileImg, originX, originY, srcW, tileH, platX + tx, platY, drawW, plat.height);
16224
16444
  }
16225
16445
  } else {
16226
16446
  const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
@@ -16254,14 +16474,15 @@ function SideView({
16254
16474
  const ppy = py - camY;
16255
16475
  const facingRight = auth.facingRight ?? true;
16256
16476
  const playerImg = pSprite ? loadImage(pSprite.url) : null;
16257
- if (playerImg) {
16477
+ const playerSrc = playerImg ? resolveAssetSource(playerImg, pSprite, NOOP) : null;
16478
+ if (playerSrc) {
16258
16479
  ctx.save();
16259
16480
  if (!facingRight) {
16260
16481
  ctx.translate(ppx + pw, ppy);
16261
16482
  ctx.scale(-1, 1);
16262
- ctx.drawImage(playerImg, 0, 0, pw, ph);
16483
+ blit(ctx, playerSrc, 0, 0, pw, ph);
16263
16484
  } else {
16264
- ctx.drawImage(playerImg, ppx, ppy, pw, ph);
16485
+ blit(ctx, playerSrc, ppx, ppy, pw, ph);
16265
16486
  }
16266
16487
  ctx.restore();
16267
16488
  } else {
@@ -16462,10 +16683,11 @@ function Canvas2D({
16462
16683
  const attackTargetSet = React74.useMemo(() => new Set(attackTargets.map((p) => `${p.x},${p.y}`)), [attackTargets]);
16463
16684
  const spriteUrls = React74.useMemo(() => {
16464
16685
  const urls = [];
16686
+ const toUrl = (x) => typeof x === "string" ? x : x?.url;
16465
16687
  for (const tile of sortedTiles) {
16466
16688
  if (tile.terrainSprite) urls.push(tile.terrainSprite.url);
16467
16689
  else if (getTerrainSprite) {
16468
- const url = getTerrainSprite(tile.terrain ?? "");
16690
+ const url = toUrl(getTerrainSprite(tile.terrain ?? ""));
16469
16691
  if (url) urls.push(url);
16470
16692
  } else {
16471
16693
  const url = assetManifest?.terrains?.[tile.terrain ?? ""]?.url;
@@ -16475,7 +16697,7 @@ function Canvas2D({
16475
16697
  for (const feature of features) {
16476
16698
  if (feature.sprite) urls.push(feature.sprite.url);
16477
16699
  else if (getFeatureSprite) {
16478
- const url = getFeatureSprite(feature.type);
16700
+ const url = toUrl(getFeatureSprite(feature.type));
16479
16701
  if (url) urls.push(url);
16480
16702
  } else {
16481
16703
  const url = assetManifest?.features?.[feature.type]?.url;
@@ -16485,7 +16707,7 @@ function Canvas2D({
16485
16707
  for (const unit of units) {
16486
16708
  if (unit.sprite) urls.push(unit.sprite.url);
16487
16709
  else if (getUnitSprite) {
16488
- const url = getUnitSprite(unit);
16710
+ const url = toUrl(getUnitSprite(unit));
16489
16711
  if (url) urls.push(url);
16490
16712
  } else if (unit.unitType) {
16491
16713
  const url = assetManifest?.units?.[unit.unitType]?.url;
@@ -16526,14 +16748,25 @@ function Canvas2D({
16526
16748
  screenToWorld,
16527
16749
  lerpToTarget
16528
16750
  } = useCamera();
16529
- const resolveTerrainSpriteUrl = React74.useCallback((tile) => {
16530
- return tile.terrainSprite?.url || getTerrainSprite?.(tile.terrain ?? "") || assetManifest?.terrains?.[tile.terrain ?? ""]?.url;
16751
+ const [, setAtlasVersion] = React74.useState(0);
16752
+ const bumpAtlas = React74.useCallback(() => setAtlasVersion((v) => v + 1), []);
16753
+ const resolveTerrainAsset = React74.useCallback((tile) => {
16754
+ if (tile.terrainSprite) return tile.terrainSprite;
16755
+ const s = getTerrainSprite?.(tile.terrain ?? "");
16756
+ if (s) return typeof s === "string" ? { url: s } : s;
16757
+ return assetManifest?.terrains?.[tile.terrain ?? ""];
16531
16758
  }, [getTerrainSprite, assetManifest]);
16532
- const resolveFeatureSpriteUrl = React74.useCallback((featureType) => {
16533
- return getFeatureSprite?.(featureType) || assetManifest?.features?.[featureType]?.url;
16759
+ const resolveFeatureAsset = React74.useCallback((feature) => {
16760
+ if (feature.sprite) return feature.sprite;
16761
+ const s = getFeatureSprite?.(feature.type);
16762
+ if (s) return typeof s === "string" ? { url: s } : s;
16763
+ return assetManifest?.features?.[feature.type];
16534
16764
  }, [getFeatureSprite, assetManifest]);
16535
- const resolveUnitSpriteUrl = React74.useCallback((unit) => {
16536
- return unit.sprite?.url || getUnitSprite?.(unit) || (unit.unitType ? assetManifest?.units?.[unit.unitType]?.url : void 0);
16765
+ const resolveUnitAsset = React74.useCallback((unit) => {
16766
+ if (unit.sprite) return unit.sprite;
16767
+ const s = getUnitSprite?.(unit);
16768
+ if (s) return typeof s === "string" ? { url: s } : s;
16769
+ return unit.unitType ? assetManifest?.units?.[unit.unitType] : void 0;
16537
16770
  }, [getUnitSprite, assetManifest]);
16538
16771
  const miniMapTiles = React74.useMemo(() => {
16539
16772
  if (!showMinimap) return [];
@@ -16597,18 +16830,17 @@ function Canvas2D({
16597
16830
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
16598
16831
  continue;
16599
16832
  }
16600
- const spriteUrl = resolveTerrainSpriteUrl(tile);
16601
- const img = spriteUrl ? getImage(spriteUrl) : null;
16602
- if (img) {
16603
- if (img.naturalWidth === 0) {
16604
- ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
16605
- } else {
16606
- const drawW = scaledTileWidth;
16607
- const drawH = scaledTileWidth * (img.naturalHeight / img.naturalWidth);
16608
- const drawX = pos.x;
16609
- const drawY = flatLike ? pos.y : pos.y + scaledTileHeight - drawH;
16610
- ctx.drawImage(img, drawX, drawY, drawW, drawH);
16611
- }
16833
+ const terrainAsset = resolveTerrainAsset(tile);
16834
+ const img = terrainAsset?.url ? getImage(terrainAsset.url) : null;
16835
+ const src = img ? resolveAssetSource(img, terrainAsset, bumpAtlas) : null;
16836
+ if (src) {
16837
+ const drawW = scaledTileWidth;
16838
+ const drawH = scaledTileWidth / src.aspect;
16839
+ const drawX = pos.x;
16840
+ const drawY = flatLike ? pos.y : pos.y + scaledTileHeight - drawH;
16841
+ blit(ctx, src, drawX, drawY, drawW, drawH);
16842
+ } else if (img && img.naturalWidth === 0) {
16843
+ ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
16612
16844
  } else {
16613
16845
  const centerX = pos.x + scaledTileWidth / 2;
16614
16846
  const topY = pos.y + scaledDiamondTopY;
@@ -16662,15 +16894,16 @@ function Canvas2D({
16662
16894
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
16663
16895
  continue;
16664
16896
  }
16665
- const spriteUrl = feature.sprite?.url || resolveFeatureSpriteUrl(feature.type);
16666
- const img = spriteUrl ? getImage(spriteUrl) : null;
16897
+ const featureAsset = resolveFeatureAsset(feature);
16898
+ const img = featureAsset?.url ? getImage(featureAsset.url) : null;
16899
+ const src = img ? resolveAssetSource(img, featureAsset, bumpAtlas) : null;
16667
16900
  const centerX = pos.x + scaledTileWidth / 2;
16668
16901
  const featureGroundY = pos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
16669
16902
  const isCastle = feature.type === "castle";
16670
16903
  const featureDrawH = isCastle ? scaledFloorHeight * 3.5 : scaledFloorHeight * 1.6;
16671
16904
  const maxFeatureW = isCastle ? scaledTileWidth * 1.8 : scaledTileWidth * 0.7;
16672
- if (img) {
16673
- const ar = img.naturalWidth / img.naturalHeight;
16905
+ if (src) {
16906
+ const ar = src.aspect;
16674
16907
  let drawH = featureDrawH;
16675
16908
  let drawW = featureDrawH * ar;
16676
16909
  if (drawW > maxFeatureW) {
@@ -16679,7 +16912,7 @@ function Canvas2D({
16679
16912
  }
16680
16913
  const drawX = centerX - drawW / 2;
16681
16914
  const drawY = featureGroundY - drawH;
16682
- ctx.drawImage(img, drawX, drawY, drawW, drawH);
16915
+ blit(ctx, src, drawX, drawY, drawW, drawH);
16683
16916
  } else {
16684
16917
  const color = exports.FEATURE_COLORS[feature.type] || exports.FEATURE_COLORS.default;
16685
16918
  ctx.beginPath();
@@ -16708,17 +16941,18 @@ function Canvas2D({
16708
16941
  const centerX = pos.x + scaledTileWidth / 2;
16709
16942
  const groundY = pos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
16710
16943
  const breatheOffset = 0;
16711
- const unitSpriteUrl = resolveUnitSpriteUrl(unit);
16712
- const img = unitSpriteUrl ? getImage(unitSpriteUrl) : null;
16944
+ const unitAsset = resolveUnitAsset(unit);
16945
+ const img = unitAsset?.url ? getImage(unitAsset.url) : null;
16713
16946
  const unitDrawH = scaledFloorHeight * spriteHeightRatio * unitScale;
16714
16947
  const maxUnitW = scaledTileWidth * spriteMaxWidthRatio * unitScale;
16715
16948
  const unitIsSheet = unit.spriteSheet?.url !== void 0;
16949
+ const unitSrc = img && !unitIsSheet ? resolveAssetSource(img, unitAsset, bumpAtlas) : null;
16716
16950
  const SHEET_ROWS = 5;
16717
16951
  const sheetFrameW = img ? img.naturalWidth / exports.SHEET_COLUMNS : 0;
16718
16952
  const sheetFrameH = img ? img.naturalHeight / SHEET_ROWS : 0;
16719
16953
  const frameW = unitIsSheet ? sheetFrameW : img?.naturalWidth ?? 1;
16720
16954
  const frameH = unitIsSheet ? sheetFrameH : img?.naturalHeight ?? 1;
16721
- const ar = frameW / (frameH || 1);
16955
+ const ar = unitIsSheet ? sheetFrameW / (sheetFrameH || 1) : unitSrc ? unitSrc.aspect : frameW / (frameH || 1);
16722
16956
  let drawH = unitDrawH;
16723
16957
  let drawW = unitDrawH * ar;
16724
16958
  if (drawW > maxUnitW) {
@@ -16734,6 +16968,8 @@ function Canvas2D({
16734
16968
  if (img) {
16735
16969
  if (unitIsSheet) {
16736
16970
  ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
16971
+ } else if (unitSrc) {
16972
+ blit(ctx, unitSrc, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
16737
16973
  } else {
16738
16974
  ctx.drawImage(img, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
16739
16975
  }
@@ -16782,6 +17018,8 @@ function Canvas2D({
16782
17018
  const drawUnit = (x) => {
16783
17019
  if (unitIsSheet) {
16784
17020
  ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, x, spriteY, drawW, drawH);
17021
+ } else if (unitSrc) {
17022
+ blit(ctx, unitSrc, x, spriteY, drawW, drawH);
16785
17023
  } else {
16786
17024
  ctx.drawImage(img, x, spriteY, drawW, drawH);
16787
17025
  }
@@ -16832,11 +17070,12 @@ function Canvas2D({
16832
17070
  flatLike,
16833
17071
  scale,
16834
17072
  debug2,
16835
- resolveTerrainSpriteUrl,
16836
- resolveFeatureSpriteUrl,
16837
- resolveUnitSpriteUrl,
17073
+ resolveTerrainAsset,
17074
+ resolveFeatureAsset,
17075
+ resolveUnitAsset,
16838
17076
  resolveFrameForUnit,
16839
17077
  getImage,
17078
+ bumpAtlas,
16840
17079
  baseOffsetX,
16841
17080
  scaledTileWidth,
16842
17081
  scaledTileHeight,
@@ -17150,7 +17389,7 @@ function Canvas2D({
17150
17389
  }
17151
17390
  );
17152
17391
  }
17153
- var PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR, DEFAULT_PLATFORMS;
17392
+ var PLATFORM_COLORS, NOOP, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR, DEFAULT_PLATFORMS;
17154
17393
  var init_Canvas2D = __esm({
17155
17394
  "components/game/2d/molecules/Canvas2D.tsx"() {
17156
17395
  "use client";
@@ -17164,6 +17403,7 @@ var init_Canvas2D = __esm({
17164
17403
  init_MiniMap();
17165
17404
  init_HealthBar();
17166
17405
  init_useImageCache();
17406
+ init_atlasSlice();
17167
17407
  init_useCamera();
17168
17408
  init_useCanvasGestures();
17169
17409
  init_useRenderInterpolation();
@@ -17177,6 +17417,8 @@ var init_Canvas2D = __esm({
17177
17417
  hazard: "#c0392b",
17178
17418
  goal: "#f1c40f"
17179
17419
  };
17420
+ NOOP = () => {
17421
+ };
17180
17422
  PLAYER_COLOR = "#3498db";
17181
17423
  PLAYER_EYE_COLOR = "#ffffff";
17182
17424
  SKY_GRADIENT_TOP = "#1a1a2e";
@@ -40929,6 +41171,7 @@ var init_molecules2 = __esm({
40929
41171
  init_PhysicsCanvas();
40930
41172
  init_BiologyCanvas();
40931
41173
  init_ChemistryCanvas();
41174
+ init_AlgorithmCanvas();
40932
41175
  init_GraphView();
40933
41176
  init_MapView();
40934
41177
  init_NumberStepper();
@@ -47587,6 +47830,7 @@ var init_component_registry_generated = __esm({
47587
47830
  init_ActionTile();
47588
47831
  init_ActivationBlock();
47589
47832
  init_ComponentPatterns();
47833
+ init_AlgorithmCanvas();
47590
47834
  init_AnimatedCounter();
47591
47835
  init_AnimatedGraphic();
47592
47836
  init_AnimatedReveal();
@@ -47885,6 +48129,7 @@ var init_component_registry_generated = __esm({
47885
48129
  "ActivationBlock": exports.ActivationBlock,
47886
48130
  "Alert": AlertPattern,
47887
48131
  "AlertPattern": AlertPattern,
48132
+ "AlgorithmCanvas": exports.AlgorithmCanvas,
47888
48133
  "AnimatedCounter": exports.AnimatedCounter,
47889
48134
  "AnimatedGraphic": exports.AnimatedGraphic,
47890
48135
  "AnimatedReveal": exports.AnimatedReveal,