@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.
- package/dist/avl/index.cjs +284 -40
- package/dist/avl/index.js +284 -40
- package/dist/components/core/molecules/index.d.ts +1 -0
- package/dist/components/game/2d/molecules/Canvas2D.d.ts +6 -6
- package/dist/components/game/shared/atlasSlice.d.ts +58 -0
- package/dist/components/index.cjs +594 -349
- package/dist/components/index.js +595 -350
- package/dist/components/learning/molecules/AlgorithmCanvas.d.ts +58 -0
- package/dist/providers/index.cjs +284 -40
- package/dist/providers/index.js +284 -40
- package/dist/runtime/index.cjs +284 -40
- package/dist/runtime/index.js +284 -40
- package/package.json +2 -2
|
@@ -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
|
-
|
|
16216
|
-
|
|
16217
|
-
const
|
|
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,
|
|
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
|
-
|
|
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
|
|
16483
|
+
blit(ctx, playerSrc, 0, 0, pw, ph);
|
|
16263
16484
|
} else {
|
|
16264
|
-
ctx
|
|
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
|
|
16530
|
-
|
|
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
|
|
16533
|
-
|
|
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
|
|
16536
|
-
|
|
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
|
|
16601
|
-
const img =
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
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
|
|
16666
|
-
const img =
|
|
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 (
|
|
16673
|
-
const ar =
|
|
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
|
|
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
|
|
16712
|
-
const img =
|
|
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
|
-
|
|
16836
|
-
|
|
16837
|
-
|
|
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,
|