@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.
@@ -11249,7 +11249,7 @@ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric")
11249
11249
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
11250
11250
  return { x: Math.round(tileX), y: Math.round(tileY) };
11251
11251
  }
11252
- var TILE_WIDTH, TILE_HEIGHT, FLOOR_HEIGHT, DIAMOND_TOP_Y, FEATURE_COLORS;
11252
+ 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;
11253
11253
  var init_isometric = __esm({
11254
11254
  "components/game/shared/isometric.ts"() {
11255
11255
  TILE_WIDTH = 256;
@@ -11266,14 +11266,79 @@ var init_isometric = __esm({
11266
11266
  mountain: "#78716c",
11267
11267
  default: "#9ca3af"
11268
11268
  };
11269
+ TERRAIN_COLORS = {
11270
+ water: "#3b82f6",
11271
+ mountain: "#78716c",
11272
+ stone: "#9ca3af",
11273
+ default: "#4ade80"
11274
+ };
11275
+ TEAM_COLORS = {
11276
+ player: "#3b82f6",
11277
+ enemy: "#ef4444",
11278
+ neutral: "#6b7280",
11279
+ default: "#6b7280"
11280
+ };
11281
+ TEAM_SHADOW_COLORS = {
11282
+ player: "rgba(0,150,255,0.6)",
11283
+ default: "rgba(255,50,50,0.6)"
11284
+ };
11285
+ HIGHLIGHT_COLORS = {
11286
+ hover: "rgba(255,255,255,0.15)",
11287
+ validMove: "rgba(74,222,128,0.25)",
11288
+ attack: "rgba(239,68,68,0.35)"
11289
+ };
11290
+ SELECTION_RING_COLOR = "rgba(0,200,255,0.8)";
11291
+ GRID_STROKE_COLOR = "rgba(0,0,0,0.2)";
11292
+ BACKGROUND_FALLBACK_COLOR = "#1a1a2e";
11293
+ MINIMAP_TERRAIN_COLORS = {
11294
+ water: "#3b82f6",
11295
+ mountain: "#78716c",
11296
+ default: "#4ade80"
11297
+ };
11298
+ MINIMAP_UNIT_COLORS = {
11299
+ player: "#60a5fa",
11300
+ enemy: "#f87171",
11301
+ neutral: "#9ca3af",
11302
+ default: "#9ca3af"
11303
+ };
11304
+ UNIT_LABEL_BG_COLORS = {
11305
+ player: "rgba(59,130,246,0.9)",
11306
+ enemy: "rgba(239,68,68,0.9)",
11307
+ neutral: "rgba(107,114,128,0.9)",
11308
+ default: "rgba(107,114,128,0.9)"
11309
+ };
11310
+ SIDE_PLATFORM_COLORS = {
11311
+ ground: "#4a7c59",
11312
+ platform: "#7c6b4a",
11313
+ hazard: "#c0392b",
11314
+ goal: "#f1c40f"
11315
+ };
11316
+ SIDE_PLAYER_COLOR = "#3498db";
11317
+ SIDE_PLAYER_EYE_COLOR = "#ffffff";
11318
+ SIDE_SKY_GRADIENT_TOP = "#1a1a2e";
11319
+ SIDE_SKY_GRADIENT_BOTTOM = "#16213e";
11320
+ SIDE_GRID_COLOR = "rgba(255, 255, 255, 0.03)";
11321
+ SIDE_FX_COLOR = "#ffe066";
11322
+ SIDE_PLATFORM_BEVEL_COLOR = "rgba(255,255,255,0.15)";
11323
+ SIDE_PLATFORM_SHADOW_COLOR = "rgba(0,0,0,0.3)";
11324
+ SIDE_HAZARD_STRIPE_COLOR = "#e74c3c";
11325
+ SIDE_GOAL_GLOW_COLOR = "rgba(241, 196, 15, 0.5)";
11326
+ DEFAULT_BG_COLOR = "#5c94fc";
11269
11327
  }
11270
11328
  });
11271
11329
 
11272
11330
  // components/game/shared/spriteSheetConstants.ts
11273
- var SHEET_COLUMNS;
11331
+ var SHEET_COLUMNS, SPRITE_SHEET_LAYOUT;
11274
11332
  var init_spriteSheetConstants = __esm({
11275
11333
  "components/game/shared/spriteSheetConstants.ts"() {
11276
11334
  SHEET_COLUMNS = 8;
11335
+ SPRITE_SHEET_LAYOUT = {
11336
+ idle: { row: 0, frames: 4, frameRate: 6, loop: true },
11337
+ walk: { row: 1, frames: 8, frameRate: 10, loop: true },
11338
+ attack: { row: 2, frames: 6, frameRate: 12, loop: false },
11339
+ hit: { row: 3, frames: 3, frameRate: 8, loop: false },
11340
+ death: { row: 4, frames: 6, frameRate: 8, loop: false }
11341
+ };
11277
11342
  }
11278
11343
  });
11279
11344
 
@@ -14006,7 +14071,7 @@ var init_useUnitSpriteAtlas = __esm({
14006
14071
  function normalizeBackdrop(bg) {
14007
14072
  return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
14008
14073
  }
14009
- function SideView({
14074
+ function SideCanvas2D({
14010
14075
  player,
14011
14076
  platforms,
14012
14077
  worldWidth,
@@ -14027,24 +14092,18 @@ function SideView({
14027
14092
  const canvasRef = React105.useRef(null);
14028
14093
  const eventBus = useEventBus();
14029
14094
  const keysRef = React105.useRef(/* @__PURE__ */ new Set());
14030
- const imageCache2 = React105.useRef(/* @__PURE__ */ new Map());
14031
- const [loadedImages, setLoadedImages] = React105.useState(/* @__PURE__ */ new Set());
14032
- const loadImage = React105.useCallback((url) => {
14033
- if (!url) return null;
14034
- const cached = imageCache2.current.get(url);
14035
- if (cached?.complete && cached.naturalWidth > 0) {
14036
- if (!loadedImages.has(url)) setLoadedImages((prev) => new Set(prev).add(url));
14037
- return cached;
14038
- }
14039
- if (!cached) {
14040
- const img = new Image();
14041
- img.crossOrigin = "anonymous";
14042
- img.src = url;
14043
- img.onload = () => setLoadedImages((prev) => new Set(prev).add(url));
14044
- imageCache2.current.set(url, img);
14095
+ const spriteUrls = React105.useMemo(() => {
14096
+ const urls = [];
14097
+ if (backgroundImage?.url) urls.push(backgroundImage.url);
14098
+ if (playerSprite?.url) urls.push(playerSprite.url);
14099
+ if (tileSprites) {
14100
+ for (const asset of Object.values(tileSprites)) {
14101
+ if (asset?.url) urls.push(asset.url);
14102
+ }
14045
14103
  }
14046
- return null;
14047
- }, [loadedImages]);
14104
+ return urls;
14105
+ }, [backgroundImage, playerSprite, tileSprites]);
14106
+ const { getImage } = useImageCache(spriteUrls);
14048
14107
  React105.useEffect(() => {
14049
14108
  if (typeof window === "undefined") return;
14050
14109
  const canvas = canvasRef.current;
@@ -14157,7 +14216,7 @@ function SideView({
14157
14216
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
14158
14217
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
14159
14218
  }
14160
- const bgImage = bgImg ? loadImage(bgImg.url) : null;
14219
+ const bgImage = bgImg ? getImage(bgImg.url) : void 0;
14161
14220
  const bgSrc = bgImage ? resolveAssetSource(bgImage, bgImg, NOOP) : null;
14162
14221
  if (bgSrc) {
14163
14222
  blit(ctx, bgSrc, 0, 0, cw, ch);
@@ -14168,12 +14227,12 @@ function SideView({
14168
14227
  ctx.fillRect(0, 0, cw, ch);
14169
14228
  } else {
14170
14229
  const grad = ctx.createLinearGradient(0, 0, 0, ch);
14171
- grad.addColorStop(0, SKY_GRADIENT_TOP);
14172
- grad.addColorStop(1, SKY_GRADIENT_BOTTOM);
14230
+ grad.addColorStop(0, SIDE_SKY_GRADIENT_TOP);
14231
+ grad.addColorStop(1, SIDE_SKY_GRADIENT_BOTTOM);
14173
14232
  ctx.fillStyle = grad;
14174
14233
  ctx.fillRect(0, 0, cw, ch);
14175
14234
  }
14176
- ctx.strokeStyle = GRID_COLOR;
14235
+ ctx.strokeStyle = SIDE_GRID_COLOR;
14177
14236
  ctx.lineWidth = 1;
14178
14237
  const gridSize = 32;
14179
14238
  for (let gx = -camX % gridSize; gx < cw; gx += gridSize) {
@@ -14193,7 +14252,7 @@ function SideView({
14193
14252
  const platY = plat.y - camY;
14194
14253
  const platType = plat.type ?? "ground";
14195
14254
  const spriteAsset = tSprites?.[platType];
14196
- const tileImg = spriteAsset ? loadImage(spriteAsset.url) : null;
14255
+ const tileImg = spriteAsset ? getImage(spriteAsset.url) : void 0;
14197
14256
  const tileSrc = tileImg ? resolveAssetSource(tileImg, spriteAsset, NOOP) : null;
14198
14257
  if (tileSrc) {
14199
14258
  const originX = tileSrc.rect ? tileSrc.rect.sx : 0;
@@ -14208,15 +14267,15 @@ function SideView({
14208
14267
  ctx.drawImage(tileImg, originX, originY, srcW, tileH, platX + tx, platY, drawW, plat.height);
14209
14268
  }
14210
14269
  } else {
14211
- const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
14270
+ const color = SIDE_PLATFORM_COLORS[platType] ?? SIDE_PLATFORM_COLORS.ground;
14212
14271
  ctx.fillStyle = color;
14213
14272
  ctx.fillRect(platX, platY, plat.width, plat.height);
14214
- ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
14273
+ ctx.fillStyle = SIDE_PLATFORM_BEVEL_COLOR;
14215
14274
  ctx.fillRect(platX, platY, plat.width, 3);
14216
- ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
14275
+ ctx.fillStyle = SIDE_PLATFORM_SHADOW_COLOR;
14217
14276
  ctx.fillRect(platX, platY + plat.height - 2, plat.width, 2);
14218
14277
  if (platType === "hazard") {
14219
- ctx.strokeStyle = "#e74c3c";
14278
+ ctx.strokeStyle = SIDE_HAZARD_STRIPE_COLOR;
14220
14279
  ctx.lineWidth = 2;
14221
14280
  for (let sx = platX; sx < platX + plat.width; sx += 12) {
14222
14281
  ctx.beginPath();
@@ -14226,7 +14285,7 @@ function SideView({
14226
14285
  }
14227
14286
  }
14228
14287
  if (platType === "goal") {
14229
- ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
14288
+ ctx.fillStyle = SIDE_GOAL_GLOW_COLOR;
14230
14289
  ctx.beginPath();
14231
14290
  ctx.arc(platX + plat.width / 2, platY - 10, 8, 0, Math.PI * 2);
14232
14291
  ctx.fill();
@@ -14238,7 +14297,7 @@ function SideView({
14238
14297
  const ppx = px - camX;
14239
14298
  const ppy = py - camY;
14240
14299
  const facingRight = auth.facingRight ?? true;
14241
- const playerImg = pSprite ? loadImage(pSprite.url) : null;
14300
+ const playerImg = pSprite ? getImage(pSprite.url) : void 0;
14242
14301
  const playerSrc = playerImg ? resolveAssetSource(playerImg, pSprite, NOOP) : null;
14243
14302
  if (playerSrc) {
14244
14303
  ctx.save();
@@ -14251,7 +14310,7 @@ function SideView({
14251
14310
  }
14252
14311
  ctx.restore();
14253
14312
  } else {
14254
- ctx.fillStyle = PLAYER_COLOR;
14313
+ ctx.fillStyle = SIDE_PLAYER_COLOR;
14255
14314
  const radius = Math.min(pw, ph) * 0.25;
14256
14315
  ctx.beginPath();
14257
14316
  ctx.moveTo(ppx + radius, ppy);
@@ -14267,7 +14326,7 @@ function SideView({
14267
14326
  const eyeY = ppy + ph * 0.3;
14268
14327
  const eyeSize = 3;
14269
14328
  const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
14270
- ctx.fillStyle = PLAYER_EYE_COLOR;
14329
+ ctx.fillStyle = SIDE_PLAYER_EYE_COLOR;
14271
14330
  ctx.beginPath();
14272
14331
  ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
14273
14332
  ctx.fill();
@@ -14281,7 +14340,7 @@ function SideView({
14281
14340
  const alpha = Math.min(1, fx.ttl / 4);
14282
14341
  const prev = ctx.globalAlpha;
14283
14342
  ctx.globalAlpha = alpha;
14284
- ctx.fillStyle = "#ffe066";
14343
+ ctx.fillStyle = SIDE_FX_COLOR;
14285
14344
  ctx.beginPath();
14286
14345
  ctx.arc(fxScreenX, fxScreenY, 12, 0, Math.PI * 2);
14287
14346
  ctx.fill();
@@ -14289,7 +14348,7 @@ function SideView({
14289
14348
  }
14290
14349
  };
14291
14350
  return interp.startLoop(drawFrame);
14292
- }, [interp.startLoop, loadImage]);
14351
+ }, [interp.startLoop, getImage]);
14293
14352
  return /* @__PURE__ */ jsxRuntime.jsx(
14294
14353
  "canvas",
14295
14354
  {
@@ -14301,6 +14360,33 @@ function SideView({
14301
14360
  }
14302
14361
  );
14303
14362
  }
14363
+ var NOOP;
14364
+ var init_SideCanvas2D = __esm({
14365
+ "components/game/2d/molecules/SideCanvas2D.tsx"() {
14366
+ "use client";
14367
+ init_cn();
14368
+ init_useEventBus();
14369
+ init_useImageCache();
14370
+ init_atlasSlice();
14371
+ init_useRenderInterpolation();
14372
+ init_verificationRegistry();
14373
+ init_isometric();
14374
+ NOOP = () => {
14375
+ };
14376
+ SideCanvas2D.displayName = "SideCanvas2D";
14377
+ }
14378
+ });
14379
+ function normalizeBackdrop2(bg) {
14380
+ return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
14381
+ }
14382
+ function resolveTileFallbackColor(tile) {
14383
+ const override = tile.color;
14384
+ return override ?? TERRAIN_COLORS[tile.terrain ?? ""] ?? TERRAIN_COLORS.default;
14385
+ }
14386
+ function resolveUnitTeamColor(unit) {
14387
+ const override = unit.tint;
14388
+ return override ?? TEAM_COLORS[unit.team ?? ""] ?? TEAM_COLORS.default;
14389
+ }
14304
14390
  function Canvas2D({
14305
14391
  // Closed-circuit
14306
14392
  className,
@@ -14350,13 +14436,13 @@ function Canvas2D({
14350
14436
  // Side-view asset resolution
14351
14437
  playerSprite,
14352
14438
  tileSprites,
14353
- bgColor = "#5c94fc",
14439
+ bgColor = DEFAULT_BG_COLOR,
14354
14440
  worldWidth = 800,
14355
14441
  worldHeight = 400,
14356
14442
  // Remote asset loading
14357
14443
  assetManifest
14358
14444
  }) {
14359
- const backgroundImage = normalizeBackdrop(backgroundImageRaw);
14445
+ const backgroundImage = normalizeBackdrop2(backgroundImageRaw);
14360
14446
  const isSide = projection === "side";
14361
14447
  const isFree = projection === "free";
14362
14448
  const flatLike = projection === "hex" || projection === "flat" || isFree;
@@ -14540,7 +14626,7 @@ function Canvas2D({
14540
14626
  return sortedTiles.map((t2) => ({
14541
14627
  x: t2.x,
14542
14628
  y: t2.y,
14543
- color: t2.terrain === "water" ? "#3b82f6" : t2.terrain === "mountain" ? "#78716c" : "#4ade80"
14629
+ color: MINIMAP_TERRAIN_COLORS[t2.terrain ?? ""] ?? MINIMAP_TERRAIN_COLORS.default
14544
14630
  }));
14545
14631
  }, [showMinimap, sortedTiles]);
14546
14632
  const miniMapUnits = React105.useMemo(() => {
@@ -14548,7 +14634,7 @@ function Canvas2D({
14548
14634
  return units.filter((u) => u.position).map((u) => ({
14549
14635
  x: u.position.x,
14550
14636
  y: u.position.y,
14551
- color: u.team === "player" ? "#60a5fa" : u.team === "enemy" ? "#f87171" : "#9ca3af",
14637
+ color: MINIMAP_UNIT_COLORS[u.team ?? ""] ?? MINIMAP_UNIT_COLORS.default,
14552
14638
  isPlayer: u.team === "player"
14553
14639
  }));
14554
14640
  }, [showMinimap, units]);
@@ -14585,7 +14671,7 @@ function Canvas2D({
14585
14671
  }
14586
14672
  }
14587
14673
  } else {
14588
- ctx.fillStyle = "#1a1a2e";
14674
+ ctx.fillStyle = BACKGROUND_FALLBACK_COLOR;
14589
14675
  ctx.fillRect(0, 0, viewportSize.width, viewportSize.height);
14590
14676
  }
14591
14677
  if (sortedTiles.length === 0 && units.length === 0 && features.length === 0) return;
@@ -14615,15 +14701,15 @@ function Canvas2D({
14615
14701
  } else if (img && img.naturalWidth === 0) {
14616
14702
  ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
14617
14703
  } else if (squareGrid) {
14618
- ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
14704
+ ctx.fillStyle = resolveTileFallbackColor(tile);
14619
14705
  ctx.fillRect(pos.x, pos.y, scaledTileWidth, scaledTileWidth);
14620
- ctx.strokeStyle = "rgba(0,0,0,0.2)";
14706
+ ctx.strokeStyle = GRID_STROKE_COLOR;
14621
14707
  ctx.lineWidth = 1;
14622
14708
  ctx.strokeRect(pos.x, pos.y, scaledTileWidth, scaledTileWidth);
14623
14709
  } else {
14624
14710
  const centerX = pos.x + scaledTileWidth / 2;
14625
14711
  const topY = pos.y + scaledDiamondTopY;
14626
- ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
14712
+ ctx.fillStyle = resolveTileFallbackColor(tile);
14627
14713
  ctx.beginPath();
14628
14714
  ctx.moveTo(centerX, topY);
14629
14715
  ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
@@ -14631,7 +14717,7 @@ function Canvas2D({
14631
14717
  ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
14632
14718
  ctx.closePath();
14633
14719
  ctx.fill();
14634
- ctx.strokeStyle = "rgba(0,0,0,0.2)";
14720
+ ctx.strokeStyle = GRID_STROKE_COLOR;
14635
14721
  ctx.lineWidth = 1;
14636
14722
  ctx.stroke();
14637
14723
  }
@@ -14652,11 +14738,11 @@ function Canvas2D({
14652
14738
  ctx.fill();
14653
14739
  };
14654
14740
  if (hoveredTile && hoveredTile.x === tile.x && hoveredTile.y === tile.y) {
14655
- drawHighlight("rgba(255, 255, 255, 0.15)");
14741
+ drawHighlight(HIGHLIGHT_COLORS.hover);
14656
14742
  }
14657
14743
  const tileKey = `${tile.x},${tile.y}`;
14658
- if (validMoveSet.has(tileKey)) drawHighlight("rgba(74, 222, 128, 0.25)");
14659
- if (attackTargetSet.has(tileKey)) drawHighlight("rgba(239, 68, 68, 0.35)");
14744
+ if (validMoveSet.has(tileKey)) drawHighlight(HIGHLIGHT_COLORS.validMove);
14745
+ if (attackTargetSet.has(tileKey)) drawHighlight(HIGHLIGHT_COLORS.attack);
14660
14746
  if (debug2) {
14661
14747
  const centerX = pos.x + scaledTileWidth / 2;
14662
14748
  const centerY = squareGrid ? pos.y + scaledTileWidth / 2 : pos.y + scaledFloorHeight / 2 + scaledDiamondTopY;
@@ -14730,9 +14816,9 @@ function Canvas2D({
14730
14816
  const maxUnitW = scaledTileWidth * spriteMaxWidthRatio * unitScale;
14731
14817
  const unitIsSheet = unit.spriteSheet?.url !== void 0;
14732
14818
  const unitSrc = img && !unitIsSheet ? resolveAssetSource(img, unitAsset, bumpAtlas) : null;
14733
- const SHEET_ROWS = 5;
14734
14819
  const sheetFrameW = img ? img.naturalWidth / SHEET_COLUMNS : 0;
14735
- const sheetFrameH = img ? img.naturalHeight / SHEET_ROWS : 0;
14820
+ const sheetFrameH = img ? img.naturalHeight / LEGACY_SHEET_ROWS : 0;
14821
+ const legacyFrame = frameRect(0, 0, SHEET_COLUMNS, sheetFrameW, sheetFrameH);
14736
14822
  const frameW = unitIsSheet ? sheetFrameW : img?.naturalWidth ?? 1;
14737
14823
  const frameH = unitIsSheet ? sheetFrameH : img?.naturalHeight ?? 1;
14738
14824
  const ar = unitIsSheet ? sheetFrameW / (sheetFrameH || 1) : unitSrc ? unitSrc.aspect : frameW / (frameH || 1);
@@ -14750,17 +14836,16 @@ function Canvas2D({
14750
14836
  ctx.globalAlpha = 0.25;
14751
14837
  if (img) {
14752
14838
  if (unitIsSheet) {
14753
- ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
14839
+ ctx.drawImage(img, legacyFrame.sx, legacyFrame.sy, legacyFrame.sw, legacyFrame.sh, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
14754
14840
  } else if (unitSrc) {
14755
14841
  blit(ctx, unitSrc, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
14756
14842
  } else {
14757
14843
  ctx.drawImage(img, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
14758
14844
  }
14759
14845
  } else {
14760
- const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
14761
14846
  ctx.beginPath();
14762
14847
  ctx.arc(ghostCenterX, ghostGroundY - 16 * scale, 20 * scale, 0, Math.PI * 2);
14763
- ctx.fillStyle = color;
14848
+ ctx.fillStyle = resolveUnitTeamColor(unit);
14764
14849
  ctx.fill();
14765
14850
  }
14766
14851
  ctx.restore();
@@ -14768,7 +14853,7 @@ function Canvas2D({
14768
14853
  if (isSelected) {
14769
14854
  ctx.beginPath();
14770
14855
  ctx.ellipse(centerX, groundY, drawW / 2 + 4 * scale, 12 * scale, 0, 0, Math.PI * 2);
14771
- ctx.strokeStyle = "rgba(0, 200, 255, 0.8)";
14856
+ ctx.strokeStyle = SELECTION_RING_COLOR;
14772
14857
  ctx.lineWidth = 3;
14773
14858
  ctx.stroke();
14774
14859
  }
@@ -14785,7 +14870,7 @@ function Canvas2D({
14785
14870
  const spriteY = groundY - fDrawH - (frame.applyBreathing ? breatheOffset : 0);
14786
14871
  ctx.save();
14787
14872
  if (unit.team) {
14788
- ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
14873
+ ctx.shadowColor = TEAM_SHADOW_COLORS[unit.team] ?? TEAM_SHADOW_COLORS.default;
14789
14874
  ctx.shadowBlur = 12 * scale;
14790
14875
  }
14791
14876
  if (frame.flipX) {
@@ -14800,7 +14885,7 @@ function Canvas2D({
14800
14885
  const spriteY = groundY - drawH - breatheOffset;
14801
14886
  const drawUnit = (x) => {
14802
14887
  if (unitIsSheet) {
14803
- ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, x, spriteY, drawW, drawH);
14888
+ ctx.drawImage(img, legacyFrame.sx, legacyFrame.sy, legacyFrame.sw, legacyFrame.sh, x, spriteY, drawW, drawH);
14804
14889
  } else if (unitSrc) {
14805
14890
  blit(ctx, unitSrc, x, spriteY, drawW, drawH);
14806
14891
  } else {
@@ -14809,7 +14894,7 @@ function Canvas2D({
14809
14894
  };
14810
14895
  if (unit.team) {
14811
14896
  ctx.save();
14812
- ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
14897
+ ctx.shadowColor = TEAM_SHADOW_COLORS[unit.team] ?? TEAM_SHADOW_COLORS.default;
14813
14898
  ctx.shadowBlur = 12 * scale;
14814
14899
  drawUnit(centerX - drawW / 2);
14815
14900
  ctx.restore();
@@ -14817,10 +14902,9 @@ function Canvas2D({
14817
14902
  drawUnit(centerX - drawW / 2);
14818
14903
  }
14819
14904
  } else {
14820
- const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
14821
14905
  ctx.beginPath();
14822
14906
  ctx.arc(centerX, groundY - 20 * scale - breatheOffset, 20 * scale, 0, Math.PI * 2);
14823
- ctx.fillStyle = color;
14907
+ ctx.fillStyle = resolveUnitTeamColor(unit);
14824
14908
  ctx.fill();
14825
14909
  ctx.strokeStyle = "rgba(255,255,255,0.8)";
14826
14910
  ctx.lineWidth = 2;
@@ -15059,7 +15143,7 @@ function Canvas2D({
15059
15143
  }
15060
15144
  if (isSide) {
15061
15145
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { ref: containerRef, className: cn("relative overflow-hidden w-full h-full", className), children: /* @__PURE__ */ jsxRuntime.jsx(
15062
- SideView,
15146
+ SideCanvas2D,
15063
15147
  {
15064
15148
  player,
15065
15149
  platforms: platforms && platforms.length ? platforms : DEFAULT_PLATFORMS,
@@ -15157,7 +15241,7 @@ function Canvas2D({
15157
15241
  {
15158
15242
  as: "span",
15159
15243
  className: "text-white text-xs font-bold px-1.5 py-0.5 rounded mb-0.5 whitespace-nowrap block",
15160
- 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)" },
15244
+ style: { background: UNIT_LABEL_BG_COLORS[unit.team ?? ""] ?? UNIT_LABEL_BG_COLORS.default },
15161
15245
  children: unit.name
15162
15246
  }
15163
15247
  ),
@@ -15181,7 +15265,7 @@ function Canvas2D({
15181
15265
  }
15182
15266
  );
15183
15267
  }
15184
- var PLATFORM_COLORS, NOOP, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR, DEFAULT_PLATFORMS;
15268
+ var LEGACY_SHEET_ROWS, DEFAULT_PLATFORMS;
15185
15269
  var init_Canvas2D = __esm({
15186
15270
  "components/game/2d/molecules/Canvas2D.tsx"() {
15187
15271
  "use client";
@@ -15203,19 +15287,9 @@ var init_Canvas2D = __esm({
15203
15287
  init_verificationRegistry();
15204
15288
  init_isometric();
15205
15289
  init_spriteSheetConstants();
15206
- PLATFORM_COLORS = {
15207
- ground: "#4a7c59",
15208
- platform: "#7c6b4a",
15209
- hazard: "#c0392b",
15210
- goal: "#f1c40f"
15211
- };
15212
- NOOP = () => {
15213
- };
15214
- PLAYER_COLOR = "#3498db";
15215
- PLAYER_EYE_COLOR = "#ffffff";
15216
- SKY_GRADIENT_TOP = "#1a1a2e";
15217
- SKY_GRADIENT_BOTTOM = "#16213e";
15218
- GRID_COLOR = "rgba(255, 255, 255, 0.03)";
15290
+ init_spriteAnimation();
15291
+ init_SideCanvas2D();
15292
+ LEGACY_SHEET_ROWS = Object.keys(SPRITE_SHEET_LAYOUT).length;
15219
15293
  DEFAULT_PLATFORMS = [
15220
15294
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
15221
15295
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
@@ -33301,13 +33375,13 @@ var init_MapView = __esm({
33301
33375
  shadowSize: [41, 41]
33302
33376
  });
33303
33377
  L.Marker.prototype.options.icon = defaultIcon;
33304
- const { useEffect: useEffect71, useRef: useRef68, useCallback: useCallback104, useState: useState105 } = React105__namespace.default;
33378
+ const { useEffect: useEffect72, useRef: useRef69, useCallback: useCallback105, useState: useState105 } = React105__namespace.default;
33305
33379
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
33306
33380
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
33307
33381
  function MapUpdater({ centerLat, centerLng, zoom }) {
33308
33382
  const map = useMap();
33309
- const prevRef = useRef68({ centerLat, centerLng, zoom });
33310
- useEffect71(() => {
33383
+ const prevRef = useRef69({ centerLat, centerLng, zoom });
33384
+ useEffect72(() => {
33311
33385
  const prev = prevRef.current;
33312
33386
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
33313
33387
  map.setView([centerLat, centerLng], zoom);
@@ -33318,7 +33392,7 @@ var init_MapView = __esm({
33318
33392
  }
33319
33393
  function MapClickHandler({ onMapClick }) {
33320
33394
  const map = useMap();
33321
- useEffect71(() => {
33395
+ useEffect72(() => {
33322
33396
  if (!onMapClick) return;
33323
33397
  const handler = (e) => {
33324
33398
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -33347,7 +33421,7 @@ var init_MapView = __esm({
33347
33421
  }) {
33348
33422
  const eventBus = useEventBus2();
33349
33423
  const [clickedPosition, setClickedPosition] = useState105(null);
33350
- const handleMapClick = useCallback104((lat, lng) => {
33424
+ const handleMapClick = useCallback105((lat, lng) => {
33351
33425
  if (showClickedPin) {
33352
33426
  setClickedPosition({ lat, lng });
33353
33427
  }
@@ -33356,7 +33430,7 @@ var init_MapView = __esm({
33356
33430
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
33357
33431
  }
33358
33432
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
33359
- const handleMarkerClick = useCallback104((marker) => {
33433
+ const handleMarkerClick = useCallback105((marker) => {
33360
33434
  onMarkerClick?.(marker);
33361
33435
  if (markerClickEvent) {
33362
33436
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });