![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/.cursor-server/data/User/History/6b963d87/ |
/**
* Simple Interactive Map - Clean Implementation
* Shows cities and villages on a Quebec map
*/
(function() {
'use strict';
const CONFIG = {
quebecBounds: {
minLat: 45.0,
maxLat: 51.0,
minLng: -80.0,
maxLng: -66.0
},
zoom: {
min: 0.5,
max: 5,
default: 1.2,
step: 1.2
}
};
let state = {
cities: [],
villages: [],
zoom: CONFIG.zoom.default,
panX: 0,
panY: 0,
isDragging: false,
dragStart: { x: 0, y: 0 }
};
let canvas, ctx, container;
const lang = document.documentElement.lang || 'en';
function init() {
container = document.getElementById('homepageInteractiveMap');
if (!container) return;
canvas = document.getElementById('homepageMapCanvas');
if (!canvas) return;
ctx = canvas.getContext('2d');
if (!ctx) return;
setupUI();
setupEvents();
resize();
loadData();
animate();
}
function setupUI() {
const zoomIn = document.getElementById('mapZoomIn');
const zoomOut = document.getElementById('mapZoomOut');
const resetView = document.getElementById('resetMapView');
if (zoomIn) zoomIn.addEventListener('click', () => zoom(1));
if (zoomOut) zoomOut.addEventListener('click', () => zoom(-1));
if (resetView) resetView.addEventListener('click', resetMapView);
}
function setupEvents() {
canvas.addEventListener('mousedown', handleMouseDown);
canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('mouseup', handleMouseUp);
canvas.addEventListener('wheel', handleWheel, { passive: false });
window.addEventListener('resize', resize);
}
async function loadData() {
// Load cities from fallback data
if (window.quebecMunicipalities) {
const bounds = CONFIG.quebecBounds;
state.cities = Object.keys(window.quebecMunicipalities).map(name => {
const data = window.quebecMunicipalities[name];
if (!data || !data.lat || !data.lng) return null;
return {
name: name,
lat: parseFloat(data.lat),
lng: parseFloat(data.lng),
region: data.region || '',
population: parseInt(data.population) || 0
};
}).filter(c =>
c !== null &&
c.lat >= bounds.minLat && c.lat <= bounds.maxLat &&
c.lng >= bounds.minLng && c.lng <= bounds.maxLng
);
}
// Try to load villages from API
try {
const response = await fetch('/api/endpoints/map-villages.php');
if (response.ok) {
const data = await response.json();
if (data.success && data.villages) {
const bounds = CONFIG.quebecBounds;
state.villages = data.villages.filter(v => {
if (!v.lat || !v.lng) return false;
const lat = parseFloat(v.lat);
const lng = parseFloat(v.lng);
return lat >= bounds.minLat && lat <= bounds.maxLat &&
lng >= bounds.minLng && lng <= bounds.maxLng;
}).map(v => ({
...v,
lat: parseFloat(v.lat),
lng: parseFloat(v.lng)
}));
}
}
} catch (e) {
// Continue without villages
}
updatePositions();
}
function resize() {
if (!canvas || !container) return;
const rect = container.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
if (state.panX === 0 && state.panY === 0) {
state.panX = canvas.width / 2;
state.panY = canvas.height / 2;
}
updatePositions();
}
function updatePositions() {
if (!canvas || canvas.width === 0 || canvas.height === 0) return;
const bounds = CONFIG.quebecBounds;
const width = canvas.width;
const height = canvas.height;
state.cities.forEach(city => {
city.x = ((city.lng - bounds.minLng) / (bounds.maxLng - bounds.minLng)) * width * state.zoom + (state.panX - (width * state.zoom) / 2);
city.y = ((bounds.maxLat - city.lat) / (bounds.maxLat - bounds.minLat)) * height * state.zoom + (state.panY - (height * state.zoom) / 2);
});
state.villages.forEach(village => {
village.x = ((village.lng - bounds.minLng) / (bounds.maxLng - bounds.minLng)) * width * state.zoom + (state.panX - (width * state.zoom) / 2);
village.y = ((bounds.maxLat - village.lat) / (bounds.maxLat - bounds.minLat)) * height * state.zoom + (state.panY - (height * state.zoom) / 2);
});
}
function zoom(direction) {
state.zoom = Math.max(CONFIG.zoom.min, Math.min(CONFIG.zoom.max,
state.zoom * (direction > 0 ? CONFIG.zoom.step : 1 / CONFIG.zoom.step)));
updatePositions();
}
function resetMapView() {
state.zoom = CONFIG.zoom.default;
state.panX = canvas.width / 2;
state.panY = canvas.height / 2;
updatePositions();
}
function handleMouseDown(e) {
state.isDragging = true;
const rect = canvas.getBoundingClientRect();
state.dragStart.x = e.clientX - rect.left;
state.dragStart.y = e.clientY - rect.top;
canvas.style.cursor = 'grabbing';
}
function handleMouseMove(e) {
if (state.isDragging) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
state.panX += x - state.dragStart.x;
state.panY += y - state.dragStart.y;
state.dragStart.x = x;
state.dragStart.y = y;
updatePositions();
}
}
function handleMouseUp() {
state.isDragging = false;
canvas.style.cursor = 'grab';
}
function handleWheel(e) {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const oldZoom = state.zoom;
const zoomFactor = e.deltaY > 0 ? 1 / CONFIG.zoom.step : CONFIG.zoom.step;
state.zoom = Math.max(CONFIG.zoom.min, Math.min(CONFIG.zoom.max, state.zoom * zoomFactor));
const zoomChange = state.zoom / oldZoom;
state.panX = x - (x - state.panX) * zoomChange;
state.panY = y - (y - state.panY) * zoomChange;
updatePositions();
}
function draw() {
if (!ctx || !canvas) return;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Background
const bgColor = getComputedStyle(document.documentElement).getPropertyValue('--color-bg-light').trim() || '#1a1a1a';
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Grid
ctx.strokeStyle = 'rgba(212, 165, 116, 0.1)';
ctx.lineWidth = 1;
const gridSize = 50;
for (let x = 0; x < canvas.width; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
for (let y = 0; y < canvas.height; y += gridSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
// Draw cities
state.cities.forEach(city => {
if (city.x === undefined || city.y === undefined || isNaN(city.x) || isNaN(city.y)) return;
const radius = city.population > 100000 ? 8 : city.population > 50000 ? 5 : 3;
const color = city.population > 100000 ? '212, 165, 116' : city.population > 50000 ? '139, 195, 74' : '100, 150, 200';
// Draw city point
ctx.fillStyle = `rgba(${color}, 0.8)`;
ctx.beginPath();
ctx.arc(city.x, city.y, radius, 0, Math.PI * 2);
ctx.fill();
// Draw border
ctx.strokeStyle = `rgba(${color}, 1)`;
ctx.lineWidth = 2;
ctx.stroke();
// Draw label for major cities
if (city.population > 50000 && state.zoom > 1.5) {
const textColor = getComputedStyle(document.documentElement).getPropertyValue('--color-text').trim() || '#f5f5f5';
ctx.fillStyle = textColor;
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText(city.name, city.x, city.y + radius + 5);
}
});
// Draw villages
if (state.zoom > 2.0) {
state.villages.forEach(village => {
if (village.x === undefined || village.y === undefined || isNaN(village.x) || isNaN(village.y)) return;
const isActive = village.status === 'active';
const radius = isActive ? 10 : 6;
const color = isActive ? '255, 215, 0' : '139, 195, 74';
// Draw village point
ctx.fillStyle = `rgba(${color}, 0.9)`;
ctx.beginPath();
ctx.arc(village.x, village.y, radius, 0, Math.PI * 2);
ctx.fill();
// Draw border
ctx.strokeStyle = `rgba(${color}, 1)`;
ctx.lineWidth = isActive ? 3 : 2;
ctx.stroke();
// Draw label
const name = (lang === 'fr' && village.name_fr) ? village.name_fr : village.name;
const textColor = getComputedStyle(document.documentElement).getPropertyValue('--color-text').trim() || '#f5f5f5';
ctx.fillStyle = textColor;
ctx.font = isActive ? 'bold 13px sans-serif' : '12px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText(name, village.x, village.y + radius + 5);
});
} else {
// Show only active villages when zoomed out
state.villages.filter(v => v.status === 'active').forEach(village => {
if (village.x === undefined || village.y === undefined || isNaN(village.x) || isNaN(village.y)) return;
const radius = 10;
const color = '255, 215, 0';
ctx.fillStyle = `rgba(${color}, 0.9)`;
ctx.beginPath();
ctx.arc(village.x, village.y, radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = `rgba(${color}, 1)`;
ctx.lineWidth = 3;
ctx.stroke();
});
}
requestAnimationFrame(animate);
}
function animate() {
draw();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
setTimeout(init, 100);
}
})();