@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.
@@ -8020,7 +8020,7 @@ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric")
8020
8020
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
8021
8021
  return { x: Math.round(tileX), y: Math.round(tileY) };
8022
8022
  }
8023
- var TILE_WIDTH, TILE_HEIGHT, FLOOR_HEIGHT, DIAMOND_TOP_Y, FEATURE_COLORS;
8023
+ 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;
8024
8024
  var init_isometric = __esm({
8025
8025
  "components/game/shared/isometric.ts"() {
8026
8026
  TILE_WIDTH = 256;
@@ -8037,14 +8037,79 @@ var init_isometric = __esm({
8037
8037
  mountain: "#78716c",
8038
8038
  default: "#9ca3af"
8039
8039
  };
8040
+ TERRAIN_COLORS = {
8041
+ water: "#3b82f6",
8042
+ mountain: "#78716c",
8043
+ stone: "#9ca3af",
8044
+ default: "#4ade80"
8045
+ };
8046
+ TEAM_COLORS = {
8047
+ player: "#3b82f6",
8048
+ enemy: "#ef4444",
8049
+ neutral: "#6b7280",
8050
+ default: "#6b7280"
8051
+ };
8052
+ TEAM_SHADOW_COLORS = {
8053
+ player: "rgba(0,150,255,0.6)",
8054
+ default: "rgba(255,50,50,0.6)"
8055
+ };
8056
+ HIGHLIGHT_COLORS = {
8057
+ hover: "rgba(255,255,255,0.15)",
8058
+ validMove: "rgba(74,222,128,0.25)",
8059
+ attack: "rgba(239,68,68,0.35)"
8060
+ };
8061
+ SELECTION_RING_COLOR = "rgba(0,200,255,0.8)";
8062
+ GRID_STROKE_COLOR = "rgba(0,0,0,0.2)";
8063
+ BACKGROUND_FALLBACK_COLOR = "#1a1a2e";
8064
+ MINIMAP_TERRAIN_COLORS = {
8065
+ water: "#3b82f6",
8066
+ mountain: "#78716c",
8067
+ default: "#4ade80"
8068
+ };
8069
+ MINIMAP_UNIT_COLORS = {
8070
+ player: "#60a5fa",
8071
+ enemy: "#f87171",
8072
+ neutral: "#9ca3af",
8073
+ default: "#9ca3af"
8074
+ };
8075
+ UNIT_LABEL_BG_COLORS = {
8076
+ player: "rgba(59,130,246,0.9)",
8077
+ enemy: "rgba(239,68,68,0.9)",
8078
+ neutral: "rgba(107,114,128,0.9)",
8079
+ default: "rgba(107,114,128,0.9)"
8080
+ };
8081
+ SIDE_PLATFORM_COLORS = {
8082
+ ground: "#4a7c59",
8083
+ platform: "#7c6b4a",
8084
+ hazard: "#c0392b",
8085
+ goal: "#f1c40f"
8086
+ };
8087
+ SIDE_PLAYER_COLOR = "#3498db";
8088
+ SIDE_PLAYER_EYE_COLOR = "#ffffff";
8089
+ SIDE_SKY_GRADIENT_TOP = "#1a1a2e";
8090
+ SIDE_SKY_GRADIENT_BOTTOM = "#16213e";
8091
+ SIDE_GRID_COLOR = "rgba(255, 255, 255, 0.03)";
8092
+ SIDE_FX_COLOR = "#ffe066";
8093
+ SIDE_PLATFORM_BEVEL_COLOR = "rgba(255,255,255,0.15)";
8094
+ SIDE_PLATFORM_SHADOW_COLOR = "rgba(0,0,0,0.3)";
8095
+ SIDE_HAZARD_STRIPE_COLOR = "#e74c3c";
8096
+ SIDE_GOAL_GLOW_COLOR = "rgba(241, 196, 15, 0.5)";
8097
+ DEFAULT_BG_COLOR = "#5c94fc";
8040
8098
  }
8041
8099
  });
8042
8100
 
8043
8101
  // components/game/shared/spriteSheetConstants.ts
8044
- var SHEET_COLUMNS;
8102
+ var SHEET_COLUMNS, SPRITE_SHEET_LAYOUT;
8045
8103
  var init_spriteSheetConstants = __esm({
8046
8104
  "components/game/shared/spriteSheetConstants.ts"() {
8047
8105
  SHEET_COLUMNS = 8;
8106
+ SPRITE_SHEET_LAYOUT = {
8107
+ idle: { row: 0, frames: 4, frameRate: 6, loop: true },
8108
+ walk: { row: 1, frames: 8, frameRate: 10, loop: true },
8109
+ attack: { row: 2, frames: 6, frameRate: 12, loop: false },
8110
+ hit: { row: 3, frames: 3, frameRate: 8, loop: false },
8111
+ death: { row: 4, frames: 6, frameRate: 8, loop: false }
8112
+ };
8048
8113
  }
8049
8114
  });
8050
8115
 
@@ -10598,7 +10663,7 @@ var init_useUnitSpriteAtlas = __esm({
10598
10663
  function normalizeBackdrop(bg) {
10599
10664
  return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
10600
10665
  }
10601
- function SideView({
10666
+ function SideCanvas2D({
10602
10667
  player,
10603
10668
  platforms,
10604
10669
  worldWidth,
@@ -10619,24 +10684,18 @@ function SideView({
10619
10684
  const canvasRef = React96.useRef(null);
10620
10685
  const eventBus = useEventBus();
10621
10686
  const keysRef = React96.useRef(/* @__PURE__ */ new Set());
10622
- const imageCache2 = React96.useRef(/* @__PURE__ */ new Map());
10623
- const [loadedImages, setLoadedImages] = React96.useState(/* @__PURE__ */ new Set());
10624
- const loadImage = React96.useCallback((url) => {
10625
- if (!url) return null;
10626
- const cached = imageCache2.current.get(url);
10627
- if (cached?.complete && cached.naturalWidth > 0) {
10628
- if (!loadedImages.has(url)) setLoadedImages((prev) => new Set(prev).add(url));
10629
- return cached;
10630
- }
10631
- if (!cached) {
10632
- const img = new Image();
10633
- img.crossOrigin = "anonymous";
10634
- img.src = url;
10635
- img.onload = () => setLoadedImages((prev) => new Set(prev).add(url));
10636
- imageCache2.current.set(url, img);
10687
+ const spriteUrls = React96.useMemo(() => {
10688
+ const urls = [];
10689
+ if (backgroundImage?.url) urls.push(backgroundImage.url);
10690
+ if (playerSprite?.url) urls.push(playerSprite.url);
10691
+ if (tileSprites) {
10692
+ for (const asset of Object.values(tileSprites)) {
10693
+ if (asset?.url) urls.push(asset.url);
10694
+ }
10637
10695
  }
10638
- return null;
10639
- }, [loadedImages]);
10696
+ return urls;
10697
+ }, [backgroundImage, playerSprite, tileSprites]);
10698
+ const { getImage } = useImageCache(spriteUrls);
10640
10699
  React96.useEffect(() => {
10641
10700
  if (typeof window === "undefined") return;
10642
10701
  const canvas = canvasRef.current;
@@ -10749,7 +10808,7 @@ function SideView({
10749
10808
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
10750
10809
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
10751
10810
  }
10752
- const bgImage = bgImg ? loadImage(bgImg.url) : null;
10811
+ const bgImage = bgImg ? getImage(bgImg.url) : void 0;
10753
10812
  const bgSrc = bgImage ? resolveAssetSource(bgImage, bgImg, NOOP) : null;
10754
10813
  if (bgSrc) {
10755
10814
  blit(ctx, bgSrc, 0, 0, cw, ch);
@@ -10760,12 +10819,12 @@ function SideView({
10760
10819
  ctx.fillRect(0, 0, cw, ch);
10761
10820
  } else {
10762
10821
  const grad = ctx.createLinearGradient(0, 0, 0, ch);
10763
- grad.addColorStop(0, SKY_GRADIENT_TOP);
10764
- grad.addColorStop(1, SKY_GRADIENT_BOTTOM);
10822
+ grad.addColorStop(0, SIDE_SKY_GRADIENT_TOP);
10823
+ grad.addColorStop(1, SIDE_SKY_GRADIENT_BOTTOM);
10765
10824
  ctx.fillStyle = grad;
10766
10825
  ctx.fillRect(0, 0, cw, ch);
10767
10826
  }
10768
- ctx.strokeStyle = GRID_COLOR;
10827
+ ctx.strokeStyle = SIDE_GRID_COLOR;
10769
10828
  ctx.lineWidth = 1;
10770
10829
  const gridSize = 32;
10771
10830
  for (let gx = -camX % gridSize; gx < cw; gx += gridSize) {
@@ -10785,7 +10844,7 @@ function SideView({
10785
10844
  const platY = plat.y - camY;
10786
10845
  const platType = plat.type ?? "ground";
10787
10846
  const spriteAsset = tSprites?.[platType];
10788
- const tileImg = spriteAsset ? loadImage(spriteAsset.url) : null;
10847
+ const tileImg = spriteAsset ? getImage(spriteAsset.url) : void 0;
10789
10848
  const tileSrc = tileImg ? resolveAssetSource(tileImg, spriteAsset, NOOP) : null;
10790
10849
  if (tileSrc) {
10791
10850
  const originX = tileSrc.rect ? tileSrc.rect.sx : 0;
@@ -10800,15 +10859,15 @@ function SideView({
10800
10859
  ctx.drawImage(tileImg, originX, originY, srcW, tileH, platX + tx, platY, drawW, plat.height);
10801
10860
  }
10802
10861
  } else {
10803
- const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
10862
+ const color = SIDE_PLATFORM_COLORS[platType] ?? SIDE_PLATFORM_COLORS.ground;
10804
10863
  ctx.fillStyle = color;
10805
10864
  ctx.fillRect(platX, platY, plat.width, plat.height);
10806
- ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
10865
+ ctx.fillStyle = SIDE_PLATFORM_BEVEL_COLOR;
10807
10866
  ctx.fillRect(platX, platY, plat.width, 3);
10808
- ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
10867
+ ctx.fillStyle = SIDE_PLATFORM_SHADOW_COLOR;
10809
10868
  ctx.fillRect(platX, platY + plat.height - 2, plat.width, 2);
10810
10869
  if (platType === "hazard") {
10811
- ctx.strokeStyle = "#e74c3c";
10870
+ ctx.strokeStyle = SIDE_HAZARD_STRIPE_COLOR;
10812
10871
  ctx.lineWidth = 2;
10813
10872
  for (let sx = platX; sx < platX + plat.width; sx += 12) {
10814
10873
  ctx.beginPath();
@@ -10818,7 +10877,7 @@ function SideView({
10818
10877
  }
10819
10878
  }
10820
10879
  if (platType === "goal") {
10821
- ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
10880
+ ctx.fillStyle = SIDE_GOAL_GLOW_COLOR;
10822
10881
  ctx.beginPath();
10823
10882
  ctx.arc(platX + plat.width / 2, platY - 10, 8, 0, Math.PI * 2);
10824
10883
  ctx.fill();
@@ -10830,7 +10889,7 @@ function SideView({
10830
10889
  const ppx = px - camX;
10831
10890
  const ppy = py - camY;
10832
10891
  const facingRight = auth.facingRight ?? true;
10833
- const playerImg = pSprite ? loadImage(pSprite.url) : null;
10892
+ const playerImg = pSprite ? getImage(pSprite.url) : void 0;
10834
10893
  const playerSrc = playerImg ? resolveAssetSource(playerImg, pSprite, NOOP) : null;
10835
10894
  if (playerSrc) {
10836
10895
  ctx.save();
@@ -10843,7 +10902,7 @@ function SideView({
10843
10902
  }
10844
10903
  ctx.restore();
10845
10904
  } else {
10846
- ctx.fillStyle = PLAYER_COLOR;
10905
+ ctx.fillStyle = SIDE_PLAYER_COLOR;
10847
10906
  const radius = Math.min(pw, ph) * 0.25;
10848
10907
  ctx.beginPath();
10849
10908
  ctx.moveTo(ppx + radius, ppy);
@@ -10859,7 +10918,7 @@ function SideView({
10859
10918
  const eyeY = ppy + ph * 0.3;
10860
10919
  const eyeSize = 3;
10861
10920
  const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
10862
- ctx.fillStyle = PLAYER_EYE_COLOR;
10921
+ ctx.fillStyle = SIDE_PLAYER_EYE_COLOR;
10863
10922
  ctx.beginPath();
10864
10923
  ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
10865
10924
  ctx.fill();
@@ -10873,7 +10932,7 @@ function SideView({
10873
10932
  const alpha = Math.min(1, fx.ttl / 4);
10874
10933
  const prev = ctx.globalAlpha;
10875
10934
  ctx.globalAlpha = alpha;
10876
- ctx.fillStyle = "#ffe066";
10935
+ ctx.fillStyle = SIDE_FX_COLOR;
10877
10936
  ctx.beginPath();
10878
10937
  ctx.arc(fxScreenX, fxScreenY, 12, 0, Math.PI * 2);
10879
10938
  ctx.fill();
@@ -10881,7 +10940,7 @@ function SideView({
10881
10940
  }
10882
10941
  };
10883
10942
  return interp.startLoop(drawFrame);
10884
- }, [interp.startLoop, loadImage]);
10943
+ }, [interp.startLoop, getImage]);
10885
10944
  return /* @__PURE__ */ jsxRuntime.jsx(
10886
10945
  "canvas",
10887
10946
  {
@@ -10893,6 +10952,33 @@ function SideView({
10893
10952
  }
10894
10953
  );
10895
10954
  }
10955
+ var NOOP;
10956
+ var init_SideCanvas2D = __esm({
10957
+ "components/game/2d/molecules/SideCanvas2D.tsx"() {
10958
+ "use client";
10959
+ init_cn();
10960
+ init_useEventBus();
10961
+ init_useImageCache();
10962
+ init_atlasSlice();
10963
+ init_useRenderInterpolation();
10964
+ init_verificationRegistry();
10965
+ init_isometric();
10966
+ NOOP = () => {
10967
+ };
10968
+ SideCanvas2D.displayName = "SideCanvas2D";
10969
+ }
10970
+ });
10971
+ function normalizeBackdrop2(bg) {
10972
+ return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
10973
+ }
10974
+ function resolveTileFallbackColor(tile) {
10975
+ const override = tile.color;
10976
+ return override ?? TERRAIN_COLORS[tile.terrain ?? ""] ?? TERRAIN_COLORS.default;
10977
+ }
10978
+ function resolveUnitTeamColor(unit) {
10979
+ const override = unit.tint;
10980
+ return override ?? TEAM_COLORS[unit.team ?? ""] ?? TEAM_COLORS.default;
10981
+ }
10896
10982
  function Canvas2D({
10897
10983
  // Closed-circuit
10898
10984
  className,
@@ -10942,13 +11028,13 @@ function Canvas2D({
10942
11028
  // Side-view asset resolution
10943
11029
  playerSprite,
10944
11030
  tileSprites,
10945
- bgColor = "#5c94fc",
11031
+ bgColor = DEFAULT_BG_COLOR,
10946
11032
  worldWidth = 800,
10947
11033
  worldHeight = 400,
10948
11034
  // Remote asset loading
10949
11035
  assetManifest
10950
11036
  }) {
10951
- const backgroundImage = normalizeBackdrop(backgroundImageRaw);
11037
+ const backgroundImage = normalizeBackdrop2(backgroundImageRaw);
10952
11038
  const isSide = projection === "side";
10953
11039
  const isFree = projection === "free";
10954
11040
  const flatLike = projection === "hex" || projection === "flat" || isFree;
@@ -11132,7 +11218,7 @@ function Canvas2D({
11132
11218
  return sortedTiles.map((t2) => ({
11133
11219
  x: t2.x,
11134
11220
  y: t2.y,
11135
- color: t2.terrain === "water" ? "#3b82f6" : t2.terrain === "mountain" ? "#78716c" : "#4ade80"
11221
+ color: MINIMAP_TERRAIN_COLORS[t2.terrain ?? ""] ?? MINIMAP_TERRAIN_COLORS.default
11136
11222
  }));
11137
11223
  }, [showMinimap, sortedTiles]);
11138
11224
  const miniMapUnits = React96.useMemo(() => {
@@ -11140,7 +11226,7 @@ function Canvas2D({
11140
11226
  return units.filter((u) => u.position).map((u) => ({
11141
11227
  x: u.position.x,
11142
11228
  y: u.position.y,
11143
- color: u.team === "player" ? "#60a5fa" : u.team === "enemy" ? "#f87171" : "#9ca3af",
11229
+ color: MINIMAP_UNIT_COLORS[u.team ?? ""] ?? MINIMAP_UNIT_COLORS.default,
11144
11230
  isPlayer: u.team === "player"
11145
11231
  }));
11146
11232
  }, [showMinimap, units]);
@@ -11177,7 +11263,7 @@ function Canvas2D({
11177
11263
  }
11178
11264
  }
11179
11265
  } else {
11180
- ctx.fillStyle = "#1a1a2e";
11266
+ ctx.fillStyle = BACKGROUND_FALLBACK_COLOR;
11181
11267
  ctx.fillRect(0, 0, viewportSize.width, viewportSize.height);
11182
11268
  }
11183
11269
  if (sortedTiles.length === 0 && units.length === 0 && features.length === 0) return;
@@ -11207,15 +11293,15 @@ function Canvas2D({
11207
11293
  } else if (img && img.naturalWidth === 0) {
11208
11294
  ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
11209
11295
  } else if (squareGrid) {
11210
- ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
11296
+ ctx.fillStyle = resolveTileFallbackColor(tile);
11211
11297
  ctx.fillRect(pos.x, pos.y, scaledTileWidth, scaledTileWidth);
11212
- ctx.strokeStyle = "rgba(0,0,0,0.2)";
11298
+ ctx.strokeStyle = GRID_STROKE_COLOR;
11213
11299
  ctx.lineWidth = 1;
11214
11300
  ctx.strokeRect(pos.x, pos.y, scaledTileWidth, scaledTileWidth);
11215
11301
  } else {
11216
11302
  const centerX = pos.x + scaledTileWidth / 2;
11217
11303
  const topY = pos.y + scaledDiamondTopY;
11218
- ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
11304
+ ctx.fillStyle = resolveTileFallbackColor(tile);
11219
11305
  ctx.beginPath();
11220
11306
  ctx.moveTo(centerX, topY);
11221
11307
  ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
@@ -11223,7 +11309,7 @@ function Canvas2D({
11223
11309
  ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
11224
11310
  ctx.closePath();
11225
11311
  ctx.fill();
11226
- ctx.strokeStyle = "rgba(0,0,0,0.2)";
11312
+ ctx.strokeStyle = GRID_STROKE_COLOR;
11227
11313
  ctx.lineWidth = 1;
11228
11314
  ctx.stroke();
11229
11315
  }
@@ -11244,11 +11330,11 @@ function Canvas2D({
11244
11330
  ctx.fill();
11245
11331
  };
11246
11332
  if (hoveredTile && hoveredTile.x === tile.x && hoveredTile.y === tile.y) {
11247
- drawHighlight("rgba(255, 255, 255, 0.15)");
11333
+ drawHighlight(HIGHLIGHT_COLORS.hover);
11248
11334
  }
11249
11335
  const tileKey = `${tile.x},${tile.y}`;
11250
- if (validMoveSet.has(tileKey)) drawHighlight("rgba(74, 222, 128, 0.25)");
11251
- if (attackTargetSet.has(tileKey)) drawHighlight("rgba(239, 68, 68, 0.35)");
11336
+ if (validMoveSet.has(tileKey)) drawHighlight(HIGHLIGHT_COLORS.validMove);
11337
+ if (attackTargetSet.has(tileKey)) drawHighlight(HIGHLIGHT_COLORS.attack);
11252
11338
  if (debug2) {
11253
11339
  const centerX = pos.x + scaledTileWidth / 2;
11254
11340
  const centerY = squareGrid ? pos.y + scaledTileWidth / 2 : pos.y + scaledFloorHeight / 2 + scaledDiamondTopY;
@@ -11322,9 +11408,9 @@ function Canvas2D({
11322
11408
  const maxUnitW = scaledTileWidth * spriteMaxWidthRatio * unitScale;
11323
11409
  const unitIsSheet = unit.spriteSheet?.url !== void 0;
11324
11410
  const unitSrc = img && !unitIsSheet ? resolveAssetSource(img, unitAsset, bumpAtlas) : null;
11325
- const SHEET_ROWS = 5;
11326
11411
  const sheetFrameW = img ? img.naturalWidth / SHEET_COLUMNS : 0;
11327
- const sheetFrameH = img ? img.naturalHeight / SHEET_ROWS : 0;
11412
+ const sheetFrameH = img ? img.naturalHeight / LEGACY_SHEET_ROWS : 0;
11413
+ const legacyFrame = frameRect(0, 0, SHEET_COLUMNS, sheetFrameW, sheetFrameH);
11328
11414
  const frameW = unitIsSheet ? sheetFrameW : img?.naturalWidth ?? 1;
11329
11415
  const frameH = unitIsSheet ? sheetFrameH : img?.naturalHeight ?? 1;
11330
11416
  const ar = unitIsSheet ? sheetFrameW / (sheetFrameH || 1) : unitSrc ? unitSrc.aspect : frameW / (frameH || 1);
@@ -11342,17 +11428,16 @@ function Canvas2D({
11342
11428
  ctx.globalAlpha = 0.25;
11343
11429
  if (img) {
11344
11430
  if (unitIsSheet) {
11345
- ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
11431
+ ctx.drawImage(img, legacyFrame.sx, legacyFrame.sy, legacyFrame.sw, legacyFrame.sh, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
11346
11432
  } else if (unitSrc) {
11347
11433
  blit(ctx, unitSrc, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
11348
11434
  } else {
11349
11435
  ctx.drawImage(img, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
11350
11436
  }
11351
11437
  } else {
11352
- const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
11353
11438
  ctx.beginPath();
11354
11439
  ctx.arc(ghostCenterX, ghostGroundY - 16 * scale, 20 * scale, 0, Math.PI * 2);
11355
- ctx.fillStyle = color;
11440
+ ctx.fillStyle = resolveUnitTeamColor(unit);
11356
11441
  ctx.fill();
11357
11442
  }
11358
11443
  ctx.restore();
@@ -11360,7 +11445,7 @@ function Canvas2D({
11360
11445
  if (isSelected) {
11361
11446
  ctx.beginPath();
11362
11447
  ctx.ellipse(centerX, groundY, drawW / 2 + 4 * scale, 12 * scale, 0, 0, Math.PI * 2);
11363
- ctx.strokeStyle = "rgba(0, 200, 255, 0.8)";
11448
+ ctx.strokeStyle = SELECTION_RING_COLOR;
11364
11449
  ctx.lineWidth = 3;
11365
11450
  ctx.stroke();
11366
11451
  }
@@ -11377,7 +11462,7 @@ function Canvas2D({
11377
11462
  const spriteY = groundY - fDrawH - (frame.applyBreathing ? breatheOffset : 0);
11378
11463
  ctx.save();
11379
11464
  if (unit.team) {
11380
- ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
11465
+ ctx.shadowColor = TEAM_SHADOW_COLORS[unit.team] ?? TEAM_SHADOW_COLORS.default;
11381
11466
  ctx.shadowBlur = 12 * scale;
11382
11467
  }
11383
11468
  if (frame.flipX) {
@@ -11392,7 +11477,7 @@ function Canvas2D({
11392
11477
  const spriteY = groundY - drawH - breatheOffset;
11393
11478
  const drawUnit = (x) => {
11394
11479
  if (unitIsSheet) {
11395
- ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, x, spriteY, drawW, drawH);
11480
+ ctx.drawImage(img, legacyFrame.sx, legacyFrame.sy, legacyFrame.sw, legacyFrame.sh, x, spriteY, drawW, drawH);
11396
11481
  } else if (unitSrc) {
11397
11482
  blit(ctx, unitSrc, x, spriteY, drawW, drawH);
11398
11483
  } else {
@@ -11401,7 +11486,7 @@ function Canvas2D({
11401
11486
  };
11402
11487
  if (unit.team) {
11403
11488
  ctx.save();
11404
- ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
11489
+ ctx.shadowColor = TEAM_SHADOW_COLORS[unit.team] ?? TEAM_SHADOW_COLORS.default;
11405
11490
  ctx.shadowBlur = 12 * scale;
11406
11491
  drawUnit(centerX - drawW / 2);
11407
11492
  ctx.restore();
@@ -11409,10 +11494,9 @@ function Canvas2D({
11409
11494
  drawUnit(centerX - drawW / 2);
11410
11495
  }
11411
11496
  } else {
11412
- const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
11413
11497
  ctx.beginPath();
11414
11498
  ctx.arc(centerX, groundY - 20 * scale - breatheOffset, 20 * scale, 0, Math.PI * 2);
11415
- ctx.fillStyle = color;
11499
+ ctx.fillStyle = resolveUnitTeamColor(unit);
11416
11500
  ctx.fill();
11417
11501
  ctx.strokeStyle = "rgba(255,255,255,0.8)";
11418
11502
  ctx.lineWidth = 2;
@@ -11651,7 +11735,7 @@ function Canvas2D({
11651
11735
  }
11652
11736
  if (isSide) {
11653
11737
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { ref: containerRef, className: cn("relative overflow-hidden w-full h-full", className), children: /* @__PURE__ */ jsxRuntime.jsx(
11654
- SideView,
11738
+ SideCanvas2D,
11655
11739
  {
11656
11740
  player,
11657
11741
  platforms: platforms && platforms.length ? platforms : DEFAULT_PLATFORMS,
@@ -11749,7 +11833,7 @@ function Canvas2D({
11749
11833
  {
11750
11834
  as: "span",
11751
11835
  className: "text-white text-xs font-bold px-1.5 py-0.5 rounded mb-0.5 whitespace-nowrap block",
11752
- 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)" },
11836
+ style: { background: UNIT_LABEL_BG_COLORS[unit.team ?? ""] ?? UNIT_LABEL_BG_COLORS.default },
11753
11837
  children: unit.name
11754
11838
  }
11755
11839
  ),
@@ -11773,7 +11857,7 @@ function Canvas2D({
11773
11857
  }
11774
11858
  );
11775
11859
  }
11776
- var PLATFORM_COLORS, NOOP, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR, DEFAULT_PLATFORMS;
11860
+ var LEGACY_SHEET_ROWS, DEFAULT_PLATFORMS;
11777
11861
  var init_Canvas2D = __esm({
11778
11862
  "components/game/2d/molecules/Canvas2D.tsx"() {
11779
11863
  "use client";
@@ -11795,19 +11879,9 @@ var init_Canvas2D = __esm({
11795
11879
  init_verificationRegistry();
11796
11880
  init_isometric();
11797
11881
  init_spriteSheetConstants();
11798
- PLATFORM_COLORS = {
11799
- ground: "#4a7c59",
11800
- platform: "#7c6b4a",
11801
- hazard: "#c0392b",
11802
- goal: "#f1c40f"
11803
- };
11804
- NOOP = () => {
11805
- };
11806
- PLAYER_COLOR = "#3498db";
11807
- PLAYER_EYE_COLOR = "#ffffff";
11808
- SKY_GRADIENT_TOP = "#1a1a2e";
11809
- SKY_GRADIENT_BOTTOM = "#16213e";
11810
- GRID_COLOR = "rgba(255, 255, 255, 0.03)";
11882
+ init_spriteAnimation();
11883
+ init_SideCanvas2D();
11884
+ LEGACY_SHEET_ROWS = Object.keys(SPRITE_SHEET_LAYOUT).length;
11811
11885
  DEFAULT_PLATFORMS = [
11812
11886
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
11813
11887
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
@@ -30525,13 +30599,13 @@ var init_MapView = __esm({
30525
30599
  shadowSize: [41, 41]
30526
30600
  });
30527
30601
  L.Marker.prototype.options.icon = defaultIcon;
30528
- const { useEffect: useEffect67, useRef: useRef66, useCallback: useCallback98, useState: useState97 } = React96__namespace.default;
30602
+ const { useEffect: useEffect68, useRef: useRef67, useCallback: useCallback99, useState: useState97 } = React96__namespace.default;
30529
30603
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
30530
30604
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
30531
30605
  function MapUpdater({ centerLat, centerLng, zoom }) {
30532
30606
  const map = useMap();
30533
- const prevRef = useRef66({ centerLat, centerLng, zoom });
30534
- useEffect67(() => {
30607
+ const prevRef = useRef67({ centerLat, centerLng, zoom });
30608
+ useEffect68(() => {
30535
30609
  const prev = prevRef.current;
30536
30610
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
30537
30611
  map.setView([centerLat, centerLng], zoom);
@@ -30542,7 +30616,7 @@ var init_MapView = __esm({
30542
30616
  }
30543
30617
  function MapClickHandler({ onMapClick }) {
30544
30618
  const map = useMap();
30545
- useEffect67(() => {
30619
+ useEffect68(() => {
30546
30620
  if (!onMapClick) return;
30547
30621
  const handler = (e) => {
30548
30622
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -30571,7 +30645,7 @@ var init_MapView = __esm({
30571
30645
  }) {
30572
30646
  const eventBus = useEventBus2();
30573
30647
  const [clickedPosition, setClickedPosition] = useState97(null);
30574
- const handleMapClick = useCallback98((lat, lng) => {
30648
+ const handleMapClick = useCallback99((lat, lng) => {
30575
30649
  if (showClickedPin) {
30576
30650
  setClickedPosition({ lat, lng });
30577
30651
  }
@@ -30580,7 +30654,7 @@ var init_MapView = __esm({
30580
30654
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
30581
30655
  }
30582
30656
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
30583
- const handleMarkerClick = useCallback98((marker) => {
30657
+ const handleMarkerClick = useCallback99((marker) => {
30584
30658
  onMarkerClick?.(marker);
30585
30659
  if (markerClickEvent) {
30586
30660
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });