AntDX316 commited on
Commit
2f784f7
Β·
1 Parent(s): 6d54fec
Files changed (4) hide show
  1. .clinerules-code +0 -0
  2. index.html +35 -3
  3. script.js +1757 -45
  4. style.css +91 -5
.clinerules-code ADDED
File without changes
index.html CHANGED
@@ -19,9 +19,41 @@
19
  <main>
20
  <div id="map"></div>
21
  <div class="info-panel">
22
- <h2>Unit Information</h2>
23
- <div id="unit-info">
24
- <p class="no-unit-selected">Click on a unit to see details</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  </div>
26
  </div>
27
  </main>
 
19
  <main>
20
  <div id="map"></div>
21
  <div class="info-panel">
22
+ <div class="panel-section">
23
+ <h2>Unit Information</h2>
24
+ <div id="unit-info">
25
+ <p class="no-unit-selected">Click on a unit to see details</p>
26
+ </div>
27
+ </div>
28
+ <div class="panel-section">
29
+ <h2>Battle Statistics</h2>
30
+ <div id="battle-stats">
31
+ <div class="stat-row">
32
+ <div class="stat-label">Active Units:</div>
33
+ <div class="stat-value"><span id="blue-active">15</span> πŸ”΅ vs <span id="red-active">15</span> πŸ”΄</div>
34
+ </div>
35
+ <div class="stat-row">
36
+ <div class="stat-label">Casualties:</div>
37
+ <div class="stat-value"><span id="blue-casualties">0</span> πŸ”΅ | <span id="red-casualties">0</span> πŸ”΄</div>
38
+ </div>
39
+ <div class="stat-row">
40
+ <div class="stat-label">Reinforcements:</div>
41
+ <div class="stat-value"><span id="blue-reinforcements">0</span> πŸ”΅ | <span id="red-reinforcements">0</span> πŸ”΄</div>
42
+ </div>
43
+ <div class="stat-row">
44
+ <div class="stat-label">Active Battles:</div>
45
+ <div class="stat-value"><span id="active-battles">0</span></div>
46
+ </div>
47
+ <div class="stat-row">
48
+ <div class="stat-label">Territory Control:</div>
49
+ <div class="stat-value">
50
+ <div class="control-bar">
51
+ <div id="blue-control" class="blue-control" style="width: 50%"></div>
52
+ <div id="red-control" class="red-control" style="width: 50%"></div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </div>
57
  </div>
58
  </div>
59
  </main>
script.js CHANGED
@@ -8,15 +8,40 @@ let hourCounter = 0;
8
  let battleIntervals = [];
9
  let timer;
10
 
11
- // Unit types with their properties
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  const unitTypes = {
13
- infantry: { speed: 0.001, attackPower: 10, range: 0.01, icon: 'πŸ‘€' },
14
- armor: { speed: 0.002, attackPower: 25, range: 0.015, icon: 'πŸ›‘οΈ' },
15
- artillery: { speed: 0.0005, attackPower: 30, range: 0.03, icon: 'πŸ’₯' },
16
- recon: { speed: 0.003, attackPower: 5, range: 0.02, icon: 'πŸ”' },
17
- airDefense: { speed: 0.0008, attackPower: 20, range: 0.025, icon: 'πŸš€' },
18
- command: { speed: 0.0006, attackPower: 5, range: 0.015, icon: '⭐' },
19
- medical: { speed: 0.001, attackPower: 0, range: 0.008, icon: '🩺' }
 
 
 
 
 
 
 
 
 
 
 
20
  };
21
 
22
  // Initialize the map when the DOM is fully loaded
@@ -98,8 +123,24 @@ function generateUnits() {
98
  }
99
 
100
  function getRandomUnitType() {
101
- const types = Object.keys(unitTypes);
102
- return types[Math.floor(Math.random() * types.length)];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  }
104
 
105
  function getUnitName(type) {
@@ -110,7 +151,16 @@ function getUnitName(type) {
110
  recon: 'Reconnaissance Unit',
111
  airDefense: 'Air Defense Unit',
112
  command: 'Command Center',
113
- medical: 'Medical Corps'
 
 
 
 
 
 
 
 
 
114
  };
115
  return names[type] || 'Unit';
116
  }
