@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.
@@ -7975,7 +7975,7 @@ function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric")
7975
7975
  const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
7976
7976
  return { x: Math.round(tileX), y: Math.round(tileY) };
7977
7977
  }
7978
- var TILE_WIDTH, TILE_HEIGHT, FLOOR_HEIGHT, DIAMOND_TOP_Y, FEATURE_COLORS;
7978
+ 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;
7979
7979
  var init_isometric = __esm({
7980
7980
  "components/game/shared/isometric.ts"() {
7981
7981
  TILE_WIDTH = 256;
@@ -7992,14 +7992,79 @@ var init_isometric = __esm({
7992
7992
  mountain: "#78716c",
7993
7993
  default: "#9ca3af"
7994
7994
  };
7995
+ TERRAIN_COLORS = {
7996
+ water: "#3b82f6",
7997
+ mountain: "#78716c",
7998
+ stone: "#9ca3af",
7999
+ default: "#4ade80"
8000
+ };
8001
+ TEAM_COLORS = {
8002
+ player: "#3b82f6",
8003
+ enemy: "#ef4444",
8004
+ neutral: "#6b7280",
8005
+ default: "#6b7280"
8006
+ };
8007
+ TEAM_SHADOW_COLORS = {
8008
+ player: "rgba(0,150,255,0.6)",
8009
+ default: "rgba(255,50,50,0.6)"
8010
+ };
8011
+ HIGHLIGHT_COLORS = {
8012
+ hover: "rgba(255,255,255,0.15)",
8013
+ validMove: "rgba(74,222,128,0.25)",
8014
+ attack: "rgba(239,68,68,0.35)"
8015
+ };
8016
+ SELECTION_RING_COLOR = "rgba(0,200,255,0.8)";
8017
+ GRID_STROKE_COLOR = "rgba(0,0,0,0.2)";
8018
+ BACKGROUND_FALLBACK_COLOR = "#1a1a2e";
8019
+ MINIMAP_TERRAIN_COLORS = {
8020
+ water: "#3b82f6",
8021
+ mountain: "#78716c",
8022
+ default: "#4ade80"
8023
+ };
8024
+ MINIMAP_UNIT_COLORS = {
8025
+ player: "#60a5fa",
8026
+ enemy: "#f87171",
8027
+ neutral: "#9ca3af",
8028
+ default: "#9ca3af"
8029
+ };
8030
+ UNIT_LABEL_BG_COLORS = {
8031
+ player: "rgba(59,130,246,0.9)",
8032
+ enemy: "rgba(239,68,68,0.9)",
8033
+ neutral: "rgba(107,114,128,0.9)",
8034
+ default: "rgba(107,114,128,0.9)"
8035
+ };
8036
+ SIDE_PLATFORM_COLORS = {
8037
+ ground: "#4a7c59",
8038
+ platform: "#7c6b4a",
8039
+ hazard: "#c0392b",
8040
+ goal: "#f1c40f"
8041
+ };
8042
+ SIDE_PLAYER_COLOR = "#3498db";
8043
+ SIDE_PLAYER_EYE_COLOR = "#ffffff";
8044
+ SIDE_SKY_GRADIENT_TOP = "#1a1a2e";
8045
+ SIDE_SKY_GRADIENT_BOTTOM = "#16213e";
8046
+ SIDE_GRID_COLOR = "rgba(255, 255, 255, 0.03)";
8047
+ SIDE_FX_COLOR = "#ffe066";
8048
+ SIDE_PLATFORM_BEVEL_COLOR = "rgba(255,255,255,0.15)";
8049
+ SIDE_PLATFORM_SHADOW_COLOR = "rgba(0,0,0,0.3)";
8050
+ SIDE_HAZARD_STRIPE_COLOR = "#e74c3c";
8051
+ SIDE_GOAL_GLOW_COLOR = "rgba(241, 196, 15, 0.5)";
8052
+ DEFAULT_BG_COLOR = "#5c94fc";
7995
8053
  }
7996
8054
  });
7997
8055
 
7998
8056
  // components/game/shared/spriteSheetConstants.ts
7999
- var SHEET_COLUMNS;
8057
+ var SHEET_COLUMNS, SPRITE_SHEET_LAYOUT;
8000
8058
  var init_spriteSheetConstants = __esm({
8001
8059
  "components/game/shared/spriteSheetConstants.ts"() {
8002
8060
  SHEET_COLUMNS = 8;
8061
+ SPRITE_SHEET_LAYOUT = {
8062
+ idle: { row: 0, frames: 4, frameRate: 6, loop: true },
8063
+ walk: { row: 1, frames: 8, frameRate: 10, loop: true },
8064
+ attack: { row: 2, frames: 6, frameRate: 12, loop: false },
8065
+ hit: { row: 3, frames: 3, frameRate: 8, loop: false },
8066
+ death: { row: 4, frames: 6, frameRate: 8, loop: false }
8067
+ };
8003
8068
  }
8004
8069
  });
8005
8070
 
@@ -10553,7 +10618,7 @@ var init_useUnitSpriteAtlas = __esm({
10553
10618
  function normalizeBackdrop(bg) {
10554
10619
  return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
10555
10620
  }
10556
- function SideView({
10621
+ function SideCanvas2D({
10557
10622
  player,
10558
10623
  platforms,
10559
10624
  worldWidth,
@@ -10574,24 +10639,18 @@ function SideView({
10574
10639
  const canvasRef = useRef(null);
10575
10640
  const eventBus = useEventBus();
10576
10641
  const keysRef = useRef(/* @__PURE__ */ new Set());
10577
- const imageCache2 = useRef(/* @__PURE__ */ new Map());
10578
- const [loadedImages, setLoadedImages] = useState(/* @__PURE__ */ new Set());
10579
- const loadImage = useCallback((url) => {
10580
- if (!url) return null;
10581
- const cached = imageCache2.current.get(url);
10582
- if (cached?.complete && cached.naturalWidth > 0) {
10583
- if (!loadedImages.has(url)) setLoadedImages((prev) => new Set(prev).add(url));
10584
- return cached;
10585
- }
10586
- if (!cached) {
10587
- const img = new Image();
10588
- img.crossOrigin = "anonymous";
10589
- img.src = url;
10590
- img.onload = () => setLoadedImages((prev) => new Set(prev).add(url));
10591
- imageCache2.current.set(url, img);
10642
+ const spriteUrls = useMemo(() => {
10643
+ const urls = [];
10644
+ if (backgroundImage?.url) urls.push(backgroundImage.url);
10645
+ if (playerSprite?.url) urls.push(playerSprite.url);
10646
+ if (tileSprites) {
10647
+ for (const asset of Object.values(tileSprites)) {
10648
+ if (asset?.url) urls.push(asset.url);
10649
+ }
10592
10650
  }
10593
- return null;
10594
- }, [loadedImages]);
10651
+ return urls;
10652
+ }, [backgroundImage, playerSprite, tileSprites]);
10653
+ const { getImage } = useImageCache(spriteUrls);
10595
10654
  useEffect(() => {
10596
10655
  if (typeof window === "undefined") return;
10597
10656
  const canvas = canvasRef.current;
@@ -10704,7 +10763,7 @@ function SideView({
10704
10763
  camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
10705
10764
  camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
10706
10765
  }
10707
- const bgImage = bgImg ? loadImage(bgImg.url) : null;
10766
+ const bgImage = bgImg ? getImage(bgImg.url) : void 0;
10708
10767
  const bgSrc = bgImage ? resolveAssetSource(bgImage, bgImg, NOOP) : null;
10709
10768
  if (bgSrc) {
10710
10769
  blit(ctx, bgSrc, 0, 0, cw, ch);
@@ -10715,12 +10774,12 @@ function SideView({
10715
10774
  ctx.fillRect(0, 0, cw, ch);
10716
10775
  } else {
10717
10776
  const grad = ctx.createLinearGradient(0, 0, 0, ch);
10718
- grad.addColorStop(0, SKY_GRADIENT_TOP);
10719
- grad.addColorStop(1, SKY_GRADIENT_BOTTOM);
10777
+ grad.addColorStop(0, SIDE_SKY_GRADIENT_TOP);
10778
+ grad.addColorStop(1, SIDE_SKY_GRADIENT_BOTTOM);
10720
10779
  ctx.fillStyle = grad;
10721
10780
  ctx.fillRect(0, 0, cw, ch);
10722
10781
  }
10723
- ctx.strokeStyle = GRID_COLOR;
10782
+ ctx.strokeStyle = SIDE_GRID_COLOR;
10724
10783
  ctx.lineWidth = 1;
10725
10784
  const gridSize = 32;
10726
10785
  for (let gx = -camX % gridSize; gx < cw; gx += gridSize) {
@@ -10740,7 +10799,7 @@ function SideView({
10740
10799
  const platY = plat.y - camY;
10741
10800
  const platType = plat.type ?? "ground";
10742
10801
  const spriteAsset = tSprites?.[platType];
10743
- const tileImg = spriteAsset ? loadImage(spriteAsset.url) : null;
10802
+ const tileImg = spriteAsset ? getImage(spriteAsset.url) : void 0;
10744
10803
  const tileSrc = tileImg ? resolveAssetSource(tileImg, spriteAsset, NOOP) : null;
10745
10804
  if (tileSrc) {
10746
10805
  const originX = tileSrc.rect ? tileSrc.rect.sx : 0;
@@ -10755,15 +10814,15 @@ function SideView({
10755
10814
  ctx.drawImage(tileImg, originX, originY, srcW, tileH, platX + tx, platY, drawW, plat.height);
10756
10815
  }
10757
10816
  } else {
10758
- const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
10817
+ const color = SIDE_PLATFORM_COLORS[platType] ?? SIDE_PLATFORM_COLORS.ground;
10759
10818
  ctx.fillStyle = color;
10760
10819
  ctx.fillRect(platX, platY, plat.width, plat.height);
10761
- ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
10820
+ ctx.fillStyle = SIDE_PLATFORM_BEVEL_COLOR;
10762
10821
  ctx.fillRect(platX, platY, plat.width, 3);
10763
- ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
10822
+ ctx.fillStyle = SIDE_PLATFORM_SHADOW_COLOR;
10764
10823
  ctx.fillRect(platX, platY + plat.height - 2, plat.width, 2);
10765
10824
  if (platType === "hazard") {
10766
- ctx.strokeStyle = "#e74c3c";
10825
+ ctx.strokeStyle = SIDE_HAZARD_STRIPE_COLOR;
10767
10826
  ctx.lineWidth = 2;
10768
10827
  for (let sx = platX; sx < platX + plat.width; sx += 12) {
10769
10828
  ctx.beginPath();
@@ -10773,7 +10832,7 @@ function SideView({
10773
10832
  }
10774
10833
  }
10775
10834
  if (platType === "goal") {
10776
- ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
10835
+ ctx.fillStyle = SIDE_GOAL_GLOW_COLOR;
10777
10836
  ctx.beginPath();
10778
10837
  ctx.arc(platX + plat.width / 2, platY - 10, 8, 0, Math.PI * 2);
10779
10838
  ctx.fill();
@@ -10785,7 +10844,7 @@ function SideView({
10785
10844
  const ppx = px - camX;
10786
10845
  const ppy = py - camY;
10787
10846
  const facingRight = auth.facingRight ?? true;
10788
- const playerImg = pSprite ? loadImage(pSprite.url) : null;
10847
+ const playerImg = pSprite ? getImage(pSprite.url) : void 0;
10789
10848
  const playerSrc = playerImg ? resolveAssetSource(playerImg, pSprite, NOOP) : null;
10790
10849
  if (playerSrc) {
10791
10850
  ctx.save();
@@ -10798,7 +10857,7 @@ function SideView({
10798
10857
  }
10799
10858
  ctx.restore();
10800
10859
  } else {
10801
- ctx.fillStyle = PLAYER_COLOR;
10860
+ ctx.fillStyle = SIDE_PLAYER_COLOR;
10802
10861
  const radius = Math.min(pw, ph) * 0.25;
10803
10862
  ctx.beginPath();
10804
10863
  ctx.moveTo(ppx + radius, ppy);
@@ -10814,7 +10873,7 @@ function SideView({
10814
10873
  const eyeY = ppy + ph * 0.3;
10815
10874
  const eyeSize = 3;
10816
10875
  const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
10817
- ctx.fillStyle = PLAYER_EYE_COLOR;
10876
+ ctx.fillStyle = SIDE_PLAYER_EYE_COLOR;
10818
10877
  ctx.beginPath();
10819
10878
  ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
10820
10879
  ctx.fill();
@@ -10828,7 +10887,7 @@ function SideView({
10828
10887
  const alpha = Math.min(1, fx.ttl / 4);
10829
10888
  const prev = ctx.globalAlpha;
10830
10889
  ctx.globalAlpha = alpha;
10831
- ctx.fillStyle = "#ffe066";
10890
+ ctx.fillStyle = SIDE_FX_COLOR;
10832
10891
  ctx.beginPath();
10833
10892
  ctx.arc(fxScreenX, fxScreenY, 12, 0, Math.PI * 2);
10834
10893
  ctx.fill();
@@ -10836,7 +10895,7 @@ function SideView({
10836
10895
  }
10837
10896
  };
10838
10897
  return interp.startLoop(drawFrame);
10839
- }, [interp.startLoop, loadImage]);
10898
+ }, [interp.startLoop, getImage]);
10840
10899
  return /* @__PURE__ */ jsx(
10841
10900
  "canvas",
10842
10901
  {
@@ -10848,6 +10907,33 @@ function SideView({
10848
10907
  }
10849
10908
  );
10850
10909
  }
10910
+ var NOOP;
10911
+ var init_SideCanvas2D = __esm({
10912
+ "components/game/2d/molecules/SideCanvas2D.tsx"() {
10913
+ "use client";
10914
+ init_cn();
10915
+ init_useEventBus();
10916
+ init_useImageCache();
10917
+ init_atlasSlice();
10918
+ init_useRenderInterpolation();
10919
+ init_verificationRegistry();
10920
+ init_isometric();
10921
+ NOOP = () => {
10922
+ };
10923
+ SideCanvas2D.displayName = "SideCanvas2D";
10924
+ }
10925
+ });
10926
+ function normalizeBackdrop2(bg) {
10927
+ return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
10928
+ }
10929
+ function resolveTileFallbackColor(tile) {
10930
+ const override = tile.color;
10931
+ return override ?? TERRAIN_COLORS[tile.terrain ?? ""] ?? TERRAIN_COLORS.default;
10932
+ }
10933
+ function resolveUnitTeamColor(unit) {
10934
+ const override = unit.tint;
10935
+ return override ?? TEAM_COLORS[unit.team ?? ""] ?? TEAM_COLORS.default;
10936
+ }
10851
10937
  function Canvas2D({
10852
10938
  // Closed-circuit
10853
10939
  className,
@@ -10897,13 +10983,13 @@ function Canvas2D({
10897
10983
  // Side-view asset resolution
10898
10984
  playerSprite,
10899
10985
  tileSprites,
10900
- bgColor = "#5c94fc",
10986
+ bgColor = DEFAULT_BG_COLOR,
10901
10987
  worldWidth = 800,
10902
10988
  worldHeight = 400,
10903
10989
  // Remote asset loading
10904
10990
  assetManifest
10905
10991
  }) {
10906
- const backgroundImage = normalizeBackdrop(backgroundImageRaw);
10992
+ const backgroundImage = normalizeBackdrop2(backgroundImageRaw);
10907
10993
  const isSide = projection === "side";
10908
10994
  const isFree = projection === "free";
10909
10995
  const flatLike = projection === "hex" || projection === "flat" || isFree;
@@ -11087,7 +11173,7 @@ function Canvas2D({
11087
11173
  return sortedTiles.map((t2) => ({
11088
11174
  x: t2.x,
11089
11175
  y: t2.y,
11090
- color: t2.terrain === "water" ? "#3b82f6" : t2.terrain === "mountain" ? "#78716c" : "#4ade80"
11176
+ color: MINIMAP_TERRAIN_COLORS[t2.terrain ?? ""] ?? MINIMAP_TERRAIN_COLORS.default
11091
11177
  }));
11092
11178
  }, [showMinimap, sortedTiles]);
11093
11179
  const miniMapUnits = useMemo(() => {
@@ -11095,7 +11181,7 @@ function Canvas2D({
11095
11181
  return units.filter((u) => u.position).map((u) => ({
11096
11182
  x: u.position.x,
11097
11183
  y: u.position.y,
11098
- color: u.team === "player" ? "#60a5fa" : u.team === "enemy" ? "#f87171" : "#9ca3af",
11184
+ color: MINIMAP_UNIT_COLORS[u.team ?? ""] ?? MINIMAP_UNIT_COLORS.default,
11099
11185
  isPlayer: u.team === "player"
11100
11186
  }));
11101
11187
  }, [showMinimap, units]);
@@ -11132,7 +11218,7 @@ function Canvas2D({
11132
11218
  }
11133
11219
  }
11134
11220
  } else {
11135
- ctx.fillStyle = "#1a1a2e";
11221
+ ctx.fillStyle = BACKGROUND_FALLBACK_COLOR;
11136
11222
  ctx.fillRect(0, 0, viewportSize.width, viewportSize.height);
11137
11223
  }
11138
11224
  if (sortedTiles.length === 0 && units.length === 0 && features.length === 0) return;
@@ -11162,15 +11248,15 @@ function Canvas2D({
11162
11248
  } else if (img && img.naturalWidth === 0) {
11163
11249
  ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
11164
11250
  } else if (squareGrid) {
11165
- ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
11251
+ ctx.fillStyle = resolveTileFallbackColor(tile);
11166
11252
  ctx.fillRect(pos.x, pos.y, scaledTileWidth, scaledTileWidth);
11167
- ctx.strokeStyle = "rgba(0,0,0,0.2)";
11253
+ ctx.strokeStyle = GRID_STROKE_COLOR;
11168
11254
  ctx.lineWidth = 1;
11169
11255
  ctx.strokeRect(pos.x, pos.y, scaledTileWidth, scaledTileWidth);
11170
11256
  } else {
11171
11257
  const centerX = pos.x + scaledTileWidth / 2;
11172
11258
  const topY = pos.y + scaledDiamondTopY;
11173
- ctx.fillStyle = tile.terrain === "water" ? "#3b82f6" : tile.terrain === "mountain" ? "#78716c" : tile.terrain === "stone" ? "#9ca3af" : "#4ade80";
11259
+ ctx.fillStyle = resolveTileFallbackColor(tile);
11174
11260
  ctx.beginPath();
11175
11261
  ctx.moveTo(centerX, topY);
11176
11262
  ctx.lineTo(pos.x + scaledTileWidth, topY + scaledFloorHeight / 2);
@@ -11178,7 +11264,7 @@ function Canvas2D({
11178
11264
  ctx.lineTo(pos.x, topY + scaledFloorHeight / 2);
11179
11265
  ctx.closePath();
11180
11266
  ctx.fill();
11181
- ctx.strokeStyle = "rgba(0,0,0,0.2)";
11267
+ ctx.strokeStyle = GRID_STROKE_COLOR;
11182
11268
  ctx.lineWidth = 1;
11183
11269
  ctx.stroke();
11184
11270
  }
@@ -11199,11 +11285,11 @@ function Canvas2D({
11199
11285
  ctx.fill();
11200
11286
  };
11201
11287
  if (hoveredTile && hoveredTile.x === tile.x && hoveredTile.y === tile.y) {
11202
- drawHighlight("rgba(255, 255, 255, 0.15)");
11288
+ drawHighlight(HIGHLIGHT_COLORS.hover);
11203
11289
  }
11204
11290
  const tileKey = `${tile.x},${tile.y}`;
11205
- if (validMoveSet.has(tileKey)) drawHighlight("rgba(74, 222, 128, 0.25)");
11206
- if (attackTargetSet.has(tileKey)) drawHighlight("rgba(239, 68, 68, 0.35)");
11291
+ if (validMoveSet.has(tileKey)) drawHighlight(HIGHLIGHT_COLORS.validMove);
11292
+ if (attackTargetSet.has(tileKey)) drawHighlight(HIGHLIGHT_COLORS.attack);
11207
11293
  if (debug2) {
11208
11294
  const centerX = pos.x + scaledTileWidth / 2;
11209
11295
  const centerY = squareGrid ? pos.y + scaledTileWidth / 2 : pos.y + scaledFloorHeight / 2 + scaledDiamondTopY;
@@ -11277,9 +11363,9 @@ function Canvas2D({
11277
11363
  const maxUnitW = scaledTileWidth * spriteMaxWidthRatio * unitScale;
11278
11364
  const unitIsSheet = unit.spriteSheet?.url !== void 0;
11279
11365
  const unitSrc = img && !unitIsSheet ? resolveAssetSource(img, unitAsset, bumpAtlas) : null;
11280
- const SHEET_ROWS = 5;
11281
11366
  const sheetFrameW = img ? img.naturalWidth / SHEET_COLUMNS : 0;
11282
- const sheetFrameH = img ? img.naturalHeight / SHEET_ROWS : 0;
11367
+ const sheetFrameH = img ? img.naturalHeight / LEGACY_SHEET_ROWS : 0;
11368
+ const legacyFrame = frameRect(0, 0, SHEET_COLUMNS, sheetFrameW, sheetFrameH);
11283
11369
  const frameW = unitIsSheet ? sheetFrameW : img?.naturalWidth ?? 1;
11284
11370
  const frameH = unitIsSheet ? sheetFrameH : img?.naturalHeight ?? 1;
11285
11371
  const ar = unitIsSheet ? sheetFrameW / (sheetFrameH || 1) : unitSrc ? unitSrc.aspect : frameW / (frameH || 1);
@@ -11297,17 +11383,16 @@ function Canvas2D({
11297
11383
  ctx.globalAlpha = 0.25;
11298
11384
  if (img) {
11299
11385
  if (unitIsSheet) {
11300
- ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
11386
+ ctx.drawImage(img, legacyFrame.sx, legacyFrame.sy, legacyFrame.sw, legacyFrame.sh, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
11301
11387
  } else if (unitSrc) {
11302
11388
  blit(ctx, unitSrc, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
11303
11389
  } else {
11304
11390
  ctx.drawImage(img, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
11305
11391
  }
11306
11392
  } else {
11307
- const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
11308
11393
  ctx.beginPath();
11309
11394
  ctx.arc(ghostCenterX, ghostGroundY - 16 * scale, 20 * scale, 0, Math.PI * 2);
11310
- ctx.fillStyle = color;
11395
+ ctx.fillStyle = resolveUnitTeamColor(unit);
11311
11396
  ctx.fill();
11312
11397
  }
11313
11398
  ctx.restore();
@@ -11315,7 +11400,7 @@ function Canvas2D({
11315
11400
  if (isSelected) {
11316
11401
  ctx.beginPath();
11317
11402
  ctx.ellipse(centerX, groundY, drawW / 2 + 4 * scale, 12 * scale, 0, 0, Math.PI * 2);
11318
- ctx.strokeStyle = "rgba(0, 200, 255, 0.8)";
11403
+ ctx.strokeStyle = SELECTION_RING_COLOR;
11319
11404
  ctx.lineWidth = 3;
11320
11405
  ctx.stroke();
11321
11406
  }
@@ -11332,7 +11417,7 @@ function Canvas2D({
11332
11417
  const spriteY = groundY - fDrawH - (frame.applyBreathing ? breatheOffset : 0);
11333
11418
  ctx.save();
11334
11419
  if (unit.team) {
11335
- ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
11420
+ ctx.shadowColor = TEAM_SHADOW_COLORS[unit.team] ?? TEAM_SHADOW_COLORS.default;
11336
11421
  ctx.shadowBlur = 12 * scale;
11337
11422
  }
11338
11423
  if (frame.flipX) {
@@ -11347,7 +11432,7 @@ function Canvas2D({
11347
11432
  const spriteY = groundY - drawH - breatheOffset;
11348
11433
  const drawUnit = (x) => {
11349
11434
  if (unitIsSheet) {
11350
- ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, x, spriteY, drawW, drawH);
11435
+ ctx.drawImage(img, legacyFrame.sx, legacyFrame.sy, legacyFrame.sw, legacyFrame.sh, x, spriteY, drawW, drawH);
11351
11436
  } else if (unitSrc) {
11352
11437
  blit(ctx, unitSrc, x, spriteY, drawW, drawH);
11353
11438
  } else {
@@ -11356,7 +11441,7 @@ function Canvas2D({
11356
11441
  };
11357
11442
  if (unit.team) {
11358
11443
  ctx.save();
11359
- ctx.shadowColor = unit.team === "player" ? "rgba(0, 150, 255, 0.6)" : "rgba(255, 50, 50, 0.6)";
11444
+ ctx.shadowColor = TEAM_SHADOW_COLORS[unit.team] ?? TEAM_SHADOW_COLORS.default;
11360
11445
  ctx.shadowBlur = 12 * scale;
11361
11446
  drawUnit(centerX - drawW / 2);
11362
11447
  ctx.restore();
@@ -11364,10 +11449,9 @@ function Canvas2D({
11364
11449
  drawUnit(centerX - drawW / 2);
11365
11450
  }
11366
11451
  } else {
11367
- const color = unit.team === "player" ? "#3b82f6" : unit.team === "enemy" ? "#ef4444" : "#6b7280";
11368
11452
  ctx.beginPath();
11369
11453
  ctx.arc(centerX, groundY - 20 * scale - breatheOffset, 20 * scale, 0, Math.PI * 2);
11370
- ctx.fillStyle = color;
11454
+ ctx.fillStyle = resolveUnitTeamColor(unit);
11371
11455
  ctx.fill();
11372
11456
  ctx.strokeStyle = "rgba(255,255,255,0.8)";
11373
11457
  ctx.lineWidth = 2;
@@ -11606,7 +11690,7 @@ function Canvas2D({
11606
11690
  }
11607
11691
  if (isSide) {
11608
11692
  return /* @__PURE__ */ jsx(Box, { ref: containerRef, className: cn("relative overflow-hidden w-full h-full", className), children: /* @__PURE__ */ jsx(
11609
- SideView,
11693
+ SideCanvas2D,
11610
11694
  {
11611
11695
  player,
11612
11696
  platforms: platforms && platforms.length ? platforms : DEFAULT_PLATFORMS,
@@ -11704,7 +11788,7 @@ function Canvas2D({
11704
11788
  {
11705
11789
  as: "span",
11706
11790
  className: "text-white text-xs font-bold px-1.5 py-0.5 rounded mb-0.5 whitespace-nowrap block",
11707
- 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)" },
11791
+ style: { background: UNIT_LABEL_BG_COLORS[unit.team ?? ""] ?? UNIT_LABEL_BG_COLORS.default },
11708
11792
  children: unit.name
11709
11793
  }
11710
11794
  ),
@@ -11728,7 +11812,7 @@ function Canvas2D({
11728
11812
  }
11729
11813
  );
11730
11814
  }
11731
- var PLATFORM_COLORS, NOOP, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR, DEFAULT_PLATFORMS;
11815
+ var LEGACY_SHEET_ROWS, DEFAULT_PLATFORMS;
11732
11816
  var init_Canvas2D = __esm({
11733
11817
  "components/game/2d/molecules/Canvas2D.tsx"() {
11734
11818
  "use client";
@@ -11750,19 +11834,9 @@ var init_Canvas2D = __esm({
11750
11834
  init_verificationRegistry();
11751
11835
  init_isometric();
11752
11836
  init_spriteSheetConstants();
11753
- PLATFORM_COLORS = {
11754
- ground: "#4a7c59",
11755
- platform: "#7c6b4a",
11756
- hazard: "#c0392b",
11757
- goal: "#f1c40f"
11758
- };
11759
- NOOP = () => {
11760
- };
11761
- PLAYER_COLOR = "#3498db";
11762
- PLAYER_EYE_COLOR = "#ffffff";
11763
- SKY_GRADIENT_TOP = "#1a1a2e";
11764
- SKY_GRADIENT_BOTTOM = "#16213e";
11765
- GRID_COLOR = "rgba(255, 255, 255, 0.03)";
11837
+ init_spriteAnimation();
11838
+ init_SideCanvas2D();
11839
+ LEGACY_SHEET_ROWS = Object.keys(SPRITE_SHEET_LAYOUT).length;
11766
11840
  DEFAULT_PLATFORMS = [
11767
11841
  { x: 0, y: 368, width: 800, height: 32, type: "ground" },
11768
11842
  { x: 150, y: 280, width: 160, height: 16, type: "platform" },
@@ -30480,13 +30554,13 @@ var init_MapView = __esm({
30480
30554
  shadowSize: [41, 41]
30481
30555
  });
30482
30556
  L.Marker.prototype.options.icon = defaultIcon;
30483
- const { useEffect: useEffect67, useRef: useRef66, useCallback: useCallback98, useState: useState97 } = React96__default;
30557
+ const { useEffect: useEffect68, useRef: useRef67, useCallback: useCallback99, useState: useState97 } = React96__default;
30484
30558
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
30485
30559
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
30486
30560
  function MapUpdater({ centerLat, centerLng, zoom }) {
30487
30561
  const map = useMap();
30488
- const prevRef = useRef66({ centerLat, centerLng, zoom });
30489
- useEffect67(() => {
30562
+ const prevRef = useRef67({ centerLat, centerLng, zoom });
30563
+ useEffect68(() => {
30490
30564
  const prev = prevRef.current;
30491
30565
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
30492
30566
  map.setView([centerLat, centerLng], zoom);
@@ -30497,7 +30571,7 @@ var init_MapView = __esm({
30497
30571
  }
30498
30572
  function MapClickHandler({ onMapClick }) {
30499
30573
  const map = useMap();
30500
- useEffect67(() => {
30574
+ useEffect68(() => {
30501
30575
  if (!onMapClick) return;
30502
30576
  const handler = (e) => {
30503
30577
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -30526,7 +30600,7 @@ var init_MapView = __esm({
30526
30600
  }) {
30527
30601
  const eventBus = useEventBus2();
30528
30602
  const [clickedPosition, setClickedPosition] = useState97(null);
30529
- const handleMapClick = useCallback98((lat, lng) => {
30603
+ const handleMapClick = useCallback99((lat, lng) => {
30530
30604
  if (showClickedPin) {
30531
30605
  setClickedPosition({ lat, lng });
30532
30606
  }
@@ -30535,7 +30609,7 @@ var init_MapView = __esm({
30535
30609
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
30536
30610
  }
30537
30611
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
30538
- const handleMarkerClick = useCallback98((marker) => {
30612
+ const handleMarkerClick = useCallback99((marker) => {
30539
30613
  onMarkerClick?.(marker);
30540
30614
  if (markerClickEvent) {
30541
30615
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.104.0",
3
+ "version": "5.105.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [