Spaces:
Running
Running
<html><head><base href="//cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/" /> | |
<title>World Map</title> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
@font-face { | |
font-family: 'Sitka Heading'; | |
src: local('Sitka Heading Semibold'); | |
font-weight: 600; | |
} | |
body { | |
margin: 0; | |
padding: 0; /* Remove padding */ | |
background: #001833; /* Changed from #1a1a1a to match ocean color */ | |
color: #fff; | |
font-family: 'Sitka Heading', serif; | |
font-weight: 600; | |
overflow: hidden; /* Prevent scrolling */ | |
} | |
.map-container { | |
width: 100vw; /* Full viewport width */ | |
height: 100vh; /* Full viewport height */ | |
margin: 0; | |
} | |
.world-map { | |
width: 100%; | |
height: 100%; | |
shape-rendering: crispEdges; | |
background-color: #001833; /* Dark blue for water */ | |
} | |
.country { | |
stroke: none; | |
stroke-width: 0; | |
vector-effect: non-scaling-stroke; | |
transition: fill 0.3s; | |
} | |
.country:hover { | |
filter: brightness(1.5); | |
cursor: pointer; | |
} | |
.tooltip { | |
position: absolute; | |
padding: 10px; | |
background: rgba(0,0,0,0.8); | |
color: #fff; | |
border-radius: 4px; | |
font-size: 14px; | |
font-family: 'Sitka Heading', serif; | |
font-weight: 600; | |
pointer-events: none; | |
opacity: 0; | |
transition: opacity 0.3s; | |
white-space: nowrap; /* Update tooltip styles to handle HTML content */ | |
z-index: 2100; /* Keep tooltips above everything */ | |
} | |
.controls { | |
display: flex; | |
gap: 10px; | |
} | |
/* Updated command button styles */ | |
.command-button { | |
width: 100%; | |
padding: 10px; | |
margin: 5px 0; | |
background: linear-gradient(45deg, #333, #3a3a3a); | |
border: none; | |
color: #fff; | |
border-radius: 4px; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
font-family: 'Sitka Heading', serif; | |
text-align: left; | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
background-size: 200% 200%; | |
background-position: 0% 0%; | |
} | |
/* Add unique gradients for each command button */ | |
.command-button:nth-child(1) { | |
background: linear-gradient(45deg, #333, #ff4444); | |
} | |
.command-button:nth-child(2) { | |
background: linear-gradient(45deg, #333, #44ff44); | |
} | |
.command-button:nth-child(3) { | |
background: linear-gradient(45deg, #333, #4444ff); | |
} | |
.command-button:nth-child(4) { | |
background: linear-gradient(45deg, #333, #ffff44); | |
} | |
.command-button:hover { | |
background-position: 100% 100%; | |
transform: translateX(5px); | |
} | |
.category-panel { | |
position: fixed; | |
top: 20px; | |
right: 20px; | |
width: 300px; | |
background: rgba(0,0,0,0.8); | |
padding: 20px; | |
border-radius: 4px; | |
z-index: 2000; /* Increase from 1000 to 2000 */ | |
} | |
.category-selector { | |
margin-bottom: 15px; | |
width: 100%; | |
padding: 8px; | |
background: #333; | |
color: #fff; | |
border: none; | |
border-radius: 4px; | |
font-family: 'Sitka Heading', serif; | |
font-weight: 600; | |
} | |
.country-list { | |
max-height: 500px; | |
overflow-y: auto; | |
color: #fff; | |
} | |
.country-item { | |
padding: 8px; | |
margin: 4px 0; | |
background: rgba(51, 51, 51, 0.5); | |
border-radius: 4px; | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
} | |
.country-name { | |
display: flex; | |
align-items: center; | |
margin-right: 10px; | |
} | |
.country-flag { | |
width: 30px; | |
height: 20px; | |
object-fit: cover; | |
border-radius: 2px; | |
} | |
.country-value { | |
color: #aaa; | |
} | |
/* Add styles for time counter */ | |
.time-counter { | |
position: fixed; | |
top: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
font-size: 24px; | |
color: #fff; | |
background: rgba(0,0,0,0.8); | |
padding: 10px 20px; | |
border-radius: 4px; | |
z-index: 2000; /* Increase from 1000 to 2000 */ | |
font-family: 'Sitka Heading', serif; | |
font-weight: 600; | |
} | |
/* Add to existing CSS */ | |
.relations-panel { | |
position: fixed; | |
top: 20px; | |
right: 20px; | |
width: 300px; | |
background: rgba(0,0,0,0.8); | |
padding: 20px; | |
border-radius: 4px; | |
z-index: 2000; /* Increase from 1000 to 2000 */ | |
display: none; | |
} | |
.relations-panel h2 img { | |
width: 20px; | |
height: auto; | |
display: inline-block; | |
margin-right: 10px; | |
vertical-align: middle; | |
border-radius: 2px; | |
} | |
.relations-list { | |
margin: 10px 0; | |
} | |
.relations-list h3 { | |
margin: 5px 0; | |
color: #fff; | |
font-size: 16px; | |
} | |
.relation-item { | |
padding: 8px; | |
margin: 4px 0; | |
background: rgba(51, 51, 51, 0.5); | |
border-radius: 4px; | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
color: #fff; | |
} | |
.relation-value { | |
display: flex; | |
align-items: center; | |
gap: 5px; | |
} | |
/* Add to existing CSS */ | |
.leader-quote { | |
position: absolute; | |
padding: 15px; | |
background: rgba(0,0,0,0.9); | |
color: #fff; | |
border-radius: 8px; | |
font-size: 14px; | |
pointer-events: none; | |
opacity: 0; | |
transition: opacity 0.3s; | |
max-width: 300px; | |
z-index: 2100; /* Keep quotes above everything */ | |
box-shadow: 0 4px 6px rgba(0,0,0,0.3); | |
border-left: 4px solid #4a90e2; | |
} | |
.leader-quote::before { | |
content: '"'; | |
font-size: 24px; | |
color: #4a90e2; | |
} | |
.leader-quote::after { | |
content: '"'; | |
font-size: 24px; | |
color: #4a90e2; | |
} | |
/* Add CSS for the new profile panel */ | |
.country-profile { | |
position: fixed; | |
top: 20px; | |
left: 20px; | |
width: 300px; | |
background: rgba(0,0,0,0.8); | |
padding: 20px; | |
border-radius: 4px; | |
z-index: 2000; /* Increase from 1000 to 2000 */ | |
display: none; | |
font-family: 'Sitka Heading', serif; | |
color: #fff; | |
} | |
.country-profile h2 { | |
margin: 0 0 15px 0; | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
margin-right: 30px; | |
} | |
/* Add styles for the close button */ | |
.close-profile { | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
background: none; | |
border: none; | |
color: #fff; | |
font-size: 20px; | |
cursor: pointer; | |
transition: transform 0.3s ease; | |
} | |
.close-profile:hover { | |
transform: scale(1.1); | |
color: #ff4444; | |
} | |
.profile-stat { | |
margin: 10px 0; | |
padding: 8px; | |
background: rgba(51, 51, 51, 0.5); | |
border-radius: 4px; | |
} | |
.profile-stat-label { | |
color: #aaa; | |
font-size: 0.9em; | |
} | |
.profile-stat-value { | |
font-size: 1.1em; | |
margin-top: 4px; | |
} | |
.allies-list { | |
margin-top: 10px; | |
} | |
.ally-item { | |
display: flex; | |
align-items: center; | |
gap: 8px; | |
margin: 5px 0; | |
} | |
/* Add styles for the force war interface */ | |
.force-war { | |
margin-top: 15px; | |
padding: 10px; | |
background: rgba(255, 0, 0, 0.2); | |
} | |
.force-war-button { | |
width: 100%; | |
padding: 8px; | |
background: #ff4444; | |
color: white; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
transition: background 0.3s; | |
} | |
.force-war-button:hover { | |
background: #ff6666; | |
} | |
.neighbor-list { | |
display: none; | |
margin-top: 10px; | |
} | |
.neighbor-option { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
padding: 8px; | |
margin: 4px 0; | |
background: rgba(51, 51, 51, 0.5); | |
border-radius: 4px; | |
cursor: pointer; | |
transition: background 0.3s; | |
} | |
.neighbor-option:hover { | |
background: rgba(71, 71, 71, 0.5); | |
} | |
.neighbor-relation { | |
display: flex; | |
align-items: center; | |
gap: 5px; | |
} | |
/* Add this to the CSS section */ | |
.battle-alert { | |
position: fixed; | |
bottom: 20px; | |
left: 20px; | |
background: rgba(0, 0, 0, 0.9); | |
color: white; | |
padding: 15px 20px; | |
border-radius: 8px; | |
font-family: 'Sitka Heading', serif; | |
z-index: 1500; /* Set lower than panels but higher than base elements */ | |
opacity: 0; | |
transform: translateY(100%); | |
transition: all 0.3s ease-in-out; | |
border-left: 4px solid #ff4444; | |
max-width: 400px; | |
box-shadow: 0 4px 8px rgba(0,0,0,0.2); | |
pointer-events: none; | |
} | |
.battle-alert.show { | |
opacity: 1; | |
transform: translateY(0); | |
} | |
/* Remove army dot styles */ | |
.army-dot { | |
display: none; /* Hide any existing dots */ | |
} | |
/* Add settings panel styles */ | |
.settings-panel { | |
position: fixed; | |
top: 20px; | |
left: 20px; | |
width: 300px; | |
background: rgba(0,0,0,0.8); | |
padding: 20px; | |
border-radius: 4px; | |
z-index: 2000; /* Increase from 1000 to 2000 */ | |
display: none; | |
font-family: 'Sitka Heading', serif; | |
color: #fff; | |
} | |
.settings-panel h2 { | |
margin: 0 0 15px 0; | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
} | |
.setting-item { | |
margin: 15px 0; | |
} | |
.setting-label { | |
display: block; | |
margin-bottom: 5px; | |
color: #aaa; | |
} | |
/* Slider styles */ | |
.setting-slider { | |
width: 100%; | |
height: 8px; | |
-webkit-appearance: none; | |
background: #444; | |
outline: none; | |
border-radius: 4px; | |
margin: 10px 0; | |
} | |
.setting-slider::-webkit-slider-thumb { | |
-webkit-appearance: none; | |
appearance: none; | |
width: 20px; | |
height: 20px; | |
background: #4a90e2; | |
cursor: pointer; | |
border-radius: 50%; | |
} | |
.setting-button { | |
width: 100%; | |
padding: 8px; | |
background: #4a90e2; | |
color: white; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
transition: background 0.3s; | |
margin: 5px 0; | |
} | |
.setting-button:hover { | |
background: #357abd; | |
} | |
/* Toggle switch styles */ | |
.toggle-switch { | |
position: relative; | |
display: inline-block; | |
width: 60px; | |
height: 34px; | |
} | |
.toggle-switch input { | |
opacity: 0; | |
width: 0; | |
height: 0; | |
} | |
.toggle-slider { | |
position: absolute; | |
cursor: pointer; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background-color: #444; | |
transition: .4s; | |
border-radius: 34px; | |
} | |
.toggle-slider:before { | |
position: absolute; | |
content: ""; | |
height: 26px; | |
width: 26px; | |
left: 4px; | |
bottom: 4px; | |
background-color: white; | |
transition: .4s; | |
border-radius: 50%; | |
} | |
input:checked + .toggle-slider { | |
background-color: #4a90e2; | |
} | |
input:checked + .toggle-slider:before { | |
transform: translateX(26px); | |
} | |
/* Add to existing CSS */ | |
.commands-panel { | |
position: fixed; | |
top: 20px; | |
left: 20px; | |
width: 300px; | |
background: rgba(0,0,0,0.8); | |
padding: 20px; | |
border-radius: 4px; | |
z-index: 2000; /* Increase from 1000 to 2000 */ | |
display: none; | |
font-family: 'Sitka Heading', serif; | |
color: #fff; | |
} | |
.command-button { | |
width: 100%; | |
padding: 10px; | |
margin: 5px 0; | |
background: linear-gradient(45deg, #333, #3a3a3a); | |
border: none; | |
color: #fff; | |
border-radius: 4px; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
font-family: 'Sitka Heading', serif; | |
text-align: left; | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
background-size: 200% 200%; | |
background-position: 0% 0%; | |
} | |
/* Add unique gradients for each command button */ | |
.command-button:nth-child(1) { | |
background: linear-gradient(45deg, #333, #ff4444); | |
} | |
.command-button:nth-child(2) { | |
background: linear-gradient(45deg, #333, #44ff44); | |
} | |
.command-button:nth-child(3) { | |
background: linear-gradient(45deg, #333, #4444ff); | |
} | |
.command-button:nth-child(4) { | |
background: linear-gradient(45deg, #333, #ffff44); | |
} | |
.command-button:hover { | |
background-position: 100% 100%; | |
transform: translateX(5px); | |
} | |
/* Add newspaper popup styles */ | |
.newspaper-popup { | |
position: fixed; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
background: #f4f0e5; /* Aged paper color */ | |
color: #000; | |
padding: 30px; | |
max-width: 600px; | |
width: 90%; | |
box-shadow: 0 0 20px rgba(0,0,0,0.5); | |
z-index: 3000; /* Keep newspaper above all other elements */ | |
font-family: 'Times New Roman', serif; | |
display: none; | |
border: 2px solid #8b4513; | |
overflow-y: auto; | |
max-height: 80vh; | |
} | |
.newspaper-popup.show { | |
display: block; | |
animation: fadeIn 0.3s ease-out; | |
} | |
.newspaper-header { | |
text-align: center; | |
border-bottom: 2px solid #000; | |
margin-bottom: 20px; | |
padding-bottom: 10px; | |
} | |
.newspaper-title { | |
font-size: 36px; | |
font-weight: bold; | |
margin: 0; | |
font-family: 'Old English Text MT', 'Times New Roman', serif; | |
} | |
.newspaper-date { | |
font-style: italic; | |
margin: 5px 0; | |
} | |
.newspaper-content { | |
column-count: 2; | |
column-gap: 20px; | |
text-align: justify; | |
line-height: 1.6; | |
} | |
.newspaper-image { | |
width: 100%; | |
margin: 10px 0; | |
filter: sepia(0.5); | |
} | |
.newspaper-close { | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
background: none; | |
border: none; | |
font-size: 24px; | |
cursor: pointer; | |
color: #000; | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; } | |
to { opacity: 1; } | |
} | |
/* Make battle alerts clickable */ | |
.battle-alert { | |
cursor: pointer; | |
pointer-events: auto; | |
} | |
.warning-text { | |
font-size: 11px; | |
color: #ff6666; | |
margin-left: 5px; | |
padding: 3px 8px; | |
background-color: rgba(255, 102, 0, 0.15); | |
border-radius: 12px; | |
display: inline-flex; | |
align-items: center; | |
gap: 4px; | |
animation: pulse 2s infinite; | |
text-shadow: 0 0 5px rgba(255,102,102,0.3); | |
} | |
.warning-text::before { | |
content: "⚠️"; | |
font-size: 12px; | |
} | |
@keyframes pulse { | |
0% { opacity: 0.6; } | |
50% { opacity: 1; } | |
100% { opacity: 0.6; } | |
} | |
</style> | |
<script src="https://d3js.org/d3.v7.min.js"></script> | |
<script src="https://d3js.org/topojson.v3.min.js"></script> | |
<script> | |
const alertSound = new Audio('/selectg2.wav'); | |
// Update the showBattleAlert function | |
function showBattleAlert(message, battleInfo = null) { | |
// Play alert sound | |
alertSound.play().catch(e => console.log('Audio play failed:', e)); | |
const existingAlerts = document.querySelectorAll('.battle-alert'); | |
// Move existing alerts up | |
existingAlerts.forEach((alert, index) => { | |
const height = alert.offsetHeight + 10; // Alert height + margin | |
alert.style.transform = `translateY(-${height * (existingAlerts.length - index)}px)`; | |
}); | |
const alert = document.createElement('div'); | |
alert.className = 'battle-alert'; | |
alert.textContent = message; | |
// Add click handler if battleInfo is provided | |
if (battleInfo) { | |
alert.onclick = () => createNewspaper(battleInfo); | |
} | |
document.body.appendChild(alert); | |
// Show alert | |
setTimeout(() => alert.classList.add('show'), 10); | |
// Remove alert after delay | |
setTimeout(() => { | |
alert.classList.remove('show'); | |
setTimeout(() => { | |
alert.remove(); | |
// Reposition remaining alerts | |
const remainingAlerts = document.querySelectorAll('.battle-alert'); | |
remainingAlerts.forEach((alert, index) => { | |
const height = alert.offsetHeight + 10; | |
alert.style.transform = `translateY(-${height * (remainingAlerts.length - index - 1)}px)`; | |
}); | |
}, 300); | |
}, 3000); | |
} | |
</script> | |
</head> | |
<body> | |
<!-- Add time counter div --> | |
<div class="time-counter">Year: 2020</div> | |
<div class="controls"> | |
<button class="map-toggle political-view" onclick="toggleMap()" title="Switch Map View"> | |
<i class="fas fa-globe"></i> | |
</button> | |
<button class="map-toggle" onclick="toggleCategoryPanel()" title="Show Categories"> | |
<i class="fas fa-list"></i> | |
</button> | |
<!-- Add to controls div after the other buttons --> | |
<button class="map-toggle" onclick="toggleCommands()" title="Commands"> | |
<i class="fas fa-terminal"></i> | |
</button> | |
</div> | |
<!-- Update the commands panel HTML --> | |
<div class="commands-panel"> | |
<h2>Commands</h2> | |
<button class="command-button" onclick="initiateCompleteCollapse()"> | |
<i class="fas fa-bomb"></i> Complete Collapse | |
<span class="warning-text">MIGHT NOT WORK ON FORMABLE EMPIRES</span> | |
</button> | |
<button class="command-button" onclick="resetMap()"> | |
<i class="fas fa-undo"></i> Reset Map | |
</button> | |
<button class="command-button" onclick="triggerRandomWar()"> | |
<i class="fas fa-dice"></i> Random War | |
</button> | |
<button class="command-button" onclick="toggleEmpireOptions()"> | |
<i class="fas fa-crown"></i> Form Empire | |
</button> | |
<div class="empire-options"> | |
<div class="empire-option" onclick="formEmpire('Arab League')">Arab League</div> | |
<div class="empire-option" onclick="formEmpire('Soviet Union')">Soviet Union</div> | |
<div class="empire-option" onclick="formEmpire('BRICS')">BRICS</div> | |
<div class="empire-option" onclick="formEmpire('Russian Empire')">Russian Empire</div> | |
<div class="empire-option" onclick="formEmpire('British Empire')">British Empire</div> | |
<div class="empire-option" onclick="formEmpire('French Empire')">French Empire</div> | |
<div class="empire-option" onclick="formEmpire('Qing Dynasty')">Qing Dynasty</div> | |
<div class="empire-option" onclick="formEmpire('African Union')">African Union</div> | |
</div> | |
</div> | |
<!-- Add settings panel --> | |
<div class="settings-panel"> | |
<h2>Settings</h2> | |
<div class="setting-item"> | |
<label class="setting-label">Independence Movements</label> | |
<label class="toggle-switch"> | |
<input type="checkbox" id="independence-toggle" checked onchange="updateSettings()"> | |
<span class="toggle-slider"></span> | |
</label> | |
</div> | |
<div class="setting-item"> | |
<label class="setting-label">Aggressiveness</label> | |
<input type="range" class="setting-slider" id="aggression-slider" | |
min="1" max="10" value="1" onchange="updateSettings()"> | |
</div> | |
<div class="setting-item"> | |
<button class="setting-button" onclick="regenerateColors()"> | |
Reset Colors | |
</button> | |
</div> | |
</div> | |
<div class="category-panel" style="display: none;"> | |
<select class="category-selector" onchange="updateCategoryList()"> | |
<option value="military">Military Strength</option> | |
<option value="economy">Economy (GDP)</option> | |
<option value="land">Land Mass</option> | |
</select> | |
<div class="country-list"></div> | |
</div> | |
<!-- Add relations panel --> | |
<div class="relations-panel"> | |
<h2 style="color: #fff; margin-top: 0;"> | |
Relations | |
</h2> | |
<div class="relations-list" id="top-relations"> | |
<h3>Top Relations</h3> | |
</div> | |
<div class="relations-list" id="bottom-relations"> | |
<h3>Bottom Relations</h3> | |
</div> | |
</div> | |
<!-- Add country profile panel --> | |
<div class="country-profile"> | |
<button class="close-profile" onclick="closeCountryProfile()"> | |
<i class="fas fa-times"></i> | |
</button> | |
<h2> | |
<img class="country-flag" alt=""> | |
<span class="country-name"></span> | |
</h2> | |
<div class="profile-stat"> | |
<div class="profile-stat-label">Military Strength</div> | |
<div class="profile-stat-value military-value"></div> | |
</div> | |
<div class="profile-stat"> | |
<div class="profile-stat-label">Economy (GDP)</div> | |
<div class="profile-stat-value economy-value"></div> | |
</div> | |
<div class="profile-stat"> | |
<div class="profile-stat-label">Top Allies</div> | |
<div class="allies-list"></div> | |
</div> | |
<div class="profile-stat"> | |
<div class="profile-stat-label">Current Wars</div> | |
<div class="profile-stat-value wars-value">None</div> | |
</div> | |
<div class="force-war"> | |
<button class="force-war-button" onclick="toggleNeighborList()"> | |
<i class="fas fa-crosshairs"></i> Force War | |
</button> | |
<div class="neighbor-list"></div> | |
</div> | |
</div> | |
<div class="map-container"> | |
<div id="tooltip" class="tooltip"></div> | |
</div> | |
<script> | |
const countryData = { | |
military: { | |
'United States': 250000, // Strongest military power | |
'China': 220000, // Second strongest | |
'Russia': 200000, // Third strongest | |
'India': 80000, // Strong regional power | |
'United Kingdom': 65000, // Major NATO power | |
'France': 62000, // Major NATO power | |
'Germany': 60000, // Major European power | |
'Japan': 50000, // Strong Asian power | |
'South Korea': 45000, // Strong Asian power | |
'Turkey': 40000, // Regional power | |
'Israel': 38000, // Regional power | |
'Italy': 35000, // European power | |
'Brazil': 35000, // South American leader | |
'Pakistan': 32000, // Regional power | |
'Iran': 32000, // Regional power | |
'Indonesia': 30000, // Add Indonesia's military power | |
'Australia': 28000, // Regional power | |
'Spain': 25000, // European power | |
'Poland': 25000, // European power | |
'Canada': 25000, // North American power | |
'Egypt': 25000, // African power | |
'Saudi Arabia': 25000, // Middle Eastern power | |
'Argentina': 22000, // South American power | |
'Vietnam': 22000, // Southeast Asian power | |
'Mexico': 20000, // North American power | |
'Sweden': 18000, // European power | |
'Thailand': 18000, // Southeast Asian power | |
'Colombia': 18000, // South American power | |
'South Africa': 18000, // African power | |
'Malaysia': 15000, // Southeast Asian power | |
'Venezuela': 15000, // South American power | |
'Peru': 15000, // South American power | |
'Chile': 15000, // South American power | |
'Ukraine': 15000, // Eastern European power | |
'Greece': 15000, // European power | |
'Romania': 15000, // European power | |
'Czech Republic': 12000, // European power | |
'Philippines': 12000, // Southeast Asian power | |
'Morocco': 12000, // North African power | |
'Algeria': 12000, // North African power | |
'Nigeria': 12000, // African power | |
'Ethiopia': 12000, // African power | |
'Ecuador': 10000, // South American power | |
'Bolivia': 10000, // South American power | |
'Paraguay': 8000, // South American power | |
'Uruguay': 8000, // South American power | |
'Guyana': 5000, // South American power | |
'Suriname': 5000, // South American power | |
}, | |
economy: { | |
'United States': 22990, | |
'China': 17730, | |
'Japan': 4872, | |
'Germany': 4271, | |
'United Kingdom': 3187, | |
'India': 3176, | |
'France': 2937, | |
'Italy': 2106, | |
'Canada': 1991, | |
'Russia': 1829, | |
'Brazil': 1608, | |
'South Korea': 1651, | |
'Australia': 1542, | |
'Spain': 1394, | |
'Indonesia': 1186, | |
}, | |
land: { | |
'Russia': 17098246, | |
'China': 9596961, | |
'United States': 9833517, | |
'Canada': 9984670, | |
'Brazil': 8515770, | |
'Australia': 7741220, | |
'India': 3287263, | |
'Argentina': 2780400, | |
'Kazakhstan': 2724900, | |
'Algeria': 2381741, | |
'Congo': 2344858, | |
'Saudi Arabia': 2149690, | |
'Mexico': 1964375, | |
'Indonesia': 1904569, | |
'Sudan': 1861484, | |
'Libya': 1759540, | |
'Iran': 1648195, | |
'Mongolia': 1564110, | |
'Peru': 1285216, | |
'Chad': 1284000, | |
} | |
}; | |
const empireNames = { | |
'Saudi Arabia': 'Arab League', | |
'Russia': { | |
'Soviet Union': 'USSR', | |
'Russian Empire': 'Imperial Russia' | |
}, | |
'United Kingdom': 'British Empire', | |
'France': 'French Empire', | |
'China': { | |
'Qing Dynasty': 'Great Qing', | |
'BRICS': 'BRICS Alliance' | |
}, | |
'South Africa': 'African Union' | |
}; | |
const conqueredTerritories = {}; // Track which countries have been conquered by whom | |
const occupationStartYears = {}; // Track when countries were conquered | |
const independenceMovements = {}; // Track independence progress | |
const INDEPENDENCE_STATES = { | |
NONE: 0, | |
PROTESTS: 1, | |
AUTONOMOUS: 2, | |
INDEPENDENT: 3 | |
}; | |
let independenceEnabled = true; | |
let aggressionLevel = 1; | |
function toggleSettings() { | |
const panel = document.querySelector('.settings-panel'); | |
panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; | |
} | |
function updateSettings() { | |
independenceEnabled = document.getElementById('independence-toggle').checked; | |
aggressionLevel = parseInt(document.getElementById('aggression-slider').value); | |
} | |
function regenerateColors() { | |
const countryCount = countries.features.length; | |
politicalColors = generateColors(countryCount); | |
updateMap(); | |
} | |
function areNeighbors(country1, country2) { | |
if (!countries || !countries.features) return false; | |
const feature1 = countries.features.find(f => f.properties.name === country1); | |
const feature2 = countries.features.find(f => f.properties.name === country2); | |
if (!feature1 || !feature2) return false; | |
// Calculate simplified geometries for the countries | |
const bounds1 = path.bounds(feature1); | |
const bounds2 = path.bounds(feature2); | |
// Check if bounding boxes overlap or are very close | |
const margin = 1; // Small margin to account for narrow connections | |
return !(bounds1[1][0] + margin < bounds2[0][0] || | |
bounds2[1][0] + margin < bounds1[0][0] || | |
bounds1[1][1] + margin < bounds2[0][1] || | |
bounds2[1][1] + margin < bounds1[0][1]); | |
} | |
// Update the triggerRandomBattles function to use aggressionLevel | |
function triggerRandomBattles() { | |
if (!countries || !countries.features) return; | |
const availableCountries = countries.features | |
.map(f => f.properties.name) | |
.filter(name => !conqueredTerritories[name]); | |
if (availableCountries.length < 2) return; | |
// Random chance for battle - increased by aggressionLevel | |
if (Math.random() < 0.1 * aggressionLevel) { // 10% * aggressionLevel chance each tick | |
const attacker = availableCountries[Math.floor(Math.random() * availableCountries.length)]; | |
const possibleDefenders = availableCountries.filter(defender => | |
defender !== attacker && | |
areNeighbors(attacker, defender) | |
); | |
if (possibleDefenders.length > 0) { | |
const defender = possibleDefenders[Math.floor(Math.random() * possibleDefenders.length)]; | |
const result = simulateBattle(attacker, defender); | |
if (result) { | |
updateMap(); | |
} | |
} | |
} | |
} | |
// Modify checkForIndependenceMovements to respect independenceEnabled setting | |
function checkForIndependenceMovements() { | |
if (!independenceEnabled) return; | |
Object.entries(conqueredTerritories).forEach(([territory, conqueror]) => { | |
const startYear = occupationStartYears[territory] || currentYear; | |
const yearsOccupied = currentYear - startYear; | |
if (independenceMovements[territory] || yearsOccupied < 50) return; | |
if (Math.random() < 0.02) { | |
independenceMovements[territory] = { | |
state: INDEPENDENCE_STATES.PROTESTS, | |
progress: 0, | |
startYear: currentYear | |
}; | |
showBattleAlert(`🗽 Independence movements have begun in ${territory}!`); | |
} | |
}); | |
} | |
// Replace the showBattleAlert function with this version | |
function showBattleAlert(message, battleInfo = null) { | |
// Play alert sound | |
alertSound.play().catch(e => console.log('Audio play failed:', e)); | |
const existingAlerts = document.querySelectorAll('.battle-alert'); | |
// Move existing alerts up | |
existingAlerts.forEach((alert, index) => { | |
const height = alert.offsetHeight + 10; // Alert height + margin | |
alert.style.transform = `translateY(-${height * (existingAlerts.length - index)}px)`; | |
}); | |
const alert = document.createElement('div'); | |
alert.className = 'battle-alert'; | |
alert.textContent = message; | |
// Add click handler if battleInfo is provided | |
if (battleInfo) { | |
alert.onclick = () => createNewspaper(battleInfo); | |
} | |
document.body.appendChild(alert); | |
// Show alert | |
setTimeout(() => alert.classList.add('show'), 10); | |
// Remove alert after delay | |
setTimeout(() => { | |
alert.classList.remove('show'); | |
setTimeout(() => { | |
alert.remove(); | |
// Reposition remaining alerts | |
const remainingAlerts = document.querySelectorAll('.battle-alert'); | |
remainingAlerts.forEach((alert, index) => { | |
const height = alert.offsetHeight + 10; | |
alert.style.transform = `translateY(-${height * (remainingAlerts.length - index - 1)}px)`; | |
}); | |
}, 300); | |
}, 3000); | |
} | |
function simulateBattle(attacker, defender) { | |
// Get military powers | |
const attackerPower = getArmyPower(attacker) * (0.8 + Math.random() * 0.4); // Add 20% random factor | |
const defenderPower = getArmyPower(defender) * (0.8 + Math.random() * 0.4); | |
// Create battle info object | |
const battleInfo = { | |
attacker, | |
defender, | |
attackerPower, | |
defenderPower, | |
winner: attackerPower > defenderPower ? attacker : defender | |
}; | |
// Show battle alert regardless of outcome | |
showBattleAlert(`🗡️ Battle Alert!\n${attacker} vs ${defender}!\n\nAttacker Power: ${Math.floor(attackerPower).toLocaleString()}\nDefender Power: ${Math.floor(defenderPower).toLocaleString()}`, battleInfo); | |
// Determine winner (stronger nation wins) | |
const winner = attackerPower > defenderPower ? attacker : defender; | |
const loser = attackerPower > defenderPower ? defender : attacker; | |
// Show victory message after slight delay | |
setTimeout(() => { | |
showBattleAlert(`${winner} has conquered ${loser}!`, battleInfo); | |
}, 2500); | |
// Transfer any territories the loser had conquered | |
Object.keys(conqueredTerritories).forEach(territory => { | |
if (conqueredTerritories[territory] === loser) { | |
conqueredTerritories[territory] = winner; | |
} | |
}); | |
// Conquer the loser | |
conqueredTerritories[loser] = winner; | |
// Record the occupation start year | |
occupationStartYears[loser] = currentYear; | |
// Add the loser's army power to the winner's army | |
countryArmies[winner] = (countryArmies[winner] || 0) + (countryArmies[loser] || 0); | |
delete countryArmies[loser]; | |
// Transfer GDP to victor | |
if (countryData.economy[loser]) { | |
countryData.economy[winner] = | |
(countryData.economy[winner] || 0) + countryData.economy[loser]; | |
delete countryData.economy[loser]; | |
} | |
// Update the category list to reflect the changes | |
updateCategoryList(); | |
return { | |
winner, | |
loser | |
}; | |
} | |
// Add newspaper-related functions | |
function createNewspaper(battleInfo) { | |
// Generate date using currentYear instead of current date | |
const date = new Date(currentYear, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1) | |
.toLocaleDateString('en-US', { | |
weekday: 'long', | |
year: 'numeric', | |
month: 'long', | |
day: 'numeric' | |
}); | |
// Generate a more unique headline based on battle outcome and circumstances | |
const headline = generateUniqueBattleHeadline(battleInfo); | |
// Create newspaper HTML | |
const newspaperHTML = ` | |
<div class="newspaper-popup"> | |
<button class="newspaper-close" onclick="closeNewspaper()">×</button> | |
<div class="newspaper-header"> | |
<h1 class="newspaper-title">The Global Herald</h1> | |
<div class="newspaper-date">${date}</div> | |
<h2>${headline}</h2> | |
</div> | |
<div class="newspaper-content"> | |
${generateBattleDescription(battleInfo)} | |
</div> | |
</div> | |
`; | |
// Add newspaper to document | |
document.body.insertAdjacentHTML('beforeend', newspaperHTML); | |
// Show newspaper | |
setTimeout(() => { | |
document.querySelector('.newspaper-popup').classList.add('show'); | |
}, 10); | |
} | |
// Add new function to generate unique headlines | |
function generateUniqueBattleHeadline(battleInfo) { | |
const { attacker, defender, attackerPower, defenderPower, winner } = battleInfo; | |
const headlines = [ | |
`BREAKING: ${winner} Triumphs in Decisive Military Victory`, | |
`Global Shock as ${winner} Forces Overwhelm ${winner === attacker ? defender : attacker}`, | |
`${winner.toUpperCase()} CONQUERS: World Map Redrawn in Stunning Campaign`, | |
`Historic Power Shift: ${winner} Emerges Victorious in Major Conflict`, | |
`${winner} Forces Claim Total Victory in Revolutionary Campaign`, | |
`WORLD ORDER SHIFTS: ${winner} Achieves Unprecedented Military Success`, | |
`${winner} Expansion: New Superpower Emerges After Decisive Victory`, | |
`Military Experts Stunned by ${winner}'s Strategic Triumph`, | |
`${winner} Dominance: Global Community Reacts to New World Power`, | |
`Geopolitical Earthquake: ${winner} Reshapes International Boundaries` | |
]; | |
// Select random headline | |
const baseHeadline = headlines[Math.floor(Math.random() * headlines.length)]; | |
// If it was a particularly close battle (within 10% difference) | |
if (Math.abs(attackerPower - defenderPower) / Math.max(attackerPower, defenderPower) < 0.1) { | |
return `In Razor-Thin Victory: ${baseHeadline}`; | |
} | |
// If it was a massive power difference (more than 3x) | |
if (Math.max(attackerPower, defenderPower) / Math.min(attackerPower, defenderPower) > 3) { | |
return `In Overwhelming Display: ${baseHeadline}`; | |
} | |
return baseHeadline; | |
} | |
// New function to generate battle description | |
function generateBattleDescription(battleInfo) { | |
const { attacker, defender, attackerPower, defenderPower, winner } = battleInfo; | |
const loser = winner === attacker ? defender : attacker; | |
// Calculate power difference percentage | |
const powerDiff = Math.abs(attackerPower - defenderPower) / Math.min(attackerPower, defenderPower) * 100; | |
// Generate random battle details | |
const locations = [ | |
'along the border', | |
'in disputed territory', | |
'near the capital', | |
'in strategic locations', | |
'across multiple fronts', | |
'in key industrial regions', | |
'around major population centers', | |
'in contested provinces' | |
]; | |
const battleLocation = locations[Math.floor(Math.random() * locations.length)]; | |
// Generate first paragraph about the battle | |
let description = ` | |
<p>In a ${powerDiff > 50 ? 'decisive' : 'hard-fought'} military campaign ${battleLocation}, | |
forces from ${winner} have achieved a complete victory over ${loser}. Military analysts estimate | |
the engagement involved approximately ${Math.floor(attackerPower).toLocaleString()} troops from | |
${attacker} facing off against ${Math.floor(defenderPower).toLocaleString()} troops from ${defender}.</p> | |
`; | |
// Add second paragraph about the outcome | |
if (powerDiff > 50) { | |
description += ` | |
<p>The overwhelming superiority of ${winner}'s forces led to a swift and decisive victory, | |
with minimal resistance from ${loser}'s military. Experts attribute this success to | |
${winner}'s superior military technology and strategic planning.</p> | |
`; | |
} else if (powerDiff > 20) { | |
description += ` | |
<p>Despite significant resistance, ${winner}'s military prowess ultimately proved superior, | |
leading to a clear victory after several days of intense combat. The conflict has | |
significantly altered the balance of power in the region.</p> | |
`; | |
} else { | |
description += ` | |
<p>In what military observers are calling one of the most closely matched conflicts in recent | |
history, ${winner}'s forces eventually prevailed through superior tactics and determination. | |
The victory came at a significant cost to both sides.</p> | |
`; | |
} | |
// Add third paragraph about consequences | |
description += ` | |
<p>Following this historic victory, ${winner} has formally annexed ${loser}, | |
marking a significant shift in global geopolitics. International observers are closely | |
monitoring the situation as ${winner} begins the process of consolidating control over | |
its newly acquired territory. The long-term implications of this conquest remain to be seen, | |
but experts agree this marks a crucial moment in world history.</p> | |
`; | |
return description; | |
} | |
function closeNewspaper() { | |
const newspaper = document.querySelector('.newspaper-popup'); | |
if (newspaper) { | |
newspaper.classList.remove('show'); | |
setTimeout(() => newspaper.remove(), 300); | |
} | |
} | |
// Add independence movement checks | |
function checkForIndependenceMovements() { | |
Object.entries(conqueredTerritories).forEach(([territory, conqueror]) => { | |
const startYear = occupationStartYears[territory] || currentYear; | |
const yearsOccupied = currentYear - startYear; | |
// Don't check if movement already started or if less than 50 years | |
if (independenceMovements[territory] || yearsOccupied < 50) return; | |
// 2% chance each year after 50 years | |
if (Math.random() < 0.02) { | |
independenceMovements[territory] = { | |
state: INDEPENDENCE_STATES.PROTESTS, | |
progress: 0, | |
startYear: currentYear | |
}; | |
// Show alert about protests beginning | |
showBattleAlert(`🗽 Independence movements have begun in ${territory}!`); | |
} | |
}); | |
} | |
// Add progress tracking for independence movements | |
function updateIndependenceMovements() { | |
Object.entries(independenceMovements).forEach(([territory, movement]) => { | |
// Calculate years since movement started | |
const yearsSinceStart = currentYear - movement.startYear; | |
// Progress through states over 30 years | |
switch(movement.state) { | |
case INDEPENDENCE_STATES.PROTESTS: | |
movement.progress += Math.random() * 0.3; // Random progress | |
if (movement.progress >= 10 && yearsSinceStart >= 10) { | |
movement.state = INDEPENDENCE_STATES.AUTONOMOUS; | |
showBattleAlert(`🏛️ ${territory} has gained autonomous status!`); | |
} | |
break; | |
case INDEPENDENCE_STATES.AUTONOMOUS: | |
movement.progress += Math.random() * 0.2; | |
if (movement.progress >= 20 && yearsSinceStart >= 30) { | |
// Grant independence | |
delete conqueredTerritories[territory]; | |
delete independenceMovements[territory]; | |
// Restore original military power | |
countryArmies[territory] = countryData.military[territory] || 5000; | |
showBattleAlert(`🎊 ${territory} has declared independence!`); | |
} | |
break; | |
} | |
}); | |
} | |
// Modify the interval timer to include battle simulation | |
// Add after all other code: | |
setInterval(() => { | |
updateYear(); | |
triggerRandomBattles(); | |
checkForIndependenceMovements(); | |
updateIndependenceMovements(); | |
}, 1000); | |
// Modify getSortedCountries to exclude conquered nations: | |
function getSortedCountries(category) { | |
const data = countryData[category]; | |
return Object.entries(data) | |
.filter(([country]) => !conqueredTerritories[country]) // Exclude conquered countries | |
.sort((a, b) => b[1] - a[1]) | |
.map(([country, value]) => ({ | |
country, | |
value: formatValue(value, category) | |
})); | |
} | |
const width = window.innerWidth; // Use window width | |
const height = window.innerHeight; // Use window height | |
let countries = null; // Move the countries variable declaration to the top level | |
const svg = d3.select(".map-container") | |
.append("svg") | |
.attr("viewBox", `0 0 ${width} ${height}`) | |
.attr("class", "world-map"); | |
const projection = d3.geoMercator() | |
.scale(Math.min(width / 2, height / 2)) // Scale based on viewport size | |
.translate([width / 2, height / 1.5]); | |
const path = d3.geoPath() | |
.projection(projection); | |
const tooltip = d3.select("#tooltip"); | |
// Add time counter variables | |
let currentYear = 2020; | |
const timeCounter = document.querySelector('.time-counter'); | |
// Update year function | |
function updateYear() { | |
currentYear++; | |
timeCounter.textContent = `Year: ${currentYear}`; | |
} | |
// Remove alliance-related constants | |
const allianceColors = { | |
'NEUTRAL': '#808080' // Keep only neutral | |
}; | |
// Remove alliance application system | |
const allianceApplications = {}; // Empty object | |
const countryAlliances = {}; // Empty object | |
// Add relation data (example values from -1 to 1, where -1 is hostile, 0 is neutral, 1 is friendly) | |
const countryRelations = {}; | |
// Update South American armies in countryArmies | |
const countryArmies = { | |
// Major Powers | |
'United States': 250000, | |
'China': 220000, | |
'Russia': 200000, | |
'India': 80000, | |
'United Kingdom': 65000, | |
'France': 62000, | |
'Germany': 60000, | |
'Japan': 50000, | |
// Medium Powers | |
'South Korea': 45000, | |
'Turkey': 40000, | |
'Israel': 38000, | |
'Italy': 35000, | |
'Brazil': 35000, | |
'Pakistan': 32000, | |
'Iran': 32000, | |
'Indonesia': 30000, | |
'Australia': 28000, | |
'Spain': 25000, | |
'Poland': 25000, | |
'Canada': 25000, | |
'Egypt': 25000, | |
'Saudi Arabia': 25000, | |
// Regional Powers | |
'Argentina': 22000, | |
'Vietnam': 22000, | |
'Mexico': 20000, | |
'Sweden': 18000, | |
'Thailand': 18000, | |
'Colombia': 18000, | |
'South Africa': 18000, | |
'Malaysia': 15000, | |
'Venezuela': 15000, | |
'Peru': 15000, | |
'Chile': 15000, | |
'Ukraine': 15000, | |
'Greece': 15000, | |
'Romania': 15000, | |
// Smaller Military Forces | |
'Czech Republic': 12000, | |
'Philippines': 12000, | |
'Morocco': 12000, | |
'Algeria': 12000, | |
'Nigeria': 12000, | |
'Ethiopia': 12000, | |
'Ecuador': 10000, | |
'Bolivia': 10000, | |
'Paraguay': 8000, | |
'Uruguay': 8000, | |
'Guyana': 5000, | |
'Suriname': 5000, | |
}; | |
// Update getArmyPower function to return a smaller default value | |
function getArmyPower(countryName) { | |
return countryArmies[countryName] || 5000; // Default reduced to 5k for unlisted countries | |
} | |
// Update the d3.json callback to initialize relations after loading: | |
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json") | |
.then(function(world) { | |
countries = topojson.feature(world, world.objects.countries); | |
const countryCount = countries.features.length; | |
politicalColors = generateColors(countryCount); | |
// Initialize relations here after countries is loaded | |
countries.features.forEach(country1 => { | |
countryRelations[country1.properties.name] = {}; | |
countries.features.forEach(country2 => { | |
if (country1.properties.name !== country2.properties.name) { | |
countryRelations[country1.properties.name][country2.properties.name] = | |
Math.random() * 2 - 1; // Random value between -1 and 1 | |
} | |
}); | |
}); | |
updateMap(); | |
}); | |
// Add relations color scale | |
const relationsColorScale = d3.scaleLinear() | |
.domain([-1, 0, 1]) | |
.range(["#ff4444", "#808080", "#44ff44"]); | |
// Add selected country tracking | |
let selectedCountry = null; | |
function checkForAllianceApplications() { | |
if (!countries) return; // Add this guard clause | |
countries.features.forEach(country => { | |
const name = country.properties.name; | |
if (countryAlliances[name] || allianceApplications[name]) return; | |
// Random chance to apply (adjust probability as needed) | |
if (Math.random() < 0.01) { // 1% chance each second | |
allianceApplications[name] = { | |
alliance: 'SCO', // Only SCO remains as an option | |
progress: 0, | |
startYear: currentYear | |
}; | |
} | |
}); | |
} | |
function processAllianceApplications() { | |
Object.entries(allianceApplications).forEach(([country, application]) => { | |
application.progress += Math.random() * 10; | |
// If application process complete (progress reaches 100) | |
if (application.progress >= 100) { | |
countryAlliances[country] = application.alliance; | |
delete allianceApplications[country]; | |
updateMap(); | |
} | |
}); | |
} | |
// Add this function after the updateCountryProfile function: | |
function closeCountryProfile() { | |
const profile = document.querySelector('.country-profile'); | |
profile.style.display = 'none'; | |
// Also hide the neighbor list if it's open | |
const neighborList = document.querySelector('.neighbor-list'); | |
neighborList.style.display = 'none'; | |
} | |
// Add this function after the processAllianceApplications function: | |
function strengthenAllianceRelations() { | |
// For each alliance | |
Object.entries(countryAlliances).forEach(([country1, alliance1]) => { | |
// Find other countries in same alliance | |
Object.entries(countryAlliances).forEach(([country2, alliance2]) => { | |
if (country1 !== country2 && alliance1 === alliance2) { | |
// Gradually improve relations between alliance members | |
if (countryRelations[country1] && countryRelations[country1][country2]) { | |
let currentRelation = countryRelations[country1][country2]; | |
// Move relation closer to 1.0 by a small random amount | |
countryRelations[country1][country2] = Math.min(1.0, | |
currentRelation + (Math.random() * 0.05)); | |
// Mirror the relation for the other country | |
countryRelations[country2][country1] = countryRelations[country1][country2]; | |
} | |
} | |
}); | |
}); | |
} | |
function generateColors(numColors) { | |
const colors = []; | |
const goldenRatio = 0.618033988749895; | |
let hue = Math.random(); | |
for (let i = 0; i < numColors; i++) { | |
hue = (hue + goldenRatio) % 1; | |
const saturation = 0.5 + Math.random() * 0.2; | |
const lightness = 0.4 + Math.random() * 0.2; | |
colors.push(`hsl(${hue * 360}, ${saturation * 100}%, ${lightness * 100}%)`); | |
} | |
return colors; | |
} | |
let isAllianceView = false; | |
let currentView = 'political'; // can be 'political' or 'relations' | |
let politicalColors; | |
function getRelationEmoji(relationValue) { | |
if (relationValue >= 0.8) return '❤️'; // Love | |
if (relationValue >= 0.5) return '😊'; // Very friendly | |
if (relationValue >= 0.2) return '🙂'; // Friendly | |
if (relationValue >= -0.2) return '😐'; // Neutral | |
if (relationValue >= -0.5) return '🙁'; // Unfriendly | |
if (relationValue >= -0.8) return '😠'; // Very unfriendly | |
return '😡'; // Hostile | |
} | |
function generateLeaderQuote(relationValue) { | |
if (relationValue >= 0.8) { | |
const quotes = [ | |
"We consider them our closest allies and friends. Our partnership is unbreakable.", | |
"Our nations share an extraordinary bond of trust and mutual respect.", | |
"Together we stand as pillars of global stability and prosperity.", | |
"Our alliance represents the strongest partnership in modern diplomacy." | |
]; | |
return quotes[Math.floor(Math.random() * quotes.length)]; | |
} else if (relationValue >= 0.5) { | |
const quotes = [ | |
"We have excellent relations and look forward to continued cooperation.", | |
"Our diplomatic and economic ties grow stronger with each passing day.", | |
"They have proven themselves to be valuable partners in regional affairs.", | |
"We share many common values and strategic objectives." | |
]; | |
return quotes[Math.floor(Math.random() * quotes.length)]; | |
} else if (relationValue >= 0.2) { | |
const quotes = [ | |
"They are a reliable partner and we maintain good diplomatic ties.", | |
"Our relationship continues to develop in a positive direction.", | |
"We see great potential in expanding our cooperation.", | |
"While we have our differences, we maintain productive dialogue." | |
]; | |
return quotes[Math.floor(Math.random() * quotes.length)]; | |
} else if (relationValue >= -0.2) { | |
const quotes = [ | |
"We maintain normal diplomatic relations, though there is room for improvement.", | |
"Our relationship is professional, but reserved.", | |
"We approach this relationship with cautious optimism.", | |
"There are both opportunities and challenges in our diplomatic exchange." | |
]; | |
return quotes[Math.floor(Math.random() * quotes.length)]; | |
} else if (relationValue >= -0.5) { | |
const quotes = [ | |
"There are several concerns we need to address in our relationship.", | |
"Recent developments have strained our diplomatic ties.", | |
"We expect more constructive behavior from their leadership.", | |
"Significant differences remain in our positions on key issues." | |
]; | |
return quotes[Math.floor(Math.random() * quotes.length)]; | |
} else if (relationValue >= -0.8) { | |
const quotes = [ | |
"We have serious disagreements with their policies and actions.", | |
"Their recent behavior is incompatible with international norms.", | |
"We strongly condemn their provocative stance.", | |
"Their actions continue to undermine regional stability." | |
]; | |
return quotes[Math.floor(Math.random() * quotes.length)]; | |
} else { | |
const quotes = [ | |
"We consider their actions hostile and a threat to regional stability.", | |
"Their aggressive behavior will not go unanswered.", | |
"We categorically reject their hostile policies.", | |
"Their actions represent a clear and present danger to peace." | |
]; | |
return quotes[Math.floor(Math.random() * quotes.length)]; | |
} | |
} | |
// Modify the updateRelationsPanel function to add the flag | |
function updateRelationsPanel() { | |
const panel = document.querySelector('.relations-panel'); | |
if (!selectedCountry || currentView !== 'relations') { | |
panel.style.display = 'none'; | |
return; | |
} | |
// Add quote tooltip div if it doesn't exist | |
if (!document.querySelector('.leader-quote')) { | |
const quoteDiv = document.createElement('div'); | |
quoteDiv.className = 'leader-quote'; | |
document.body.appendChild(quoteDiv); | |
} | |
const quoteTooltip = document.querySelector('.leader-quote'); | |
panel.style.display = 'block'; | |
// Update the panel title to include flag | |
panel.querySelector('h2').innerHTML = ` | |
<img src="https://flagcdn.com/w20/${getCountryCode(selectedCountry).toLowerCase()}.png" | |
alt="${selectedCountry} flag" | |
style="vertical-align: middle; margin-right: 10px;"> | |
Relations | |
`; | |
const relations = Object.entries(countryRelations[selectedCountry] || {}) | |
.map(([country, value]) => ({country, value})); | |
const sortedRelations = relations.sort((a, b) => b.value - a.value); | |
const topRelations = sortedRelations.slice(0, 5); | |
const bottomRelations = sortedRelations.slice(-5).reverse(); | |
function createRelationsList(relations, elementId) { | |
document.getElementById(elementId).innerHTML = ` | |
<h3>${elementId === 'top-relations' ? 'Top' : 'Bottom'} Relations</h3> | |
${relations.map(r => ` | |
<div class="relation-item" | |
data-relation="${r.value}" | |
data-country="${r.country}" | |
onmouseover="showLeaderQuote(event, '${r.country.replace(/'/g, "\\'")}', ${r.value})" | |
onmouseout="hideLeaderQuote()"> | |
<span class="country-name">${r.country}</span> | |
<span class="relation-value"> | |
${(r.value * 100).toFixed(0)}% ${getRelationEmoji(r.value)} | |
</span> | |
</div> | |
`).join('')} | |
`; | |
} | |
createRelationsList(topRelations, 'top-relations'); | |
createRelationsList(bottomRelations, 'bottom-relations'); | |
} | |
// Add these functions to handle the quote tooltip | |
function showLeaderQuote(event, country, relationValue) { | |
const quoteTooltip = document.querySelector('.leader-quote'); | |
const quote = generateLeaderQuote(relationValue); | |
quoteTooltip.textContent = quote; | |
quoteTooltip.style.opacity = '1'; | |
quoteTooltip.style.left = (event.pageX + 15) + 'px'; | |
quoteTooltip.style.top = (event.pageY - 15) + 'px'; | |
} | |
function hideLeaderQuote() { | |
const quoteTooltip = document.querySelector('.leader-quote'); | |
quoteTooltip.style.opacity = '0'; | |
} | |
function updateMap() { | |
svg.selectAll("path") | |
.data(countries.features) | |
.join("path") | |
.attr("d", path) | |
.attr("class", "country") | |
.style("fill", (d, i) => { | |
const countryName = d.properties.name; | |
const conqueror = conqueredTerritories[countryName]; | |
if (conqueror) { | |
// Get independence movement state if any | |
const movement = independenceMovements[countryName]; | |
// Special empire colors | |
const empireColors = { | |
'USSR': '#ff0000', // Red | |
'African Union': '#00cc00', // Green | |
'BRICS Alliance': '#ff8c00', // Orange | |
'British Empire': '#ff9999', // Light red | |
'French Empire': '#0066ff', // Blue | |
'Arab League': '#98fb98', // Pale green | |
'Great Qing': '#ffff00', // Yellow | |
'Imperial Russia': '#ffa500' // Orange | |
}; | |
if (currentView === 'relations') { | |
if (!selectedCountry) return '#808080'; | |
if (conqueror === selectedCountry) return '#0088ff'; | |
color = relationsColorScale(countryRelations[selectedCountry][conqueror] || 0); | |
} else { | |
// Use empire colors if available | |
if (empireColors[conqueror]) { | |
color = empireColors[conqueror]; | |
} else { | |
const conquerorIndex = countries.features.findIndex( | |
f => f.properties.name === conqueror | |
); | |
color = politicalColors[conquerorIndex]; | |
} | |
} | |
// Lighten color based on autonomy status | |
if (movement && movement.state === INDEPENDENCE_STATES.AUTONOMOUS) { | |
return d3.color(color).brighter(0.5); | |
} | |
return color; | |
} | |
switch(currentView) { | |
case 'relations': | |
if (!selectedCountry) return '#808080'; | |
if (countryName === selectedCountry) return '#0088ff'; | |
return relationsColorScale(countryRelations[selectedCountry][countryName] || 0); | |
default: // political | |
return politicalColors[i]; | |
} | |
}) | |
.on("click", function(event, d) { | |
if (currentView === 'relations') { | |
// If clicking a conquered territory, select its conqueror instead | |
const clickedCountry = d.properties.name; | |
const controller = conqueredTerritories[clickedCountry] || clickedCountry; | |
selectedCountry = selectedCountry === controller ? null : controller; | |
updateMap(); | |
} else if (currentView === 'political') { | |
const displayCountry = conqueredTerritories[d.properties.name] || d.properties.name; | |
updateCountryProfile(displayCountry); | |
} | |
}) | |
.style("stroke", "none") | |
.style("stroke-width", "0") | |
.on("mouseover", function(event, d) { | |
// Highlight the hovered country | |
d3.select(this) | |
.transition() | |
.duration(200); | |
// Get the actual controller of the hovered country | |
const hoveredCountry = d.properties.name; | |
const controller = conqueredTerritories[hoveredCountry] || hoveredCountry; | |
// Highlight all territories controlled by this controller | |
svg.selectAll("path") | |
.style("filter", path => { | |
const countryName = path.properties.name; | |
if (countryName === controller || | |
conqueredTerritories[countryName] === controller) { | |
return "brightness(1.5)"; | |
} | |
return "none"; | |
}); | |
// Create tooltip content with flag and name | |
let tooltipContent = ` | |
<div style="display: flex; align-items: center; gap: 8px;"> | |
<img src="https://flagcdn.com/w20/${getCountryCode(controller).toLowerCase()}.png" | |
alt="${controller} flag" | |
style="width: 20px; height: auto;"> | |
<span style="font-size: 16px;">${controller}</span> | |
</div> | |
`; | |
// Add original territory name if it's conquered | |
if (conqueredTerritories[hoveredCountry]) { | |
tooltipContent += ` | |
<div style="font-size: 12px; color: #aaa; margin-top: 4px; margin-left: 28px;"> | |
Originally: ${hoveredCountry} | |
</div> | |
`; | |
} | |
// Add independence movement status if applicable | |
if (conqueredTerritories[hoveredCountry]) { | |
const movement = independenceMovements[hoveredCountry]; | |
if (movement) { | |
let statusText = ''; | |
switch(movement.state) { | |
case INDEPENDENCE_STATES.PROTESTS: | |
statusText = "⚠️ Active Independence Protests"; | |
break; | |
case INDEPENDENCE_STATES.AUTONOMOUS: | |
statusText = "🏛️ Autonomous Region"; | |
break; | |
} | |
if (statusText) { | |
tooltipContent += ` | |
<div style="font-size: 12px; color: #ff9999; margin-top: 4px; margin-left: 28px;"> | |
${statusText} | |
</div> | |
`; | |
} | |
} | |
} | |
// Update tooltip styles to accommodate HTML content | |
tooltip | |
.style("opacity", 1) | |
.style("left", (event.pageX + 10) + "px") | |
.style("top", (event.pageY - 10) + "px") | |
.html(tooltipContent); // Use .html() instead of .text() | |
}) | |
.on("mouseout", function() { | |
// Reset all highlights when mouse leaves | |
svg.selectAll("path") | |
.style("filter", "none"); | |
tooltip.style("opacity", 0); | |
}); | |
// Update the army dots | |
// Removed army dots update for the new plan | |
// Add this at the end: to update the relations panel | |
updateRelationsPanel(); | |
} | |
const zoom = d3.zoom() | |
.scaleExtent([1, 8]) | |
.on("zoom", function(event) { | |
svg.selectAll("path") | |
.attr("transform", event.transform); | |
// Removed the dots group transform line | |
}); | |
svg.call(zoom); | |
// Initialize category list when the page loads | |
document.addEventListener('DOMContentLoaded', () => { | |
updateCategoryList(); | |
}); | |
// Function to toggle the map view | |
function toggleMap() { | |
// Rotate through only political and relations views | |
switch(currentView) { | |
case 'political': | |
currentView = 'relations'; | |
selectedCountry = null; | |
break; | |
case 'relations': | |
currentView = 'political'; | |
selectedCountry = null; | |
break; | |
default: | |
currentView = 'political'; | |
} | |
// Update button classes | |
const button = document.querySelector('.map-toggle'); | |
button.classList.remove('political-view', 'alliance-view', 'relations-view'); | |
button.classList.add(`${currentView}-view`); | |
updateMap(); | |
} | |
// Function to get sorted countries for the selected category | |
function getSortedCountries(category) { | |
const data = countryData[category]; | |
return Object.entries(data) | |
.filter(([country]) => !conqueredTerritories[country]) // Exclude conquered countries | |
.sort((a, b) => b[1] - a[1]) | |
.map(([country, value]) => ({ | |
country, | |
value: formatValue(value, category) | |
})); | |
} | |
function formatValue(value, category) { | |
switch(category) { | |
case 'military': | |
// Display military power in thousands (k) for better readability | |
return value >= 100000 ? | |
`${(value/1000).toLocaleString()}k power` : | |
`${value.toLocaleString()} power`; | |
case 'economy': | |
return `$${value}B`; | |
case 'land': | |
return `${(value/1000000).toFixed(2)}M km²`; | |
default: | |
return value; | |
} | |
} | |
function updateCategoryList() { | |
const category = document.querySelector('.category-selector').value; | |
const countryList = document.querySelector('.country-list'); | |
const sortedCountries = getSortedCountries(category); | |
countryList.innerHTML = sortedCountries | |
.map(({country, value}) => ` | |
<div class="country-item"> | |
<div class="country-name"> | |
<img src="https://flagcdn.com/w20/${getCountryCode(country).toLowerCase()}.png" | |
alt="${country} flag" | |
style="width: 20px; height: auto; margin-right: 8px; vertical-align: middle;"> | |
${country} | |
</div> | |
<span class="country-value">${value}</span> | |
</div> | |
`) | |
.join(''); | |
} | |
function toggleCategoryPanel() { | |
const panel = document.querySelector('.category-panel'); | |
const isHidden = panel.style.display === 'none'; | |
panel.style.display = isHidden ? 'block' : 'none'; | |
if (isHidden) { | |
updateCategoryList(); | |
} | |
} | |
// Add the updateCountryProfile function | |
function updateCountryProfile(countryName) { | |
const profile = document.querySelector('.country-profile'); | |
// Get country relations sorted by value to find top allies | |
const relations = Object.entries(countryRelations[countryName] || {}) | |
.sort((a, b) => b[1] - a[1]) | |
.slice(0, 3); | |
// Update profile content | |
profile.querySelector('.country-name').textContent = countryName; | |
profile.querySelector('.country-flag').src = | |
`https://flagcdn.com/w80/${getCountryCode(countryName).toLowerCase()}.png`; | |
profile.querySelector('.country-flag').alt = `${countryName} flag`; | |
profile.querySelector('.military-value').textContent = | |
`${(countryArmies[countryName] || 0).toLocaleString()} troops`; | |
profile.querySelector('.economy-value').textContent = | |
countryData.economy[countryName] ? | |
`$${countryData.economy[countryName]}B` : | |
'Data unavailable'; | |
profile.querySelector('.allies-list').innerHTML = relations | |
.map(([country, value]) => ` | |
<div class="ally-item"> | |
<img src="https://flagcdn.com/w20/${getCountryCode(country).toLowerCase()}.png" | |
alt="${country} flag" width="20"> | |
${country} | |
</div> | |
`) | |
.join(''); | |
// Reset force war interface | |
document.querySelector('.neighbor-list').style.display = 'none'; | |
profile.style.display = 'block'; | |
} | |
// Helper function to convert country names to ISO codes for flags | |
function getCountryCode(countryName) { | |
// Add empire flag mappings | |
const empireFlags = { | |
'USSR': 'SU', // Historical Soviet Union flag code | |
'Imperial Russia': 'RU', | |
'British Empire': 'GB', | |
'French Empire': 'FR', | |
'Great Qing': 'CN', | |
'BRICS Alliance': 'CN', | |
'Arab League': 'SA', | |
'African Union': 'ZA' | |
}; | |
// Check for empire flags first | |
if (empireFlags[countryName]) { | |
return empireFlags[countryName]; | |
} | |
// Existing country code mappings... | |
const countryCodeMap = { | |
// Existing entries... | |
// Add Indonesia's code | |
'Indonesia': 'ID', | |
// Rest of the entries... | |
'Canada': 'CA', | |
'United States': 'US', // Already exists but including for completeness | |
'Mexico': 'MX', | |
'Guatemala': 'GT', | |
'Belize': 'BZ', | |
'Honduras': 'HN', | |
'El Salvador': 'SV', | |
'Nicaragua': 'NI', | |
'Costa Rica': 'CR', | |
'Panama': 'PA', | |
'Bahamas': 'BS', | |
'Cuba': 'CU', | |
'Jamaica': 'JM', | |
'Haiti': 'HT', | |
'Dominican Republic': 'DO', | |
'Saint Kitts and Nevis': 'KN', | |
'Antigua and Barbuda': 'AG', | |
'Dominica': 'DM', | |
'Saint Lucia': 'LC', | |
'Saint Vincent and the Grenadines': 'VC', | |
'Barbados': 'BB', | |
'Grenada': 'GD', | |
'Trinidad and Tobago': 'TT', | |
// Existing codes | |
'United Kingdom': 'GB', | |
'Russia': 'RU', | |
'China': 'CN', // Already exists but including for completeness | |
'Brazil': 'BR', | |
'Argentina': 'AR', | |
'Colombia': 'CO', | |
'Venezuela': 'VE', | |
'Peru': 'PE', | |
'Chile': 'CL', | |
'Ecuador': 'EC', | |
'Paraguay': 'PY', | |
'Uruguay': 'UY', | |
'Guyana': 'GY', | |
'Suriname': 'SR', | |
// Adding European country codes | |
'Albania': 'AL', | |
'Andorra': 'AD', | |
'Austria': 'AT', | |
'Belarus': 'BY', | |
'Belgium': 'BE', | |
'Bosnia and Herzegovina': 'BA', | |
'Bulgaria': 'BG', | |
'Croatia': 'HR', | |
'Cyprus': 'CY', | |
'Czech Republic': 'CZ', | |
'Czechia': 'CZ', | |
'Denmark': 'DK', | |
'Estonia': 'EE', | |
'Finland': 'FI', | |
'France': 'FR', | |
'Germany': 'DE', | |
'Greece': 'GR', | |
'Hungary': 'HU', | |
'Iceland': 'IS', | |
'Ireland': 'IE', | |
'Italy': 'IT', | |
'Kosovo': 'XK', | |
'Latvia': 'LV', | |
'Liechtenstein': 'LI', | |
'Lithuania': 'LT', | |
'Luxembourg': 'LU', | |
'Malta': 'MT', | |
'Moldova': 'MD', | |
'Monaco': 'MC', | |
'Montenegro': 'ME', | |
'Netherlands': 'NL', | |
'North Macedonia': 'MK', | |
'Norway': 'NO', | |
'Poland': 'PL', | |
'Portugal': 'PT', | |
'Romania': 'RO', | |
'San Marino': 'SM', | |
'Serbia': 'RS', | |
'Slovakia': 'SK', | |
'Slovenia': 'SI', | |
'Spain': 'ES', | |
'Sweden': 'SE', | |
'Switzerland': 'CH', | |
'Ukraine': 'UA', | |
// African countries | |
'Algeria': 'DZ', | |
'Angola': 'AO', | |
'Benin': 'BJ', | |
'Botswana': 'BW', | |
'Burkina Faso': 'BF', | |
'Burundi': 'BI', | |
'Cameroon': 'CM', | |
'Cape Verde': 'CV', | |
'Central African Republic': 'CF', // Code for Central African Republic | |
'Chad': 'TD', | |
'Comoros': 'KM', | |
'Congo': 'CG', | |
'Democratic Republic of the Congo': 'CD', | |
'Djibouti': 'DJ', | |
'Egypt': 'EG', | |
'Equatorial Guinea': 'GQ', | |
'Eritrea': 'ER', | |
'Eswatini': 'SZ', | |
'Ethiopia': 'ET', | |
'Gabon': 'GA', | |
'Gambia': 'GM', | |
'Ghana': 'GH', | |
'Guinea': 'GN', | |
'Guinea-Bissau': 'GW', | |
'Ivory Coast': 'CI', | |
"Côte d'Ivoire": 'CI', | |
'Kenya': 'KE', | |
'Lesotho': 'LS', | |
'Liberia': 'LR', | |
'Libya': 'LY', | |
'Madagascar': 'MG', | |
'Malawi': 'MW', | |
'Mali': 'ML', | |
'Mauritania': 'MR', | |
'Mauritius': 'MU', | |
'Morocco': 'MA', | |
'Mozambique': 'MZ', | |
'Namibia': 'NA', | |
'Niger': 'NE', | |
'Nigeria': 'NG', | |
'Rwanda': 'RW', | |
'São Tomé and Príncipe': 'ST', | |
'Senegal': 'SN', | |
'Seychelles': 'SC', | |
'Sierra Leone': 'SL', | |
'Somalia': 'SO', | |
'South Africa': 'ZA', | |
'South Sudan': 'SS', // Code for South Sudan | |
'Sudan': 'SD', | |
'Tanzania': 'TZ', | |
'Togo': 'TG', | |
'Tunisia': 'TN', | |
'Uganda': 'UG', | |
'Western Sahara': 'EH', | |
'Zambia': 'ZM', | |
'Zimbabwe': 'ZW', | |
// East Asia | |
'Japan': 'JP', | |
'Mongolia': 'MN', | |
'North Korea': 'KP', | |
'South Korea': 'KR', | |
'Taiwan': 'TW', | |
// Southeast Asia | |
'Brunei': 'BN', | |
'Cambodia': 'KH', | |
'East Timor': 'TL', | |
'Laos': 'LA', | |
'Malaysia': 'MY', | |
'Myanmar': 'MM', | |
'Philippines': 'PH', | |
'Singapore': 'SG', | |
'Thailand': 'TH', | |
'Vietnam': 'VN', | |
// South Asia | |
'Afghanistan': 'AF', | |
'Bangladesh': 'BD', | |
'Bhutan': 'BT', | |
'India': 'IN', | |
'Maldives': 'MV', | |
'Nepal': 'NP', | |
'Pakistan': 'PK', | |
'Sri Lanka': 'LK', | |
// Central Asia | |
'Kazakhstan': 'KZ', | |
'Kyrgyzstan': 'KG', | |
'Tajikistan': 'TJ', | |
'Turkmenistan': 'TM', | |
'Uzbekistan': 'UZ', | |
// Western Asia (Middle East) | |
'Armenia': 'AM', | |
'Azerbaijan': 'AZ', | |
'Bahrain': 'BH', | |
'Georgia': 'GE', | |
'Iran': 'IR', | |
'Iraq': 'IQ', | |
'Israel': 'IL', | |
'Jordan': 'JO', | |
'Kuwait': 'KW', | |
'Lebanon': 'LB', | |
'Oman': 'OM', | |
'Palestine': 'PS', | |
'Qatar': 'QA', | |
'Saudi Arabia': 'SA', | |
'Syria': 'SY', | |
'Turkey': 'TR', | |
'United Arab Emirates': 'AE', | |
'Yemen': 'YE', | |
// Additions based on the plan | |
'Somaliland': 'SO', // Using Somalia's code as Somaliland isn't internationally recognized | |
'Northern Cyprus': 'CY', // Using Cyprus's code as N.Cyprus isn't internationally recognized | |
'Falkland Islands': 'FK', | |
}; | |
return countryCodeMap[countryName] || 'UN'; // UN flag as fallback | |
} | |
function toggleNeighborList() { | |
const selectedCountry = document.querySelector('.country-profile .country-name').textContent; | |
const neighborList = document.querySelector('.neighbor-list'); | |
if (neighborList.style.display === 'none' || !neighborList.style.display) { | |
// Get all neighbors | |
const neighbors = getNeighboringCountries(selectedCountry); | |
// Create neighbor list HTML | |
neighborList.innerHTML = neighbors | |
.map(neighbor => { | |
const relation = countryRelations[selectedCountry][neighbor] || 0; | |
const emoji = getRelationEmoji(relation); | |
return ` | |
<div class="neighbor-option" onclick="initiateWar('${selectedCountry}', '${neighbor}')"> | |
<span class="neighbor-name">${neighbor}</span> | |
<span class="neighbor-relation"> | |
${(relation * 100).toFixed(0)}% ${emoji} | |
</span> | |
</div> | |
`; | |
}) | |
.join(''); | |
neighborList.style.display = 'block'; | |
} else { | |
neighborList.style.display = 'none'; | |
} | |
} | |
function getNeighboringCountries(countryName) { | |
if (!countries || !countries.features) return []; | |
// Get all territories controlled by this country | |
const controlledTerritories = [countryName, | |
...Object.entries(conqueredTerritories) | |
.filter(([_, conqueror]) => conqueror === countryName) | |
.map(([territory]) => territory) | |
]; | |
// Get unique neighbors of all controlled territories | |
const neighbors = new Set(); | |
controlledTerritories.forEach(territory => { | |
const country = countries.features.find(f => f.properties.name === territory); | |
if (!country) return; | |
countries.features.forEach(f => { | |
const neighborName = f.properties.name; | |
if (!controlledTerritories.includes(neighborName) && // Not already controlled | |
!conqueredTerritories[neighborName] && // Not conquered by someone else | |
neighborName !== countryName && // Not self | |
areNeighbors(territory, neighborName)) { | |
neighbors.add(neighborName); | |
} | |
}); | |
}); | |
return Array.from(neighbors); | |
} | |
function initiateWar(attacker, defender) { | |
// Hide the neighbor list | |
document.querySelector('.neighbor-list').style.display = 'none'; | |
// Simulate the battle | |
const result = simulateBattle(attacker, defender); | |
// Update map and lists | |
if (result) { | |
updateMap(); | |
updateCategoryList(); | |
// Refresh the neighbor list to show new neighbors after conquest | |
// Only if the profile panel is still open and showing the attacker | |
const profile = document.querySelector('.country-profile'); | |
const displayedCountry = profile.querySelector('.country-name').textContent; | |
if (profile.style.display !== 'none' && displayedCountry === attacker) { | |
toggleNeighborList(); // Hide current list | |
toggleNeighborList(); // Show updated list | |
} | |
} | |
} | |
// Toggle commands panel visibility | |
function toggleCommands() { | |
const panel = document.querySelector('.commands-panel'); | |
panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; | |
// Hide empire options when closing panel | |
if (panel.style.display === 'none') { | |
document.querySelector('.empire-options').style.display = 'none'; | |
} | |
} | |
// Toggle empire options visibility | |
function toggleEmpireOptions() { | |
const options = document.querySelector('.empire-options'); | |
options.style.display = options.style.display === 'none' ? 'block' : 'none'; | |
} | |
// Function to trigger complete collapse when a nation is clicked | |
function initiateCompleteCollapse() { | |
// Add visual feedback that collapse mode is active | |
svg.style.cursor = 'crosshair'; | |
// Add one-time click handler to the map | |
const clickHandler = function(event, d) { | |
const countryName = d.properties.name; | |
// Find all territories controlled by this country | |
const territoriesControlled = Object.entries(conqueredTerritories) | |
.filter(([_, conqueror]) => conqueror === countryName) | |
.map(([territory]) => territory); | |
// Release all territories | |
territoriesControlled.forEach(territory => { | |
delete conqueredTerritories[territory]; | |
delete occupationStartYears[territory]; | |
delete independenceMovements[territory]; | |
}); | |
// Update display | |
updateMap(); | |
// Reset cursor and remove click handler | |
svg.style.cursor = 'default'; | |
svg.selectAll("path").on("click", null); | |
// Restore original click handlers | |
updateMap(); | |
}; | |
// Apply the temporary click handler | |
svg.selectAll("path") | |
.on("click", clickHandler); | |
} | |
// Function to reset the map to original state | |
function resetMap() { | |
// Clear all conquests and related data | |
Object.keys(conqueredTerritories).forEach(country => { | |
delete conqueredTerritories[country]; | |
delete occupationStartYears[country]; | |
delete independenceMovements[country]; | |
}); | |
// Reset year to 2020 | |
currentYear = 2020; | |
// Reset country armies to original values | |
Object.keys(countryArmies).forEach(country => { | |
countryArmies[country] = countryData.military[country] || 5000; | |
}); | |
// Update display | |
updateMap(); | |
updateCategoryList(); | |
// Hide any open panels | |
document.querySelector('.commands-panel').style.display = 'none'; | |
} | |
// Function to trigger a random war between neighbors | |
function triggerRandomWar() { | |
const availableCountries = countries.features | |
.map(f => f.properties.name) | |
.filter(name => !conqueredTerritories[name]); | |
if (availableCountries.length < 2) return; | |
// Pick a random country | |
const attacker = availableCountries[Math.floor(Math.random() * availableCountries.length)]; | |
// Get its neighbors | |
const neighbors = getNeighboringCountries(attacker); | |
if (neighbors.length > 0) { | |
// Pick a random neighbor | |
const defender = neighbors[Math.floor(Math.random() * neighbors.length)]; | |
// Start the war | |
simulateBattle(attacker, defender); | |
updateMap(); | |
} | |
} | |
// Function to form empires | |
function formEmpire(empireName) { | |
let coreNation; | |
let territoriesToConquer = []; | |
switch(empireName) { | |
case 'Arab League': | |
coreNation = 'Saudi Arabia'; | |
territoriesToConquer = [ | |
'Saudi Arabia', // Add core nation | |
'Egypt', 'Iraq', 'Syria', 'Yemen', 'Oman', 'UAE', 'Kuwait', | |
'Qatar', 'Bahrain', 'Lebanon', 'Jordan', 'Libya', 'Algeria', | |
'Tunisia', 'Morocco', 'Sudan', 'United Arab Emirates' | |
]; | |
break; | |
case 'Soviet Union': | |
coreNation = 'Russia'; | |
territoriesToConquer = [ | |
'Russia', // Add core nation | |
'Ukraine', 'Belarus', 'Kazakhstan', 'Georgia', 'Azerbaijan', | |
'Armenia', 'Moldova', 'Latvia', 'Lithuania', 'Estonia', | |
'Uzbekistan', 'Kyrgyzstan', 'Tajikistan', 'Turkmenistan' | |
]; | |
break; | |
case 'Russian Empire': | |
coreNation = 'Russia'; | |
territoriesToConquer = [ | |
'Russia', // Add core nation | |
'Finland', 'Poland', 'Ukraine', 'Belarus', 'Kazakhstan', | |
'Georgia', 'Azerbaijan', 'Armenia', 'Uzbekistan', | |
'Kyrgyzstan', 'Tajikistan', 'Turkmenistan' | |
]; | |
break; | |
case 'British Empire': | |
coreNation = 'United Kingdom'; | |
territoriesToConquer = [ | |
'United Kingdom', // Add core nation | |
'Canada', 'Australia', 'India', 'South Africa', 'Nigeria', 'Kenya', | |
'Sudan', 'South Sudan', 'Uganda', 'Tanzania', 'Ghana', 'Sierra Leone', | |
'Gambia', 'Lesotho', 'Eswatini', 'Malawi', 'Namibia', 'Botswana', | |
'Zambia', 'Zimbabwe', 'Egypt', 'South Sudan', | |
'Israel', 'Palestine', 'Pakistan', 'Bangladesh', 'Myanmar', 'Sri Lanka', | |
'Nepal', 'Bhutan', 'Iraq', 'Syria', 'Oman', 'Yemen', 'United Arab Emirates', | |
'Malaysia', 'Fiji', 'Brunei', 'New Zealand', | |
'Guyana', 'Jamaica', 'Bahamas', 'Belize', 'Falkland Islands', | |
'Cyprus', 'Ireland' | |
]; | |
break; | |
case 'French Empire': | |
coreNation = 'France'; | |
territoriesToConquer = [ | |
'France', // Add core nation | |
'Algeria', 'Morocco', 'Tunisia', 'Madagascar', 'Senegal', 'Mali', | |
'Mauritania', 'Niger', 'Burkina Faso', 'Chad', 'Guinea', | |
"Côte d'Ivoire", 'Benin', 'Congo', 'Gabon', 'Djibouti', | |
'Vietnam', 'Laos', 'Cambodia', 'Lebanon', 'Syria' | |
]; | |
break; | |
case 'Qing Dynasty': | |
coreNation = 'China'; | |
territoriesToConquer = [ | |
'China', // Core nation | |
'Mongolia' | |
]; | |
break; | |
case 'BRICS': | |
coreNation = 'China'; | |
territoriesToConquer = [ | |
'China', // Core nation | |
'Brazil', | |
'Russia', | |
'India', | |
'South Africa' | |
]; | |
break; | |
case 'African Union': | |
coreNation = 'South Africa'; | |
territoriesToConquer = [ | |
'South Africa', // Core nation | |
'Nigeria', | |
'Ethiopia', | |
'Egypt', | |
'Kenya', | |
'Tanzania', | |
'Sudan', | |
'South Sudan', | |
'Uganda', | |
'Ghana', | |
'Mozambique', | |
'Madagascar', | |
'Cameroon', | |
'Ivory Coast', | |
'Algeria', | |
'Tunisia', | |
'Libya', | |
'Somalia', | |
'Somaliland', | |
'Zimbabwe', | |
'Zambia', | |
'Rwanda', | |
'Burundi', | |
'Malawi', | |
'Namibia', | |
'Botswana', | |
'Lesotho', | |
'Eswatini', | |
'Senegal', | |
'Mali', | |
'Burkina Faso', | |
'Niger', | |
'Chad', | |
'Central African Republic', | |
'Republic of the Congo', | |
'Democratic Republic of the Congo', | |
'Gabon', | |
'Equatorial Guinea', | |
'Benin', | |
'Togo', | |
'Guinea', | |
'Guinea-Bissau', | |
'Sierra Leone', | |
'Liberia', | |
'Gambia', | |
'Mauritania', | |
'Western Sahara', | |
'Eritrea', | |
'Djibouti', | |
'Comoros', | |
'Seychelles', | |
'Cape Verde', | |
'São Tomé and Príncipe', | |
'Mauritius' | |
]; | |
break; | |
} | |
// Conquer all territories | |
territoriesToConquer.forEach(territory => { | |
conqueredTerritories[territory] = coreNation; | |
occupationStartYears[territory] = currentYear; | |
}); | |
// Rename core nation based on empire type | |
const empireMapping = empireNames[coreNation]; | |
if (empireMapping) { | |
// Handle nested empire names (like Russia's multiple empires) | |
if (typeof empireMapping === 'object') { | |
const newName = empireMapping[empireName]; | |
if (newName) { | |
// Update all references to the core nation | |
Object.keys(conqueredTerritories).forEach(territory => { | |
if (conqueredTerritories[territory] === coreNation) { | |
conqueredTerritories[territory] = newName; | |
} | |
}); | |
// Transfer military power and other data to new empire name | |
countryArmies[newName] = countryArmies[coreNation]; | |
countryData.military[newName] = countryData.military[coreNation]; | |
countryData.economy[newName] = countryData.economy[coreNation]; | |
countryData.land[newName] = countryData.land[coreNation]; | |
// Delete old core nation data | |
delete countryArmies[coreNation]; | |
delete countryData.military[coreNation]; | |
delete countryData.economy[coreNation]; | |
delete countryData.land[coreNation]; | |
} | |
} else { | |
// Simple empire name mapping | |
Object.keys(conqueredTerritories).forEach(territory => { | |
if (conqueredTerritories[territory] === coreNation) { | |
conqueredTerritories[territory] = empireMapping; | |
} | |
}); | |
// Transfer military power and other data to new empire name | |
countryArmies[empireMapping] = countryArmies[coreNation]; | |
countryData.military[empireMapping] = countryData.military[coreNation]; | |
countryData.economy[empireMapping] = countryData.economy[coreNation]; | |
countryData.land[empireMapping] = countryData.land[coreNation]; | |
// Delete old core nation data | |
delete countryArmies[coreNation]; | |
delete countryData.military[coreNation]; | |
delete countryData.economy[coreNation]; | |
delete countryData.land[coreNation]; | |
} | |
} | |
// Update display | |
updateMap(); | |
updateCategoryList(); | |
// Hide empire options | |
document.querySelector('.empire-options').style.display = 'none'; | |
} | |
</script> | |
</body> | |
</html> | |