@@ -229,13 +279,50 @@ function startSimulation() {
229
  if (!isPaused) {
230
  updateTime();
231
 
232
- // Every 3 seconds, assign new targets to some units
233
- if (hourCounter % 3 === 0) {
234
  assignRandomTargets();
235
  }
236
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  moveUnits();
238
  checkForBattles();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
  // Update the selected unit's info if one is selected
241
  if (selectedUnit) {
@@ -245,16 +332,1263 @@ function startSimulation() {
245
  }, 1000);
246
  }
247
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  function updateTime() {
249
  hourCounter++;
250
  if (hourCounter >= 24) {
251
  hourCounter = 0;
252
  dayCounter++;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  }
254
 
255
  // Format hours with leading zero
256
  const formattedHours = hourCounter.toString().padStart(2, '0');
257
- document.getElementById('day-counter').textContent = `Day ${dayCounter} - ${formattedHours}:00 hours`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  }
259
 
260
  function togglePause() {
@@ -263,27 +1597,87 @@ function togglePause() {
263
  pauseButton.textContent = isPaused ? 'Resume' : 'Pause';
264
  }
265
 
266
- function assignRandomTargets() {
267
  const activeFactions = ['blue', 'red'];
268
 
269
  // Randomly select units to assign new targets
270
  units.forEach(unit => {
271
- // Only assign new targets to units that aren't in battle and at random (30% chance)
272
- if (!unit.inBattle && Math.random() < 0.3) {
273
  // Find potential enemy targets
274
  const enemyFaction = unit.faction === 'blue' ? 'red' : 'blue';
275
  const enemies = units.filter(u => u.faction === enemyFaction && u.health > 0);
276
 
277
  if (enemies.length > 0) {
278
- // Choose random enemy as target
279
- const target = enemies[Math.floor(Math.random() * enemies.length)];
 
 
 
 
 
 
 
 
 
 
280
  unit.targetLat = target.lat;
281
  unit.targetLng = target.lng;
282
  unit.movingToTarget = true;
283
- unit.status = 'Advancing';
 
 
 
284
  }
285
  }
286
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  }
288
 
289
  function moveUnits() {
@@ -328,8 +1722,19 @@ function moveUnits() {
328
  }
329
 
330
  function checkForBattles() {
331
- // Clear any existing battle animations
332
- document.querySelectorAll('.battle-indicator').forEach(el => el.remove());
 
 
 
 
 
 
 
 
 
 
 
333
 
334
  // Check each unit against potential enemies
335
  units.forEach(unit => {
@@ -339,6 +1744,7 @@ function checkForBattles() {
339
  const enemies = units.filter(u => u.faction === enemyFaction && u.health > 0);
340
 
341
  // Reset battle state
 
342
  unit.inBattle = false;
343
 
344
  enemies.forEach(enemy => {
@@ -353,60 +1759,320 @@ function checkForBattles() {
353
  unit.inBattle = true;
354
  enemy.inBattle = true;
355
 
356
- // Update statuses
357
- unit.status = 'Engaged';
358
- enemy.status = 'Engaged';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
 
360
- // Create battle animation at midpoint
361
- const battleLat = (unit.lat + enemy.lat) / 2;
362
- const battleLng = (unit.lng + enemy.lng) / 2;
363
 
364
- createBattleAnimation(battleLat, battleLng);
 
 
365
 
366
- // Calculate damage based on unit properties (simplified for this demo)
367
- const unitDamage = Math.round(Math.random() * unit.attackPower * 0.2);
368
- const enemyDamage = Math.round(Math.random() * enemy.attackPower * 0.2);
369
 
370
  // Apply damage
371
  enemy.health = Math.max(0, enemy.health - unitDamage);
372
  unit.health = Math.max(0, unit.health - enemyDamage);
373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  // Check if unit defeated enemy
375
  if (enemy.health <= 0 && unitDamage > 0) {
376
  unit.kills++;
377
  checkUnitDeath(enemy);
 
 
 
 
 
378
  }
379
 
380
  // Check if enemy defeated unit
381
  if (unit.health <= 0 && enemyDamage > 0) {
382
  enemy.kills++;
383
  checkUnitDeath(unit);
 
 
 
 
 
384
  return; // Stop checking more enemies if this unit is dead
385
  }
386
  }
387
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  });
 
 
389
  }
390
 
391
  function createBattleAnimation(lat, lng) {
392
  // Convert lat/lng to pixel coordinates
393
  const point = map.latLngToLayerPoint([lat, lng]);
394
 
395
- // Create battle indicator element
396
- const battleEl = document.createElement('div');
397
- battleEl.className = 'battle-indicator';
398
- battleEl.style.left = point.x + 'px';
399
- battleEl.style.top = point.y + 'px';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
 
401
- // Add to map container
402
- document.querySelector('#map').appendChild(battleEl);
403
 
404
- // Remove element after animation completes
405
- setTimeout(() => {
406
- if (battleEl.parentNode) {
407
- battleEl.parentNode.removeChild(battleEl);
408
- }
409
- }, 2000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  }
411
 
412
  function checkUnitDeath(unit) {
@@ -432,8 +2098,54 @@ function checkUnitDeath(unit) {
432
  unit.marker.setIcon(destroyedIcon);
433
  }
434
 
 
435
  unit.status = 'Destroyed';
436
  unit.movingToTarget = false;
437
  unit.inBattle = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  }
439
  }
 
8
  let battleIntervals = [];
9
  let timer;
10
 
11
+ // Battle statistics
12
+ let statistics = {
13
+ blueActive: 15,
14
+ redActive: 15,
15
+ blueCasualties: 0,
16
+ redCasualties: 0,
17
+ blueReinforcements: 0,
18
+ redReinforcements: 0,
19
+ activeBattles: 0,
20
+ blueControl: 50,
21
+ redControl: 50,
22
+ nextUnitId: 30 // Starting after initial 30 units (15 blue + 15 red)
23
+ };
24
+
25
+ // Unit types with their properties - expanded with new types that unlock over time
26
  const unitTypes = {
27
+ // Base units available from start
28
+ infantry: { speed: 0.001, attackPower: 10, range: 0.01, icon: 'πŸ‘€', unlockDay: 0 },
29
+ armor: { speed: 0.002, attackPower: 25, range: 0.015, icon: 'πŸ›‘οΈ', unlockDay: 0 },
30
+ artillery: { speed: 0.0005, attackPower: 30, range: 0.03, icon: 'πŸ’₯', unlockDay: 0 },
31
+ recon: { speed: 0.003, attackPower: 5, range: 0.02, icon: 'πŸ”', unlockDay: 0 },
32
+ airDefense: { speed: 0.0008, attackPower: 20, range: 0.025, icon: 'πŸš€', unlockDay: 0 },
33
+ command: { speed: 0.0006, attackPower: 5, range: 0.015, icon: '⭐', unlockDay: 0 },
34
+ medical: { speed: 0.001, attackPower: 0, range: 0.008, icon: '🩺', unlockDay: 0 },
35
+
36
+ // Units that unlock over time
37
+ airborne: { speed: 0.0025, attackPower: 15, range: 0.02, icon: 'πŸͺ‚', unlockDay: 2 },
38
+ specialForces: { speed: 0.0035, attackPower: 18, range: 0.015, icon: 'πŸ₯·', unlockDay: 3 },
39
+ mechanized: { speed: 0.0015, attackPower: 22, range: 0.018, icon: '🚜', unlockDay: 4 },
40
+ engineers: { speed: 0.001, attackPower: 8, range: 0.01, icon: 'πŸ”§', unlockDay: 5, special: 'fortify' },
41
+ naval: { speed: 0.0008, attackPower: 35, range: 0.04, icon: 'βš“', unlockDay: 6 },
42
+ airForce: { speed: 0.004, attackPower: 28, range: 0.05, icon: '✈️', unlockDay: 7 },
43
+ missile: { speed: 0.0005, attackPower: 40, range: 0.06, icon: 'πŸš€', unlockDay: 10 },
44
+ elite: { speed: 0.002, attackPower: 35, range: 0.03, icon: 'πŸ‘‘', unlockDay: 15 }
45
  };
46
 
47
  // Initialize the map when the DOM is fully loaded
 
123
  }
124
 
125
  function getRandomUnitType() {
126
+ // Filter unit types based on the current day
127
+ const availableTypes = Object.entries(unitTypes)
128
+ .filter(([type, properties]) => properties.unlockDay <= dayCounter)
129
+ .map(([type]) => type);
130
+
131
+ // Give higher probability to newly unlocked units to make them more noticeable
132
+ const newlyUnlockedTypes = availableTypes.filter(type =>
133
+ unitTypes[type].unlockDay === dayCounter);
134
+
135
+ // 30% chance to select a newly unlocked unit type if available
136
+ if (newlyUnlockedTypes.length > 0 && Math.random() < 0.3) {
137
+ const randomIndex = Math.floor(Math.random() * newlyUnlockedTypes.length);
138
+ return newlyUnlockedTypes[randomIndex];
139
+ }
140
+
141
+ // Otherwise select randomly from all available types
142
+ const randomIndex = Math.floor(Math.random() * availableTypes.length);
143
+ return availableTypes[randomIndex];
144
  }
145
 
146
  function getUnitName(type) {
 
151
  recon: 'Reconnaissance Unit',
152
  airDefense: 'Air Defense Unit',
153
  command: 'Command Center',
154
+ medical: 'Medical Corps',
155
+ // New unit types
156
+ airborne: 'Airborne Division',
157
+ specialForces: 'Special Forces',
158
+ mechanized: 'Mechanized Infantry',
159
+ engineers: 'Combat Engineers',
160
+ naval: 'Naval Support',
161
+ airForce: 'Air Squadron',
162
+ missile: 'Missile Battery',
163
+ elite: 'Elite Force'
164
  };
165
  return names[type] || 'Unit';
166
  }
 
279
  if (!isPaused) {
280
  updateTime();
281
 
282
+ // More frequent target assignments on busier days
283
+ if (hourCounter % (3 - Math.min(2, Math.floor(dayCounter / 5))) === 0) {
284
  assignRandomTargets();
285
  }
286
 
287
+ // Dynamic reinforcement spawning
288
+ // Base chance increases with time and if there are fewer active battles
289
+ const baseSpawnChance = 0.15 + (dayCounter / 50);
290
+ const battleAdjustment = Math.max(0, 0.1 - (statistics.activeBattles / 30));
291
+
292
+ if (Math.random() < baseSpawnChance + battleAdjustment) {
293
+ spawnReinforcements();
294
+ }
295
+
296
+ // Create sudden "hot zones" where units are redirected
297
+ if (Math.random() < 0.03) {
298
+ createHotZone();
299
+ }
300
+
301
  moveUnits();
302
  checkForBattles();
303
+ updateBattleStatistics();
304
+
305
+ // Increase frequency of random events for more action
306
+ if (Math.random() < 0.05) { // 5x more frequent events
307
+ triggerRandomEvent();
308
+ }
309
+
310
+ // Ensure battle never ends by checking unit counts and adding reinforcements if needed
311
+ const blueSurvivors = units.filter(u => u.faction === 'blue' && u.health > 0).length;
312
+ const redSurvivors = units.filter(u => u.faction === 'red' && u.health > 0).length;
313
+
314
+ // If either faction is below minimum threshold, add emergency reinforcements
315
+ const MIN_UNITS_PER_FACTION = 8; // Ensure at least 8 units per side
316
+ if (blueSurvivors < MIN_UNITS_PER_FACTION) {
317
+ for (let i = 0; i < (MIN_UNITS_PER_FACTION - blueSurvivors); i++) {
318
+ spawnNewUnit('blue');
319
+ }
320
+ }
321
+ if (redSurvivors < MIN_UNITS_PER_FACTION) {
322
+ for (let i = 0; i < (MIN_UNITS_PER_FACTION - redSurvivors); i++) {
323
+ spawnNewUnit('red');
324
+ }
325
+ }
326
 
327
  // Update the selected unit's info if one is selected
328
  if (selectedUnit) {
 
332
  }, 1000);
333
  }
334
 
335
+ // Helper function to get a random unit type based on day counter
336
+ function getRandomUnitType() {
337
+ // Get all unit types that have been unlocked by the current day
338
+ const availableTypes = Object.entries(unitTypes)
339
+ .filter(([type, properties]) => properties.unlockDay <= dayCounter)
340
+ .map(([type]) => type);
341
+
342
+ // Select a random type from available ones
343
+ return availableTypes[Math.floor(Math.random() * availableTypes.length)];
344
+ }
345
+
346
+ // Helper function to get a formatted name for a unit type
347
+ function getUnitName(type) {
348
+ // Format the type name with proper capitalization and spacing
349
+ const formattedName = type
350
+ .replace(/([A-Z])/g, ' $1') // Add space before capital letters
351
+ .replace(/^./, str => str.toUpperCase()); // Capitalize first letter
352
+
353
+ return formattedName;
354
+ }
355
+
356
+ function createHotZone() {
357
+ // Create a random focal point on the map with faction control bias
358
+ // This makes hotspots appear more often in controlled territory
359
+ let hotspotLat, hotspotLng;
360
+
361
+ if (Math.random() < 0.5) {
362
+ // Neutral hotspot
363
+ hotspotLat = 40.7 + (Math.random() * 0.1 - 0.05);
364
+ hotspotLng = -74.0 + (Math.random() * 0.1 - 0.05);
365
+ } else {
366
+ // Faction-biased hotspot
367
+ const dominantFaction = statistics.blueControl > statistics.redControl ? 'blue' : 'red';
368
+
369
+ if (dominantFaction === 'blue') {
370
+ // Blue territory tends to be west/south
371
+ hotspotLat = 40.68 + (Math.random() * 0.06);
372
+ hotspotLng = -74.03 - (Math.random() * 0.04);
373
+ } else {
374
+ // Red territory tends to be east/north
375
+ hotspotLat = 40.72 + (Math.random() * 0.06);
376
+ hotspotLng = -73.97 - (Math.random() * 0.04);
377
+ }
378
+ }
379
+
380
+ // Divert some portion of units to this hotspot
381
+ units.forEach(unit => {
382
+ if (unit.health > 0 && Math.random() < 0.3) {
383
+ unit.targetLat = hotspotLat;
384
+ unit.targetLng = hotspotLng;
385
+ unit.movingToTarget = true;
386
+ unit.status = 'Redeploying';
387
+ }
388
+ });
389
+
390
+ // Determine the type of hotspot (battle, supply drop, strategic location)
391
+ const hotspotTypes = [
392
+ { name: 'battle', probability: 0.5, color: 'rgba(255, 0, 0, 0.2)', shadowColor: 'rgba(255, 0, 0, 0.5)' },
393
+ { name: 'supply', probability: 0.3, color: 'rgba(0, 255, 0, 0.2)', shadowColor: 'rgba(0, 255, 0, 0.5)' },
394
+ { name: 'strategic', probability: 0.2, color: 'rgba(255, 255, 0, 0.2)', shadowColor: 'rgba(255, 255, 0, 0.5)' }
395
+ ];
396
+
397
+ // Select a hotspot type based on probability
398
+ let hotspotType;
399
+ const random = Math.random();
400
+ let cumulativeProbability = 0;
401
+
402
+ for (const type of hotspotTypes) {
403
+ cumulativeProbability += type.probability;
404
+ if (random < cumulativeProbability) {
405
+ hotspotType = type;
406
+ break;
407
+ }
408
+ }
409
+
410
+ // Determine if this is a major event (less common, but more dramatic)
411
+ const isMajorEvent = Math.random() < 0.3;
412
+ const size = isMajorEvent ? 60 : 40;
413
+ const duration = isMajorEvent ? 8000 : 4000;
414
+
415
+ // Create visual effect for the hotspot
416
+ const point = map.latLngToLayerPoint([hotspotLat, hotspotLng]);
417
+
418
+ // Create main hotspot element
419
+ const hotspotEl = document.createElement('div');
420
+ hotspotEl.style.position = 'absolute';
421
+ hotspotEl.style.left = (point.x - size/2) + 'px';
422
+ hotspotEl.style.top = (point.y - size/2) + 'px';
423
+ hotspotEl.style.width = size + 'px';
424
+ hotspotEl.style.height = size + 'px';
425
+ hotspotEl.style.backgroundColor = hotspotType.color;
426
+ hotspotEl.style.borderRadius = '50%';
427
+ hotspotEl.style.zIndex = '600';
428
+ hotspotEl.style.boxShadow = `0 0 ${size/2}px ${hotspotType.shadowColor}`;
429
+ hotspotEl.style.animation = `battle-expand ${duration/1000}s forwards`;
430
+
431
+ // Add pulsing effect to make it more dynamic
432
+ const pulseAnimation = document.createElement('style');
433
+ pulseAnimation.textContent = `
434
+ @keyframes pulse-${Date.now()} {
435
+ 0% { transform: scale(1); opacity: 1; }
436
+ 50% { transform: scale(1.2); opacity: 0.8; }
437
+ 100% { transform: scale(1); opacity: 1; }
438
+ }
439
+ `;
440
+ document.head.appendChild(pulseAnimation);
441
+ hotspotEl.style.animation = `pulse-${Date.now()} 1.5s infinite`;
442
+
443
+ document.querySelector('#map').appendChild(hotspotEl);
444
+
445
+ // Add secondary ring effect for major events
446
+ if (isMajorEvent) {
447
+ const outerRing = document.createElement('div');
448
+ outerRing.style.position = 'absolute';
449
+ outerRing.style.left = (point.x - size) + 'px';
450
+ outerRing.style.top = (point.y - size) + 'px';
451
+ outerRing.style.width = (size * 2) + 'px';
452
+ outerRing.style.height = (size * 2) + 'px';
453
+ outerRing.style.border = `2px solid ${hotspotType.color}`;
454
+ outerRing.style.borderRadius = '50%';
455
+ outerRing.style.zIndex = '599';
456
+ outerRing.style.opacity = '0.7';
457
+
458
+ // Outward expansion animation
459
+ const expandAnimation = document.createElement('style');
460
+ expandAnimation.textContent = `
461
+ @keyframes expand-${Date.now()} {
462
+ 0% { transform: scale(0.5); opacity: 0.7; }
463
+ 100% { transform: scale(2); opacity: 0; }
464
+ }
465
+ `;
466
+ document.head.appendChild(expandAnimation);
467
+ outerRing.style.animation = `expand-${Date.now()} 4s infinite`;
468
+
469
+ document.querySelector('#map').appendChild(outerRing);
470
+
471
+ // Add notification for major events
472
+ let eventTitle = '';
473
+ let eventDesc = '';
474
+
475
+ if (hotspotType.name === 'battle') {
476
+ eventTitle = 'Major Battle Erupting';
477
+ eventDesc = 'Heavy fighting reported in sector';
478
+ } else if (hotspotType.name === 'supply') {
479
+ eventTitle = 'Supply Drop Zone';
480
+ eventDesc = 'Critical supplies have been airdropped';
481
+ } else {
482
+ eventTitle = 'Strategic Location';
483
+ eventDesc = 'Forces moving to secure key position';
484
+ }
485
+
486
+ displayEventNotification(eventTitle, eventDesc);
487
+
488
+ // Remove the outer ring after the duration
489
+ setTimeout(() => {
490
+ if (outerRing.parentNode) {
491
+ outerRing.parentNode.removeChild(outerRing);
492
+ }
493
+ }, duration);
494
+ }
495
+
496
+ // Remove the hotspot element after the duration
497
+ setTimeout(() => {
498
+ if (hotspotEl.parentNode) {
499
+ hotspotEl.parentNode.removeChild(hotspotEl);
500
+ }
501
+ }, duration);
502
+ }
503
+
504
+ // Function to spawn a new unit for a given faction
505
+ function spawnNewUnit(faction) {
506
+ // Determine entry point based on faction
507
+ let entryLat, entryLng;
508
+
509
+ if (faction === 'blue') {
510
+ // Blue forces come from west or south
511
+ if (Math.random() < 0.5) {
512
+ entryLat = 40.7 + (Math.random() * 0.05 - 0.025);
513
+ entryLng = -74.05 - (Math.random() * 0.02); // West
514
+ } else {
515
+ entryLat = 40.65 - (Math.random() * 0.02); // South
516
+ entryLng = -74.0 + (Math.random() * 0.05 - 0.025);
517
+ }
518
+ } else {
519
+ // Red forces come from east or north
520
+ if (Math.random() < 0.5) {
521
+ entryLat = 40.7 + (Math.random() * 0.05 - 0.025);
522
+ entryLng = -73.95 + (Math.random() * 0.02); // East
523
+ } else {
524
+ entryLat = 40.75 + (Math.random() * 0.02); // North
525
+ entryLng = -74.0 + (Math.random() * 0.05 - 0.025);
526
+ }
527
+ }
528
+
529
+ // Create a new unit
530
+ const type = getRandomUnitType();
531
+ const unitId = `${faction}-${statistics.nextUnitId++}`;
532
+
533
+ const newUnit = {
534
+ id: unitId,
535
+ name: `${faction.charAt(0).toUpperCase() + faction.slice(1)} Force ${getUnitName(type)} ${statistics.nextUnitId}`,
536
+ type: type,
537
+ faction: faction,
538
+ strength: 50 + Math.floor(Math.random() * 50), // 50-99
539
+ health: 100,
540
+ status: 'Reinforcing',
541
+ lat: entryLat,
542
+ lng: entryLng,
543
+ targetLat: null,
544
+ targetLng: null,
545
+ marker: null,
546
+ icon: unitTypes[type].icon,
547
+ speed: unitTypes[type].speed,
548
+ attackPower: unitTypes[type].attackPower,
549
+ range: unitTypes[type].range,
550
+ inBattle: false,
551
+ movingToTarget: false,
552
+ kills: 0,
553
+ lastMoveTimestamp: Date.now()
554
+ };
555
+
556
+ units.push(newUnit);
557
+
558
+ // Create marker for the new unit
559
+ const markerSize = newUnit.strength / 15 + 20;
560
+
561
+ const markerHtml = `
562
+ <div class="unit-marker ${newUnit.faction}-unit new-unit" style="width: ${markerSize}px; height: ${markerSize}px; line-height: ${markerSize}px; font-size: ${markerSize/2}px;">
563
+ ${newUnit.icon}
564
+ </div>
565
+ `;
566
+
567
+ const customIcon = L.divIcon({
568
+ html: markerHtml,
569
+ className: '',
570
+ iconSize: [markerSize, markerSize],
571
+ iconAnchor: [markerSize/2, markerSize/2]
572
+ });
573
+
574
+ newUnit.marker = L.marker([newUnit.lat, newUnit.lng], { icon: customIcon })
575
+ .addTo(map)
576
+ .on('click', function() {
577
+ selectUnit(newUnit);
578
+ });
579
+
580
+ // Add an entrance effect
581
+ const point = map.latLngToLayerPoint([newUnit.lat, newUnit.lng]);
582
+
583
+ const entryEffect = document.createElement('div');
584
+ entryEffect.style.position = 'absolute';
585
+ entryEffect.style.left = point.x + 'px';
586
+ entryEffect.style.top = point.y + 'px';
587
+ entryEffect.style.width = '30px';
588
+ entryEffect.style.height = '30px';
589
+ entryEffect.style.backgroundColor = faction === 'blue' ? 'rgba(0, 100, 255, 0.5)' : 'rgba(255, 0, 0, 0.5)';
590
+ entryEffect.style.borderRadius = '50%';
591
+ entryEffect.style.zIndex = '700';
592
+ entryEffect.style.boxShadow = `0 0 15px ${faction === 'blue' ? 'blue' : 'red'}`;
593
+ entryEffect.style.animation = 'unit-entry 1.5s forwards';
594
+
595
+ // Add custom animation if not already defined
596
+ if (!document.querySelector('#unit-entry-animation')) {
597
+ const style = document.createElement('style');
598
+ style.id = 'unit-entry-animation';
599
+ style.textContent = `
600
+ @keyframes unit-entry {
601
+ 0% { transform: scale(0); opacity: 1; }
602
+ 70% { transform: scale(1.5); opacity: 0.7; }
603
+ 100% { transform: scale(2); opacity: 0; }
604
+ }
605
+
606
+ .new-unit {
607
+ animation: highlight-new 2s;
608
+ }
609
+
610
+ @keyframes highlight-new {
611
+ 0% { box-shadow: 0 0 15px gold; transform: scale(1.2); }
612
+ 100% { box-shadow: none; transform: scale(1); }
613
+ }
614
+ `;
615
+ document.head.appendChild(style);
616
+ }
617
+
618
+ document.querySelector('#map').appendChild(entryEffect);
619
+
620
+ // Remove effect after animation completes
621
+ setTimeout(() => {
622
+ if (entryEffect.parentNode) entryEffect.parentNode.removeChild(entryEffect);
623
+ }, 1500);
624
+
625
+ // Assign target to the new unit
626
+ assignTargetToUnit(newUnit);
627
+
628
+ // Update faction statistics
629
+ if (faction === 'blue') {
630
+ statistics.blueActive++;
631
+ statistics.blueReinforcements++;
632
+ } else {
633
+ statistics.redActive++;
634
+ statistics.redReinforcements++;
635
+ }
636
+
637
+ return newUnit;
638
+ }
639
+
640
+ function triggerRandomEvent() {
641
+ // Increment hour counter - every 12 hours is a new day
642
+ hourCounter++;
643
+ if (hourCounter >= 12) {
644
+ hourCounter = 0;
645
+ dayCounter++;
646
+
647
+ // Update the day counter in UI if it exists
648
+ const dayCounterElement = document.getElementById('day-counter');
649
+ if (dayCounterElement) {
650
+ dayCounterElement.textContent = `Day ${dayCounter}`;
651
+ } else {
652
+ // Create day counter element if it doesn't exist
653
+ const statsContainer = document.querySelector('.statistics-container');
654
+ if (statsContainer) {
655
+ const dayElement = document.createElement('div');
656
+ dayElement.id = 'day-counter';
657
+ dayElement.style.fontSize = '18px';
658
+ dayElement.style.fontWeight = 'bold';
659
+ dayElement.style.marginBottom = '10px';
660
+ dayElement.textContent = `Day ${dayCounter}`;
661
+ statsContainer.prepend(dayElement);
662
+ }
663
+ }
664
+
665
+ // Check if new unit types have been unlocked
666
+ const newlyUnlocked = Object.entries(unitTypes)
667
+ .filter(([type, properties]) => properties.unlockDay === dayCounter)
668
+ .map(([type]) => type);
669
+
670
+ if (newlyUnlocked.length > 0) {
671
+ // Announce new technology
672
+ const techAnnouncement = document.createElement('div');
673
+ techAnnouncement.className = 'tech-announcement';
674
+ techAnnouncement.style.position = 'absolute';
675
+ techAnnouncement.style.top = '50%';
676
+ techAnnouncement.style.left = '50%';
677
+ techAnnouncement.style.transform = 'translate(-50%, -50%)';
678
+ techAnnouncement.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
679
+ techAnnouncement.style.color = 'white';
680
+ techAnnouncement.style.padding = '20px';
681
+ techAnnouncement.style.borderRadius = '10px';
682
+ techAnnouncement.style.zIndex = '2000';
683
+ techAnnouncement.style.textAlign = 'center';
684
+ techAnnouncement.style.boxShadow = '0 0 20px gold';
685
+
686
+ techAnnouncement.innerHTML = `
687
+ <h3>New Technology Unlocked!</h3>
688
+ <div>${newlyUnlocked.map(type => `${unitTypes[type].icon} ${getUnitName(type)}`).join('<br>')}</div>
689
+ `;
690
+
691
+ document.body.appendChild(techAnnouncement);
692
+
693
+ // Remove after 5 seconds
694
+ setTimeout(() => {
695
+ techAnnouncement.style.opacity = '0';
696
+ techAnnouncement.style.transition = 'opacity 1s';
697
+ setTimeout(() => {
698
+ if (techAnnouncement.parentNode) {
699
+ techAnnouncement.parentNode.removeChild(techAnnouncement);
700
+ }
701
+ }, 1000);
702
+ }, 5000);
703
+ }
704
+ }
705
+
706
+ // List of possible random events with weighted probabilities
707
+ const events = [
708
+ { name: 'Reinforcement Surge', weight: 0.3, handler: () => {
709
+ // Spawn extra units for a random faction
710
+ const faction = Math.random() < 0.5 ? 'blue' : 'red';
711
+ for (let i = 0; i < 3 + Math.floor(Math.random() * 3); i++) {
712
+ spawnNewUnit(faction);
713
+ }
714
+
715
+ // Display notification
716
+ displayEventNotification(
717
+ 'Reinforcement Surge',
718
+ `${faction.charAt(0).toUpperCase() + faction.slice(1)} forces receiving reinforcements`
719
+ );
720
+ }},
721
+ { name: 'Supply Line Disruption', weight: 0.15, handler: () => {
722
+ // Randomly weaken some units of one faction
723
+ const faction = Math.random() < 0.5 ? 'blue' : 'red';
724
+ const affectedUnits = units.filter(u => u.faction === faction && u.health > 30);
725
+
726
+ if (affectedUnits.length > 0) {
727
+ affectedUnits.forEach(unit => {
728
+ unit.health = Math.max(30, unit.health - 20);
729
+ updateUnitMarkerAppearance(unit);
730
+ });
731
+
732
+ // Display notification
733
+ displayEventNotification(
734
+ 'Supply Line Disruption',
735
+ `${faction.charAt(0).toUpperCase() + faction.slice(1)} forces weakened by supply issues`
736
+ );
737
+ }
738
+ }},
739
+ { name: 'Strategic Redeployment', weight: 0.2, handler: () => {
740
+ // Randomly reassign targets to create new battle lines
741
+ assignRandomTargets(0.7); // Higher chance of reassignment
742
+
743
+ // Display notification
744
+ displayEventNotification(
745
+ 'Strategic Redeployment',
746
+ 'Forces repositioning for tactical advantage'
747
+ );
748
+ }},
749
+ { name: 'Environmental Event', weight: 0.2, handler: () => {
750
+ // Trigger a weather or environment event
751
+ triggerEnvironmentalEvent();
752
+ }},
753
+ { name: 'Hot Zone', weight: 0.15, handler: () => {
754
+ // Create a hot zone where forces converge
755
+ createHotZone();
756
+
757
+ // Display notification
758
+ displayEventNotification(
759
+ 'Strategic Hotspot',
760
+ 'A critical location has been identified for control'
761
+ );
762
+ }}
763
+ ];
764
+
765
+ // Weighted random selection
766
+ const totalWeight = events.reduce((sum, event) => sum + event.weight, 0);
767
+ let random = Math.random() * totalWeight;
768
+ let selectedEvent = null;
769
+
770
+ for (const event of events) {
771
+ random -= event.weight;
772
+ if (random <= 0) {
773
+ selectedEvent = event;
774
+ break;
775
+ }
776
+ }
777
+
778
+ // Execute the selected event
779
+ selectedEvent.handler();
780
+
781
+ // Auto-balance forces if one side is getting too dominant
782
+ const blueSurvivors = units.filter(u => u.faction === 'blue' && u.health > 0).length;
783
+ const redSurvivors = units.filter(u => u.faction === 'red' && u.health > 0).length;
784
+
785
+ if (blueSurvivors < 5 || redSurvivors < 5) {
786
+ // If either side is getting too low, reinforce them
787
+ const weakerFaction = blueSurvivors < redSurvivors ? 'blue' : 'red';
788
+ const reinforcementCount = 3 + Math.floor(Math.random() * 3);
789
+
790
+ for (let i = 0; i < reinforcementCount; i++) {
791
+ spawnNewUnit(weakerFaction);
792
+ }
793
+
794
+ displayEventNotification(
795
+ 'Emergency Reinforcements',
796
+ `${weakerFaction.charAt(0).toUpperCase() + weakerFaction.slice(1)} forces receiving critical support`
797
+ );
798
+ }
799
+ }
800
+
801
+ // Function to display event notifications
802
+ function displayEventNotification(title, message) {
803
+ const notification = document.createElement('div');
804
+ notification.className = 'event-notification';
805
+ notification.style.position = 'absolute';
806
+ notification.style.top = '20px';
807
+ notification.style.right = '20px';
808
+ notification.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
809
+ notification.style.color = 'white';
810
+ notification.style.padding = '10px 15px';
811
+ notification.style.borderRadius = '5px';
812
+ notification.style.zIndex = '2000';
813
+ notification.style.maxWidth = '250px';
814
+ notification.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
815
+ notification.style.opacity = '0';
816
+ notification.style.transition = 'opacity 0.5s';
817
+
818
+ notification.innerHTML = `
819
+ <div style="font-weight: bold; font-size: 14px; margin-bottom: 5px;">${title}</div>
820
+ <div style="font-size: 12px;">${message}</div>
821
+ `;
822
+
823
+ document.body.appendChild(notification);
824
+
825
+ // Fade in
826
+ setTimeout(() => {
827
+ notification.style.opacity = '1';
828
+ }, 100);
829
+
830
+ // Remove after 5 seconds
831
+ setTimeout(() => {
832
+ notification.style.opacity = '0';
833
+ setTimeout(() => {
834
+ if (notification.parentNode) {
835
+ notification.parentNode.removeChild(notification);
836
+ }
837
+ }, 500);
838
+ }, 5000);
839
+ }
840
+
841
+ // Environmental events function
842
+ function triggerEnvironmentalEvent() {
843
+ // Random selection of environmental events
844
+ const events = [
845
+ {
846
+ name: 'Fog of War',
847
+ probability: 0.3,
848
+ handler: () => {
849
+ // Reduce all units' range temporarily
850
+ units.forEach(unit => {
851
+ if (!unit.originalRange) unit.originalRange = unit.range;
852
+ unit.range *= 0.5; // 50% reduction in range
853
+ });
854
+
855
+ // Add a fog overlay to the map
856
+ const fogOverlay = document.createElement('div');
857
+ fogOverlay.id = 'fog-overlay';
858
+ fogOverlay.style.position = 'absolute';
859
+ fogOverlay.style.top = '0';
860
+ fogOverlay.style.left = '0';
861
+ fogOverlay.style.width = '100%';
862
+ fogOverlay.style.height = '100%';
863
+ fogOverlay.style.backgroundColor = 'rgba(255, 255, 255, 0.5)';
864
+ fogOverlay.style.pointerEvents = 'none';
865
+ fogOverlay.style.zIndex = '500';
866
+ fogOverlay.style.transition = 'opacity 2s';
867
+
868
+ document.querySelector('#map').appendChild(fogOverlay);
869
+
870
+ // Show event notification
871
+ displayEventNotification('Fog of War', 'Visibility reduced for all units');
872
+
873
+ // Restore normal visibility after duration
874
+ setTimeout(() => {
875
+ // Fade out fog
876
+ fogOverlay.style.opacity = '0';
877
+
878
+ // Remove fog and restore ranges
879
+ setTimeout(() => {
880
+ if (fogOverlay.parentNode) fogOverlay.parentNode.removeChild(fogOverlay);
881
+ units.forEach(unit => {
882
+ if (unit.originalRange) {
883
+ unit.range = unit.originalRange;
884
+ delete unit.originalRange;
885
+ }
886
+ });
887
+ }, 2000);
888
+ }, 15000); // 15 seconds for simulation
889
+
890
+ return true;
891
+ }
892
+ },
893
+ {
894
+ name: 'Artillery Barrage',
895
+ probability: 0.3,
896
+ handler: () => {
897
+ // Random faction launches barrage
898
+ const launchingFaction = Math.random() < 0.5 ? 'blue' : 'red';
899
+ const targetFaction = launchingFaction === 'blue' ? 'red' : 'blue';
900
+
901
+ // Find target units
902
+ const targetUnits = units.filter(u => u.faction === targetFaction && u.health > 0);
903
+
904
+ if (targetUnits.length > 0) {
905
+ // Calculate target area (center of enemy forces)
906
+ let centerLat = 0, centerLng = 0;
907
+ targetUnits.forEach(unit => {
908
+ centerLat += unit.lat;
909
+ centerLng += unit.lng;
910
+ });
911
+ centerLat /= targetUnits.length;
912
+ centerLng /= targetUnits.length;
913
+
914
+ displayEventNotification(
915
+ 'Artillery Barrage',
916
+ `${launchingFaction.charAt(0).toUpperCase() + launchingFaction.slice(1)} forces launching artillery strike`
917
+ );
918
+
919
+ // Visual effects - multiple explosions
920
+ for (let i = 0; i < 5; i++) {
921
+ setTimeout(() => {
922
+ const offsetLat = centerLat + (Math.random() * 0.01 - 0.005);
923
+ const offsetLng = centerLng + (Math.random() * 0.01 - 0.005);
924
+
925
+ // Create explosion animation
926
+ const point = map.latLngToLayerPoint([offsetLat, offsetLng]);
927
+
928
+ const explosionEl = document.createElement('div');
929
+ explosionEl.style.position = 'absolute';
930
+ explosionEl.style.left = point.x + 'px';
931
+ explosionEl.style.top = point.y + 'px';
932
+ explosionEl.style.width = '40px';
933
+ explosionEl.style.height = '40px';
934
+ explosionEl.style.backgroundColor = '#ff4500';
935
+ explosionEl.style.borderRadius = '50%';
936
+ explosionEl.style.zIndex = '700';
937
+ explosionEl.style.boxShadow = '0 0 30px red';
938
+ explosionEl.style.animation = 'explosion 1.5s forwards';
939
+
940
+ // Add custom animation if not already defined
941
+ if (!document.querySelector('#explosion-animation')) {
942
+ const style = document.createElement('style');
943
+ style.id = 'explosion-animation';
944
+ style.textContent = `
945
+ @keyframes explosion {
946
+ 0% { transform: scale(0.2); opacity: 0.8; }
947
+ 50% { transform: scale(1.5); opacity: 0.9; }
948
+ 100% { transform: scale(2); opacity: 0; }
949
+ }
950
+ `;
951
+ document.head.appendChild(style);
952
+ }
953
+
954
+ document.querySelector('#map').appendChild(explosionEl);
955
+
956
+ // Apply damage to units in radius
957
+ targetUnits.forEach(unit => {
958
+ const dLat = unit.lat - offsetLat;
959
+ const dLng = unit.lng - offsetLng;
960
+ const distance = Math.sqrt(dLat * dLat + dLng * dLng);
961
+
962
+ // Units within blast radius take damage
963
+ if (distance < 0.005) {
964
+ const damage = Math.round(20 * (1 - distance / 0.005));
965
+ unit.health = Math.max(0, unit.health - damage);
966
+
967
+ // Update unit appearance
968
+ updateUnitMarkerAppearance(unit);
969
+
970
+ // Check for death
971
+ if (unit.health <= 0) {
972
+ checkUnitDeath(unit);
973
+ }
974
+ }
975
+ });
976
+
977
+ // Remove explosion after animation
978
+ setTimeout(() => {
979
+ if (explosionEl.parentNode) {
980
+ explosionEl.parentNode.removeChild(explosionEl);
981
+ }
982
+ }, 1500);
983
+ }, i * 800); // Staggered explosions
984
+ }
985
+
986
+ return true;
987
+ }
988
+
989
+ return false;
990
+ }
991
+ },
992
+ {
993
+ name: 'Supply Drop',
994
+ probability: 0.4,
995
+ handler: () => {
996
+ // Random faction receives supplies
997
+ const targetFaction = Math.random() < 0.5 ? 'blue' : 'red';
998
+
999
+ // Find units that would benefit
1000
+ const targetUnits = units.filter(u => u.faction === targetFaction && u.health < 80 && u.health > 0);
1001
+
1002
+ if (targetUnits.length > 0) {
1003
+ // Calculate drop zone
1004
+ let dropLat = 0, dropLng = 0;
1005
+ const selectedUnits = targetUnits.slice(0, Math.min(5, targetUnits.length));
1006
+
1007
+ selectedUnits.forEach(unit => {
1008
+ dropLat += unit.lat;
1009
+ dropLng += unit.lng;
1010
+ });
1011
+ dropLat /= selectedUnits.length;
1012
+ dropLng /= selectedUnits.length;
1013
+
1014
+ displayEventNotification(
1015
+ 'Supply Drop',
1016
+ `${targetFaction.charAt(0).toUpperCase() + targetFaction.slice(1)} forces receiving supplies`
1017
+ );
1018
+
1019
+ // Visual effect for supply drop
1020
+ const point = map.latLngToLayerPoint([dropLat, dropLng]);
1021
+
1022
+ const supplyEl = document.createElement('div');
1023
+ supplyEl.style.position = 'absolute';
1024
+ supplyEl.style.left = point.x + 'px';
1025
+ supplyEl.style.top = point.y + 'px';
1026
+ supplyEl.style.fontSize = '30px';
1027
+ supplyEl.style.zIndex = '700';
1028
+ supplyEl.textContent = 'πŸ“¦';
1029
+ supplyEl.style.animation = 'supply-drop 2s forwards';
1030
+
1031
+ // Add custom animation if not already defined
1032
+ if (!document.querySelector('#supply-drop-animation')) {
1033
+ const style = document.createElement('style');
1034
+ style.id = 'supply-drop-animation';
1035
+ style.textContent = `
1036
+ @keyframes supply-drop {
1037
+ 0% { transform: translateY(-50px); opacity: 0; }
1038
+ 80% { transform: translateY(0); opacity: 1; }
1039
+ 100% { transform: translateY(0); opacity: 0; }
1040
+ }
1041
+ `;
1042
+ document.head.appendChild(style);
1043
+ }
1044
+
1045
+ document.querySelector('#map').appendChild(supplyEl);
1046
+
1047
+ // Apply healing to nearby units
1048
+ selectedUnits.forEach(unit => {
1049
+ const dLat = unit.lat - dropLat;
1050
+ const dLng = unit.lng - dropLng;
1051
+ const distance = Math.sqrt(dLat * dLat + dLng * dLng);
1052
+
1053
+ // Units near the drop get healed
1054
+ if (distance < 0.008) {
1055
+ const healAmount = 15 + Math.floor(Math.random() * 20);
1056
+ unit.health = Math.min(100, unit.health + healAmount);
1057
+
1058
+ // Update unit appearance
1059
+ updateUnitMarkerAppearance(unit);
1060
+
1061
+ // Show healing effect
1062
+ if (unit.marker) {
1063
+ const unitPoint = map.latLngToLayerPoint([unit.lat, unit.lng]);
1064
+
1065
+ const healEl = document.createElement('div');
1066
+ healEl.style.position = 'absolute';
1067
+ healEl.style.left = unitPoint.x + 'px';
1068
+ healEl.style.top = unitPoint.y + 'px';
1069
+ healEl.style.fontSize = '14px';
1070
+ healEl.style.fontWeight = 'bold';
1071
+ healEl.style.color = '#2ecc71';
1072
+ healEl.style.textShadow = '0 0 3px white';
1073
+ healEl.style.zIndex = '1000';
1074
+ healEl.style.transition = 'transform 1.5s, opacity 1.5s';
1075
+ healEl.style.opacity = '1';
1076
+ healEl.textContent = `+${healAmount}`;
1077
+
1078
+ document.querySelector('#map').appendChild(healEl);
1079
+
1080
+ setTimeout(() => {
1081
+ healEl.style.transform = 'translateY(-20px)';
1082
+ healEl.style.opacity = '0';
1083
+ }, 100);
1084
+
1085
+ setTimeout(() => {
1086
+ if (healEl.parentNode) {
1087
+ healEl.parentNode.removeChild(healEl);
1088
+ }
1089
+ }, 1600);
1090
+ }
1091
+ }
1092
+ });
1093
+
1094
+ // Remove supply after animation
1095
+ setTimeout(() => {
1096
+ if (supplyEl.parentNode) {
1097
+ supplyEl.parentNode.removeChild(supplyEl);
1098
+ }
1099
+ }, 2000);
1100
+
1101
+ return true;
1102
+ }
1103
+
1104
+ return false;
1105
+ }
1106
+ }
1107
+ ];
1108
+
1109
+ // Randomly select an event based on probability
1110
+ const randomValue = Math.random();
1111
+ let cumulativeProbability = 0;
1112
+
1113
+ for (const event of events) {
1114
+ cumulativeProbability += event.probability;
1115
+ if (randomValue < cumulativeProbability) {
1116
+ // Execute the selected event and check if it was successful
1117
+ if (event.handler()) {
1118
+ return event.name;
1119
+ }
1120
+ }
1121
+ }
1122
+
1123
+ // If no event was triggered or all failed, default to a supply drop for a random faction
1124
+ const faction = Math.random() < 0.5 ? 'blue' : 'red';
1125
+ for (let i = 0; i < 2; i++) {
1126
+ spawnNewUnit(faction);
1127
+ }
1128
+
1129
+ displayEventNotification(
1130
+ 'Tactical Reinforcements',
1131
+ `${faction.charAt(0).toUpperCase() + faction.slice(1)} forces receiving light support`
1132
+ );
1133
+
1134
+ return 'Tactical Reinforcements';
1135
+ }
1136
+
1137
+ // Function to update unit type counts display
1138
+ function updateUnitTypeCounts() {
1139
+ // Container for unit type statistics
1140
+ let unitCountsContainer = document.getElementById('unit-counts-container');
1141
+
1142
+ if (!unitCountsContainer) {
1143
+ // Create container if it doesn't exist
1144
+ const statsContainer = document.querySelector('.statistics-container');
1145
+ if (statsContainer) {
1146
+ unitCountsContainer = document.createElement('div');
1147
+ unitCountsContainer.id = 'unit-counts-container';
1148
+ unitCountsContainer.style.marginTop = '15px';
1149
+ unitCountsContainer.style.padding = '10px';
1150
+ unitCountsContainer.style.borderTop = '1px solid #444';
1151
+ unitCountsContainer.style.display = 'flex';
1152
+ unitCountsContainer.style.flexWrap = 'wrap';
1153
+ unitCountsContainer.style.justifyContent = 'space-between';
1154
+
1155
+ const title = document.createElement('div');
1156
+ title.style.width = '100%';
1157
+ title.style.fontWeight = 'bold';
1158
+ title.style.marginBottom = '5px';
1159
+ title.textContent = 'Active Unit Types:';
1160
+ unitCountsContainer.appendChild(title);
1161
+
1162
+ statsContainer.appendChild(unitCountsContainer);
1163
+ }
1164
+ }
1165
+
1166
+ if (unitCountsContainer) {
1167
+ // Clear previous counts (except the title)
1168
+ while (unitCountsContainer.childNodes.length > 1) {
1169
+ unitCountsContainer.removeChild(unitCountsContainer.lastChild);
1170
+ }
1171
+
1172
+ // Get counts by unit type for each faction
1173
+ const blueUnitCounts = {};
1174
+ const redUnitCounts = {};
1175
+
1176
+ units.forEach(unit => {
1177
+ if (unit.health <= 0) return; // Skip dead units
1178
+
1179
+ if (unit.faction === 'blue') {
1180
+ blueUnitCounts[unit.type] = (blueUnitCounts[unit.type] || 0) + 1;
1181
+ } else {
1182
+ redUnitCounts[unit.type] = (redUnitCounts[unit.type] || 0) + 1;
1183
+ }
1184
+ });
1185
+
1186
+ // Get all unit types currently in use
1187
+ const activeTypes = Object.keys({...blueUnitCounts, ...redUnitCounts})
1188
+ .filter(type => unitTypes[type].unlockDay <= dayCounter)
1189
+ .sort();
1190
+
1191
+ // Create unit type count display
1192
+ activeTypes.forEach(type => {
1193
+ const blueCount = blueUnitCounts[type] || 0;
1194
+ const redCount = redUnitCounts[type] || 0;
1195
+
1196
+ const unitTypeEl = document.createElement('div');
1197
+ unitTypeEl.style.display = 'flex';
1198
+ unitTypeEl.style.alignItems = 'center';
1199
+ unitTypeEl.style.margin = '3px 0';
1200
+ unitTypeEl.style.width = '48%';
1201
+
1202
+ unitTypeEl.innerHTML = `
1203
+ <span style="font-size: 16px; margin-right: 5px;">${unitTypes[type].icon}</span>
1204
+ <span style="flex-grow: 1;">${getUnitName(type)}</span>
1205
+ <span style="color: #3498db; margin-right: 5px;">${blueCount}</span>
1206
+ <span style="color: #e74c3c;">${redCount}</span>
1207
+ `;
1208
+
1209
+ unitCountsContainer.appendChild(unitTypeEl);
1210
+ });
1211
+ }
1212
+ }
1213
+
1214
+ function updateBattleStatistics() {
1215
+ // Update UI statistics
1216
+ document.getElementById('blue-active').textContent = statistics.blueActive;
1217
+ document.getElementById('red-active').textContent = statistics.redActive;
1218
+ document.getElementById('blue-casualties').textContent = statistics.blueCasualties;
1219
+ document.getElementById('red-casualties').textContent = statistics.redCasualties;
1220
+ document.getElementById('blue-reinforcements').textContent = statistics.blueReinforcements;
1221
+ document.getElementById('red-reinforcements').textContent = statistics.redReinforcements;
1222
+ document.getElementById('active-battles').textContent = statistics.activeBattles;
1223
+
1224
+ // Update territory control visualization
1225
+ document.getElementById('blue-control').style.width = `${statistics.blueControl}%`;
1226
+ document.getElementById('red-control').style.width = `${statistics.redControl}%`;
1227
+
1228
+ // Update day counter or create it if it doesn't exist
1229
+ if (!document.getElementById('day-counter')) {
1230
+ const statsContainer = document.querySelector('.statistics-container');
1231
+ if (statsContainer) {
1232
+ const dayElement = document.createElement('div');
1233
+ dayElement.id = 'day-counter';
1234
+ dayElement.style.fontSize = '18px';
1235
+ dayElement.style.fontWeight = 'bold';
1236
+ dayElement.style.marginBottom = '10px';
1237
+ dayElement.style.textAlign = 'center';
1238
+ dayElement.style.color = '#FFD700'; // Gold color for day counter
1239
+ dayElement.textContent = `Day ${dayCounter}`;
1240
+ statsContainer.prepend(dayElement);
1241
+
1242
+ // Add hour indicator
1243
+ const hourElement = document.createElement('span');
1244
+ hourElement.id = 'hour-indicator';
1245
+ hourElement.style.fontSize = '14px';
1246
+ hourElement.style.marginLeft = '10px';
1247
+ hourElement.style.color = '#AAAAAA';
1248
+ hourElement.textContent = `(Hour ${hourCounter})`;
1249
+ dayElement.appendChild(hourElement);
1250
+ }
1251
+ } else {
1252
+ document.getElementById('day-counter').textContent = `Day ${dayCounter}`;
1253
+
1254
+ // Update hour indicator if it exists, or create it
1255
+ if (!document.getElementById('hour-indicator')) {
1256
+ const dayCounterElement = document.getElementById('day-counter');
1257
+ const hourElement = document.createElement('span');
1258
+ hourElement.id = 'hour-indicator';
1259
+ hourElement.style.fontSize = '14px';
1260
+ hourElement.style.marginLeft = '10px';
1261
+ hourElement.style.color = '#AAAAAA';
1262
+ hourElement.textContent = `(Hour ${hourCounter})`;
1263
+ dayCounterElement.appendChild(hourElement);
1264
+ } else {
1265
+ document.getElementById('hour-indicator').textContent = `(Hour ${hourCounter})`;
1266
+ }
1267
+ }
1268
+
1269
+ // Update unit type counts
1270
+ updateUnitTypeCounts();
1271
+
1272
+ // Randomize territory control slightly for visual effect
1273
+ let blueVariation = Math.random() * 5 - 2.5; // -2.5 to +2.5
1274
+ let newBlueControl = Math.min(Math.max(statistics.blueControl + blueVariation, 10), 90); // Keep between 10% and 90%
1275
+
1276
+ statistics.blueControl = newBlueControl;
1277
+ statistics.redControl = 100 - newBlueControl;
1278
+ }
1279
+
1280
+ function spawnReinforcements() {
1281
+ // Ensure minimum active forces for each side
1282
+ const minimumForceSize = 10 + Math.floor(dayCounter / 2); // Increases with time
1283
+
1284
+ // Calculate the total active units
1285
+ const totalActive = statistics.blueActive + statistics.redActive;
1286
+ const maxTotalUnits = 60 + (dayCounter * 5) + Math.floor(Math.random() * 20); // More variance in unit cap
1287
+
1288
+ // If we're under the maximum total, proceed with reinforcements
1289
+ if (totalActive < maxTotalUnits) {
1290
+ // Track reinforcements for notification
1291
+ let blueSpawned = 0;
1292
+ let redSpawned = 0;
1293
+
1294
+ // Always spawn at least one unit for each faction that's below minimum strength
1295
+ if (statistics.blueActive < minimumForceSize) {
1296
+ spawnNewUnit('blue');
1297
+ blueSpawned++;
1298
+ statistics.blueReinforcements++;
1299
+ }
1300
+
1301
+ if (statistics.redActive < minimumForceSize) {
1302
+ spawnNewUnit('red');
1303
+ redSpawned++;
1304
+ statistics.redReinforcements++;
1305
+ }
1306
+
1307
+ // Additional random reinforcements (favor the losing side)
1308
+ let spawnBlueSide = Math.random() < (statistics.redActive / (statistics.blueActive + statistics.redActive));
1309
+ let faction = spawnBlueSide ? 'blue' : 'red';
1310
+
1311
+ // Determine how many extra units to spawn (1-4, increasing with time)
1312
+ // Increased maximum from 3+1 to 5+1 for more variance
1313
+ const extraUnitCount = Math.floor(Math.random() * 5) + 1 + Math.floor(dayCounter / 10);
1314
+
1315
+ // Generate units for the chosen faction
1316
+ for (let i = 0; i < extraUnitCount; i++) {
1317
+ // 50% chance to switch to the other faction for balance
1318
+ if (i > 0 && Math.random() < 0.5) {
1319
+ faction = faction === 'blue' ? 'red' : 'blue';
1320
+ }
1321
+
1322
+ // 20% chance to use a specific unit type for variety
1323
+ if (Math.random() < 0.2) {
1324
+ // Get all available unit types
1325
+ const availableTypes = Object.entries(unitTypes)
1326
+ .filter(([type, props]) => props.unlockDay <= dayCounter)
1327
+ .map(([type]) => type);
1328
+
1329
+ // Pick a random type
1330
+ const randomType = availableTypes[Math.floor(Math.random() * availableTypes.length)];
1331
+ spawnNewUnit(faction, randomType);
1332
+ } else {
1333
+ spawnNewUnit(faction);
1334
+ }
1335
+
1336
+ // Count reinforcements
1337
+ if (faction === 'blue') {
1338
+ blueSpawned++;
1339
+ statistics.blueReinforcements++;
1340
+ } else {
1341
+ redSpawned++;
1342
+ statistics.redReinforcements++;
1343
+ }
1344
+ }
1345
+
1346
+ // Display notification if significant reinforcements arrive
1347
+ if (blueSpawned >= 3) {
1348
+ displayEventNotification(
1349
+ 'Blue Reinforcements',
1350
+ `${blueSpawned} Blue units have arrived as reinforcements`
1351
+ );
1352
+ }
1353
+
1354
+ if (redSpawned >= 3) {
1355
+ displayEventNotification(
1356
+ 'Red Reinforcements',
1357
+ `${redSpawned} Red units have arrived as reinforcements`
1358
+ );
1359
+ }
1360
+ }
1361
+
1362
+ // Randomly vary some statistics to make the display more dynamic even if not much is happening
1363
+ // This creates the impression of ongoing battle even if units are repositioning
1364
+
1365
+ // Randomize territory control slightly for visual effect
1366
+ const controlVariation = Math.random() * 4 - 2; // -2 to +2 percent
1367
+ statistics.blueControl = Math.min(Math.max(statistics.blueControl + controlVariation, 15), 85);
1368
+ statistics.redControl = 100 - statistics.blueControl;
1369
+
1370
+ // Vary active battles count slightly
1371
+ const battleDelta = Math.random() < 0.3 ? (Math.random() < 0.5 ? 1 : -1) : 0;
1372
+ statistics.activeBattles = Math.max(1, statistics.activeBattles + battleDelta);
1373
+ }
1374
+
1375
+ function spawnNewUnit(faction) {
1376
+ const type = getRandomUnitType();
1377
+ const unitId = statistics.nextUnitId++;
1378
+
1379
+ // Calculate spawn location - at the edges of the map with some randomness
1380
+ let lat, lng;
1381
+
1382
+ if (faction === 'blue') {
1383
+ // Blue forces spawn from the west
1384
+ lat = 40.7 + (Math.random() * 0.1 - 0.05);
1385
+ lng = -74.05 - (Math.random() * 0.02); // West of center
1386
+ statistics.blueActive++;
1387
+ statistics.blueReinforcements++;
1388
+ } else {
1389
+ // Red forces spawn from the east
1390
+ lat = 40.7 + (Math.random() * 0.1 - 0.05);
1391
+ lng = -73.95 + (Math.random() * 0.02); // East of center
1392
+ statistics.redActive++;
1393
+ statistics.redReinforcements++;
1394
+ }
1395
+
1396
+ // Create the new unit
1397
+ const newUnit = {
1398
+ id: `${faction}-${unitId}`,
1399
+ name: `${faction.charAt(0).toUpperCase() + faction.slice(1)} Force ${getUnitName(type)} ${unitId}`,
1400
+ type: type,
1401
+ faction: faction,
1402
+ strength: Math.floor(Math.random() * 50) + 50, // 50-99
1403
+ health: 100,
1404
+ status: 'Reinforcement',
1405
+ lat: lat,
1406
+ lng: lng,
1407
+ targetLat: null,
1408
+ targetLng: null,
1409
+ marker: null,
1410
+ icon: unitTypes[type].icon,
1411
+ speed: unitTypes[type].speed,
1412
+ attackPower: unitTypes[type].attackPower,
1413
+ range: unitTypes[type].range,
1414
+ inBattle: false,
1415
+ movingToTarget: false,
1416
+ kills: 0,
1417
+ lastMoveTimestamp: Date.now()
1418
+ };
1419
+
1420
+ units.push(newUnit);
1421
+
1422
+ // Create marker for the new unit
1423
+ const markerSize = newUnit.strength / 15 + 20;
1424
+
1425
+ const markerHtml = `
1426
+ <div class="unit-marker ${newUnit.faction}-unit" style="width: ${markerSize}px; height: ${markerSize}px; line-height: ${markerSize}px; font-size: ${markerSize/2}px;">
1427
+ ${newUnit.icon}
1428
+ </div>
1429
+ `;
1430
+
1431
+ const customIcon = L.divIcon({
1432
+ html: markerHtml,
1433
+ className: '',
1434
+ iconSize: [markerSize, markerSize],
1435
+ iconAnchor: [markerSize/2, markerSize/2]
1436
+ });
1437
+
1438
+ newUnit.marker = L.marker([newUnit.lat, newUnit.lng], { icon: customIcon })
1439
+ .addTo(map)
1440
+ .on('click', function() {
1441
+ selectUnit(newUnit);
1442
+ });
1443
+
1444
+ // Immediately assign target to move toward
1445
+ assignTargetToUnit(newUnit);
1446
+ }
1447
+
1448
+ function assignTargetToUnit(unit) {
1449
+ // Find a strategic position to move to
1450
+ const enemyFaction = unit.faction === 'blue' ? 'red' : 'blue';
1451
+ const enemies = units.filter(u => u.faction === enemyFaction && u.health > 0);
1452
+
1453
+ if (enemies.length > 0) {
1454
+ // Choose random enemy as target
1455
+ const target = enemies[Math.floor(Math.random() * enemies.length)];
1456
+ unit.targetLat = target.lat;
1457
+ unit.targetLng = target.lng;
1458
+ unit.movingToTarget = true;
1459
+ unit.status = 'Advancing';
1460
+ } else {
1461
+ // Move toward center of map if no enemies
1462
+ unit.targetLat = 40.7;
1463
+ unit.targetLng = -74.0;
1464
+ unit.movingToTarget = true;
1465
+ unit.status = 'Advancing';
1466
+ }
1467
+ }
1468
+
1469
  function updateTime() {
1470
  hourCounter++;
1471
  if (hourCounter >= 24) {
1472
  hourCounter = 0;
1473
  dayCounter++;
1474
+
1475
+ // Check for newly unlocked units on day change
1476
+ const newlyUnlockedTypes = Object.entries(unitTypes)
1477
+ .filter(([type, props]) => props.unlockDay === dayCounter)
1478
+ .map(([type]) => type);
1479
+
1480
+ if (newlyUnlockedTypes.length > 0) {
1481
+ displayTechUnlockNotification(newlyUnlockedTypes);
1482
+ }
1483
+
1484
+ // Add additional random reinforcements on new day
1485
+ const blueSurvivors = units.filter(u => u.faction === 'blue' && u.health > 0).length;
1486
+ const redSurvivors = units.filter(u => u.faction === 'red' && u.health > 0).length;
1487
+
1488
+ // Add 1-3 reinforcements for each faction at day change
1489
+ const blueReinforcements = 1 + Math.floor(Math.random() * 3);
1490
+ const redReinforcements = 1 + Math.floor(Math.random() * 3);
1491
+
1492
+ for (let i = 0; i < blueReinforcements; i++) {
1493
+ spawnNewUnit('blue');
1494
+ }
1495
+
1496
+ for (let i = 0; i < redReinforcements; i++) {
1497
+ spawnNewUnit('red');
1498
+ }
1499
+
1500
+ // Display day change notification
1501
+ displayEventNotification(
1502
+ `Day ${dayCounter} Begins`,
1503
+ 'Forces are repositioning and receiving reinforcements'
1504
+ );
1505
  }
1506
 
1507
  // Format hours with leading zero
1508
  const formattedHours = hourCounter.toString().padStart(2, '0');
1509
+
1510
+ // Make sure day-counter element exists before updating
1511
+ const dayCounter_el = document.getElementById('day-counter');
1512
+ if (dayCounter_el) {
1513
+ dayCounter_el.textContent = `Day ${dayCounter} - ${formattedHours}:00 hours`;
1514
+ }
1515
+ }
1516
+
1517
+ // Display tech unlock notification
1518
+ function displayTechUnlockNotification(unlockTypes) {
1519
+ // Create notification element
1520
+ const notification = document.createElement('div');
1521
+ notification.className = 'tech-unlock-notification';
1522
+ notification.style.position = 'absolute';
1523
+ notification.style.top = '50%';
1524
+ notification.style.left = '50%';
1525
+ notification.style.transform = 'translate(-50%, -50%)';
1526
+ notification.style.backgroundColor = 'rgba(0, 0, 0, 0.85)';
1527
+ notification.style.color = 'white';
1528
+ notification.style.padding = '20px';
1529
+ notification.style.borderRadius = '10px';
1530
+ notification.style.zIndex = '2000';
1531
+ notification.style.minWidth = '300px';
1532
+ notification.style.textAlign = 'center';
1533
+ notification.style.boxShadow = '0 0 30px gold';
1534
+ notification.style.border = '2px solid gold';
1535
+
1536
+ // Create content
1537
+ let innerHTML = `
1538
+ <h2 style="margin: 0 0 15px 0; color: gold;">New Technology Unlocked!</h2>
1539
+ <div style="display: flex; flex-direction: column; gap: 10px;">
1540
+ `;
1541
+
1542
+ unlockTypes.forEach(type => {
1543
+ innerHTML += `
1544
+ <div style="display: flex; align-items: center; justify-content: center; gap: 10px;">
1545
+ <span style="font-size: 24px;">${unitTypes[type].icon}</span>
1546
+ <span style="font-size: 18px;">${getUnitName(type)}</span>
1547
+ </div>
1548
+ `;
1549
+ });
1550
+
1551
+ innerHTML += '</div>';
1552
+ notification.innerHTML = innerHTML;
1553
+
1554
+ document.body.appendChild(notification);
1555
+
1556
+ // Add animation CSS if not already defined
1557
+ if (!document.getElementById('notification-animations')) {
1558
+ const styleEl = document.createElement('style');
1559
+ styleEl.id = 'notification-animations';
1560
+ styleEl.textContent = `
1561
+ @keyframes fadeIn {
1562
+ from { opacity: 0; transform: translate(-50%, -60%); }
1563
+ to { opacity: 1; transform: translate(-50%, -50%); }
1564
+ }
1565
+
1566
+ @keyframes fadeOut {
1567
+ from { opacity: 1; transform: translate(-50%, -50%); }
1568
+ to { opacity: 0; transform: translate(-50%, -40%); }
1569
+ }
1570
+
1571
+ .tech-unlock-notification {
1572
+ animation: fadeIn 0.8s forwards;
1573
+ }
1574
+
1575
+ .tech-unlock-notification.fadeout {
1576
+ animation: fadeOut 0.8s forwards;
1577
+ }
1578
+ `;
1579
+ document.head.appendChild(styleEl);
1580
+ }
1581
+
1582
+ // Remove after delay
1583
+ setTimeout(() => {
1584
+ notification.classList.add('fadeout');
1585
+
1586
+ setTimeout(() => {
1587
+ if (notification.parentNode) {
1588
+ notification.parentNode.removeChild(notification);
1589
+ }
1590
+ }, 800);
1591
+ }, 5000);
1592
  }
1593
 
1594
  function togglePause() {
 
1597
  pauseButton.textContent = isPaused ? 'Resume' : 'Pause';
1598
  }
1599
 
1600
+ function assignRandomTargets(chance = 0.3) {
1601
  const activeFactions = ['blue', 'red'];
1602
 
1603
  // Randomly select units to assign new targets
1604
  units.forEach(unit => {
1605
+ // Only assign new targets to units that aren't in battle and at random (default 30% chance)
1606
+ if (!unit.inBattle && Math.random() < chance) {
1607
  // Find potential enemy targets
1608
  const enemyFaction = unit.faction === 'blue' ? 'red' : 'blue';
1609
  const enemies = units.filter(u => u.faction === enemyFaction && u.health > 0);
1610
 
1611
  if (enemies.length > 0) {
1612
+ // Choose random enemy as target - prefer weaker enemies occasionally
1613
+ let target;
1614
+ if (Math.random() < 0.2) {
1615
+ // Sort by health ascending and pick from the weakest third
1616
+ const sortedEnemies = [...enemies].sort((a, b) => a.health - b.health);
1617
+ const weakEnemyIndex = Math.floor(Math.random() * Math.ceil(sortedEnemies.length / 3));
1618
+ target = sortedEnemies[weakEnemyIndex];
1619
+ } else {
1620
+ // Random target
1621
+ target = enemies[Math.floor(Math.random() * enemies.length)];
1622
+ }
1623
+
1624
  unit.targetLat = target.lat;
1625
  unit.targetLng = target.lng;
1626
  unit.movingToTarget = true;
1627
+
1628
+ // Occasionally give units different movement statuses for variety
1629
+ const statuses = ['Advancing', 'Flanking', 'Pursuing', 'Engaging'];
1630
+ unit.status = statuses[Math.floor(Math.random() * statuses.length)];
1631
  }
1632
  }
1633
  });
1634
+
1635
+ // Occasionally upgrade some units
1636
+ if (Math.random() < 0.1) {
1637
+ upgradeRandomUnit();
1638
+ }
1639
+ }
1640
+
1641
+ function upgradeRandomUnit() {
1642
+ // Find a unit that's active and has been in battles
1643
+ const eligibleUnits = units.filter(u => u.health > 50 && u.kills > 0);
1644
+
1645
+ if (eligibleUnits.length > 0) {
1646
+ const unit = eligibleUnits[Math.floor(Math.random() * eligibleUnits.length)];
1647
+
1648
+ // Boost its stats
1649
+ unit.attackPower = Math.min(100, unit.attackPower + 5);
1650
+ unit.range = Math.min(0.05, unit.range + 0.002);
1651
+ unit.speed = Math.min(0.005, unit.speed * 1.1);
1652
+
1653
+ // Visual upgrade - make it slightly larger and add a star if it's reached elite status
1654
+ if (unit.marker) {
1655
+ const markerSize = unit.strength / 15 + 20 + (unit.kills * 0.5);
1656
+
1657
+ // Add a visual indicator for veteran/elite units
1658
+ let eliteIndicator = '';
1659
+ if (unit.kills >= 10) {
1660
+ eliteIndicator = '⭐';
1661
+ } else if (unit.kills >= 5) {
1662
+ eliteIndicator = 'β˜…';
1663
+ }
1664
+
1665
+ const markerHtml = `
1666
+ <div class="unit-marker ${unit.faction}-unit" style="width: ${markerSize}px; height: ${markerSize}px; line-height: ${markerSize}px; font-size: ${markerSize/2}px;">
1667
+ ${unit.icon}${eliteIndicator}
1668
+ </div>
1669
+ `;
1670
+
1671
+ const customIcon = L.divIcon({
1672
+ html: markerHtml,
1673
+ className: '',
1674
+ iconSize: [markerSize, markerSize],
1675
+ iconAnchor: [markerSize/2, markerSize/2]
1676
+ });
1677
+
1678
+ unit.marker.setIcon(customIcon);
1679
+ }
1680
+ }
1681
  }
1682
 
1683
  function moveUnits() {
 
1722
  }
1723
 
1724
  function checkForBattles() {
1725
+ // Clear any existing battle animations that are outdated
1726
+ document.querySelectorAll('.battle-indicator').forEach(el => {
1727
+ // Only remove indicators that have been around for more than 3 seconds
1728
+ if (el.dataset.timestamp && (Date.now() - parseInt(el.dataset.timestamp)) > 3000) {
1729
+ el.remove();
1730
+ }
1731
+ });
1732
+
1733
+ // Reset active battles counter
1734
+ statistics.activeBattles = 0;
1735
+
1736
+ // Create a set to track unique battle pairs
1737
+ const battlePairs = new Set();
1738
 
1739
  // Check each unit against potential enemies
1740
  units.forEach(unit => {
 
1744
  const enemies = units.filter(u => u.faction === enemyFaction && u.health > 0);
1745
 
1746
  // Reset battle state
1747
+ const wasInBattle = unit.inBattle;
1748
  unit.inBattle = false;
1749
 
1750
  enemies.forEach(enemy => {
 
1759
  unit.inBattle = true;
1760
  enemy.inBattle = true;
1761
 
1762
+ // Create a unique identifier for this battle pair
1763
+ const battleId = [unit.id, enemy.id].sort().join('-');
1764
+
1765
+ // Only count unique battles
1766
+ if (!battlePairs.has(battleId)) {
1767
+ battlePairs.add(battleId);
1768
+ statistics.activeBattles++;
1769
+
1770
+ // Create battle animation at midpoint with slight randomness
1771
+ const battleLat = (unit.lat + enemy.lat) / 2 + (Math.random() * 0.002 - 0.001);
1772
+ const battleLng = (unit.lng + enemy.lng) / 2 + (Math.random() * 0.002 - 0.001);
1773
+
1774
+ // More frequent battle animations for increased visual impact
1775
+ // but avoid too many by only creating on every other check or for elite units
1776
+ if (Math.random() < 0.7 || unit.kills >= 3 || enemy.kills >= 3) {
1777
+ createBattleAnimation(battleLat, battleLng);
1778
+ }
1779
+ }
1780
+
1781
+ // More varied battle statuses
1782
+ const battleStatuses = ['Engaged', 'In Combat', 'Fighting', 'Attacking', 'Defending'];
1783
+ unit.status = battleStatuses[Math.floor(Math.random() * battleStatuses.length)];
1784
+ enemy.status = battleStatuses[Math.floor(Math.random() * battleStatuses.length)];
1785
+
1786
+ // More dynamic damage calculation
1787
+ // Base damage
1788
+ let unitBaseDamage = unit.attackPower * 0.15 * (0.5 + Math.random());
1789
+ let enemyBaseDamage = enemy.attackPower * 0.15 * (0.5 + Math.random());
1790
 
1791
+ // Modify damage based on unit kills/experience (veteran bonus)
1792
+ unitBaseDamage *= (1 + (unit.kills * 0.05));
1793
+ enemyBaseDamage *= (1 + (enemy.kills * 0.05));
1794
 
1795
+ // Health-based modifications (wounded units deal less damage)
1796
+ unitBaseDamage *= (0.5 + (unit.health / 200));
1797
+ enemyBaseDamage *= (0.5 + (enemy.health / 200));
1798
 
1799
+ // Final randomization and rounding
1800
+ const unitDamage = Math.round(unitBaseDamage * (0.8 + Math.random() * 0.4));
1801
+ const enemyDamage = Math.round(enemyBaseDamage * (0.8 + Math.random() * 0.4));
1802
 
1803
  // Apply damage
1804
  enemy.health = Math.max(0, enemy.health - unitDamage);
1805
  unit.health = Math.max(0, unit.health - enemyDamage);
1806
 
1807
+ // Update marker appearance based on health
1808
+ updateUnitMarkerAppearance(unit);
1809
+ updateUnitMarkerAppearance(enemy);
1810
+
1811
+ // Critical hits for more drama (rare but powerful)
1812
+ if (Math.random() < 0.05) {
1813
+ const criticalTarget = Math.random() < 0.5 ? unit : enemy;
1814
+ const criticalDamage = Math.round(criticalTarget.health * 0.3);
1815
+ criticalTarget.health = Math.max(0, criticalTarget.health - criticalDamage);
1816
+
1817
+ // Visual indication of critical hit
1818
+ if (criticalTarget.marker) {
1819
+ const point = map.latLngToLayerPoint([criticalTarget.lat, criticalTarget.lng]);
1820
+
1821
+ const criticalEl = document.createElement('div');
1822
+ criticalEl.style.position = 'absolute';
1823
+ criticalEl.style.left = point.x + 'px';
1824
+ criticalEl.style.top = point.y + 'px';
1825
+ criticalEl.style.fontSize = '16px';
1826
+ criticalEl.style.fontWeight = 'bold';
1827
+ criticalEl.style.color = '#ff0000';
1828
+ criticalEl.style.textShadow = '0 0 3px #ffffff';
1829
+ criticalEl.style.zIndex = '1000';
1830
+ criticalEl.style.transition = 'transform 1s, opacity 1s';
1831
+ criticalEl.style.opacity = '1';
1832
+ criticalEl.textContent = "CRITICAL!";
1833
+
1834
+ document.querySelector('#map').appendChild(criticalEl);
1835
+
1836
+ setTimeout(() => {
1837
+ criticalEl.style.transform = 'translateY(-20px) scale(1.5)';
1838
+ criticalEl.style.opacity = '0';
1839
+ }, 100);
1840
+
1841
+ setTimeout(() => {
1842
+ if (criticalEl.parentNode) {
1843
+ criticalEl.parentNode.removeChild(criticalEl);
1844
+ }
1845
+ }, 1100);
1846
+ }
1847
+ }
1848
+
1849
  // Check if unit defeated enemy
1850
  if (enemy.health <= 0 && unitDamage > 0) {
1851
  unit.kills++;
1852
  checkUnitDeath(enemy);
1853
+ // Victory effect
1854
+ if (unit.marker) {
1855
+ const point = map.latLngToLayerPoint([unit.lat, unit.lng]);
1856
+ showVictoryEffect(point.x, point.y, unit.faction);
1857
+ }
1858
  }
1859
 
1860
  // Check if enemy defeated unit
1861
  if (unit.health <= 0 && enemyDamage > 0) {
1862
  enemy.kills++;
1863
  checkUnitDeath(unit);
1864
+ // Victory effect
1865
+ if (enemy.marker) {
1866
+ const point = map.latLngToLayerPoint([enemy.lat, enemy.lng]);
1867
+ showVictoryEffect(point.x, point.y, enemy.faction);
1868
+ }
1869
  return; // Stop checking more enemies if this unit is dead
1870
  }
1871
  }
1872
  });
1873
+
1874
+ // If unit was in battle but no longer is, potentially assign new target
1875
+ if (wasInBattle && !unit.inBattle) {
1876
+ // Higher chance to pursue new targets for experienced units
1877
+ const reassignChance = 0.3 + (unit.kills * 0.05);
1878
+ if (Math.random() < reassignChance) {
1879
+ assignTargetToUnit(unit);
1880
+ }
1881
+ }
1882
+ });
1883
+ }
1884
+
1885
+ function showVictoryEffect(x, y, faction) {
1886
+ const victoryEl = document.createElement('div');
1887
+ victoryEl.style.position = 'absolute';
1888
+ victoryEl.style.left = x + 'px';
1889
+ victoryEl.style.top = y + 'px';
1890
+ victoryEl.style.fontSize = '14px';
1891
+ victoryEl.style.fontWeight = 'bold';
1892
+ victoryEl.style.color = faction === 'blue' ? '#3498db' : '#e74c3c';
1893
+ victoryEl.style.textShadow = '0 0 3px white';
1894
+ victoryEl.style.zIndex = '1000';
1895
+ victoryEl.style.transition = 'transform 1.5s, opacity 1.5s';
1896
+ victoryEl.style.opacity = '1';
1897
+ victoryEl.textContent = "Victory!";
1898
+
1899
+ document.querySelector('#map').appendChild(victoryEl);
1900
+
1901
+ setTimeout(() => {
1902
+ victoryEl.style.transform = 'translateY(-25px) scale(1.2)';
1903
+ victoryEl.style.opacity = '0';
1904
+ }, 100);
1905
+
1906
+ setTimeout(() => {
1907
+ if (victoryEl.parentNode) {
1908
+ victoryEl.parentNode.removeChild(victoryEl);
1909
+ }
1910
+ }, 1600);
1911
+ }
1912
+
1913
+ function updateUnitMarkerAppearance(unit) {
1914
+ if (!unit.marker || unit.health <= 0) return;
1915
+
1916
+ const markerSize = unit.strength / 15 + 20 + (unit.kills * 0.5);
1917
+
1918
+ // Visual indicator for health status
1919
+ let healthIndicator = '';
1920
+ let glowColor = '';
1921
+
1922
+ if (unit.health < 30) {
1923
+ healthIndicator = '🩸'; // Injured
1924
+ glowColor = 'rgba(255, 0, 0, 0.5)';
1925
+ } else if (unit.inBattle) {
1926
+ healthIndicator = 'βš”οΈ'; // In battle
1927
+ glowColor = 'rgba(255, 165, 0, 0.5)';
1928
+ }
1929
+
1930
+ // Add a visual indicator for veteran/elite units
1931
+ let eliteIndicator = '';
1932
+ if (unit.kills >= 10) {
1933
+ eliteIndicator = '⭐';
1934
+ } else if (unit.kills >= 5) {
1935
+ eliteIndicator = 'β˜…';
1936
+ }
1937
+
1938
+ const markerHtml = `
1939
+ <div class="unit-marker ${unit.faction}-unit"
1940
+ style="width: ${markerSize}px; height: ${markerSize}px;
1941
+ line-height: ${markerSize}px;
1942
+ font-size: ${markerSize/2}px;
1943
+ box-shadow: 0 0 8px ${glowColor};">
1944
+ ${unit.icon}${healthIndicator}${eliteIndicator}
1945
+ </div>
1946
+ `;
1947
+
1948
+ const customIcon = L.divIcon({
1949
+ html: markerHtml,
1950
+ className: '',
1951
+ iconSize: [markerSize, markerSize],
1952
+ iconAnchor: [markerSize/2, markerSize/2]
1953
  });
1954
+
1955
+ unit.marker.setIcon(customIcon);
1956
  }
1957
 
1958
  function createBattleAnimation(lat, lng) {
1959
  // Convert lat/lng to pixel coordinates
1960
  const point = map.latLngToLayerPoint([lat, lng]);
1961
 
1962
+ // Create multiple battle indicators for more intense visuals
1963
+ const indicatorCount = 1 + Math.floor(Math.random() * 3); // 1-3 indicators per battle
1964
+
1965
+ for (let i = 0; i < indicatorCount; i++) {
1966
+ // Add slight position variance for multiple indicators
1967
+ const offsetX = Math.random() * 30 - 15;
1968
+ const offsetY = Math.random() * 30 - 15;
1969
+
1970
+ // Choose a random battle animation type to add variety
1971
+ const animationTypes = ['flash', 'expand', 'pulse', 'burst'];
1972
+ const battleType = animationTypes[Math.floor(Math.random() * animationTypes.length)];
1973
+
1974
+ // Randomly vary the size and color of battle indicators
1975
+ const size = 15 + Math.floor(Math.random() * 20); // 15-35px
1976
+
1977
+ // More varied colors including faction colors
1978
+ const baseColors = ['#ffcc00', '#ff9900', '#ff3300', '#cc0000', '#3498db', '#e74c3c'];
1979
+ const color = baseColors[Math.floor(Math.random() * baseColors.length)];
1980
+
1981
+ // Create battle indicator element with staggered timing
1982
+ setTimeout(() => {
1983
+ const battleEl = document.createElement('div');
1984
+ battleEl.className = `battle-indicator battle-${battleType}`;
1985
+ battleEl.style.left = (point.x + offsetX) + 'px';
1986
+ battleEl.style.top = (point.y + offsetY) + 'px';
1987
+ battleEl.style.width = size + 'px';
1988
+ battleEl.style.height = size + 'px';
1989
+ battleEl.style.backgroundColor = color;
1990
+
1991
+ // Add to map container
1992
+ document.querySelector('#map').appendChild(battleEl);
1993
+
1994
+ // Remove element after animation completes
1995
+ setTimeout(() => {
1996
+ if (battleEl.parentNode) {
1997
+ battleEl.parentNode.removeChild(battleEl);
1998
+ }
1999
+ }, 2000 + Math.random() * 1000);
2000
+ }, i * 150); // Staggered start times
2001
+ }
2002
 
2003
+ // Add combat symbols with more variety and quantity
2004
+ const symbolCount = Math.floor(Math.random() * 4) + 1; // 1-4 symbols
2005
 
2006
+ for (let i = 0; i < symbolCount; i++) {
2007
+ setTimeout(() => {
2008
+ // Expanded symbol set with more variety
2009
+ const symbols = ['πŸ’₯', 'πŸ”₯', 'βš”οΈ', 'πŸ’£', '🧨', 'πŸ’€', '⚑', 'πŸ›‘οΈ', '🏹', 'πŸ—‘οΈ', 'πŸ”«'];
2010
+ const symbol = symbols[Math.floor(Math.random() * symbols.length)];
2011
+
2012
+ const symbolEl = document.createElement('div');
2013
+ symbolEl.style.position = 'absolute';
2014
+ symbolEl.style.left = (point.x + Math.random() * 40 - 20) + 'px';
2015
+ symbolEl.style.top = (point.y + Math.random() * 40 - 20) + 'px';
2016
+ symbolEl.style.fontSize = (12 + Math.random() * 16) + 'px';
2017
+ symbolEl.style.zIndex = '1000';
2018
+ symbolEl.style.transition = 'transform 1.5s, opacity 1.5s';
2019
+ symbolEl.style.transform = 'translateY(0) rotate(0deg)';
2020
+ symbolEl.style.opacity = '1';
2021
+ symbolEl.textContent = symbol;
2022
+
2023
+ document.querySelector('#map').appendChild(symbolEl);
2024
+
2025
+ // Random movement direction
2026
+ const dirX = Math.random() * 30 - 15;
2027
+ const dirY = -10 - Math.random() * 20; // Always move up somewhat
2028
+ const rotation = Math.random() * 360;
2029
+
2030
+ setTimeout(() => {
2031
+ symbolEl.style.transform = `translate(${dirX}px, ${dirY}px) rotate(${rotation}deg)`;
2032
+ symbolEl.style.opacity = '0';
2033
+ }, 100);
2034
+
2035
+ setTimeout(() => {
2036
+ if (symbolEl.parentNode) {
2037
+ symbolEl.parentNode.removeChild(symbolEl);
2038
+ }
2039
+ }, 1600);
2040
+ }, i * 200); // Staggered symbols
2041
+ }
2042
+
2043
+ // Occasionally add damage numbers for more battle feedback
2044
+ if (Math.random() < 0.4) {
2045
+ setTimeout(() => {
2046
+ const damageEl = document.createElement('div');
2047
+ damageEl.style.position = 'absolute';
2048
+ damageEl.style.left = (point.x + Math.random() * 20 - 10) + 'px';
2049
+ damageEl.style.top = (point.y + Math.random() * 20 - 10) + 'px';
2050
+ damageEl.style.fontSize = '14px';
2051
+ damageEl.style.fontWeight = 'bold';
2052
+ damageEl.style.color = 'red';
2053
+ damageEl.style.textShadow = '0 0 3px white';
2054
+ damageEl.style.zIndex = '1000';
2055
+ damageEl.style.transition = 'transform 1.5s, opacity 1.5s';
2056
+ damageEl.style.opacity = '1';
2057
+
2058
+ // Generate a random damage number
2059
+ const damage = Math.floor(Math.random() * 20) + 5;
2060
+ damageEl.textContent = `-${damage}`;
2061
+
2062
+ document.querySelector('#map').appendChild(damageEl);
2063
+
2064
+ setTimeout(() => {
2065
+ damageEl.style.transform = 'translateY(-20px)';
2066
+ damageEl.style.opacity = '0';
2067
+ }, 100);
2068
+
2069
+ setTimeout(() => {
2070
+ if (damageEl.parentNode) {
2071
+ damageEl.parentNode.removeChild(damageEl);
2072
+ }
2073
+ }, 1600);
2074
+ }, Math.random() * 500);
2075
+ }
2076
  }
2077
 
2078
  function checkUnitDeath(unit) {
 
2098
  unit.marker.setIcon(destroyedIcon);
2099
  }
2100
 
2101
+ // Update unit status
2102
  unit.status = 'Destroyed';
2103
  unit.movingToTarget = false;
2104
  unit.inBattle = false;
2105
+
2106
+ // Update statistics
2107
+ if (unit.faction === 'blue') {
2108
+ statistics.blueActive--;
2109
+ statistics.blueCasualties++;
2110
+
2111
+ // Adjust territory control slightly toward red
2112
+ statistics.redControl = Math.min(statistics.redControl + 2, 90);
2113
+ statistics.blueControl = 100 - statistics.redControl;
2114
+ } else {
2115
+ statistics.redActive--;
2116
+ statistics.redCasualties++;
2117
+
2118
+ // Adjust territory control slightly toward blue
2119
+ statistics.blueControl = Math.min(statistics.blueControl + 2, 90);
2120
+ statistics.redControl = 100 - statistics.blueControl;
2121
+ }
2122
+
2123
+ // After a delay, remove the unit marker from the map
2124
+ setTimeout(() => {
2125
+ if (unit.marker) {
2126
+ // Fade out effect (optional)
2127
+ const icon = unit.marker.options.icon;
2128
+ const html = icon.options.html.replace('opacity: 0.5', 'opacity: 0.2');
2129
+
2130
+ const fadedIcon = L.divIcon({
2131
+ html: html,
2132
+ className: icon.options.className,
2133
+ iconSize: icon.options.iconSize,
2134
+ iconAnchor: icon.options.iconAnchor
2135
+ });
2136
+
2137
+ unit.marker.setIcon(fadedIcon);
2138
+
2139
+ // After another longer delay, units will disappear completely
2140
+ // This creates a nice visual effect of "cleaning up" the battlefield
2141
+ // Left commented out to keep destroyed units visible on the map
2142
+ /*
2143
+ setTimeout(() => {
2144
+ map.removeLayer(unit.marker);
2145
+ unit.marker = null;
2146
+ }, 30000);
2147
+ */
2148
+ }
2149
+ }, 5000);
2150
  }
2151
  }
style.css CHANGED
@@ -73,15 +73,66 @@ main {
73
  border-radius: 5px;
74
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
75
  overflow-y: auto;
 
 
 
 
 
 
 
76
  }
77
 
78
- .info-panel h2 {
79
  margin-bottom: 1rem;
80
  padding-bottom: 0.5rem;
81
  border-bottom: 1px solid #ddd;
82
  color: #2c3e50;
83
  }
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  #unit-info {
86
  line-height: 1.6;
87
  }
@@ -196,19 +247,54 @@ footer {
196
  z-index: 1000 !important;
197
  }
198
 
199
- /* Battle animation */
200
  @keyframes battle-pulse {
201
  0% { transform: scale(1); opacity: 1; }
202
  50% { transform: scale(1.5); opacity: 0.7; }
203
  100% { transform: scale(2); opacity: 0; }
204
  }
205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  .battle-indicator {
207
  position: absolute;
208
- width: 20px;
209
- height: 20px;
210
- background-color: yellow;
211
  border-radius: 50%;
212
  pointer-events: none;
 
 
 
 
 
213
  animation: battle-pulse 1s infinite;
 
 
 
 
 
 
 
 
 
 
 
 
214
  }
 
73
  border-radius: 5px;
74
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
75
  overflow-y: auto;
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: 1.5rem;
79
+ }
80
+
81
+ .panel-section {
82
+ flex: 1;
83
  }
