@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.
@@ -7494,7 +7494,7 @@ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric")
7494
7494
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
7495
7495
  return { x: Math.round(tileX), y: Math.round(tileY) };
7496
7496
  }
7497
- var TILE_WIDTH, TILE_HEIGHT, FLOOR_HEIGHT, DIAMOND_TOP_Y, FEATURE_COLORS;
7497
+ 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;
7498
7498
  var init_isometric = __esm({
7499
7499
  "components/game/shared/isometric.ts"() {
7500
7500
  TILE_WIDTH = 256;
@@ -7511,14 +7511,79 @@ var init_isometric = __esm({
7511
7511
  mountain: "#78716c",
7512
7512
  default: "#9ca3af"
7513
7513
  };
7514
+ TERRAIN_COLORS = {
7515
+ water: "#3b82f6",
7516
+ mountain: "#78716c",
7517
+ stone: "#9ca3af",
7518
+ default: "#4ade80"
7519
+ };
7520
+ TEAM_COLORS = {
7521
+ player: "#3b82f6",
7522
+ enemy: "#ef4444",
7523
+ neutral: "#6b7280",
7524
+ default: "#6b7280"
7525
+ };
7526
+ TEAM_SHADOW_COLORS = {
7527
+ player: "rgba(0,150,255,0.6)",
7528
+ default: "rgba(255,50,50,0.6)"
7529
+ };
7530
+ HIGHLIGHT_COLORS = {
7531
+ hover: "rgba(255,255,255,0.15)",
7532
+ validMove: "rgba(74,222,128,0.25)",
7533
+ attack: "rgba(239,68,68,0.35)"
7534
+ };
7535
+ SELECTION_RING_COLOR = "rgba(0,200,255,0.8)";
7536
+ GRID_STROKE_COLOR = "rgba(0,0,0,0.2)";
7537
+ BACKGROUND_FALLBACK_COLOR = "#1a1a2e";
7538
+ MINIMAP_TERRAIN_COLORS = {
7539
+ water: "#3b82f6",
7540
+ mountain: "#78716c",
7541
+ default: "#4ade80"
7542
+ };
7543
+ MINIMAP_UNIT_COLORS = {
7544
+ player: "#60a5fa",
7545
+ enemy: "#f87171",
7546
+ neutral: "#9ca3af",
7547
+ default: "#9ca3af"
7548
+ };
7549
+ UNIT_LABEL_BG_COLORS = {
7550
+ player: "rgba(59,130,246,0.9)",
7551
+ enemy: "rgba(239,68,68,0.9)",
7552
+ neutral: "rgba(107,114,128,0.9)",
7553
+ default: "rgba(107,114,128,0.9)"
7554
+ };
7555
+ SIDE_PLATFORM_COLORS = {
7556
+ ground: "#4a7c59",
7557
+ platform: "#7c6b4a",
7558
+ hazard: "#c0392b",
7559
+ goal: "#f1c40f"
7560
+ };
7561
+ SIDE_PLAYER_COLOR = "#3498db";
7562
+ SIDE_PLAYER_EYE_COLOR = "#ffffff";
7563
+ SIDE_SKY_GRADIENT_TOP = "#1a1a2e";
7564
+ SIDE_SKY_GRADIENT_BOTTOM = "#16213e";
7565
+ SIDE_GRID_COLOR = "rgba(255, 255, 255, 0.03)";
7566
+ SIDE_FX_COLOR = "#ffe066";
7567
+ SIDE_PLATFORM_BEVEL_COLOR = "rgba(255,255,255,0.15)";
7568
+ SIDE_PLATFORM_SHADOW_COLOR = "rgba(0,0,0,0.3)";
7569
+ SIDE_HAZARD_STRIPE_COLOR = "#e74c3c";
7570
+ SIDE_GOAL_GLOW_COLOR = "rgba(241, 196, 15, 0.5)";
7571
+ DEFAULT_BG_COLOR = "#5c94fc";
7514
7572
  }
7515
7573
  });
7516
7574
 
7517
7575
  // components/game/shared/spriteSheetConstants.ts
7518
- var SHEET_COLUMNS;
7576
+ var SHEET_COLUMNS, SPRITE_SHEET_LAYOUT;
7519
7577
  var init_spriteSheetConstants = __esm({
7520
7578
  "components/game/shared/spriteSheetConstants.ts"() {
7521
7579
  SHEET_COLUMNS = 8;
7580
+ SPRITE_SHEET_LAYOUT = {
7581
+ idle: { row: 0, frames: 4, frameRate: 6, loop: true },
7582
+ walk: { row: 1, frames: 8, frameRate: 10, loop: true },
7583
+ attack: { row: 2, frames: 6, frameRate: 12, loop: false },
7584
+ hit: { row: 3, frames: 3, frameRate: 8, loop: false },
7585
+ death: { row: 4, frames: 6, frameRate: 8, loop: false }
7586
+ };
7522
7587
  }
7523
7588
  });
7524
7589
 
@@ -10251,7 +10316,7 @@ var init_useUnitSpriteAtlas = __esm({
10251
10316
  function normalizeBackdrop(bg) {
10252
10317
  return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
10253
10318
  }
10254
- function SideView({
10319
+ function SideCanvas2D({
10255
10320
  player,
10256
10321
  platforms,
10257
10322
  worldWidth,
@@ -10272,24 +10337,18 @@ function SideView({
10272
10337
  const canvasRef = React98.useRef(null);
10273
10338
  const eventBus = useEventBus();
10274
10339
  const keysRef = React98.useRef(/* @__PURE__ */ new Set());
10275
- const imageCache2 = React98.useRef(/* @__PURE__ */ new Map());
10276
- const [loadedImages, setLoadedImages] = React98.useState(/* @__PURE__ */ new Set());
10277
- const loadImage = React98.useCallback((url) => {
10278
- if (!url) return null;
10279
- const cached = imageCache2.current.get(url);
10280
- if (cached?.complete && cached.naturalWidth > 0) {
10281
- if (!loadedImages.has(url)) setLoadedImages((prev) => new Set(prev).add(url));
10282
- return cached;
10283
- }
10284
- if (!cached) {
10285
- const img = new Image();
10286
- img.crossOrigin = "anonymous";
10287
- img.src = url;
10288
- img.onload = () => setLoadedImages((prev) => new Set(prev).add(url));
10289
- imageCache2.current.set(url, img);
10340
+ const spriteUrls = React98.useMemo(() => {
10341
+ const urls = [];
10342
+ if (backgroundImage?.url) urls.push(backgroundImage.url);
10343
+ if (playerSprite?.url) urls.push(playerSprite.url);
10344
+ if (tileSprites) {
10345
+ for (const asset of Object.values(tileSprites)) {
10346
+ if (asset?.url) urls.push(asset.url);
10347
+ }
10290
10348
  }
10291
- return null;
10292
- }, [loadedImages]);
10349
+ return urls;
10350
+ }, [backgroundImage, playerSprite, tileSprites]);
10351
+ const { getImage } = useImageCache(spriteUrls);
10293
10352
  React98.useEffect(() => {
10294
10353
  if (typeof window === "undefined") return;
10295
10354
  const canvas = canvasRef.current;
@@ -10402,7 +10461,7 @@ function SideView({
10402
10461
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
10403
10462
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
10404
10463
  }
10405
- const bgImage = bgImg ? loadImage(bgImg.url) : null;
10464
+ const bgImage = bgImg ? getImage(bgImg.url) : void 0;
10406
10465
  const bgSrc = bgImage ? resolveAssetSource(bgImage, bgImg, NOOP) : null;
10407
10466
  if (bgSrc) {
10408
10467
  blit(ctx, bgSrc, 0, 0, cw, ch);
@@ -10413,12 +10472,12 @@ function SideView({
10413
10472
  ctx.fillRect(0, 0, cw, ch);
10414
10473
  } else {
10415
10474
  const grad = ctx.createLinearGradient(0, 0, 0, ch);
10416
- grad.addColorStop(0, SKY_GRADIENT_TOP);
10417
- grad.addColorStop(1, SKY_GRADIENT_BOTTOM);
10475
+ grad.addColorStop(0, SIDE_SKY_GRADIENT_TOP);
10476
+ grad.addColorStop(1, SIDE_SKY_GRADIENT_BOTTOM);
10418
10477
  ctx.fillStyle = grad;
10419
10478
  ctx.fillRect(0, 0, cw, ch);
10420
10479
  }
10421
- ctx.strokeStyle = GRID_COLOR;
10480
+ ctx.strokeStyle = SIDE_GRID_COLOR;
10422
10481
  ctx.lineWidth = 1;
10423
10482
  const gridSize = 32;
10424
10483
  for (let gx = -camX % gridSize; gx < cw; gx += gridSize) {
@@ -10438,7 +10497,7 @@ function SideView({
10438
10497
  const platY = plat.y - camY;
10439
10498
  const platType = plat.type ?? "ground";
10440
10499
  const spriteAsset = tSprites?.[platType];
10441
- const tileImg = spriteAsset ? loadImage(spriteAsset.url) : null;
10500
+ const tileImg = spriteAsset ? getImage(spriteAsset.url) : void 0;
10442
10501
  const tileSrc = tileImg ? resolveAssetSource(tileImg, spriteAsset, NOOP) : null;
10443
10502
  if (tileSrc) {
10444
10503
  const originX = tileSrc.rect ? tileSrc.rect.sx : 0;
@@ -10453,15 +10512,15 @@ function SideView({
10453
10512
  ctx.drawImage(tileImg, originX, originY, srcW, tileH, platX + tx, platY, drawW, plat.height);
10454
10513
  }
10455
10514
  } else {
10456
- const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
10515
+ const color = SIDE_PLATFORM_COLORS[platType] ?? SIDE_PLATFORM_COLORS.ground;
10457
10516
  ctx.fillStyle = color;
10458
10517
  ctx.fillRect(platX, platY, plat.width, plat.height);
10459
- ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
10518
+ ctx.fillStyle = SIDE_PLATFORM_BEVEL_COLOR;
10460
10519
  ctx.fillRect(platX, platY, plat.width, 3);
10461
- ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
10520
+ ctx.fillStyle = SIDE_PLATFORM_SHADOW_COLOR;
10462
10521
  ctx.fillRect(platX, platY + plat.height - 2, plat.width, 2);
10463
10522
  if (platType === "hazard") {
10464
- ctx.strokeStyle = "#e74c3c";
10523
+ ctx.strokeStyle = SIDE_HAZARD_STRIPE_COLOR;
10465
10524
  ctx.lineWidth = 2;
10466
10525
  for (let sx = platX; sx < platX + plat.width; sx += 12) {
10467
10526
  ctx.beginPath();
@@ -10471,7 +10530,7 @@ function SideView({
10471
10530
  }
10472
10531
  }
10473
10532
  if (platType === "goal") {
10474
- ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
10533
+ ctx.fillStyle = SIDE_GOAL_GLOW_COLOR;
10475
10534
  ctx.beginPath();
10476
10535
  ctx.arc(platX + plat.width / 2, platY - 10, 8, 0, Math.PI * 2);
10477
10536
  ctx.fill();
@@ -10483,7 +10542,7 @@ function SideView({
10483
10542
  const ppx = px - camX;
10484
10543
  const ppy = py - camY;
10485
10544
  const facingRight = auth.facingRight ?? true;
10486
- const playerImg = pSprite ? loadImage(pSprite.url) : null;
10545
+ const playerImg = pSprite ? getImage(pSprite.url) : void 0;
10487
10546
  const playerSrc = playerImg ? resolveAssetSource(playerImg, pSprite, NOOP) : null;
10488
10547
  if (playerSrc) {
10489
10548
  ctx.save();
@@ -10496,7 +10555,7 @@ function SideView({
10496
10555
  }
10497
10556
  ctx.restore();
10498
10557
  } else {
10499
- ctx.fillStyle = PLAYER_COLOR;
10558
+ ctx.fillStyle = SIDE_PLAYER_COLOR;
10500
10559
  const radius = Math.min(pw, ph) * 0.25;
10501
10560
  ctx.beginPath();
10502
10561
  ctx.moveTo(ppx + radius, ppy);
@@ -10512,7 +10571,7 @@ function SideView({
10512
10571
  const eyeY = ppy + ph * 0.3;
10513
10572
  const eyeSize = 3;
10514
10573
  const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
10515
- ctx.fillStyle = PLAYER_EYE_COLOR;
10574
+ ctx.fillStyle = SIDE_PLAYER_EYE_COLOR;
10516
10575
  ctx.beginPath();
10517
10576
  ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
10518
10577
  ctx.fill();
@@ -10526,7 +10585,7 @@ function SideView({
10526
10585
  const alpha = Math.min(1, fx.ttl / 4);
10527
10586
  const prev = ctx.globalAlpha;
10528
10587
  ctx.globalAlpha = alpha;
10529
- ctx.fillStyle = "#ffe066";
10588
+ ctx.fillStyle = SIDE_FX_COLOR;
10530
10589
  ctx.beginPath();
10531
10590
  ctx.arc(fxScreenX, fxScreenY, 12, 0, Math.PI * 2);
10532
10591
  ctx.fill();
@@ -10534,7 +10593,7 @@ function SideView({
10534
10593
  }
10535
10594
  };
10536
10595
  return interp.startLoop(drawFrame);
10537
- }, [interp.startLoop, loadImage]);
10596
+ }, [interp.startLoop, getImage]);
10538
10597
  return /* @__PURE__ */ jsxRuntime.jsx(
10539
10598
  "canvas",
10540
10599
  {
@@ -10546,6 +10605,33 @@ function SideView({
10546
10605
  }
10547
10606
  );
10548
10607
  }
10608
+ var NOOP;
10609
+ var init_SideCanvas2D = __esm({
10610
+ "components/game/2d/molecules/SideCanvas2D.tsx"() {
10611
+ "use client";
10612
+ init_cn();
10613
+ init_useEventBus();
10614
+ init_useImageCache();
10615
+ init_atlasSlice();
10616
+ init_useRenderInterpolation();
10617
+ init_verificationRegistry();
10618
+ init_isometric();
10619
+ NOOP = () => {
10620
+ };
10621
+ SideCanvas2D.displayName = "SideCanvas2D";
10622
+ }
10623
+ });
10624
+ function normalizeBackdrop2(bg) {
10625
+ return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
10626
+ }
10627
+ function resolveTileFallbackColor(tile) {
10628
+ const override = tile.color;
10629
+ return override ?? TERRAIN_COLORS[tile.terrain ?? ""] ?? TERRAIN_COLORS.default;
10630
+ }
10631
+ function resolveUnitTeamColor(unit) {
10632
+ const override = unit.tint;
10633
+ return override ?? TEAM_COLORS[unit.team ?? ""] ?? TEAM_COLORS.default;
10634
+ }
10549
10635
  function Canvas2D({
10550
10636
  // Closed-circuit
10551
10637
  className,
@@ -10595,13 +10681,13 @@ function Canvas2D({
10595
10681
  // Side-view asset resolution
10596
10682
  playerSprite,
10597
10683
  tileSprites,
10598
- bgColor = "#5c94fc",
10684
+ bgColor = DEFAULT_BG_COLOR,
10599
10685
  worldWidth = 800,
10600
10686
  worldHeight = 400,
10601
10687
  // Remote asset loading
10602
10688
  assetManifest
10603
10689
  }) {
10604
- const backgroundImage = normalizeBackdrop(backgroundImageRaw);
10690
+ const backgroundImage = normalizeBackdrop2(backgroundImageRaw);
10605
10691
  const isSide = projection === "side";
10606
10692
  const isFree = projection === "free";
10607
10693
  const flatLike = projection === "hex" || projection === "flat" || isFree;
@@ -10785,7 +10871,7 @@ function Canvas2D({
10785
10871
  return sortedTiles.map((t2) => ({
10786
10872
  x: t2.x,
10787
10873
  y: t2.y,
10788
- color: t2.terrain === "water" ? "#3b82f6" : t2.terrain === "mountain" ? "#78716c" : "#4ade80"
10874
+ color: MINIMAP_TERRAIN_COLORS[t2.terrain ?? ""] ?? MINIMAP_TERRAIN_COLORS.default
10789
10875
  }));
10790
10876
  }, [showMinimap, sortedTiles]);
10791
10877
  const miniMapUnits = React98.useMemo(() => {
@@ -10793,7 +10879,7 @@ function Canvas2D({
10793
10879
  return units.filter((u) => u.position).map((u) => ({
10794
10880
  x: u.position.x,
10795
10881
  y: u.position.y,
10796
- color: u.team === "player" ? "#60a5fa" : u.team === "enemy" ? "#f87171" : "#9ca3af",
10882
+ color: MINIMAP_UNIT_COLORS[u.team ?? ""] ?? MINIMAP_UNIT_COLORS.default,
10797
10883
  isPlayer: u.team === "player"
10798
10884
  }));
10799
10885
  }, [showMinimap, units]);
@@ -10830,7 +10916,7 @@ function Canvas2D({
10830
10916
  }
10831
10917
  }
10832
10918
  } else {
10833
- ctx.fillStyle = "#1a1a2e";
10919
+ ctx.fillStyle = BACKGROUND_FALLBACK_COLOR;
10834
10920
  ctx.fillRect(0, 0, viewportSize.width, viewportSize.height);
10835
10921
  }
10836
10922
  if (sortedTiles.length === 0 && units.length === 0 && features.length === 0) return;
@@ -10860,15 +10946,15 @@ function Canvas2D({
10860
10946
  } else if (img && img.naturalWidth === 0) {
10861
10947
  ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
10862
10948
  } else if (squareGrid) {
10863
- ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
10949
+ ctx.fillStyle = resolveTileFallbackColor(tile);
10864
10950
  ctx.fillRect(pos.x, pos.y, scaledTileWidth, scaledTileWidth);
10865
- ctx.strokeStyle = "rgba(0,0,0,0.2)";
10951
+ ctx.strokeStyle = GRID_STROKE_COLOR;
10866
10952
  ctx.lineWidth = 1;
10867
10953
  ctx.strokeRect(pos.x, pos.y, scaledTileWidth, scaledTileWidth);
10868
10954
  } else {
10869
10955
  const centerX = pos.x + scaledTileWidth / 2;
10870
10956
  const topY = pos.y + scaledDiamondTopY;
10871
- ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
10957
+ ctx.fillStyle = resolveTileFallbackColor(tile);
10872
10958
  ctx.beginPath();
10873
10959
  ctx.moveTo(centerX, topY);
10874
10960
  ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
@@ -10876,7 +10962,7 @@ function Canvas2D({
10876
10962
  ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
10877
10963
  ctx.closePath();
10878
10964
  ctx.fill();
10879
- ctx.strokeStyle = "rgba(0,0,0,0.2)";
10965
+ ctx.strokeStyle = GRID_STROKE_COLOR;
10880
10966
  ctx.lineWidth = 1;
10881
10967
  ctx.stroke();
10882
10968
  }
@@ -10897,11 +10983,11 @@ function Canvas2D({
10897
10983
  ctx.fill();
10898
10984
  };
10899
10985
  if (hoveredTile && hoveredTile.x === tile.x && hoveredTile.y === tile.y) {
10900
- drawHighlight("rgba(255, 255, 255, 0.15)");
10986
+ drawHighlight(HIGHLIGHT_COLORS.hover);
10901
10987
  }
10902
10988
  const tileKey = `${tile.x},${tile.y}`;
10903
- if (validMoveSet.has(tileKey)) drawHighlight("rgba(74, 222, 128, 0.25)");
10904
- if (attackTargetSet.has(tileKey)) drawHighlight("rgba(239, 68, 68, 0.35)");
10989
+ if (validMoveSet.has(tileKey)) drawHighlight(HIGHLIGHT_COLORS.validMove);
10990
+ if (attackTargetSet.has(tileKey)) drawHighlight(HIGHLIGHT_COLORS.attack);
10905
10991
  if (debug2) {
10906
10992
  const centerX = pos.x + scaledTileWidth / 2;
10907
10993
  const centerY = squareGrid ? pos.y + scaledTileWidth / 2 : pos.y + scaledFloorHeight / 2 + scaledDiamondTopY;
@@ -10975,9 +11061,9 @@ function Canvas2D({
10975
11061
  const maxUnitW = scaledTileWidth * spriteMaxWidthRatio * unitScale;
10976
11062
  const unitIsSheet = unit.spriteSheet?.url !== void 0;
10977
11063
  const unitSrc = img && !unitIsSheet ? resolveAssetSource(img, unitAsset, bumpAtlas) : null;
10978
- const SHEET_ROWS = 5;
10979
11064
  const sheetFrameW = img ? img.naturalWidth / SHEET_COLUMNS : 0;
10980
- const sheetFrameH = img ? img.naturalHeight / SHEET_ROWS : 0;
11065
+ const sheetFrameH = img ? img.naturalHeight / LEGACY_SHEET_ROWS : 0;
11066
+ const legacyFrame = frameRect(0, 0, SHEET_COLUMNS, sheetFrameW, sheetFrameH);
10981
11067
  const frameW = unitIsSheet ? sheetFrameW : img?.naturalWidth ?? 1;
10982
11068
  const frameH = unitIsSheet ? sheetFrameH : img?.naturalHeight ?? 1;
10983
11069
  const ar = unitIsSheet ? sheetFrameW / (sheetFrameH || 1) : unitSrc ? unitSrc.aspect : frameW / (frameH || 1);
@@ -10995,17 +11081,16 @@ function Canvas2D({
10995
11081
  ctx.globalAlpha = 0.25;
10996
11082
  if (img) {
10997
11083
  if (unitIsSheet) {
10998
- ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
11084
+ ctx.drawImage(img, legacyFrame.sx, legacyFrame.sy, legacyFrame.sw, legacyFrame.sh, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
10999
11085
  } else if (unitSrc) {
11000
11086
  blit(ctx, unitSrc, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
11001
11087
  } else {
11002
11088
  ctx.drawImage(img, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
11003
11089
  }
11004
11090
  } else {
11005
- const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
11006
11091
  ctx.beginPath();
11007
11092
  ctx.arc(ghostCenterX, ghostGroundY - 16 * scale, 20 * scale, 0, Math.PI * 2);
11008
- ctx.fillStyle = color;
11093
+ ctx.fillStyle = resolveUnitTeamColor(unit);
11009
11094
  ctx.fill();
11010
11095
  }
11011
11096
  ctx.restore();
@@ -11013,7 +11098,7 @@ function Canvas2D({
11013
11098
  if (isSelected) {
11014
11099
  ctx.beginPath();
11015
11100
  ctx.ellipse(centerX, groundY, drawW / 2 + 4 * scale, 12 * scale, 0, 0, Math.PI * 2);
11016
- ctx.strokeStyle = "rgba(0, 200, 255, 0.8)";
11101
+ ctx.strokeStyle = SELECTION_RING_COLOR;
11017
11102
  ctx.lineWidth = 3;
11018
11103
  ctx.stroke();
11019
11104
  }
@@ -11030,7 +11115,7 @@ function Canvas2D({
11030
11115
  const spriteY = groundY - fDrawH - (frame.applyBreathing ? breatheOffset : 0);
11031
11116
  ctx.save();
11032
11117
  if (unit.team) {
11033
- ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
11118
+ ctx.shadowColor = TEAM_SHADOW_COLORS[unit.team] ?? TEAM_SHADOW_COLORS.default;
11034
11119
  ctx.shadowBlur = 12 * scale;
11035
11120
  }
11036
11121
  if (frame.flipX) {
@@ -11045,7 +11130,7 @@ function Canvas2D({
11045
11130
  const spriteY = groundY - drawH - breatheOffset;
11046
11131
  const drawUnit = (x) => {
11047
11132
  if (unitIsSheet) {
11048
- ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, x, spriteY, drawW, drawH);
11133
+ ctx.drawImage(img, legacyFrame.sx, legacyFrame.sy, legacyFrame.sw, legacyFrame.sh, x, spriteY, drawW, drawH);
11049
11134
  } else if (unitSrc) {
11050
11135
  blit(ctx, unitSrc, x, spriteY, drawW, drawH);
11051
11136
  } else {
@@ -11054,7 +11139,7 @@ function Canvas2D({
11054
11139
  };
11055
11140
  if (unit.team) {
11056
11141
  ctx.save();
11057
- ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
11142
+ ctx.shadowColor = TEAM_SHADOW_COLORS[unit.team] ?? TEAM_SHADOW_COLORS.default;
11058
11143
  ctx.shadowBlur = 12 * scale;
11059
11144
  drawUnit(centerX - drawW / 2);
11060
11145
  ctx.restore();
@@ -11062,10 +11147,9 @@ function Canvas2D({
11062
11147
  drawUnit(centerX - drawW / 2);
11063
11148
  }
11064
11149
  } else {
11065
- const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
11066
11150
  ctx.beginPath();
11067
11151
  ctx.arc(centerX, groundY - 20 * scale - breatheOffset, 20 * scale, 0, Math.PI * 2);
11068
- ctx.fillStyle = color;
11152
+ ctx.fillStyle = resolveUnitTeamColor(unit);
11069
11153
  ctx.fill();
11070
11154
  ctx.strokeStyle = "rgba(255,255,255,0.8)";
11071
11155
  ctx.lineWidth = 2;
@@ -11304,7 +11388,7 @@ function Canvas2D({
11304
11388
  }
11305
11389
  if (isSide) {
11306
11390
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { ref: containerRef, className: cn("relative overflow-hidden w-full h-full", className), children: /* @__PURE__ */ jsxRuntime.jsx(
11307
- SideView,
11391
+ SideCanvas2D,
11308
11392
  {
11309
11393
  player,
11310
11394
  platforms: platforms && platforms.length ? platforms : DEFAULT_PLATFORMS,
@@ -11402,7 +11486,7 @@ function Canvas2D({
11402
11486
  {
11403
11487
  as: "span",
11404
11488
  className: "text-white text-xs font-bold px-1.5 py-0.5 rounded mb-0.5 whitespace-nowrap block",
11405
- 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)" },
11489
+ style: { background: UNIT_LABEL_BG_COLORS[unit.team ?? ""] ?? UNIT_LABEL_BG_COLORS.default },
11406
11490
  children: unit.name
11407
11491
  }
11408
11492
  ),
@@ -11426,7 +11510,7 @@ function Canvas2D({
11426
11510
  }
11427
11511
  );
11428
11512
  }
11429
- var PLATFORM_COLORS, NOOP, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR, DEFAULT_PLATFORMS;
11513
+ var LEGACY_SHEET_ROWS, DEFAULT_PLATFORMS;
11430
11514
  var init_Canvas2D = __esm({
11431
11515
  "components/game/2d/molecules/Canvas2D.tsx"() {
11432
11516
  "use client";
@@ -11448,19 +11532,9 @@ var init_Canvas2D = __esm({
11448
11532
  init_verificationRegistry();
11449
11533
  init_isometric();
11450
11534
  init_spriteSheetConstants();
11451
- PLATFORM_COLORS = {
11452
- ground: "#4a7c59",
11453
- platform: "#7c6b4a",
11454
- hazard: "#c0392b",
11455
- goal: "#f1c40f"
11456
- };
11457
- NOOP = () => {
11458
- };
11459
- PLAYER_COLOR = "#3498db";
11460
- PLAYER_EYE_COLOR = "#ffffff";
11461
- SKY_GRADIENT_TOP = "#1a1a2e";
11462
- SKY_GRADIENT_BOTTOM = "#16213e";
11463
- GRID_COLOR = "rgba(255, 255, 255, 0.03)";
11535
+ init_spriteAnimation();
11536
+ init_SideCanvas2D();
11537
+ LEGACY_SHEET_ROWS = Object.keys(SPRITE_SHEET_LAYOUT).length;
11464
11538
  DEFAULT_PLATFORMS = [
11465
11539
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
11466
11540
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
@@ -30756,13 +30830,13 @@ var init_MapView = __esm({
30756
30830
  shadowSize: [41, 41]
30757
30831
  });
30758
30832
  L.Marker.prototype.options.icon = defaultIcon;
30759
- const { useEffect: useEffect68, useRef: useRef65, useCallback: useCallback103, useState: useState98 } = React98__namespace.default;
30833
+ const { useEffect: useEffect69, useRef: useRef66, useCallback: useCallback104, useState: useState98 } = React98__namespace.default;
30760
30834
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
30761
30835
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
30762
30836
  function MapUpdater({ centerLat, centerLng, zoom }) {
30763
30837
  const map = useMap();
30764
- const prevRef = useRef65({ centerLat, centerLng, zoom });
30765
- useEffect68(() => {
30838
+ const prevRef = useRef66({ centerLat, centerLng, zoom });
30839
+ useEffect69(() => {
30766
30840
  const prev = prevRef.current;
30767
30841
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
30768
30842
  map.setView([centerLat, centerLng], zoom);
@@ -30773,7 +30847,7 @@ var init_MapView = __esm({
30773
30847
  }
30774
30848
  function MapClickHandler({ onMapClick }) {
30775
30849
  const map = useMap();
30776
- useEffect68(() => {
30850
+ useEffect69(() => {
30777
30851
  if (!onMapClick) return;
30778
30852
  const handler = (e) => {
30779
30853
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -30802,7 +30876,7 @@ var init_MapView = __esm({
30802
30876
  }) {
30803
30877
  const eventBus = useEventBus2();
30804
30878
  const [clickedPosition, setClickedPosition] = useState98(null);
30805
- const handleMapClick = useCallback103((lat, lng) => {
30879
+ const handleMapClick = useCallback104((lat, lng) => {
30806
30880
  if (showClickedPin) {
30807
30881
  setClickedPosition({ lat, lng });
30808
30882
  }
@@ -30811,7 +30885,7 @@ var init_MapView = __esm({
30811
30885
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
30812
30886
  }
30813
30887
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
30814
- const handleMarkerClick = useCallback103((marker) => {
30888
+ const handleMarkerClick = useCallback104((marker) => {
30815
30889
  onMarkerClick?.(marker);
30816
30890
  if (markerClickEvent) {
30817
30891
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });