+
📍 (${Math.round(p.x)}, ${Math.round(p.y)})
📂 ${p.category || 'Unknown'}` + ); + markerLayer.addLayer(m); + }); + markerLayer.addTo(map); + L.control.layers(null, { "POIs (11k+)": markerLayer }, { collapsed: true }).addTo(map); + }); + } + + // Init map when page loads or switches to map + document.addEventListener('DOMContentLoaded', () => { + // Defer map init until tab is clicked + }); + document.querySelector('.nav a[data-page="map"]').addEventListener('click', () => { + setTimeout(initMap, 200); + }); + + // ====== POI Browser ====== + let allPOIs = []; + async function loadPOIs() { + const results = document.getElementById('poiResults'); + if (allPOIs.length > 0) return renderPOIs(); + try { + const resp = await fetch('/api/pois?limit=11309'); + allPOIs = await resp.json(); + // Populate categories + const cats = new Set(allPOIs.map(p => p.category).filter(Boolean)); + const sel = document.getElementById('poiCategory'); + cats.forEach(c => { + const opt = document.createElement('option'); + opt.value = c; + opt.textContent = c.replace(/_/g, ' '); + sel.appendChild(opt); + }); + renderPOIs(); + } catch { + results.innerHTML = '
+
-
+
+
+
+
+ composite.addTo(map);
+ map.fitBounds(bounds);
+
+ L.control.layers(baseMaps, null, { collapsed: false }).addTo(map);
+
+ // Coordinate display
+ const coordDisplay = L.control({ position: 'bottomleft' });
+ coordDisplay.onAdd = function() {
+ const div = L.DomUtil.create('div', 'coord-display');
+ div.style.cssText = 'background:rgba(20,20,31,0.9); color:#d4d4dc; padding:4px 10px; border-radius:4px; font-size:12px; border:1px solid #2a2a3e;';
+ div.innerHTML = 'Move cursor for coordinates';
+ return div;
+ };
+ coordDisplay.addTo(map);
+
+ map.on('mousemove', function(e) {
+ const x = Math.round(e.latlng.lng);
+ const y = Math.round(e.latlng.lat);
+ document.querySelector('.coord-display').innerHTML = `📍 (${x}, ${y})`;
+ });
+
+ // POI markers from database
+ fetch('/api/pois?limit=500')
+ .then(r => r.json())
+ .then(pois => {
+ const poiIcons = {
+ 'point_of_interest': L.divIcon({ html: '📍', className: 'poi-icon', iconSize: [20, 20] }),
+ 'boss': L.divIcon({ html: '👑', className: 'poi-icon', iconSize: [20, 20] }),
+ 'dungeon': L.divIcon({ html: '🏚️', className: 'poi-icon', iconSize: [20, 20] }),
+ 'resource': L.divIcon({ html: '⛏️', className: 'poi-icon', iconSize: [20, 20] }),
+ 'structure': L.divIcon({ html: '🏠', className: 'poi-icon', iconSize: [20, 20] }),
+ 'spawner': L.divIcon({ html: '👾', className: 'poi-icon', iconSize: [20, 20] }),
+ 'altar': L.divIcon({ html: '🪦', className: 'poi-icon', iconSize: [20, 20] }),
+ 'vendor': L.divIcon({ html: '🏪', className: 'poi-icon', iconSize: [20, 20] }),
+ 'other': L.divIcon({ html: '❓', className: 'poi-icon', iconSize: [20, 20] }),
+ 'default': L.divIcon({ html: '📍', className: 'poi-icon', iconSize: [20, 20] })
+ };
+ const markerLayer = L.layerGroup();
+ pois.forEach(p => {
+ const icon = poiIcons[p.category] || poiIcons['default'];
+ const m = L.marker([p.y, p.x], { icon }).bindPopup(
+ `${p.prefab || 'Unknown'}🏰 Welcome to Nerdhalla
++ A vanilla Valheim community server — no mods, no BepInEx, just pure Viking survival. + Explore a hand-mapped world, find the best base locations, and conquer the tenth world together. +
+📍 (${Math.round(p.x)}, ${Math.round(p.y)})
📂 ${p.category || 'Unknown'}` + ); + markerLayer.addLayer(m); + }); + markerLayer.addTo(map); + L.control.layers(null, { "POIs (11k+)": markerLayer }, { collapsed: true }).addTo(map); + }); + } + + // Init map when page loads or switches to map + document.addEventListener('DOMContentLoaded', () => { + // Defer map init until tab is clicked + }); + document.querySelector('.nav a[data-page="map"]').addEventListener('click', () => { + setTimeout(initMap, 200); + }); + + // ====== POI Browser ====== + let allPOIs = []; + async function loadPOIs() { + const results = document.getElementById('poiResults'); + if (allPOIs.length > 0) return renderPOIs(); + try { + const resp = await fetch('/api/pois?limit=11309'); + allPOIs = await resp.json(); + // Populate categories + const cats = new Set(allPOIs.map(p => p.category).filter(Boolean)); + const sel = document.getElementById('poiCategory'); + cats.forEach(c => { + const opt = document.createElement('option'); + opt.value = c; + opt.textContent = c.replace(/_/g, ' '); + sel.appendChild(opt); + }); + renderPOIs(); + } catch { + results.innerHTML = '
Failed to load POIs
'; + } + } + + function renderPOIs() { + const search = document.getElementById('poiSearch').value.toLowerCase(); + const cat = document.getElementById('poiCategory').value; + const filtered = allPOIs.filter(p => { + const name = (p.prefab || '').toLowerCase(); + const catVal = (p.category || ''); + return name.includes(search) && (!cat || catVal === cat); + }); + const results = document.getElementById('poiResults'); + if (filtered.length === 0) { + results.innerHTML = 'No matching POIs
'; + return; + } + results.innerHTML = filtered.slice(0, 200).map(p => ` +
+ ${p.prefab || 'Unknown'} (${Math.round(p.x)}, ${Math.round(p.y)})
+ ${(p.category || '').replace(/_/g, ' ')}
+
+ `).join('');
+ if (filtered.length > 200) {
+ results.innerHTML += `Showing 200 of ${filtered.length} results
`; + } + } + + document.getElementById('poiSearch').addEventListener('input', renderPOIs); + document.getElementById('poiCategory').addEventListener('change', renderPOIs); + - \ No newline at end of file +