84
 
85
+ .panel-section h2 {
86
  margin-bottom: 1rem;
87
  padding-bottom: 0.5rem;
88
  border-bottom: 1px solid #ddd;
89
  color: #2c3e50;
90
  }
91
 
92
+ /* Battle statistics styles */
93
+ #battle-stats {
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: 0.8rem;
97
+ }
98
+
99
+ .stat-row {
100
+ display: flex;
101
+ justify-content: space-between;
102
+ padding: 0.3rem 0;
103
+ }
104
+
105
+ .stat-label {
106
+ font-weight: bold;
107
+ color: #555;
108
+ }
109
+
110
+ .stat-value {
111
+ font-family: monospace;
112
+ font-size: 1.1em;
113
+ }
114
+
115
+ .control-bar {
116
+ height: 15px;
117
+ width: 100%;
118
+ background-color: #eee;
119
+ border-radius: 7px;
120
+ overflow: hidden;
121
+ display: flex;
122
+ }
123
+
124
+ .blue-control {
125
+ height: 100%;
126
+ background-color: #3498db;
127
+ transition: width 0.5s ease;
128
+ }
129
+
130
+ .red-control {
131
+ height: 100%;
132
+ background-color: #e74c3c;
133
+ transition: width 0.5s ease;
134
+ }
135
+
136
  #unit-info {
