LifePaths/map.html

502 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Minimalist Interactive World Map</title>
<!-- Leaflet CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" />
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<!-- Google Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- Page Styles -->
<style>
/* Reset default margins and paddings */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Set height for html and body to ensure the map covers the full viewport */
html, body {
height: 100%;
font-family: 'Roboto', sans-serif;
}
/* Map container styling */
#map {
width: 100%;
height: 100%;
background-color: #282A36; /* Dracula dark gray for water */
}
/* UI Menu Styling */
#menu {
position: absolute;
bottom: 20px; /* Position 20px from the bottom */
right: 20px; /* Position 20px from the right */
background-color: rgba(40, 42, 54, 0.9); /* Semi-transparent Dracula dark gray */
padding: 10px 15px;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
display: flex;
gap: 10px;
z-index: 1000; /* Ensure it stays above the map */
}
/* Button Styling remains unchanged */
#menu button {
background-color: #6272A4; /* Dracula muted purple */
border: none;
color: #F8F8F2; /* Dracula light gray text */
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
/* Button Hover Effect remains unchanged */
#menu button:hover {
background-color: #50fa7b; /* Dracula green */
color: #282A36; /* Dark gray text on hover */
}
/* Popup Styling (Overridden for Dark Theme) */
.leaflet-popup-content-wrapper {
background: #44475A; /* Dark background matching land fill */
color: #F8F8F2; /* Light text color */
border: 1px solid #6272A4; /* Optional: Subtle border */
}
.leaflet-popup-tip {
background: #44475A; /* Match the popup background */
}
.leaflet-popup-content {
font-size: 14px;
color: #F8F8F2;
}
/* Hide all controls in the top-left corner */
.leaflet-top.leaflet-left {
display: none;
}
/* Hide all controls in the bottom-right corner */
.leaflet-bottom.leaflet-right {
display: none;
}
/* Popup Styling (optional, can be customized further) */
.leaflet-popup-content {
font-size: 14px;
color: #F8F8F2;
}
/* Top Menu Bar Styling */
#topMenu {
position: absolute;
top: 0;
left: 0;
width: 100%;
background-color: rgba(40, 42, 54, 0.9); /* Semi-transparent Dracula dark gray */
display: flex;
justify-content: flex-start;
align-items: center;
padding: 10px 20px;
gap: 20px;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
z-index: 1001; /* Above the existing #menu which has z-index: 1000 */
}
/* Individual Menu Item Styling */
#topMenu .menu-item {
display: flex;
align-items: center;
gap: 8px;
color: #F8F8F2; /* Light text color */
font-size: 16px;
cursor: default; /* Change to 'pointer' if interactive */
}
/* Material Icons Styling */
#topMenu .material-icons {
font-size: 24px; /* Adjust icon size as needed */
color: #50fa7b; /* Dracula green for icons */
}
/* Hover Effect (Optional) */
#topMenu .menu-item:hover {
color: #50fa7b; /* Change text color on hover */
}
#topMenu .material-icons:hover {
color: #f1fa8c; /* Change icon color on hover */
}
/* Custom Attribution Styling */
#attribution {
position: absolute;
bottom: 10px;
left: 10px;
background-color: rgba(40, 42, 54, 0.8); /* Semi-transparent background */
color: #F8F8F2; /* Light text color */
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
z-index: 1000; /* Ensure it stays above the map */
}
#attribution a {
color: #50fa7b; /* Dracula green for links */
text-decoration: none;
}
#attribution a:hover {
text-decoration: underline;
}
/* Floating Event Window Styling */
.popup {
position: fixed;
width: 300px;
padding: 20px;
border-radius: 15px;
background-color: #2E2E2E; /* Dark background */
color: white;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 2000; /* Above all other elements */
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: 'Roboto', sans-serif;
display: none; /* Hidden by default */
cursor: move; /* Indicate draggable */
}
/* Popup header styling */
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
}
/* Title in the header */
.popup-title {
font-size: 18px;
font-weight: bold;
}
/* Close button styling */
.popup-close-btn {
background: none;
border: none;
color: white;
font-size: 20px;
cursor: pointer;
}
/* Content area */
.popup-content {
margin-top: 15px;
font-size: 14px;
}
/* Action buttons */
.popup-actions {
margin-top: 20px;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.popup-action-btn {
padding: 8px 16px;
border-radius: 5px;
background-color: #50fa7b; /* Green */
color: #282A36;
border: none;
cursor: pointer;
transition: background-color 0.3s;
font-size: 14px;
}
.popup-action-btn:hover {
background-color: #62d96b;
}
/* Responsive Adjustments for Popup */
@media (max-width: 400px) {
.popup {
width: 90%;
}
}
</style>
</head>
<body>
<!-- Top Menu Bar -->
<div id="topMenu">
<div class="menu-item">
<span class="material-icons">attach_money</span>
<span class="menu-text">Money: $1,000</span>
</div>
<div class="menu-item">
<span class="material-icons">access_time</span>
<span class="menu-text" id="currentTime">Time: 12:00 PM</span>
</div>
<!-- Add more menu items as needed -->
</div>
<!-- Map Container -->
<div id="map"></div>
<!-- UI Menu with Zoom Controls -->
<div id="menu">
<button id="zoomInBtn">Zoom In</button>
<button id="zoomOutBtn">Zoom Out</button>
</div>
<!-- Floating Event Window -->
<div id="eventWindow" class="popup">
<div class="popup-header">
<span class="popup-title">Event Title</span>
<button class="popup-close-btn">&times;</button>
</div>
<div class="popup-content">
<p>Event description goes here. This area will be updated with dynamic text.</p>
</div>
<div class="popup-actions">
<button class="popup-action-btn" id="actionOk">OK</button>
<button class="popup-action-btn" id="actionCancel">Cancel</button>
</div>
</div>
<!-- Leaflet JS -->
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
<!-- Initialize Map and Add Layers -->
<script>
// Initialize the map centered at latitude 20, longitude 0 with zoom level 2
const map = L.map('map').setView([20, 0], 2);
// Create custom panes to control layer order
map.createPane('landPane');
map.getPane('landPane').style.zIndex = 400; // Below citiesPane
map.createPane('citiesPane');
map.getPane('citiesPane').style.zIndex = 650; // Above landPane
// Define styles for land and water
const landStyle = {
color: "#F8F8F2", // Light gray border (optional)
fillColor: "#44475A", // Light gray land fill (Dracula theme)
fillOpacity: 1,
weight: 0.5
};
// Add land GeoJSON layer to the 'landPane'
fetch('ne_50m_land.json')
.then(response => response.json())
.then(data => {
L.geoJSON(data, {
style: landStyle,
pane: 'landPane' // Assign to landPane
}).addTo(map);
})
.catch(error => {
console.error('Error loading ne_50m_land.json:', error);
});
// Define styles for cities using Dracula color scheme
const cityStyle = {
radius: 4,
fillColor: "#FF79C6", // Dracula pink
color: "#BD93F9", // Dracula purple border
weight: 1,
opacity: 1,
fillOpacity: 0.9
};
// Load the filtered cities JSON file (from worldcities.csv)
fetch('filtered_cities.json')
.then(response => response.json())
.then(cities => {
const cityLayer = L.layerGroup(); // Group cities into a layer
cities.forEach(city => {
// Convert lat/lng to numbers
const lat = parseFloat(city.lat);
const lng = parseFloat(city.lng);
// Create a circle marker for each city
L.circleMarker([lat, lng], {
...cityStyle,
pane: 'citiesPane' // Assign to citiesPane
})
.bindPopup(`<strong>${city.city}</strong><br>Population: ${city.population}`)
.addTo(cityLayer);
});
// Add city layer on top of the map
cityLayer.addTo(map);
})
.catch(error => {
console.error('Error loading filtered_cities.json:', error);
});
// Zoom In Button Functionality
document.getElementById('zoomInBtn').addEventListener('click', () => {
map.zoomIn();
});
// Zoom Out Button Functionality
document.getElementById('zoomOutBtn').addEventListener('click', () => {
map.zoomOut();
});
// Optional: Add keyboard controls for zooming
map.keyboard.enable();
</script>
<!-- JavaScript for Dynamic Time Update -->
<script>
// Function to format time in HH:MM AM/PM
function formatTime(date) {
let hours = date.getHours();
let minutes = date.getMinutes();
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12; // the hour '0' should be '12'
minutes = minutes < 10 ? '0'+minutes : minutes;
const strTime = hours + ':' + minutes + ' ' + ampm;
return strTime;
}
// Function to update the current time every second
function updateTime() {
const now = new Date();
const formattedTime = formatTime(now);
document.getElementById('currentTime').textContent = `Time: ${formattedTime}`;
}
// Initialize time update
updateTime(); // Initial call
setInterval(updateTime, 1000); // Update every second
</script>
<!-- JavaScript for Floating Event Window -->
<script>
// Get popup elements
const popup = document.getElementById("eventWindow");
const closeBtn = document.querySelector(".popup-close-btn");
const actionOkBtn = document.getElementById("actionOk");
const actionCancelBtn = document.getElementById("actionCancel");
// Function to open popup with dynamic content
function showPopup(eventTitle, eventDescription, actions = { ok: null, cancel: null }) {
popup.style.display = "block";
document.querySelector(".popup-title").textContent = eventTitle;
document.querySelector(".popup-content p").textContent = eventDescription;
// Assign actions if provided
if (actions.ok) {
actionOkBtn.textContent = actions.ok.text || "OK";
actionOkBtn.onclick = actions.ok.handler || closePopup;
} else {
actionOkBtn.style.display = "none";
}
if (actions.cancel) {
actionCancelBtn.textContent = actions.cancel.text || "Cancel";
actionCancelBtn.onclick = actions.cancel.handler || closePopup;
} else {
actionCancelBtn.style.display = "none";
}
}
// Function to close popup
function closePopup() {
popup.style.display = "none";
// Reset action buttons display
actionOkBtn.style.display = "inline-block";
actionCancelBtn.style.display = "inline-block";
// Reset action buttons handlers
actionOkBtn.onclick = () => { closePopup(); };
actionCancelBtn.onclick = () => { closePopup(); };
}
// Close button click handler
closeBtn.addEventListener("click", closePopup);
// Action buttons handler (for demo purposes)
actionOkBtn.addEventListener("click", () => {
alert("OK clicked!");
closePopup();
});
actionCancelBtn.addEventListener("click", () => {
alert("Cancel clicked!");
closePopup();
});
// Example: Trigger popup with event information
// This can be replaced by your game logic
// Uncomment the line below to test the popup on page load
// showPopup("Promotion Event", "You have been promoted to the rank of Godmother in the Versace family!");
// Making the Popup Draggable
(function() {
let isDragging = false;
let offsetX, offsetY;
const header = popup.querySelector(".popup-header");
header.addEventListener("mousedown", (e) => {
isDragging = true;
const rect = popup.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
popup.style.transition = "none"; // Disable transition while dragging
});
document.addEventListener("mousemove", (e) => {
if (isDragging) {
let newLeft = e.clientX - offsetX;
let newTop = e.clientY - offsetY;
// Optional: Prevent the popup from going off-screen
const popupWidth = popup.offsetWidth;
const popupHeight = popup.offsetHeight;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
// Clamp the positions
newLeft = Math.max(0, Math.min(newLeft, windowWidth - popupWidth));
newTop = Math.max(0, Math.min(newTop, windowHeight - popupHeight));
popup.style.left = `${newLeft}px`;
popup.style.top = `${newTop}px`;
}
});
document.addEventListener("mouseup", () => {
if (isDragging) {
isDragging = false;
popup.style.transition = ""; // Re-enable transition if needed
}
});
})();
// Example: Trigger the sample event after 5 seconds
setTimeout(triggerSampleEvent, 5000);
</script>
</body>
</html>