@almadar/ui 5.104.0 → 5.105.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.
@@ -7449,7 +7449,7 @@ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric")
7449
7449
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
7450
7450
  return { x: Math.round(tileX), y: Math.round(tileY) };
7451
7451
  }
7452
- var TILE_WIDTH, TILE_HEIGHT, FLOOR_HEIGHT, DIAMOND_TOP_Y, FEATURE_COLORS;
7452
+ var TILE_WIDTH, TILE_HEIGHT, FLOOR_HEIGHT, DIAMOND_TOP_Y, FEATURE_COLORS, TERRAIN_COLORS, TEAM_COLORS, TEAM_SHADOW_COLORS, HIGHLIGHT_COLORS, SELECTION_RING_COLOR, GRID_STROKE_COLOR, BACKGROUND_FALLBACK_COLOR, MINIMAP_TERRAIN_COLORS, MINIMAP_UNIT_COLORS, UNIT_LABEL_BG_COLORS, SIDE_PLATFORM_COLORS, SIDE_PLAYER_COLOR, SIDE_PLAYER_EYE_COLOR, SIDE_SKY_GRADIENT_TOP, SIDE_SKY_GRADIENT_BOTTOM, SIDE_GRID_COLOR, SIDE_FX_COLOR, SIDE_PLATFORM_BEVEL_COLOR, SIDE_PLATFORM_SHADOW_COLOR, SIDE_HAZARD_STRIPE_COLOR, SIDE_GOAL_GLOW_COLOR, DEFAULT_BG_COLOR;
7453
7453
  var init_isometric = __esm({
7454
7454
  "components/game/shared/isometric.ts"() {
7455
7455
  TILE_WIDTH = 256;
@@ -7466,14 +7466,79 @@ var init_isometric = __esm({
7466
7466
  mountain: "#78716c",
7467
7467
  default: "#9ca3af"
7468
7468
  };
7469
+ TERRAIN_COLORS = {
7470
+ water: "#3b82f6",
7471
+ mountain: "#78716c",
7472
+ stone: "#9ca3af",
7473
+ default: "#4ade80"
7474
+ };
7475
+ TEAM_COLORS = {
7476
+ player: "#3b82f6",
7477
+ enemy: "#ef4444",
7478
+ neutral: "#6b7280",
7479
+ default: "#6b7280"
7480
+ };
7481
+ TEAM_SHADOW_COLORS = {
7482
+ player: "rgba(0,150,255,0.6)",
7483
+ default: "rgba(255,50,50,0.6)"
7484
+ };
7485
+ HIGHLIGHT_COLORS = {
7486
+ hover: "rgba(255,255,255,0.15)",
7487
+ validMove: "rgba(74,222,128,0.25)",
7488
+ attack: "rgba(239,68,68,0.35)"
7489
+ };
7490
+ SELECTION_RING_COLOR = "rgba(0,200,255,0.8)";
7491
+ GRID_STROKE_COLOR = "rgba(0,0,0,0.2)";
7492
+ BACKGROUND_FALLBACK_COLOR = "#1a1a2e";
7493
+ MINIMAP_TERRAIN_COLORS = {
7494
+ water: "#3b82f6",
7495
+ mountain: "#78716c",
7496
+ default: "#4ade80"
7497
+ };
7498
+ MINIMAP_UNIT_COLORS = {
7499
+ player: "#60a5fa",
7500
+ enemy: "#f87171",
7501
+ neutral: "#9ca3af",
7502
+ default: "#9ca3af"
7503
+ };
7504
+ UNIT_LABEL_BG_COLORS = {
7505
+ player: "rgba(59,130,246,0.9)",
7506
+ enemy: "rgba(239,68,68,0.9)",
7507
+ neutral: "rgba(107,114,128,0.9)",
7508
+ default: "rgba(107,114,128,0.9)"
7509
+ };
7510
+ SIDE_PLATFORM_COLORS = {
7511
+ ground: "#4a7c59",
7512
+ platform: "#7c6b4a",
7513
+ hazard: "#c0392b",
7514
+ goal: "#f1c40f"
7515
+ };
7516
+ SIDE_PLAYER_COLOR = "#3498db";
7517
+ SIDE_PLAYER_EYE_COLOR = "#ffffff";
7518
+ SIDE_SKY_GRADIENT_TOP = "#1a1a2e";
7519
+ SIDE_SKY_GRADIENT_BOTTOM = "#16213e";
7520
+ SIDE_GRID_COLOR = "rgba(255, 255, 255, 0.03)";
7521
+ SIDE_FX_COLOR = "#ffe066";
7522
+ SIDE_PLATFORM_BEVEL_COLOR = "rgba(255,255,255,0.15)";
7523
+ SIDE_PLATFORM_SHADOW_COLOR = "rgba(0,0,0,0.3)";
7524
+ SIDE_HAZARD_STRIPE_COLOR = "#e74c3c";
7525
+ SIDE_GOAL_GLOW_COLOR = "rgba(241, 196, 15, 0.5)";
7526
+ DEFAULT_BG_COLOR = "#5c94fc";
7469
7527
  }
7470
7528
  });
7471
7529
 
7472
7530
  // components/game/shared/spriteSheetConstants.ts
7473
- var SHEET_COLUMNS;
7531
+ var SHEET_COLUMNS, SPRITE_SHEET_LAYOUT;
7474
7532
  var init_spriteSheetConstants = __esm({
7475
7533
  "components/game/shared/spriteSheetConstants.ts"() {
7476
7534
  SHEET_COLUMNS = 8;
7535
+ SPRITE_SHEET_LAYOUT = {
7536
+ idle: { row: 0, frames: 4, frameRate: 6, loop: true },
7537
+ walk: { row: 1, frames: 8, frameRate: 10, loop: true },
7538
+ attack: { row: 2, frames: 6, frameRate: 12, loop: false },
7539
+ hit: { row: 3, frames: 3, frameRate: 8, loop: false },
7540
+ death: { row: 4, frames: 6, frameRate: 8, loop: false }
7541
+ };
7477
7542
  }
7478
7543
  });
7479
7544
 
@@ -10206,7 +10271,7 @@ var init_useUnitSpriteAtlas = __esm({
10206
10271
  function normalizeBackdrop(bg) {
10207
10272
  return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
10208
10273
  }
10209
- function SideView({
10274
+ function SideCanvas2D({
10210
10275
  player,
10211
10276
  platforms,
10212
10277
  worldWidth,
@@ -10227,24 +10292,18 @@ function SideView({
10227
10292
  const canvasRef = useRef(null);
10228
10293
  const eventBus = useEventBus();
10229
10294
  const keysRef = useRef(/* @__PURE__ */ new Set());
10230
- const imageCache2 = useRef(/* @__PURE__ */ new Map());
10231
- const [loadedImages, setLoadedImages] = useState(/* @__PURE__ */ new Set());
10232
- const loadImage = useCallback((url) => {
10233
- if (!url) return null;
10234
- const cached = imageCache2.current.get(url);
10235
- if (cached?.complete && cached.naturalWidth > 0) {
10236
- if (!loadedImages.has(url)) setLoadedImages((prev) => new Set(prev).add(url));
10237
- return cached;
10238
- }
10239
- if (!cached) {
10240
- const img = new Image();
10241
- img.crossOrigin = "anonymous";
10242
- img.src = url;
10243
- img.onload = () => setLoadedImages((prev) => new Set(prev).add(url));
10244
- imageCache2.current.set(url, img);
10295
+ const spriteUrls = useMemo(() => {
10296
+ const urls = [];
10297
+ if (backgroundImage?.url) urls.push(backgroundImage.url);
10298
+ if (playerSprite?.url) urls.push(playerSprite.url);
10299
+ if (tileSprites) {
10300
+ for (const asset of Object.values(tileSprites)) {
10301
+ if (asset?.url) urls.push(asset.url);
10302
+ }
10245
10303
  }
10246
- return null;
10247
- }, [loadedImages]);
10304
+ return urls;
10305
+ }, [backgroundImage, playerSprite, tileSprites]);
10306
+ const { getImage } = useImageCache(spriteUrls);
10248
10307
  useEffect(() => {
10249
10308
  if (typeof window === "undefined") return;
10250
10309
  const canvas = canvasRef.current;
@@ -10357,7 +10416,7 @@ function SideView({
10357
10416
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
10358
10417
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
10359
10418
  }
10360
- const bgImage = bgImg ? loadImage(bgImg.url) : null;
10419
+ const bgImage = bgImg ? getImage(bgImg.url) : void 0;
10361
10420
  const bgSrc = bgImage ? resolveAssetSource(bgImage, bgImg, NOOP) : null;
10362
10421
  if (bgSrc) {
10363
10422
  blit(ctx, bgSrc, 0, 0, cw, ch);
@@ -10368,12 +10427,12 @@ function SideView({
10368
10427
  ctx.fillRect(0, 0, cw, ch);
10369
10428
  } else {
10370
10429
  const grad = ctx.createLinearGradient(0, 0, 0, ch);
10371
- grad.addColorStop(0, SKY_GRADIENT_TOP);
10372
- grad.addColorStop(1, SKY_GRADIENT_BOTTOM);
10430
+ grad.addColorStop(0, SIDE_SKY_GRADIENT_TOP);
10431
+ grad.addColorStop(1, SIDE_SKY_GRADIENT_BOTTOM);
10373
10432
  ctx.fillStyle = grad;
10374
10433
  ctx.fillRect(0, 0, cw, ch);
10375
10434
  }
10376
- ctx.strokeStyle = GRID_COLOR;
10435
+ ctx.strokeStyle = SIDE_GRID_COLOR;
10377
10436
  ctx.lineWidth = 1;
10378
10437
  const gridSize = 32;
10379
10438
  for (let gx = -camX % gridSize; gx < cw; gx += gridSize) {
@@ -10393,7 +10452,7 @@ function SideView({
10393
10452
  const platY = plat.y - camY;
10394
10453
  const platType = plat.type ?? "ground";
10395
10454
  const spriteAsset = tSprites?.[platType];
10396
- const tileImg = spriteAsset ? loadImage(spriteAsset.url) : null;
10455
+ const tileImg = spriteAsset ? getImage(spriteAsset.url) : void 0;
10397
10456
  const tileSrc = tileImg ? resolveAssetSource(tileImg, spriteAsset, NOOP) : null;
10398
10457
  if (tileSrc) {
10399
10458
  const originX = tileSrc.rect ? tileSrc.rect.sx : 0;
@@ -10408,15 +10467,15 @@ function SideView({
10408
10467
  ctx.drawImage(tileImg, originX, originY, srcW, tileH, platX + tx, platY, drawW, plat.height);
10409
10468
  }
10410
10469
  } else {
10411
- const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
10470
+ const color = SIDE_PLATFORM_COLORS[platType] ?? SIDE_PLATFORM_COLORS.ground;
10412
10471
  ctx.fillStyle = color;
10413
10472
  ctx.fillRect(platX, platY, plat.width, plat.height);
10414
- ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
10473
+ ctx.fillStyle = SIDE_PLATFORM_BEVEL_COLOR;
10415
10474
  ctx.fillRect(platX, platY, plat.width, 3);
10416
- ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
10475
+ ctx.fillStyle = SIDE_PLATFORM_SHADOW_COLOR;
10417
10476
  ctx.fillRect(platX, platY + plat.height - 2, plat.width, 2);
10418
10477
  if (platType === "hazard") {
10419
- ctx.strokeStyle = "#e74c3c";
10478
+ ctx.strokeStyle = SIDE_HAZARD_STRIPE_COLOR;
10420
10479
  ctx.lineWidth = 2;
10421
10480
  for (let sx = platX; sx < platX + plat.width; sx += 12) {
10422
10481
  ctx.beginPath();
@@ -10426,7 +10485,7 @@ function SideView({
10426
10485
  }
10427
10486
  }
10428
10487
  if (platType === "goal") {
10429
- ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
10488
+ ctx.fillStyle = SIDE_GOAL_GLOW_COLOR;
10430
10489
  ctx.beginPath();
10431
10490
  ctx.arc(platX + plat.width / 2, platY - 10, 8, 0, Math.PI * 2);
10432
10491
  ctx.fill();
@@ -10438,7 +10497,7 @@ function SideView({
10438
10497
  const ppx = px - camX;
10439
10498
  const ppy = py - camY;
10440
10499
  const facingRight = auth.facingRight ?? true;
10441
- const playerImg = pSprite ? loadImage(pSprite.url) : null;
10500
+ const playerImg = pSprite ? getImage(pSprite.url) : void 0;
10442
10501
  const playerSrc = playerImg ? resolveAssetSource(playerImg, pSprite, NOOP) : null;
10443
10502
  if (playerSrc) {
10444
10503
  ctx.save();
@@ -10451,7 +10510,7 @@ function SideView({
10451
10510
  }
10452
10511
  ctx.restore();
10453
10512
  } else {
10454
- ctx.fillStyle = PLAYER_COLOR;
10513
+ ctx.fillStyle = SIDE_PLAYER_COLOR;
10455
10514
  const radius = Math.min(pw, ph) * 0.25;
10456
10515
  ctx.beginPath();
10457
10516
  ctx.moveTo(ppx + radius, ppy);
@@ -10467,7 +10526,7 @@ function SideView({
10467
10526
  const eyeY = ppy + ph * 0.3;
10468
10527
  const eyeSize = 3;
10469
10528
  const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
10470
- ctx.fillStyle = PLAYER_EYE_COLOR;
10529
+ ctx.fillStyle = SIDE_PLAYER_EYE_COLOR;
10471
10530
  ctx.beginPath();
10472
10531
  ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
10473
10532
  ctx.fill();
@@ -10481,7 +10540,7 @@ function SideView({
10481
10540
  const alpha = Math.min(1, fx.ttl / 4);
10482
10541
  const prev = ctx.globalAlpha;
10483
10542
  ctx.globalAlpha = alpha;
10484
- ctx.fillStyle = "#ffe066";
10543
+ ctx.fillStyle = SIDE_FX_COLOR;
10485
10544
  ctx.beginPath();
10486
10545
  ctx.arc(fxScreenX, fxScreenY, 12, 0, Math.PI * 2);
10487
10546
  ctx.fill();
@@ -10489,7 +10548,7 @@ function SideView({
10489
10548
  }
10490
10549
  };
10491
10550
  return interp.startLoop(drawFrame);
10492
- }, [interp.startLoop, loadImage]);
10551
+ }, [interp.startLoop, getImage]);
10493
10552
  return /* @__PURE__ */ jsx(
10494
10553
  "canvas",
10495
10554
  {
@@ -10501,6 +10560,33 @@ function SideView({
10501
10560
  }
10502
10561
  );
10503
10562
  }
10563
+ var NOOP;
10564
+ var init_SideCanvas2D = __esm({
10565
+ "components/game/2d/molecules/SideCanvas2D.tsx"() {
10566
+ "use client";
10567
+ init_cn();
10568
+ init_useEventBus();
10569
+ init_useImageCache();
10570
+ init_atlasSlice();
10571
+ init_useRenderInterpolation();
10572
+ init_verificationRegistry();
10573
+ init_isometric();
10574
+ NOOP = () => {
10575
+ };
10576
+ SideCanvas2D.displayName = "SideCanvas2D";
10577
+ }
10578
+ });
10579
+ function normalizeBackdrop2(bg) {
10580
+ return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
10581
+ }
10582
+ function resolveTileFallbackColor(tile) {
10583
+ const override = tile.color;
10584
+ return override ?? TERRAIN_COLORS[tile.terrain ?? ""] ?? TERRAIN_COLORS.default;
10585
+ }
10586
+ function resolveUnitTeamColor(unit) {
10587
+ const override = unit.tint;
10588
+ return override ?? TEAM_COLORS[unit.team ?? ""] ?? TEAM_COLORS.default;
10589
+ }
10504
10590
  function Canvas2D({
10505
10591
  // Closed-circuit
10506
10592
  className,
@@ -10550,13 +10636,13 @@ function Canvas2D({
10550
10636
  // Side-view asset resolution
10551
10637
  playerSprite,
10552
10638
  tileSprites,
10553
- bgColor = "#5c94fc",
10639
+ bgColor = DEFAULT_BG_COLOR,
10554
10640
  worldWidth = 800,
10555
10641
  worldHeight = 400,
10556
10642
  // Remote asset loading
10557
10643
  assetManifest
10558
10644
  }) {
10559
- const backgroundImage = normalizeBackdrop(backgroundImageRaw);
10645
+ const backgroundImage = normalizeBackdrop2(backgroundImageRaw);
10560
10646
  const isSide = projection === "side";
10561
10647
  const isFree = projection === "free";
10562
10648
  const flatLike = projection === "hex" || projection === "flat" || isFree;
@@ -10740,7 +10826,7 @@ function Canvas2D({
10740
10826
  return sortedTiles.map((t2) => ({
10741
10827
  x: t2.x,
10742
10828
  y: t2.y,
10743
- color: t2.terrain === "water" ? "#3b82f6" : t2.terrain === "mountain" ? "#78716c" : "#4ade80"
10829
+ color: MINIMAP_TERRAIN_COLORS[t2.terrain ?? ""] ?? MINIMAP_TERRAIN_COLORS.default
10744
10830
  }));
10745
10831
  }, [showMinimap, sortedTiles]);
10746
10832
  const miniMapUnits = useMemo(() => {
@@ -10748,7 +10834,7 @@ function Canvas2D({
10748
10834
  return units.filter((u) => u.position).map((u) => ({
10749
10835
  x: u.position.x,
10750
10836
  y: u.position.y,
10751
- color: u.team === "player" ? "#60a5fa" : u.team === "enemy" ? "#f87171" : "#9ca3af",
10837
+ color: MINIMAP_UNIT_COLORS[u.team ?? ""] ?? MINIMAP_UNIT_COLORS.default,
10752
10838
  isPlayer: u.team === "player"
10753
10839
  }));
10754
10840
  }, [showMinimap, units]);
@@ -10785,7 +10871,7 @@ function Canvas2D({
10785
10871
  }
10786
10872
  }
10787
10873
  } else {
10788
- ctx.fillStyle = "#1a1a2e";
10874
+ ctx.fillStyle = BACKGROUND_FALLBACK_COLOR;
10789
10875
  ctx.fillRect(0, 0, viewportSize.width, viewportSize.height);
10790
10876
  }
10791
10877
  if (sortedTiles.length === 0 && units.length === 0 && features.length === 0) return;
@@ -10815,15 +10901,15 @@ function Canvas2D({
10815
10901
  } else if (img && img.naturalWidth === 0) {
10816
10902
  ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
10817
10903
  } else if (squareGrid) {
10818
- ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
10904
+ ctx.fillStyle = resolveTileFallbackColor(tile);
10819
10905
  ctx.fillRect(pos.x, pos.y, scaledTileWidth, scaledTileWidth);
10820
- ctx.strokeStyle = "rgba(0,0,0,0.2)";
10906
+ ctx.strokeStyle = GRID_STROKE_COLOR;
10821
10907
  ctx.lineWidth = 1;
10822
10908
  ctx.strokeRect(pos.x, pos.y, scaledTileWidth, scaledTileWidth);
10823
10909
  } else {
10824
10910
  const centerX = pos.x + scaledTileWidth / 2;
10825
10911
  const topY = pos.y + scaledDiamondTopY;
10826
- ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
10912
+ ctx.fillStyle = resolveTileFallbackColor(tile);
10827
10913
  ctx.beginPath();
10828
10914
  ctx.moveTo(centerX, topY);
10829
10915
  ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
@@ -10831,7 +10917,7 @@ function Canvas2D({
10831
10917
  ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
10832
10918
  ctx.closePath();
10833
10919
  ctx.fill();
10834
- ctx.strokeStyle = "rgba(0,0,0,0.2)";
10920
+ ctx.strokeStyle = GRID_STROKE_COLOR;
10835
10921
  ctx.lineWidth = 1;
10836
10922
  ctx.stroke();
10837
10923
  }
@@ -10852,11 +10938,11 @@ function Canvas2D({
10852
10938
  ctx.fill();
10853
10939
  };
10854
10940
  if (hoveredTile && hoveredTile.x === tile.x && hoveredTile.y === tile.y) {
10855
- drawHighlight("rgba(255, 255, 255, 0.15)");
10941
+ drawHighlight(HIGHLIGHT_COLORS.hover);
10856
10942
  }
10857
10943
  const tileKey = `${tile.x},${tile.y}`;
10858
- if (validMoveSet.has(tileKey)) drawHighlight("rgba(74, 222, 128, 0.25)");
10859
- if (attackTargetSet.has(tileKey)) drawHighlight("rgba(239, 68, 68, 0.35)");
10944
+ if (validMoveSet.has(tileKey)) drawHighlight(HIGHLIGHT_COLORS.validMove);
10945
+ if (attackTargetSet.has(tileKey)) drawHighlight(HIGHLIGHT_COLORS.attack);
10860
10946
  if (debug2) {
10861
10947
  const centerX = pos.x + scaledTileWidth / 2;
10862
10948
  const centerY = squareGrid ? pos.y + scaledTileWidth / 2 : pos.y + scaledFloorHeight / 2 + scaledDiamondTopY;
@@ -10930,9 +11016,9 @@ function Canvas2D({
10930
11016
  const maxUnitW = scaledTileWidth * spriteMaxWidthRatio * unitScale;
10931
11017
  const unitIsSheet = unit.spriteSheet?.url !== void 0;
10932
11018
  const unitSrc = img && !unitIsSheet ? resolveAssetSource(img, unitAsset, bumpAtlas) : null;
10933
- const SHEET_ROWS = 5;
10934
11019
  const sheetFrameW = img ? img.naturalWidth / SHEET_COLUMNS : 0;
10935
- const sheetFrameH = img ? img.naturalHeight / SHEET_ROWS : 0;
11020
+ const sheetFrameH = img ? img.naturalHeight / LEGACY_SHEET_ROWS : 0;
11021
+ const legacyFrame = frameRect(0, 0, SHEET_COLUMNS, sheetFrameW, sheetFrameH);
10936
11022
  const frameW = unitIsSheet ? sheetFrameW : img?.naturalWidth ?? 1;
10937
11023
  const frameH = unitIsSheet ? sheetFrameH : img?.naturalHeight ?? 1;
10938
11024
  const ar = unitIsSheet ? sheetFrameW / (sheetFrameH || 1) : unitSrc ? unitSrc.aspect : frameW / (frameH || 1);
@@ -10950,17 +11036,16 @@ function Canvas2D({
10950
11036
  ctx.globalAlpha = 0.25;
10951
11037
  if (img) {
10952
11038
  if (unitIsSheet) {
10953
- ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
11039
+ ctx.drawImage(img, legacyFrame.sx, legacyFrame.sy, legacyFrame.sw, legacyFrame.sh, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
10954
11040
  } else if (unitSrc) {
10955
11041
  blit(ctx, unitSrc, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
10956
11042
  } else {
10957
11043
  ctx.drawImage(img, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
10958
11044
  }
10959
11045
  } else {
10960
- const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
10961
11046
  ctx.beginPath();
10962
11047
  ctx.arc(ghostCenterX, ghostGroundY - 16 * scale, 20 * scale, 0, Math.PI * 2);
10963
- ctx.fillStyle = color;
11048
+ ctx.fillStyle = resolveUnitTeamColor(unit);
10964
11049
  ctx.fill();
10965
11050
  }
10966
11051
  ctx.restore();
@@ -10968,7 +11053,7 @@ function Canvas2D({
10968
11053
  if (isSelected) {
10969
11054
  ctx.beginPath();
10970
11055
  ctx.ellipse(centerX, groundY, drawW / 2 + 4 * scale, 12 * scale, 0, 0, Math.PI * 2);
10971
- ctx.strokeStyle = "rgba(0, 200, 255, 0.8)";
11056
+ ctx.strokeStyle = SELECTION_RING_COLOR;
10972
11057
  ctx.lineWidth = 3;
10973
11058
  ctx.stroke();
10974
11059
  }
@@ -10985,7 +11070,7 @@ function Canvas2D({
10985
11070
  const spriteY = groundY - fDrawH - (frame.applyBreathing ? breatheOffset : 0);
10986
11071
  ctx.save();
10987
11072
  if (unit.team) {
10988
- ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
11073
+ ctx.shadowColor = TEAM_SHADOW_COLORS[unit.team] ?? TEAM_SHADOW_COLORS.default;
10989
11074
  ctx.shadowBlur = 12 * scale;
10990
11075
  }
10991
11076
  if (frame.flipX) {
@@ -11000,7 +11085,7 @@ function Canvas2D({
11000
11085
  const spriteY = groundY - drawH - breatheOffset;
11001
11086
  const drawUnit = (x) => {
11002
11087
  if (unitIsSheet) {
11003
- ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, x, spriteY, drawW, drawH);
11088
+ ctx.drawImage(img, legacyFrame.sx, legacyFrame.sy, legacyFrame.sw, legacyFrame.sh, x, spriteY, drawW, drawH);
11004
11089
  } else if (unitSrc) {
11005
11090
  blit(ctx, unitSrc, x, spriteY, drawW, drawH);
11006
11091
  } else {
@@ -11009,7 +11094,7 @@ function Canvas2D({
11009
11094
  };
11010
11095
  if (unit.team) {
11011
11096
  ctx.save();
11012
- ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
11097
+ ctx.shadowColor = TEAM_SHADOW_COLORS[unit.team] ?? TEAM_SHADOW_COLORS.default;
11013
11098
  ctx.shadowBlur = 12 * scale;
11014
11099
  drawUnit(centerX - drawW / 2);
11015
11100
  ctx.restore();
@@ -11017,10 +11102,9 @@ function Canvas2D({
11017
11102
  drawUnit(centerX - drawW / 2);
11018
11103
  }
11019
11104
  } else {
11020
- const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
11021
11105
  ctx.beginPath();
11022
11106
  ctx.arc(centerX, groundY - 20 * scale - breatheOffset, 20 * scale, 0, Math.PI * 2);
11023
- ctx.fillStyle = color;
11107
+ ctx.fillStyle = resolveUnitTeamColor(unit);
11024
11108
  ctx.fill();
11025
11109
  ctx.strokeStyle = "rgba(255,255,255,0.8)";
11026
11110
  ctx.lineWidth = 2;
@@ -11259,7 +11343,7 @@ function Canvas2D({
11259
11343
  }
11260
11344
  if (isSide) {
11261
11345
  return /* @__PURE__ */ jsx(Box, { ref: containerRef, className: cn("relative overflow-hidden w-full h-full", className), children: /* @__PURE__ */ jsx(
11262
- SideView,
11346
+ SideCanvas2D,
11263
11347
  {
11264
11348
  player,
11265
11349
  platforms: platforms && platforms.length ? platforms : DEFAULT_PLATFORMS,
@@ -11357,7 +11441,7 @@ function Canvas2D({
11357
11441
  {
11358
11442
  as: "span",
11359
11443
  className: "text-white text-xs font-bold px-1.5 py-0.5 rounded mb-0.5 whitespace-nowrap block",
11360
- style: { background: unit.team === "player" ? "rgba(59,130,246,0.9)" : unit.team === "enemy" ? "rgba(239,68,68,0.9)" : "rgba(107,114,128,0.9)" },
11444
+ style: { background: UNIT_LABEL_BG_COLORS[unit.team ?? ""] ?? UNIT_LABEL_BG_COLORS.default },
11361
11445
  children: unit.name
11362
11446
  }
11363
11447
  ),
@@ -11381,7 +11465,7 @@ function Canvas2D({
11381
11465
  }
11382
11466
  );
11383
11467
  }
11384
- var PLATFORM_COLORS, NOOP, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR, DEFAULT_PLATFORMS;
11468
+ var LEGACY_SHEET_ROWS, DEFAULT_PLATFORMS;
11385
11469
  var init_Canvas2D = __esm({
11386
11470
  "components/game/2d/molecules/Canvas2D.tsx"() {
11387
11471
  "use client";
@@ -11403,19 +11487,9 @@ var init_Canvas2D = __esm({
11403
11487
  init_verificationRegistry();
11404
11488
  init_isometric();
11405
11489
  init_spriteSheetConstants();
11406
- PLATFORM_COLORS = {
11407
- ground: "#4a7c59",
11408
- platform: "#7c6b4a",
11409
- hazard: "#c0392b",
11410
- goal: "#f1c40f"
11411
- };
11412
- NOOP = () => {
11413
- };
11414
- PLAYER_COLOR = "#3498db";
11415
- PLAYER_EYE_COLOR = "#ffffff";
11416
- SKY_GRADIENT_TOP = "#1a1a2e";
11417
- SKY_GRADIENT_BOTTOM = "#16213e";
11418
- GRID_COLOR = "rgba(255, 255, 255, 0.03)";
11490
+ init_spriteAnimation();
11491
+ init_SideCanvas2D();
11492
+ LEGACY_SHEET_ROWS = Object.keys(SPRITE_SHEET_LAYOUT).length;
11419
11493
  DEFAULT_PLATFORMS = [
11420
11494
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
11421
11495
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
@@ -30711,13 +30785,13 @@ var init_MapView = __esm({
30711
30785
  shadowSize: [41, 41]
30712
30786
  });
30713
30787
  L.Marker.prototype.options.icon = defaultIcon;
30714
- const { useEffect: useEffect68, useRef: useRef65, useCallback: useCallback103, useState: useState98 } = React98__default;
30788
+ const { useEffect: useEffect69, useRef: useRef66, useCallback: useCallback104, useState: useState98 } = React98__default;
30715
30789
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
30716
30790
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
30717
30791
  function MapUpdater({ centerLat, centerLng, zoom }) {
30718
30792
  const map = useMap();
30719
- const prevRef = useRef65({ centerLat, centerLng, zoom });
30720
- useEffect68(() => {
30793
+ const prevRef = useRef66({ centerLat, centerLng, zoom });
30794
+ useEffect69(() => {
30721
30795
  const prev = prevRef.current;
30722
30796
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
30723
30797
  map.setView([centerLat, centerLng], zoom);
@@ -30728,7 +30802,7 @@ var init_MapView = __esm({
30728
30802
  }
30729
30803
  function MapClickHandler({ onMapClick }) {
30730
30804
  const map = useMap();
30731
- useEffect68(() => {
30805
+ useEffect69(() => {
30732
30806
  if (!onMapClick) return;
30733
30807
  const handler = (e) => {
30734
30808
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -30757,7 +30831,7 @@ var init_MapView = __esm({
30757
30831
  }) {
30758
30832
  const eventBus = useEventBus2();
30759
30833
  const [clickedPosition, setClickedPosition] = useState98(null);
30760
- const handleMapClick = useCallback103((lat, lng) => {
30834
+ const handleMapClick = useCallback104((lat, lng) => {
30761
30835
  if (showClickedPin) {
30762
30836
  setClickedPosition({ lat, lng });
30763
30837
  }
@@ -30766,7 +30840,7 @@ var init_MapView = __esm({
30766
30840
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
30767
30841
  }
30768
30842
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
30769
- const handleMarkerClick = useCallback103((marker) => {
30843
+ const handleMarkerClick = useCallback104((marker) => {
30770
30844
  onMarkerClick?.(marker);
30771
30845
  if (markerClickEvent) {
30772
30846
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });