![]() 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/ |
/**
* Homepage Map - SIMPLE WORKING VERSION
* Uses exact same coordinate system as advanced map
*/
(function() {
'use strict';
const BOUNDS = {
minLat: 45.0,
maxLat: 51.0,
minLng: -80.0,
maxLng: -66.0
};
let map = {
canvas: null,
ctx: null,
container: null,
cities: [],
villages: [],
zoom: 1.2,
panX: 0,
panY: 0,
dragging: false,
dragStart: { x: 0, y: 0 }
};
function init() {
console.log('🗺️ INIT: Starting homepage map');
map.container = document.getElementById('homepageInteractiveMap');
map.canvas = document.getElementById('homepageMapCanvas');
if (!map.container || !map.canvas) {
console.error('🗺️ ERROR: Container or canvas missing');
return;
}
map.ctx = map.canvas.getContext('2d');
if (!map.ctx) {
console.error('🗺️ ERROR: No canvas context');
return;
}
setupCanvas();
setupControls();
loadData();
}
function setupCanvas() {
const rect = map.container.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) {
setTimeout(setupCanvas, 100);
return;
}
map.canvas.width = rect.width;
map.canvas.height = rect.height;
map.panX = map.canvas.width / 2;
map.panY = map.canvas.height / 2;
console.log('🗺️ Canvas:', map.canvas.width, 'x', map.canvas.height);
map.canvas.addEventListener('mousedown', (e) => {
map.dragging = true;
const rect = map.canvas.getBoundingClientRect();
map.dragStart.x = e.clientX - rect.left;
map.dragStart.y = e.clientY - rect.top;
});
map.canvas.addEventListener('mousemove', (e) => {
if (map.dragging) {
const rect = map.canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
map.panX += x - map.dragStart.x;
map.panY += y - map.dragStart.y;
map.dragStart.x = x;
map.dragStart.y = y;
draw();
}
});
map.canvas.addEventListener('mouseup', () => {
map.dragging = false;
});
map.canvas.addEventListener('wheel', (e) => {
e.preventDefault();
const rect = map.canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const oldZoom = map.zoom;
map.zoom *= e.deltaY > 0 ? 0.9 : 1.1;
map.zoom = Math.max(0.5, Math.min(5, map.zoom));
const zoomChange = map.zoom / oldZoom;
map.panX = x - (x - map.panX) * zoomChange;
map.panY = y - (y - map.panY) * zoomChange;
draw();
});
window.addEventListener('resize', () => {
const rect = map.container.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
map.canvas.width = rect.width;
map.canvas.height = rect.height;
draw();
}
});
}
function setupControls() {
const zoomIn = document.getElementById('mapZoomIn');
const zoomOut = document.getElementById('mapZoomOut');
const reset = document.getElementById('resetMapView');
if (zoomIn) zoomIn.onclick = () => { map.zoom = Math.min(5, map.zoom * 1.2); draw(); };
if (zoomOut) zoomOut.onclick = () => { map.zoom = Math.max(0.5, map.zoom / 1.2); draw(); };
if (reset) reset.onclick = () => {
map.zoom = 1.2;
map.panX = map.canvas.width / 2;
map.panY = map.canvas.height / 2;
draw();
};
}
function latLngToXY(lat, lng) {
// EXACT same formula as advanced map
const x = ((lng - BOUNDS.minLng) / (BOUNDS.maxLng - BOUNDS.minLng)) * map.canvas.width * map.zoom + (map.panX - (map.canvas.width * map.zoom) / 2);
const y = ((BOUNDS.maxLat - lat) / (BOUNDS.maxLat - BOUNDS.minLat)) * map.canvas.height * map.zoom + (map.panY - (map.canvas.height * map.zoom) / 2);
return { x, y };
}
function loadData() {
// Wait for quebecMunicipalities
let attempts = 0;
function tryLoad() {
attempts++;
if (window.quebecMunicipalities && Object.keys(window.quebecMunicipalities).length > 0) {
console.log('🗺️ Found quebecMunicipalities:', Object.keys(window.quebecMunicipalities).length, 'cities');
loadCities();
loadVillages();
} else if (attempts < 100) {
setTimeout(tryLoad, 100);
} else {
console.error('🗺️ ERROR: quebecMunicipalities not found after 10 seconds');
// Try loading anyway
loadCities();
loadVillages();
}
}
tryLoad();
}
function loadCities() {
if (!window.quebecMunicipalities) {
console.error('🗺️ ERROR: quebecMunicipalities not available');
return;
}
map.cities = [];
for (const [name, data] of Object.entries(window.quebecMunicipalities)) {
if (!data || !data.lat || !data.lng) continue;
const lat = parseFloat(data.lat);
const lng = parseFloat(data.lng);
if (isNaN(lat) || isNaN(lng)) continue;
if (lat >= BOUNDS.minLat && lat <= BOUNDS.maxLat &&
lng >= BOUNDS.minLng && lng <= BOUNDS.maxLng) {
map.cities.push({
name: name,
lat: lat,
lng: lng,
population: parseInt(data.population) || 0,
region: data.region || '',
member_count: 0
});
}
}
console.log('🗺️ Loaded', map.cities.length, 'cities');
if (map.cities.length > 0) {
console.log('🗺️ First 3 cities:', map.cities.slice(0, 3).map(c => c.name));
}
draw();
}
function loadVillages() {
fetch('/api/map')
.then(res => res.json())
.then(data => {
const villages = data.villages || data.cities || [];
map.villages = villages
.map(v => {
const lat = parseFloat(v.location_lat || v.lat);
const lng = parseFloat(v.location_lng || v.lng);
if (isNaN(lat) || isNaN(lng)) return null;
return {
id: v.id,
name: v.name,
name_fr: v.name_fr,
slug: v.slug,
lat: lat,
lng: lng,
status: v.status || 'forming',
member_count: v.member_count || 0
};
})
.filter(v => v !== null &&
v.lat >= BOUNDS.minLat && v.lat <= BOUNDS.maxLat &&
v.lng >= BOUNDS.minLng && v.lng <= BOUNDS.maxLng
);
console.log('🗺️ Loaded', map.villages.length, 'villages');
// Merge into cities
villages.forEach(v => {
if (!v.location_lat || !v.location_lng) return;
const vLat = parseFloat(v.location_lat);
const vLng = parseFloat(v.location_lng);
if (isNaN(vLat) || isNaN(vLng)) return;
let closest = null;
let minDist = Infinity;
map.cities.forEach(city => {
const dist = Math.sqrt(Math.pow(city.lat - vLat, 2) + Math.pow(city.lng - vLng, 2));
if (dist < minDist && dist < 0.1) {
minDist = dist;
closest = city;
}
});
if (closest) {
closest.member_count += v.member_count || 0;
}
});
draw();
})
.catch(err => console.error('🗺️ ERROR loading villages:', err));
}
function draw() {
if (!map.ctx || !map.canvas || map.canvas.width === 0 || map.canvas.height === 0) {
return;
}
const ctx = map.ctx;
const w = map.canvas.width;
const h = map.canvas.height;
// Clear
ctx.clearRect(0, 0, w, h);
// Background
ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--color-bg-light').trim() || '#1a1a1a';
ctx.fillRect(0, 0, w, h);
// Grid
ctx.strokeStyle = 'rgba(212, 165, 116, 0.1)';
ctx.lineWidth = 1;
for (let x = 0; x < w; x += 50) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
ctx.stroke();
}
for (let y = 0; y < h; y += 50) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(w, y);
ctx.stroke();
}
// Draw cities
if (map.cities.length === 0) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.font = '16px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('Loading cities...', w / 2, h / 2);
console.log('🗺️ DRAW: No cities to draw');
return;
}
console.log('🗺️ DRAW: Drawing', map.cities.length, 'cities');
map.cities.forEach(city => {
const pos = latLngToXY(city.lat, city.lng);
if (isNaN(pos.x) || isNaN(pos.y)) {
console.log('🗺️ Invalid pos for', city.name, pos);
return;
}
// Only draw if on screen
if (pos.x < -50 || pos.x > w + 50 || pos.y < -50 || pos.y > h + 50) return;
const isMajor = city.population > 100000;
const isMedium = city.population > 50000;
const hasMembers = (city.member_count || 0) > 0;
const radius = isMajor ? 12 : isMedium ? 8 : city.population > 10000 ? 5 : 3;
const color = hasMembers ? '#d4a574' : (isMajor ? '#d4a574' : isMedium ? '#8bc34a' : '#6496c8');
const opacity = hasMembers ? 0.9 : 0.7;
// Glow for cities with members
if (hasMembers) {
const gradient = ctx.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, radius * 3);
gradient.addColorStop(0, 'rgba(212, 165, 116, 0.5)');
gradient.addColorStop(1, 'rgba(212, 165, 116, 0)');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius * 3, 0, Math.PI * 2);
ctx.fill();
}
// City circle
ctx.fillStyle = color;
ctx.globalAlpha = opacity;
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = color;
ctx.globalAlpha = 1;
ctx.lineWidth = hasMembers ? 3 : 2;
ctx.stroke();
// Member dot
if (hasMembers) {
ctx.fillStyle = '#10b981';
ctx.beginPath();
ctx.arc(pos.x + radius * 0.6, pos.y - radius * 0.6, radius * 0.3, 0, Math.PI * 2);
ctx.fill();
}
// Label
if ((isMajor || hasMembers || map.zoom > 1.8) && map.zoom > 1.2) {
const labelText = city.name + (hasMembers ? ` (${city.member_count})` : '');
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
ctx.font = '12px sans-serif';
const metrics = ctx.measureText(labelText);
const pad = 6;
ctx.fillRect(pos.x - metrics.width / 2 - pad, pos.y + radius + 2, metrics.width + pad * 2, 18);
ctx.fillStyle = color;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText(labelText, pos.x, pos.y + radius + 5);
}
});
// Draw villages
map.villages.forEach(village => {
const pos = latLngToXY(village.lat, village.lng);
if (pos.x < -50 || pos.x > w + 50 || pos.y < -50 || pos.y > h + 50) return;
const hasMembers = (village.member_count || 0) > 0;
const radius = Math.max(8, Math.min(20, 8 + (village.member_count || 0) * 0.5));
const color = '#d4a574';
// Glow
if (hasMembers) {
const gradient = ctx.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, radius * 4);
gradient.addColorStop(0, 'rgba(212, 165, 116, 0.8)');
gradient.addColorStop(1, 'rgba(212, 165, 116, 0)');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius * 4, 0, Math.PI * 2);
ctx.fill();
}
// Outer ring
ctx.strokeStyle = 'rgba(212, 165, 116, 1)';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius + 2, 0, Math.PI * 2);
ctx.stroke();
// Village circle
ctx.fillStyle = color;
ctx.globalAlpha = hasMembers ? 0.9 : 0.7;
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = color;
ctx.globalAlpha = 1;
ctx.lineWidth = 2;
ctx.stroke();
// Label
if (map.zoom > 1.5 || hasMembers) {
const name = (document.documentElement.lang === 'fr' && village.name_fr) ? village.name_fr : village.name;
const labelText = name + (hasMembers ? ` (${village.member_count})` : '');
ctx.fillStyle = 'rgba(0, 0, 0, 0.9)';
ctx.font = '13px sans-serif';
const metrics = ctx.measureText(labelText);
const pad = 8;
ctx.fillRect(pos.x - metrics.width / 2 - pad, pos.y + radius + 3, metrics.width + pad * 2, 20);
ctx.fillStyle = color;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText(labelText, pos.x, pos.y + radius + 5);
}
});
}
// Initialize
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
setTimeout(init, 100);
}
})();