@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.js CHANGED
@@ -13370,6 +13370,76 @@ var init_StateGraph = __esm({
13370
13370
  init_TransitionArrow();
13371
13371
  }
13372
13372
  });
13373
+
13374
+ // components/game/shared/atlasSlice.ts
13375
+ function isTilesheet(a) {
13376
+ return typeof a.tileWidth === "number";
13377
+ }
13378
+ function getAtlas(url, onReady) {
13379
+ if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
13380
+ atlasCache.set(url, void 0);
13381
+ fetch(url).then((r2) => r2.json()).then((json) => {
13382
+ atlasCache.set(url, json);
13383
+ onReady();
13384
+ }).catch(() => {
13385
+ atlasCache.set(url, null);
13386
+ });
13387
+ return void 0;
13388
+ }
13389
+ function subRectFor(atlas, sprite) {
13390
+ if (isTilesheet(atlas)) {
13391
+ let col;
13392
+ let row;
13393
+ if (sprite.includes(",")) {
13394
+ const [c, r2] = sprite.split(",").map((n) => Number(n.trim()));
13395
+ col = c;
13396
+ row = r2;
13397
+ } else {
13398
+ const i = Number(sprite);
13399
+ if (!Number.isFinite(i)) return null;
13400
+ col = i % atlas.columns;
13401
+ row = Math.floor(i / atlas.columns);
13402
+ }
13403
+ if (!Number.isFinite(col) || !Number.isFinite(row) || col < 0 || row < 0 || col >= atlas.columns || row >= atlas.rows) return null;
13404
+ const margin = atlas.margin ?? 0;
13405
+ const spacing = atlas.spacing ?? 0;
13406
+ return {
13407
+ sx: margin + col * (atlas.tileWidth + spacing),
13408
+ sy: margin + row * (atlas.tileHeight + spacing),
13409
+ sw: atlas.tileWidth,
13410
+ sh: atlas.tileHeight
13411
+ };
13412
+ }
13413
+ const st = atlas.subTextures[sprite];
13414
+ if (!st) return null;
13415
+ return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
13416
+ }
13417
+ function isAtlasAsset(asset) {
13418
+ return !!asset && typeof asset.atlas === "string" && typeof asset.sprite === "string";
13419
+ }
13420
+ function resolveAssetSource(img, asset, onReady) {
13421
+ if (isAtlasAsset(asset)) {
13422
+ const atlas = getAtlas(asset.atlas, onReady);
13423
+ if (!atlas) return null;
13424
+ const rect = subRectFor(atlas, asset.sprite);
13425
+ if (!rect) return null;
13426
+ return { img, rect, aspect: rect.sw / rect.sh };
13427
+ }
13428
+ const natW = img.naturalWidth || 1;
13429
+ const natH = img.naturalHeight || 1;
13430
+ return { img, rect: null, aspect: natW / natH };
13431
+ }
13432
+ function blit(ctx, src, dx, dy, dw, dh) {
13433
+ if (src.rect) ctx.drawImage(src.img, src.rect.sx, src.rect.sy, src.rect.sw, src.rect.sh, dx, dy, dw, dh);
13434
+ else ctx.drawImage(src.img, dx, dy, dw, dh);
13435
+ }
13436
+ var atlasCache;
13437
+ var init_atlasSlice = __esm({
13438
+ "components/game/shared/atlasSlice.ts"() {
13439
+ "use client";
13440
+ atlasCache = /* @__PURE__ */ new Map();
13441
+ }
13442
+ });
13373
13443
  function useCamera() {
13374
13444
  const cameraRef = useRef({ x: 0, y: 0, zoom: 1 });
13375
13445
  const targetCameraRef = useRef(null);
@@ -13941,15 +14011,18 @@ function SideView({
13941
14011
  const platType = plat.type ?? "ground";
13942
14012
  const spriteAsset = tSprites?.[platType];
13943
14013
  const tileImg = spriteAsset ? loadImage(spriteAsset.url) : null;
13944
- if (tileImg) {
13945
- const tileW = tileImg.naturalWidth;
13946
- const tileH = tileImg.naturalHeight;
14014
+ const tileSrc = tileImg ? resolveAssetSource(tileImg, spriteAsset, NOOP) : null;
14015
+ if (tileSrc) {
14016
+ const originX = tileSrc.rect ? tileSrc.rect.sx : 0;
14017
+ const originY = tileSrc.rect ? tileSrc.rect.sy : 0;
14018
+ const tileW = tileSrc.rect ? tileSrc.rect.sw : tileImg.naturalWidth;
14019
+ const tileH = tileSrc.rect ? tileSrc.rect.sh : tileImg.naturalHeight;
13947
14020
  const scaleH = plat.height / tileH;
13948
14021
  const scaledW = tileW * scaleH;
13949
14022
  for (let tx = 0; tx < plat.width; tx += scaledW) {
13950
14023
  const drawW = Math.min(scaledW, plat.width - tx);
13951
14024
  const srcW = drawW / scaleH;
13952
- ctx.drawImage(tileImg, 0, 0, srcW, tileH, platX + tx, platY, drawW, plat.height);
14025
+ ctx.drawImage(tileImg, originX, originY, srcW, tileH, platX + tx, platY, drawW, plat.height);
13953
14026
  }
13954
14027
  } else {
13955
14028
  const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
@@ -13983,14 +14056,15 @@ function SideView({
13983
14056
  const ppy = py - camY;
13984
14057
  const facingRight = auth.facingRight ?? true;
13985
14058
  const playerImg = pSprite ? loadImage(pSprite.url) : null;
13986
- if (playerImg) {
14059
+ const playerSrc = playerImg ? resolveAssetSource(playerImg, pSprite, NOOP) : null;
14060
+ if (playerSrc) {
13987
14061
  ctx.save();
13988
14062
  if (!facingRight) {
13989
14063
  ctx.translate(ppx + pw, ppy);
13990
14064
  ctx.scale(-1, 1);
13991
- ctx.drawImage(playerImg, 0, 0, pw, ph);
14065
+ blit(ctx, playerSrc, 0, 0, pw, ph);
13992
14066
  } else {
13993
- ctx.drawImage(playerImg, ppx, ppy, pw, ph);
14067
+ blit(ctx, playerSrc, ppx, ppy, pw, ph);
13994
14068
  }
13995
14069
  ctx.restore();
13996
14070
  } else {
@@ -14191,10 +14265,11 @@ function Canvas2D({
14191
14265
  const attackTargetSet = useMemo(() => new Set(attackTargets.map((p) => `${p.x},${p.y}`)), [attackTargets]);
14192
14266
  const spriteUrls = useMemo(() => {
14193
14267
  const urls = [];
14268
+ const toUrl = (x) => typeof x === "string" ? x : x?.url;
14194
14269
  for (const tile of sortedTiles) {
14195
14270
  if (tile.terrainSprite) urls.push(tile.terrainSprite.url);
14196
14271
  else if (getTerrainSprite) {
14197
- const url = getTerrainSprite(tile.terrain ?? "");
14272
+ const url = toUrl(getTerrainSprite(tile.terrain ?? ""));
14198
14273
  if (url) urls.push(url);
14199
14274
  } else {
14200
14275
  const url = assetManifest?.terrains?.[tile.terrain ?? ""]?.url;
@@ -14204,7 +14279,7 @@ function Canvas2D({
14204
14279
  for (const feature of features) {
14205
14280
  if (feature.sprite) urls.push(feature.sprite.url);
14206
14281
  else if (getFeatureSprite) {
14207
- const url = getFeatureSprite(feature.type);
14282
+ const url = toUrl(getFeatureSprite(feature.type));
14208
14283
  if (url) urls.push(url);
14209
14284
  } else {
14210
14285
  const url = assetManifest?.features?.[feature.type]?.url;
@@ -14214,7 +14289,7 @@ function Canvas2D({
14214
14289
  for (const unit of units) {
14215
14290
  if (unit.sprite) urls.push(unit.sprite.url);
14216
14291
  else if (getUnitSprite) {
14217
- const url = getUnitSprite(unit);
14292
+ const url = toUrl(getUnitSprite(unit));
14218
14293
  if (url) urls.push(url);
14219
14294
  } else if (unit.unitType) {
14220
14295
  const url = assetManifest?.units?.[unit.unitType]?.url;
@@ -14255,14 +14330,25 @@ function Canvas2D({
14255
14330
  screenToWorld,
14256
14331
  lerpToTarget
14257
14332
  } = useCamera();
14258
- const resolveTerrainSpriteUrl = useCallback((tile) => {
14259
- return tile.terrainSprite?.url || getTerrainSprite?.(tile.terrain ?? "") || assetManifest?.terrains?.[tile.terrain ?? ""]?.url;
14333
+ const [, setAtlasVersion] = useState(0);
14334
+ const bumpAtlas = useCallback(() => setAtlasVersion((v) => v + 1), []);
14335
+ const resolveTerrainAsset = useCallback((tile) => {
14336
+ if (tile.terrainSprite) return tile.terrainSprite;
14337
+ const s = getTerrainSprite?.(tile.terrain ?? "");
14338
+ if (s) return typeof s === "string" ? { url: s } : s;
14339
+ return assetManifest?.terrains?.[tile.terrain ?? ""];
14260
14340
  }, [getTerrainSprite, assetManifest]);
14261
- const resolveFeatureSpriteUrl = useCallback((featureType) => {
14262
- return getFeatureSprite?.(featureType) || assetManifest?.features?.[featureType]?.url;
14341
+ const resolveFeatureAsset = useCallback((feature) => {
14342
+ if (feature.sprite) return feature.sprite;
14343
+ const s = getFeatureSprite?.(feature.type);
14344
+ if (s) return typeof s === "string" ? { url: s } : s;
14345
+ return assetManifest?.features?.[feature.type];
14263
14346
  }, [getFeatureSprite, assetManifest]);
14264
- const resolveUnitSpriteUrl = useCallback((unit) => {
14265
- return unit.sprite?.url || getUnitSprite?.(unit) || (unit.unitType ? assetManifest?.units?.[unit.unitType]?.url : void 0);
14347
+ const resolveUnitAsset = useCallback((unit) => {
14348
+ if (unit.sprite) return unit.sprite;
14349
+ const s = getUnitSprite?.(unit);
14350
+ if (s) return typeof s === "string" ? { url: s } : s;
14351
+ return unit.unitType ? assetManifest?.units?.[unit.unitType] : void 0;
14266
14352
  }, [getUnitSprite, assetManifest]);
14267
14353
  const miniMapTiles = useMemo(() => {
14268
14354
  if (!showMinimap) return [];
@@ -14326,18 +14412,17 @@ function Canvas2D({
14326
14412
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
14327
14413
  continue;
14328
14414
  }
14329
- const spriteUrl = resolveTerrainSpriteUrl(tile);
14330
- const img = spriteUrl ? getImage(spriteUrl) : null;
14331
- if (img) {
14332
- if (img.naturalWidth === 0) {
14333
- ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
14334
- } else {
14335
- const drawW = scaledTileWidth;
14336
- const drawH = scaledTileWidth * (img.naturalHeight / img.naturalWidth);
14337
- const drawX = pos.x;
14338
- const drawY = flatLike ? pos.y : pos.y + scaledTileHeight - drawH;
14339
- ctx.drawImage(img, drawX, drawY, drawW, drawH);
14340
- }
14415
+ const terrainAsset = resolveTerrainAsset(tile);
14416
+ const img = terrainAsset?.url ? getImage(terrainAsset.url) : null;
14417
+ const src = img ? resolveAssetSource(img, terrainAsset, bumpAtlas) : null;
14418
+ if (src) {
14419
+ const drawW = scaledTileWidth;
14420
+ const drawH = scaledTileWidth / src.aspect;
14421
+ const drawX = pos.x;
14422
+ const drawY = flatLike ? pos.y : pos.y + scaledTileHeight - drawH;
14423
+ blit(ctx, src, drawX, drawY, drawW, drawH);
14424
+ } else if (img && img.naturalWidth === 0) {
14425
+ ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
14341
14426
  } else {
14342
14427
  const centerX = pos.x + scaledTileWidth / 2;
14343
14428
  const topY = pos.y + scaledDiamondTopY;
@@ -14391,15 +14476,16 @@ function Canvas2D({
14391
14476
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
14392
14477
  continue;
14393
14478
  }
14394
- const spriteUrl = feature.sprite?.url || resolveFeatureSpriteUrl(feature.type);
14395
- const img = spriteUrl ? getImage(spriteUrl) : null;
14479
+ const featureAsset = resolveFeatureAsset(feature);
14480
+ const img = featureAsset?.url ? getImage(featureAsset.url) : null;
14481
+ const src = img ? resolveAssetSource(img, featureAsset, bumpAtlas) : null;
14396
14482
  const centerX = pos.x + scaledTileWidth / 2;
14397
14483
  const featureGroundY = pos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
14398
14484
  const isCastle = feature.type === "castle";
14399
14485
  const featureDrawH = isCastle ? scaledFloorHeight * 3.5 : scaledFloorHeight * 1.6;
14400
14486
  const maxFeatureW = isCastle ? scaledTileWidth * 1.8 : scaledTileWidth * 0.7;
14401
- if (img) {
14402
- const ar = img.naturalWidth / img.naturalHeight;
14487
+ if (src) {
14488
+ const ar = src.aspect;
14403
14489
  let drawH = featureDrawH;
14404
14490
  let drawW = featureDrawH * ar;
14405
14491
  if (drawW > maxFeatureW) {
@@ -14408,7 +14494,7 @@ function Canvas2D({
14408
14494
  }
14409
14495
  const drawX = centerX - drawW / 2;
14410
14496
  const drawY = featureGroundY - drawH;
14411
- ctx.drawImage(img, drawX, drawY, drawW, drawH);
14497
+ blit(ctx, src, drawX, drawY, drawW, drawH);
14412
14498
  } else {
14413
14499
  const color = FEATURE_COLORS[feature.type] || FEATURE_COLORS.default;
14414
14500
  ctx.beginPath();
@@ -14437,17 +14523,18 @@ function Canvas2D({
14437
14523
  const centerX = pos.x + scaledTileWidth / 2;
14438
14524
  const groundY = pos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
14439
14525
  const breatheOffset = 0;
14440
- const unitSpriteUrl = resolveUnitSpriteUrl(unit);
14441
- const img = unitSpriteUrl ? getImage(unitSpriteUrl) : null;
14526
+ const unitAsset = resolveUnitAsset(unit);
14527
+ const img = unitAsset?.url ? getImage(unitAsset.url) : null;
14442
14528
  const unitDrawH = scaledFloorHeight * spriteHeightRatio * unitScale;
14443
14529
  const maxUnitW = scaledTileWidth * spriteMaxWidthRatio * unitScale;
14444
14530
  const unitIsSheet = unit.spriteSheet?.url !== void 0;
14531
+ const unitSrc = img && !unitIsSheet ? resolveAssetSource(img, unitAsset, bumpAtlas) : null;
14445
14532
  const SHEET_ROWS = 5;
14446
14533
  const sheetFrameW = img ? img.naturalWidth / SHEET_COLUMNS : 0;
14447
14534
  const sheetFrameH = img ? img.naturalHeight / SHEET_ROWS : 0;
14448
14535
  const frameW = unitIsSheet ? sheetFrameW : img?.naturalWidth ?? 1;
14449
14536
  const frameH = unitIsSheet ? sheetFrameH : img?.naturalHeight ?? 1;
14450
- const ar = frameW / (frameH || 1);
14537
+ const ar = unitIsSheet ? sheetFrameW / (sheetFrameH || 1) : unitSrc ? unitSrc.aspect : frameW / (frameH || 1);
14451
14538
  let drawH = unitDrawH;
14452
14539
  let drawW = unitDrawH * ar;
14453
14540
  if (drawW > maxUnitW) {
@@ -14463,6 +14550,8 @@ function Canvas2D({
14463
14550
  if (img) {
14464
14551
  if (unitIsSheet) {
14465
14552
  ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
14553
+ } else if (unitSrc) {
14554
+ blit(ctx, unitSrc, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
14466
14555
  } else {
14467
14556
  ctx.drawImage(img, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
14468
14557
  }
@@ -14511,6 +14600,8 @@ function Canvas2D({
14511
14600
  const drawUnit = (x) => {
14512
14601
  if (unitIsSheet) {
14513
14602
  ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, x, spriteY, drawW, drawH);
14603
+ } else if (unitSrc) {
14604
+ blit(ctx, unitSrc, x, spriteY, drawW, drawH);
14514
14605
  } else {
14515
14606
  ctx.drawImage(img, x, spriteY, drawW, drawH);
14516
14607
  }
@@ -14561,11 +14652,12 @@ function Canvas2D({
14561
14652
  flatLike,
14562
14653
  scale,
14563
14654
  debug2,
14564
- resolveTerrainSpriteUrl,
14565
- resolveFeatureSpriteUrl,
14566
- resolveUnitSpriteUrl,
14655
+ resolveTerrainAsset,
14656
+ resolveFeatureAsset,
14657
+ resolveUnitAsset,
14567
14658
  resolveFrameForUnit,
14568
14659
  getImage,
14660
+ bumpAtlas,
14569
14661
  baseOffsetX,
14570
14662
  scaledTileWidth,
14571
14663
  scaledTileHeight,
@@ -14879,7 +14971,7 @@ function Canvas2D({
14879
14971
  }
14880
14972
  );
14881
14973
  }
14882
- var PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR, DEFAULT_PLATFORMS;
14974
+ var PLATFORM_COLORS, NOOP, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR, DEFAULT_PLATFORMS;
14883
14975
  var init_Canvas2D = __esm({
14884
14976
  "components/game/2d/molecules/Canvas2D.tsx"() {
14885
14977
  "use client";
@@ -14893,6 +14985,7 @@ var init_Canvas2D = __esm({
14893
14985
  init_MiniMap();
14894
14986
  init_HealthBar();
14895
14987
  init_useImageCache();
14988
+ init_atlasSlice();
14896
14989
  init_useCamera();
14897
14990
  init_useCanvasGestures();
14898
14991
  init_useRenderInterpolation();
@@ -14906,6 +14999,8 @@ var init_Canvas2D = __esm({
14906
14999
  hazard: "#c0392b",
14907
15000
  goal: "#f1c40f"
14908
15001
  };
15002
+ NOOP = () => {
15003
+ };
14909
15004
  PLAYER_COLOR = "#3498db";
14910
15005
  PLAYER_EYE_COLOR = "#ffffff";
14911
15006
  SKY_GRADIENT_TOP = "#1a1a2e";
@@ -19564,6 +19659,153 @@ var init_ComponentPatterns = __esm({
19564
19659
  AlertPattern.displayName = "AlertPattern";
19565
19660
  }
19566
19661
  });
19662
+ var DEFAULT_BAR_COLOR, DEFAULT_CELL_COLOR, DEFAULT_POINTER_COLOR, POINTER_BAND, TOP_PAD, AlgorithmCanvas;
19663
+ var init_AlgorithmCanvas = __esm({
19664
+ "components/learning/molecules/AlgorithmCanvas.tsx"() {
19665
+ "use client";
19666
+ init_atoms();
19667
+ init_Stack();
19668
+ init_LearningCanvas();
19669
+ DEFAULT_BAR_COLOR = "#3b82f6";
19670
+ DEFAULT_CELL_COLOR = "#e5e7eb";
19671
+ DEFAULT_POINTER_COLOR = "#dc2626";
19672
+ POINTER_BAND = 34;
19673
+ TOP_PAD = 12;
19674
+ AlgorithmCanvas = ({
19675
+ className,
19676
+ width = 600,
19677
+ height = 400,
19678
+ title,
19679
+ backgroundColor,
19680
+ bars = [],
19681
+ cells = [],
19682
+ pointers = [],
19683
+ shapes = [],
19684
+ interactive = false,
19685
+ animate = false,
19686
+ onShapeClick,
19687
+ isLoading,
19688
+ error
19689
+ }) => {
19690
+ const derivedShapes = useMemo(() => {
19691
+ const out = [];
19692
+ if (bars.length > 0) {
19693
+ const slot = width / bars.length;
19694
+ const barW = slot * 0.8;
19695
+ const gap = slot * 0.1;
19696
+ const baseline = height - POINTER_BAND;
19697
+ const usableH = baseline - TOP_PAD;
19698
+ const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
19699
+ bars.forEach((bar, i) => {
19700
+ const v = Number.isFinite(bar.value) ? bar.value : 0;
19701
+ const bh = Math.max(0, v / maxV * usableH);
19702
+ const x = i * slot + gap;
19703
+ const color = bar.color ?? DEFAULT_BAR_COLOR;
19704
+ out.push({
19705
+ type: "rect",
19706
+ id: `bar-${i}`,
19707
+ x,
19708
+ y: baseline - bh,
19709
+ width: barW,
19710
+ height: bh,
19711
+ color,
19712
+ fill: color
19713
+ });
19714
+ const label = bar.label ?? (bars.length <= 24 ? String(v) : void 0);
19715
+ if (label) {
19716
+ out.push({
19717
+ type: "text",
19718
+ x: x + barW / 2,
19719
+ y: baseline - bh - 8,
19720
+ text: label,
19721
+ color: "#374151",
19722
+ fontSize: 11,
19723
+ align: "center"
19724
+ });
19725
+ }
19726
+ });
19727
+ pointers.forEach((p) => {
19728
+ if (p.index < 0 || p.index >= bars.length) return;
19729
+ const cx = p.index * slot + slot / 2;
19730
+ const color = p.color ?? DEFAULT_POINTER_COLOR;
19731
+ out.push({
19732
+ type: "arrow",
19733
+ x1: cx,
19734
+ y1: height - 6,
19735
+ x2: cx,
19736
+ y2: baseline + 4,
19737
+ color,
19738
+ lineWidth: 2
19739
+ });
19740
+ if (p.label) {
19741
+ out.push({
19742
+ type: "text",
19743
+ x: cx,
19744
+ y: height - 22,
19745
+ text: p.label,
19746
+ color,
19747
+ fontSize: 11,
19748
+ align: "center"
19749
+ });
19750
+ }
19751
+ });
19752
+ }
19753
+ if (cells.length > 0) {
19754
+ const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
19755
+ const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
19756
+ const cw = width / maxCol;
19757
+ const ch = height / maxRow;
19758
+ cells.forEach((c, i) => {
19759
+ const x = c.col * cw;
19760
+ const y = c.row * ch;
19761
+ const color = c.color ?? DEFAULT_CELL_COLOR;
19762
+ out.push({
19763
+ type: "rect",
19764
+ id: `cell-${i}`,
19765
+ x: x + 1,
19766
+ y: y + 1,
19767
+ width: cw - 2,
19768
+ height: ch - 2,
19769
+ color: "#9ca3af",
19770
+ fill: color
19771
+ });
19772
+ const label = c.label ?? (c.value != null ? String(c.value) : void 0);
19773
+ if (label && cw >= 18 && ch >= 14) {
19774
+ out.push({
19775
+ type: "text",
19776
+ x: x + cw / 2,
19777
+ y: y + ch / 2,
19778
+ text: label,
19779
+ color: "#111827",
19780
+ fontSize: 12,
19781
+ align: "center"
19782
+ });
19783
+ }
19784
+ });
19785
+ }
19786
+ out.push(...shapes);
19787
+ return out;
19788
+ }, [bars, cells, pointers, shapes, width, height]);
19789
+ return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", children: [
19790
+ title ? /* @__PURE__ */ jsx(Typography, { variant: "h4", children: title }) : null,
19791
+ /* @__PURE__ */ jsx(
19792
+ LearningCanvas,
19793
+ {
19794
+ width,
19795
+ height,
19796
+ backgroundColor,
19797
+ shapes: derivedShapes,
19798
+ interactive,
19799
+ animate,
19800
+ onShapeClick,
19801
+ isLoading,
19802
+ error
19803
+ }
19804
+ )
19805
+ ] }) });
19806
+ };
19807
+ }
19808
+ });
19567
19809
  var AuthLayout;
19568
19810
  var init_AuthLayout = __esm({
19569
19811
  "components/marketing/templates/AuthLayout.tsx"() {
@@ -48019,6 +48261,7 @@ var init_component_registry_generated = __esm({
48019
48261
  init_ActionTile();
48020
48262
  init_ActivationBlock();
48021
48263
  init_ComponentPatterns();
48264
+ init_AlgorithmCanvas();
48022
48265
  init_AnimatedCounter();
48023
48266
  init_AnimatedGraphic();
48024
48267
  init_AnimatedReveal();
@@ -48317,6 +48560,7 @@ var init_component_registry_generated = __esm({
48317
48560
  "ActivationBlock": ActivationBlock,
48318
48561
  "Alert": AlertPattern,
48319
48562
  "AlertPattern": AlertPattern,
48563
+ "AlgorithmCanvas": AlgorithmCanvas,
48320
48564
  "AnimatedCounter": AnimatedCounter,
48321
48565
  "AnimatedGraphic": AnimatedGraphic,
48322
48566
  "AnimatedReveal": AnimatedReveal,
@@ -56,6 +56,7 @@ export { MathCanvas, type MathCanvasProps, type MathCurve, type MathPoint, type
56
56
  export { PhysicsCanvas, type PhysicsCanvasProps, type LearningPhysicsBody, type LearningPhysicsConstraint, } from '../../learning/molecules/PhysicsCanvas';
57
57
  export { BiologyCanvas, type BiologyCanvasProps, type BiologyNode, type BiologyEdge, } from '../../learning/molecules/BiologyCanvas';
58
58
  export { ChemistryCanvas, type ChemistryCanvasProps, type ChemistryAtom, type ChemistryBond, type ChemistryArrow, } from '../../learning/molecules/ChemistryCanvas';
59
+ export { AlgorithmCanvas, type AlgorithmCanvasProps, type AlgorithmBar, type AlgorithmCell, type AlgorithmPointer, } from '../../learning/molecules/AlgorithmCanvas';
59
60
  export { GraphView, type GraphViewProps, type GraphViewNode, type GraphViewEdge } from './GraphView';
60
61
  export { MapView, type MapViewProps, type MapMarkerData, type MapRouteData, type MapRouteWaypoint } from './MapView';
61
62
  export { NumberStepper, type NumberStepperProps, type NumberStepperSize } from './NumberStepper';
@@ -134,12 +134,12 @@ export interface Canvas2DProps {
134
134
  spriteMaxWidthRatio?: number;
135
135
  /** Override for the diamond-top Y offset within the tile sprite. */
136
136
  diamondTopY?: number;
137
- /** Resolve terrain sprite URL from terrain key */
138
- getTerrainSprite?: (terrain: string) => string | undefined;
139
- /** Resolve feature sprite URL from feature type key */
140
- getFeatureSprite?: (featureType: string) => string | undefined;
141
- /** Resolve unit static sprite URL */
142
- getUnitSprite?: (unit: IsometricUnit) => string | undefined;
137
+ /** Resolve terrain sprite from terrain key — a bare URL or an Asset (atlas sub-texture ref). */
138
+ getTerrainSprite?: (terrain: string) => string | Asset | undefined;
139
+ /** Resolve feature sprite from feature type key — a bare URL or an Asset. */
140
+ getFeatureSprite?: (featureType: string) => string | Asset | undefined;
141
+ /** Resolve unit static sprite — a bare URL or an Asset. */
142
+ getUnitSprite?: (unit: IsometricUnit) => string | Asset | undefined;
143
143
  /** Resolve animated sprite sheet frame for a unit */
144
144
  resolveUnitFrame?: (unitId: string) => ResolvedFrame | null;
145
145
  /** Additional sprite URLs to preload (e.g., effect sprites) */
@@ -0,0 +1,58 @@
1
+ /**
2
+ * atlasSlice — the ONLY place a packed sheet is cut at runtime.
3
+ *
4
+ * An `Asset` may carry `{ url: <sheet.png>, atlas: <atlas.json>, sprite: <name|index> }`. This
5
+ * module fetches + caches the atlas JSON (a `TextureAtlas` of named sub-rects or a uniform
6
+ * `Tilesheet`) and resolves the source rectangle for a `sprite`, so the canvas can `drawImage`
7
+ * exactly that sub-region of the sheet. Coexists with the whole-PNG path (an Asset with no
8
+ * `atlas`/`sprite` draws the whole image) and the `useUnitSpriteAtlas` animation path.
9
+ *
10
+ * See `docs/Almadar_Std_Assets.md` §2, §7.
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ import type { TextureAtlas, Tilesheet } from '@almadar/core';
15
+ /** The minimal draw-relevant projection of an `Asset` — a sheet URL plus an optional atlas
16
+ * sub-texture reference. A full `@almadar/core` `Asset` is structurally assignable. */
17
+ export interface SpriteRef {
18
+ url?: string;
19
+ atlas?: string;
20
+ sprite?: string;
21
+ }
22
+ type ParsedAtlas = TextureAtlas | Tilesheet;
23
+ /**
24
+ * Get a parsed atlas by URL, fetching+caching on first request. Returns undefined until loaded;
25
+ * `onReady` fires once when the fetch resolves so the caller can trigger a re-render.
26
+ */
27
+ export declare function getAtlas(url: string, onReady: () => void): ParsedAtlas | undefined;
28
+ export interface SubRect {
29
+ sx: number;
30
+ sy: number;
31
+ sw: number;
32
+ sh: number;
33
+ }
34
+ /**
35
+ * Resolve the source rect for `sprite` in `atlas`.
36
+ * - `TextureAtlas`: `sprite` is a SubTexture name (e.g. `"blockBrown.png"`).
37
+ * - `Tilesheet`: `sprite` is `"col,row"` or a flat index into the row-major grid.
38
+ * Returns null if the name/index doesn't resolve.
39
+ */
40
+ export declare function subRectFor(atlas: ParsedAtlas, sprite: string): SubRect | null;
41
+ /** True when the asset points into a sheet (needs slicing) rather than being a whole PNG. */
42
+ export declare function isAtlasAsset(asset: SpriteRef | undefined | null): boolean;
43
+ export interface AssetSource {
44
+ img: HTMLImageElement;
45
+ /** Sub-rect to blit; null = draw the whole image. */
46
+ rect: SubRect | null;
47
+ /** Source aspect ratio (width/height) — sub-rect for a slice, natural otherwise. */
48
+ aspect: number;
49
+ }
50
+ /**
51
+ * Resolve what to draw for `asset` given its loaded sheet `img`. Returns null when the asset is
52
+ * an atlas ref whose JSON isn't loaded yet (or whose sprite name doesn't resolve) — `onReady`
53
+ * will trigger a re-render once the fetch lands. Callers use `.aspect` to size, then `blit`.
54
+ */
55
+ export declare function resolveAssetSource(img: HTMLImageElement, asset: SpriteRef | undefined | null, onReady: () => void): AssetSource | null;
56
+ /** Draw a resolved source at `(dx,dy,dw,dh)` — sliced sub-rect or whole image. */
57
+ export declare function blit(ctx: CanvasRenderingContext2D, src: AssetSource, dx: number, dy: number, dw: number, dh: number): void;
58
+ export {};