137
  line-height: 1.6;
138
  }
 
247
  z-index: 1000 !important;
248
  }
249
 
250
+ /* Battle animations */
251
  @keyframes battle-pulse {
252
  0% { transform: scale(1); opacity: 1; }
253
  50% { transform: scale(1.5); opacity: 0.7; }
254
  100% { transform: scale(2); opacity: 0; }
255
  }
256
 
257
+ @keyframes battle-flash {
258
+ 0% { opacity: 1; transform: scale(1); }
259
+ 25% { opacity: 0.8; transform: scale(1.1); }
260
+ 50% { opacity: 1; transform: scale(1); }
261
+ 75% { opacity: 0.5; transform: scale(1.2); }
262
+ 100% { opacity: 0; transform: scale(1.5); }
263
+ }
264
+
265
+ @keyframes battle-expand {
266
+ 0% { transform: scale(0.5); opacity: 1; }
267
+ 100% { transform: scale(3); opacity: 0; }
268
+ }
269
+
270
+ @keyframes battle-burst {
271
+ 0% { transform: scale(0.2); opacity: 0; }
272
+ 20% { transform: scale(1.2); opacity: 1; }
273
+ 50% { transform: scale(1.5); opacity: 0.8; }
274
+ 80% { transform: scale(1.8); opacity: 0.4; }
275
+ 100% { transform: scale(2.5); opacity: 0; }
276
+ }
277
+
278
  .battle-indicator {
279
  position: absolute;
 
 
 
280
  border-radius: 50%;
281
  pointer-events: none;
282
+ box-shadow: 0 0 10px rgba(255, 200, 0, 0.8);
283
+ z-index: 800;
284
+ }
285
+
286
+ .battle-pulse {
287
  animation: battle-pulse 1s infinite;
288
+ }
289
+
290
+ .battle-flash {
291
+ animation: battle-flash 0.8s infinite;
292
+ }
293
+
294
+ .battle-expand {
295
+ animation: battle-expand 1.2s forwards;
296
+ }
297
+
298
+ .battle-burst {
299
+ animation: battle-burst 1s forwards;
300
  }