From 8010688d277edc725433e1a402b0a9dd06b9b661 Mon Sep 17 00:00:00 2001
From: yanir <100792995+Boostry123@users.noreply.github.com>
Date: Fri, 5 Sep 2025 21:25:17 +0300
Subject: [PATCH] Feat/breathing animation around spawn cell (#1951)
## Description:
Noticed many people are struggling to see where they choose to spawn or
forget where they chose , what leads to confusion.
solved it with this animation around the spawning player cell.

ALSO: Added a missing translation for hebrew
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
boostry
Co-authored-by: evanpelle
---
src/client/graphics/layers/TerritoryLayer.ts | 46 ++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts
index 354a23df8..3ccd54f91 100644
--- a/src/client/graphics/layers/TerritoryLayer.ts
+++ b/src/client/graphics/layers/TerritoryLayer.ts
@@ -23,6 +23,7 @@ export class TerritoryLayer implements Layer {
private context: CanvasRenderingContext2D;
private imageData: ImageData;
private alternativeImageData: ImageData;
+ private borderAnimTime = 0;
private cachedTerritoryPatternsEnabled: boolean | undefined;
@@ -195,6 +196,37 @@ export class TerritoryLayer implements Layer {
}
}
}
+ // Breathing border animation
+ this.borderAnimTime += 1;
+ const minPadding = 3;
+ const maxPadding = 8;
+ // Range: [minPadding..maxPadding]
+ const breathingPadding =
+ minPadding +
+ (maxPadding - minPadding) *
+ (0.5 + 0.5 * Math.sin(this.borderAnimTime * 0.3));
+
+ if (focusedPlayer) {
+ // Clear previous animated border
+ if (this.highlightContext) {
+ this.highlightContext.clearRect(
+ 0,
+ 0,
+ this.game.width(),
+ this.game.height(),
+ );
+ }
+
+ const center = focusedPlayer.nameLocation();
+ if (center) {
+ this.drawBreathingRing(
+ center.x,
+ center.y,
+ breathingPadding,
+ this.theme.spawnHighlightColor(),
+ );
+ }
+ }
}
init() {
@@ -550,4 +582,18 @@ export class TerritoryLayer implements Layer {
const y = this.game.y(tile);
this.highlightContext.clearRect(x, y, 1, 1);
}
+ private drawBreathingRing(
+ cx: number,
+ cy: number,
+ radius: number,
+ color: Colord,
+ ) {
+ const ctx = this.highlightContext;
+ if (!ctx) return;
+ ctx.beginPath();
+ ctx.arc(cx, cy, radius, 0, Math.PI * 2);
+ ctx.strokeStyle = color.toRgbString();
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ }
}