T.ME/BIBIL_0DAY
CasperSecurity


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/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/.cursor-server/data/User/History/6b963d87/ZB7L.js
/**
 * Homepage Interactive Map
 * Loads cities from database and shows active villages when zoomed
 * Based on working advanced-interactive-map.js pattern
 */

(function() {
    'use strict';

    const CONFIG = {
        quebecBounds: {
            minLat: 45.0,
            maxLat: 51.0,
            minLng: -80.0,
            maxLng: -66.0
        },
        zoom: {
            min: 0.5,
            max: 6,
            default: 1.2,
            step: 1.3,
            showVillagesThreshold: 2.5
        }
    };

    let state = {
        cities: [],
        villages: [],
        filteredCities: [],
        filteredVillages: [],
        selectedVillage: null,
        hoveredCity: null,
        hoveredVillage: null,
        zoom: CONFIG.zoom.default,
        panX: 0,
        panY: 0,
        isDragging: false,
        dragStart: { x: 0, y: 0 },
        searchQuery: '',
        regionFilter: 'all'
    };

    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');
        const searchInput = document.getElementById('mapSearch');
        const regionFilter = document.getElementById('mapRegionFilter');
        const closePanel = document.getElementById('closeVillagePanel');

        if (zoomIn) zoomIn.addEventListener('click', () => zoom(1));
        if (zoomOut) zoomOut.addEventListener('click', () => zoom(-1));
        if (resetView) resetView.addEventListener('click', resetMapView);
        if (searchInput) searchInput.addEventListener('input', handleSearch);
        if (regionFilter) regionFilter.addEventListener('change', handleRegionFilter);
        if (closePanel) closePanel.addEventListener('click', closeVillagePanel);
    }

    function setupEvents() {
        canvas.addEventListener('mousedown', handleMouseDown);
        canvas.addEventListener('mousemove', handleMouseMove);
        canvas.addEventListener('mouseup', handleMouseUp);
        canvas.addEventListener('wheel', handleWheel);
        canvas.addEventListener('click', handleClick);
        window.addEventListener('resize', resize);
    }

    async function loadData() {
        try {
            // Load cities
            const citiesResponse = await fetch('/api/endpoints/cities.php');
            if (citiesResponse.ok) {
                const text = await citiesResponse.text();
                if (!text || text.trim() === '') {
                    throw new Error('Empty response from cities API');
                }
                const citiesData = JSON.parse(text);
                if (citiesData.success && citiesData.cities && citiesData.cities.length > 0) {
                    const bounds = CONFIG.quebecBounds;
                    state.cities = citiesData.cities.filter(c => {
                        const lat = parseFloat(c.lat);
                        const lng = parseFloat(c.lng);
                        return lat >= bounds.minLat && lat <= bounds.maxLat &&
                               lng >= bounds.minLng && lng <= bounds.maxLng;
                    }).map(c => ({
                        ...c,
                        lat: parseFloat(c.lat),
                        lng: parseFloat(c.lng),
                        population: parseInt(c.population) || 0,
                        x: 0,
                        y: 0,
                        radius: getCityRadius(parseInt(c.population) || 0)
                    }));
                }
            }
            
            // Fallback to JavaScript data
            if (state.cities.length === 0 && window.quebecMunicipalities) {
                const bounds = CONFIG.quebecBounds;
                state.cities = Object.keys(window.quebecMunicipalities).map(name => {
                    const data = window.quebecMunicipalities[name];
                    return {
                        id: name,
                        name: name,
                        lat: parseFloat(data.lat),
                        lng: parseFloat(data.lng),
                        region: data.region,
                        population: parseInt(data.population) || 0,
                        is_active: 0,
                        x: 0,
                        y: 0,
                        radius: getCityRadius(parseInt(data.population) || 0)
                    };
                }).filter(c => 
                    c.lat >= bounds.minLat && c.lat <= bounds.maxLat &&
                    c.lng >= bounds.minLng && c.lng <= bounds.maxLng
                );
            }

            state.filteredCities = [...state.cities];

            // Load villages
            const villagesResponse = await fetch('/api/endpoints/map-villages.php');
            if (villagesResponse.ok) {
                const villagesData = await villagesResponse.json();
                if (villagesData.success && villagesData.villages) {
                    const bounds = CONFIG.quebecBounds;
                    state.villages = villagesData.villages.filter(v => {
                        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),
                        x: 0,
                        y: 0,
                        radius: 8
                    }));
                }
            }

            state.filteredVillages = [...state.villages];
            updateRegionFilter();
            updatePositions();
        } catch (error) {
            console.error('Error loading map data:', error);
        }
    }

    function updateRegionFilter() {
        const regionFilter = document.getElementById('mapRegionFilter');
        if (!regionFilter) return;

        while (regionFilter.children.length > 1) {
            regionFilter.removeChild(regionFilter.lastChild);
        }

        const regions = [...new Set(state.cities.map(c => c.region))].sort();
        regions.forEach(region => {
            const option = document.createElement('option');
            option.value = region;
            option.textContent = region;
            regionFilter.appendChild(option);
        });
    }

    function getCityRadius(population) {
        if (population > 100000) return 12;
        if (population > 50000) return 8;
        if (population > 10000) return 5;
        return 3;
    }

    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.filteredCities.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.filteredVillages.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) {
        const oldZoom = state.zoom;
        state.zoom = Math.max(CONFIG.zoom.min, Math.min(CONFIG.zoom.max, 
            state.zoom * (direction > 0 ? CONFIG.zoom.step : 1 / CONFIG.zoom.step)));
        
        const zoomFactor = state.zoom / oldZoom;
        state.panX = canvas.width / 2 - (canvas.width / 2 - state.panX) * zoomFactor;
        state.panY = canvas.height / 2 - (canvas.height / 2 - state.panY) * zoomFactor;
        
        updatePositions();
    }

    function resetMapView() {
        state.zoom = CONFIG.zoom.default;
        state.panX = canvas.width / 2;
        state.panY = canvas.height / 2;
        updatePositions();
    }

    function handleSearch(e) {
        state.searchQuery = e.target.value.toLowerCase();
        applyFilters();
    }

    function handleRegionFilter(e) {
        state.regionFilter = e.target.value;
        applyFilters();
    }

    function applyFilters() {
        state.filteredCities = state.cities.filter(city => {
            const matchesSearch = !state.searchQuery || 
                city.name.toLowerCase().includes(state.searchQuery) ||
                (city.region && city.region.toLowerCase().includes(state.searchQuery));
            const matchesRegion = state.regionFilter === 'all' || city.region === state.regionFilter;
            return matchesSearch && matchesRegion;
        });
        updatePositions();
    }

    function handleMouseDown(e) {
        const rect = canvas.getBoundingClientRect();
        state.isDragging = true;
        state.dragStart.x = e.clientX - rect.left;
        state.dragStart.y = e.clientY - rect.top;
        canvas.style.cursor = 'grabbing';
    }

    function handleMouseMove(e) {
        const rect = canvas.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;

        if (state.isDragging) {
            state.panX += x - state.dragStart.x;
            state.panY += y - state.dragStart.y;
            state.dragStart.x = x;
            state.dragStart.y = y;
            updatePositions();
        } else {
            const item = getItemAtPosition(x, y);
            if (state.zoom >= CONFIG.zoom.showVillagesThreshold) {
                state.hoveredVillage = item && item.slug ? item : null;
                state.hoveredCity = null;
            } else {
                state.hoveredCity = item && !item.slug ? item : null;
                state.hoveredVillage = null;
            }
        }
    }

    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 handleClick(e) {
        if (state.isDragging) {
            state.isDragging = false;
            return;
        }
        
        const rect = canvas.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;
        
        const item = getItemAtPosition(x, y);
        if (item && item.slug && state.zoom >= CONFIG.zoom.showVillagesThreshold) {
            state.selectedVillage = item;
            showVillageDetails(item);
        }
    }

    function getItemAtPosition(x, y) {
        // Check villages first if zoomed in
        if (state.zoom >= CONFIG.zoom.showVillagesThreshold) {
            for (let i = state.filteredVillages.length - 1; i >= 0; i--) {
                const village = state.filteredVillages[i];
                const distance = Math.sqrt(Math.pow(x - village.x, 2) + Math.pow(y - village.y, 2));
                if (distance <= village.radius + 5) return village;
            }
        }
        
        // Check cities
        for (let i = state.filteredCities.length - 1; i >= 0; i--) {
            const city = state.filteredCities[i];
            const distance = Math.sqrt(Math.pow(x - city.x, 2) + Math.pow(y - city.y, 2));
            if (distance <= city.radius + 5) return city;
        }
        
        return null;
    }

    function showVillageDetails(village) {
        const panel = document.getElementById('villageDetailsPanel');
        const content = document.getElementById('villageDetailsContent');
        if (!panel || !content) return;

        const isFr = lang === 'fr';
        const villageName = isFr && village.name_fr ? village.name_fr : village.name;
        const villageDesc = isFr && village.description_fr ? village.description_fr : village.description;

        content.innerHTML = `
            <h3 style="font-size: 1.8rem; font-weight: 700; color: var(--color-accent); margin-bottom: 1.5rem; padding-right: 2rem;">${villageName}</h3>
            <div class="village-info" style="margin-bottom: 1.5rem;">
                <div class="info-item" style="display: flex; justify-content: space-between; padding: 0.75rem 0; border-bottom: 1px solid var(--color-border);">
                    <span class="info-label" style="font-weight: 600; color: var(--color-text-secondary);">${isFr ? 'Statut' : 'Status'}:</span>
                    <span class="info-value" style="color: var(--color-text); font-weight: 500;">${village.status === 'active' ? (isFr ? 'Actif' : 'Active') : (isFr ? 'En Formation' : 'Forming')}</span>
                </div>
                <div class="info-item" style="display: flex; justify-content: space-between; padding: 0.75rem 0; border-bottom: 1px solid var(--color-border);">
                    <span class="info-label" style="font-weight: 600; color: var(--color-text-secondary);">${isFr ? 'Région' : 'Region'}:</span>
                    <span class="info-value" style="color: var(--color-text); font-weight: 500;">${village.region || 'N/A'}</span>
                </div>
                <div class="info-item" style="display: flex; justify-content: space-between; padding: 0.75rem 0; border-bottom: 1px solid var(--color-border);">
                    <span class="info-label" style="font-weight: 600; color: var(--color-text-secondary);">${isFr ? 'Membres' : 'Members'}:</span>
                    <span class="info-value" style="color: var(--color-text); font-weight: 500;">${village.member_count || 0}</span>
                </div>
            </div>
            ${villageDesc ? `<p style="color: var(--color-text-secondary); margin-bottom: 1.5rem; line-height: 1.6;">${villageDesc.substring(0, 200)}${villageDesc.length > 200 ? '...' : ''}</p>` : ''}
            <div class="village-actions" style="margin-top: 1.5rem; padding-top: 1.5rem; border-top: 2px solid var(--color-border);">
                <a href="/land/village/${village.slug}" class="action-btn" style="display: block; width: 100%; padding: 1rem; background: var(--color-primary); color: var(--color-text); text-decoration: none; text-align: center; border-radius: 12px; font-weight: 600; margin-bottom: 0.5rem; transition: all 0.3s ease;">
                    ${isFr ? '👁️ Voir le Village' : '👁️ View Village'}
                </a>
                ${!village.is_member ? `<a href="/land/join?slug=${village.slug}" class="action-btn" style="display: block; width: 100%; padding: 1rem; background: var(--color-accent); color: var(--color-text); text-decoration: none; text-align: center; border-radius: 12px; font-weight: 600; transition: all 0.3s ease;">
                    ${isFr ? '➕ Rejoindre' : '➕ Join Village'}
                </a>` : `<span style="display: block; width: 100%; padding: 1rem; background: rgba(16, 185, 129, 0.2); color: #10b981; text-align: center; border-radius: 12px; font-weight: 600; border: 2px solid rgba(16, 185, 129, 0.5);">✓ ${isFr ? 'Membre' : 'Member'}</span>`}
            </div>
        `;
        
        panel.style.display = 'block';
        setTimeout(() => {
            panel.style.right = '2rem';
        }, 10);
    }

    function closeVillagePanel() {
        const panel = document.getElementById('villageDetailsPanel');
        if (panel) {
            panel.style.right = '-400px';
            setTimeout(() => {
                panel.style.display = 'none';
            }, 400);
            state.selectedVillage = null;
        }
    }

    function draw() {
        if (!ctx || !canvas) return;

        const bgColor = getComputedStyle(document.documentElement).getPropertyValue('--color-bg-light').trim() || '#1a1a1a';
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = bgColor;
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        // Draw 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
        drawCities();

        // Draw villages if zoomed in enough
        if (state.zoom >= CONFIG.zoom.showVillagesThreshold) {
            drawVillages();
        }
    }

    function drawCities() {
        state.filteredCities.forEach(city => {
            const isHovered = state.hoveredCity === city;
            const radius = city.radius;
            const hoverRadius = isHovered ? radius * 1.3 : radius;

            // Glow on hover
            if (isHovered) {
                const gradient = ctx.createRadialGradient(city.x, city.y, 0, city.x, city.y, hoverRadius * 2);
                gradient.addColorStop(0, 'rgba(212, 165, 116, 0.4)');
                gradient.addColorStop(1, 'rgba(212, 165, 116, 0)');
                ctx.fillStyle = gradient;
                ctx.beginPath();
                ctx.arc(city.x, city.y, hoverRadius * 2, 0, Math.PI * 2);
                ctx.fill();
            }

            // City marker
            const population = city.population || 0;
            const color = population > 100000 ? '212, 165, 116' : population > 50000 ? '139, 195, 74' : '100, 150, 200';
            ctx.fillStyle = `rgba(${color}, ${isHovered ? 0.9 : 0.7})`;
            ctx.beginPath();
            ctx.arc(city.x, city.y, hoverRadius, 0, Math.PI * 2);
            ctx.fill();

            ctx.strokeStyle = `rgba(${color}, ${isHovered ? 1 : 0.8})`;
            ctx.lineWidth = 2;
            ctx.stroke();
        });
    }

    function drawVillages() {
        state.filteredVillages.forEach(village => {
            const isHovered = state.hoveredVillage === village;
            const isSelected = state.selectedVillage === village;
            const isActive = village.status === 'active';
            const radius = isSelected ? 10 : isHovered ? 9 : 8;

            // Glow for active villages
            if (isActive || isHovered || isSelected) {
                const gradient = ctx.createRadialGradient(village.x, village.y, 0, village.x, village.y, radius * 3);
                const glowColor = isActive ? '212, 165, 116' : '139, 195, 74';
                gradient.addColorStop(0, `rgba(${glowColor}, ${isSelected ? 0.6 : 0.4})`);
                gradient.addColorStop(1, `rgba(${glowColor}, 0)`);
                ctx.fillStyle = gradient;
                ctx.beginPath();
                ctx.arc(village.x, village.y, radius * 3, 0, Math.PI * 2);
                ctx.fill();
            }

            // Village marker
            const color = isActive ? '212, 165, 116' : '139, 195, 74';
            ctx.fillStyle = `rgba(${color}, ${isSelected ? 1 : isHovered ? 0.9 : 0.8})`;
            ctx.beginPath();
            ctx.arc(village.x, village.y, radius, 0, Math.PI * 2);
            ctx.fill();

            ctx.strokeStyle = `rgba(${color}, ${isSelected ? 1 : 0.9})`;
            ctx.lineWidth = isSelected ? 3 : 2;
            ctx.stroke();
        });
    }

    function animate() {
        draw();
        requestAnimationFrame(animate);
    }

    // Initialize when DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        setTimeout(init, 100);
    }
})();

CasperSecurity Mini