You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
771 lines
29 KiB
771 lines
29 KiB
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Map Theme Editor - HikeMap</title>
|
|
<script src="https://unpkg.com/maplibre-gl@4.1.0/dist/maplibre-gl.js"></script>
|
|
<link href="https://unpkg.com/maplibre-gl@4.1.0/dist/maplibre-gl.css" rel="stylesheet" />
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
|
|
|
#map {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
}
|
|
|
|
.control-panel {
|
|
position: absolute;
|
|
top: 10px;
|
|
left: 10px;
|
|
background: white;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
z-index: 1000;
|
|
width: 300px;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.control-panel h3 {
|
|
margin-bottom: 10px;
|
|
color: #333;
|
|
font-size: 16px;
|
|
border-bottom: 2px solid #4285f4;
|
|
padding-bottom: 8px;
|
|
}
|
|
|
|
.section {
|
|
margin-bottom: 15px;
|
|
padding-bottom: 15px;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.section:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
color: #666;
|
|
margin-bottom: 10px;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.color-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.color-row label {
|
|
font-size: 13px;
|
|
width: 90px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.color-row input[type="color"] {
|
|
width: 50px;
|
|
height: 30px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
padding: 2px;
|
|
}
|
|
|
|
.color-row .hex-value {
|
|
font-size: 11px;
|
|
color: #666;
|
|
font-family: monospace;
|
|
}
|
|
|
|
.checkbox-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.checkbox-row label {
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.slider-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.slider-row label {
|
|
font-size: 12px;
|
|
width: 70px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.slider-row input[type="range"] {
|
|
flex: 1;
|
|
}
|
|
|
|
.slider-row .value {
|
|
font-size: 11px;
|
|
width: 40px;
|
|
text-align: right;
|
|
}
|
|
|
|
.btn {
|
|
display: inline-block;
|
|
padding: 8px 16px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: #4285f4;
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: #3367d6;
|
|
}
|
|
|
|
.btn-success {
|
|
background: #34a853;
|
|
color: white;
|
|
}
|
|
|
|
.btn-success:hover {
|
|
background: #2d8e47;
|
|
}
|
|
|
|
.btn-danger {
|
|
background: #ea4335;
|
|
color: white;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background: #c5372b;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: #666;
|
|
color: white;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: #555;
|
|
}
|
|
|
|
.btn-block {
|
|
display: block;
|
|
width: 100%;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.btn-group {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.btn-group .btn {
|
|
flex: 1;
|
|
}
|
|
|
|
.theme-name-input {
|
|
width: 100%;
|
|
padding: 8px 12px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.theme-name-input:focus {
|
|
outline: none;
|
|
border-color: #4285f4;
|
|
}
|
|
|
|
.saved-themes {
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.theme-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 8px 10px;
|
|
background: #f5f5f5;
|
|
border-radius: 4px;
|
|
margin-bottom: 6px;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.theme-item:hover {
|
|
background: #e8f0fe;
|
|
}
|
|
|
|
.theme-item.active {
|
|
background: #4285f4;
|
|
color: white;
|
|
}
|
|
|
|
.theme-item .theme-name {
|
|
flex: 1;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.theme-item .theme-colors {
|
|
display: flex;
|
|
gap: 3px;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.theme-item .color-dot {
|
|
width: 14px;
|
|
height: 14px;
|
|
border-radius: 50%;
|
|
border: 1px solid rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.theme-item .delete-btn {
|
|
background: none;
|
|
border: none;
|
|
color: #999;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
padding: 0 4px;
|
|
}
|
|
|
|
.theme-item .delete-btn:hover {
|
|
color: #ea4335;
|
|
}
|
|
|
|
.theme-item.active .delete-btn {
|
|
color: rgba(255,255,255,0.7);
|
|
}
|
|
|
|
.theme-item.active .delete-btn:hover {
|
|
color: white;
|
|
}
|
|
|
|
.no-themes {
|
|
text-align: center;
|
|
color: #999;
|
|
font-size: 12px;
|
|
padding: 15px;
|
|
}
|
|
|
|
.preset-btns {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 6px;
|
|
}
|
|
|
|
.preset-btn {
|
|
padding: 8px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 6px;
|
|
background: #f8f8f8;
|
|
cursor: pointer;
|
|
font-size: 11px;
|
|
text-align: center;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.preset-btn:hover {
|
|
border-color: #4285f4;
|
|
background: #e8f0fe;
|
|
}
|
|
|
|
.preset-btn .preset-name {
|
|
font-weight: bold;
|
|
display: block;
|
|
}
|
|
|
|
.preset-btn .preset-colors {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 3px;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.preset-btn .color-dot {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
border: 1px solid rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.success-msg {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
padding: 8px 12px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
margin-bottom: 10px;
|
|
display: none;
|
|
}
|
|
|
|
.info-text {
|
|
font-size: 11px;
|
|
color: #666;
|
|
margin-top: 8px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="map"></div>
|
|
|
|
<div class="control-panel">
|
|
<h3>Map Theme Editor</h3>
|
|
|
|
<div id="successMsg" class="success-msg"></div>
|
|
|
|
<div class="section">
|
|
<div class="section-title">Color Palette</div>
|
|
|
|
<div class="color-row">
|
|
<label>Background</label>
|
|
<input type="color" id="colorLand" value="#1a1a2e">
|
|
<span class="hex-value" id="hexLand">#1a1a2e</span>
|
|
</div>
|
|
<div class="color-row">
|
|
<label>Water</label>
|
|
<input type="color" id="colorWater" value="#0f3460">
|
|
<span class="hex-value" id="hexWater">#0f3460</span>
|
|
</div>
|
|
<div class="color-row">
|
|
<label>Roads</label>
|
|
<input type="color" id="colorRoads" value="#e94560">
|
|
<span class="hex-value" id="hexRoads">#e94560</span>
|
|
</div>
|
|
<div class="color-row">
|
|
<label>Buildings</label>
|
|
<input type="color" id="colorBuildings" value="#16213e">
|
|
<span class="hex-value" id="hexBuildings">#16213e</span>
|
|
</div>
|
|
<div class="color-row">
|
|
<label>Parks/Nature</label>
|
|
<input type="color" id="colorParks" value="#1b4332">
|
|
<span class="hex-value" id="hexParks">#1b4332</span>
|
|
</div>
|
|
|
|
<div class="checkbox-row">
|
|
<input type="checkbox" id="buildings3d" checked>
|
|
<label for="buildings3d">3D Buildings (200% height)</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-title">Presets</div>
|
|
<div class="preset-btns">
|
|
<button class="preset-btn" data-preset="fantasy-dark">
|
|
<span class="preset-name">Fantasy Dark</span>
|
|
<div class="preset-colors">
|
|
<span class="color-dot" style="background:#1a1a2e"></span>
|
|
<span class="color-dot" style="background:#0f3460"></span>
|
|
<span class="color-dot" style="background:#e94560"></span>
|
|
</div>
|
|
</button>
|
|
<button class="preset-btn" data-preset="forest">
|
|
<span class="preset-name">Enchanted Forest</span>
|
|
<div class="preset-colors">
|
|
<span class="color-dot" style="background:#2d3a2d"></span>
|
|
<span class="color-dot" style="background:#1a3a3a"></span>
|
|
<span class="color-dot" style="background:#8b7355"></span>
|
|
</div>
|
|
</button>
|
|
<button class="preset-btn" data-preset="desert">
|
|
<span class="preset-name">Desert Oasis</span>
|
|
<div class="preset-colors">
|
|
<span class="color-dot" style="background:#c2b280"></span>
|
|
<span class="color-dot" style="background:#4a90a4"></span>
|
|
<span class="color-dot" style="background:#8b4513"></span>
|
|
</div>
|
|
</button>
|
|
<button class="preset-btn" data-preset="ice">
|
|
<span class="preset-name">Ice Kingdom</span>
|
|
<div class="preset-colors">
|
|
<span class="color-dot" style="background:#e8f4f8"></span>
|
|
<span class="color-dot" style="background:#4a90d9"></span>
|
|
<span class="color-dot" style="background:#6a8caf"></span>
|
|
</div>
|
|
</button>
|
|
<button class="preset-btn" data-preset="volcano">
|
|
<span class="preset-name">Volcanic</span>
|
|
<div class="preset-colors">
|
|
<span class="color-dot" style="background:#1a0a0a"></span>
|
|
<span class="color-dot" style="background:#2a1a1a"></span>
|
|
<span class="color-dot" style="background:#ff4500"></span>
|
|
</div>
|
|
</button>
|
|
<button class="preset-btn" data-preset="candy">
|
|
<span class="preset-name">Candy Land</span>
|
|
<div class="preset-colors">
|
|
<span class="color-dot" style="background:#ffe4ec"></span>
|
|
<span class="color-dot" style="background:#a8d5e5"></span>
|
|
<span class="color-dot" style="background:#ff69b4"></span>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-title">Save Theme</div>
|
|
<input type="text" id="themeName" class="theme-name-input" placeholder="Enter theme name...">
|
|
<button class="btn btn-success btn-block" id="saveTheme">Save Theme</button>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-title">Saved Themes</div>
|
|
<div id="savedThemes" class="saved-themes">
|
|
<div class="no-themes">No saved themes yet</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-title">Apply to Game</div>
|
|
<button class="btn btn-primary btn-block" id="applyToGame">Set as Active Game Theme</button>
|
|
<p class="info-text">This will save the current colors as the active theme used in HikeMap.</p>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-title">Camera Controls</div>
|
|
<div class="slider-row">
|
|
<label>Pitch</label>
|
|
<input type="range" id="pitch" min="0" max="85" value="45">
|
|
<span class="value" id="pitchVal">45°</span>
|
|
</div>
|
|
<div class="slider-row">
|
|
<label>Bearing</label>
|
|
<input type="range" id="bearing" min="0" max="360" value="0">
|
|
<span class="value" id="bearingVal">0°</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Presets
|
|
const presets = {
|
|
'fantasy-dark': {
|
|
land: '#1a1a2e', water: '#0f3460', roads: '#e94560', buildings: '#16213e', parks: '#1b4332'
|
|
},
|
|
'forest': {
|
|
land: '#2d3a2d', water: '#1a3a3a', roads: '#8b7355', buildings: '#3d4a3d', parks: '#1b5e20'
|
|
},
|
|
'desert': {
|
|
land: '#c2b280', water: '#4a90a4', roads: '#8b4513', buildings: '#a89060', parks: '#6b8e23'
|
|
},
|
|
'ice': {
|
|
land: '#e8f4f8', water: '#4a90d9', roads: '#6a8caf', buildings: '#b8c8d8', parks: '#90b8a8'
|
|
},
|
|
'volcano': {
|
|
land: '#1a0a0a', water: '#2a1a1a', roads: '#ff4500', buildings: '#2a1515', parks: '#3d2a1a'
|
|
},
|
|
'candy': {
|
|
land: '#ffe4ec', water: '#a8d5e5', roads: '#ff69b4', buildings: '#ffc0cb', parks: '#98fb98'
|
|
}
|
|
};
|
|
|
|
// Load saved themes from localStorage
|
|
function loadSavedThemes() {
|
|
const themes = JSON.parse(localStorage.getItem('hikemap_themes') || '{}');
|
|
return themes;
|
|
}
|
|
|
|
// Save themes to localStorage
|
|
function saveSavedThemes(themes) {
|
|
localStorage.setItem('hikemap_themes', JSON.stringify(themes));
|
|
}
|
|
|
|
// Get current colors from inputs
|
|
function getCurrentColors() {
|
|
return {
|
|
land: document.getElementById('colorLand').value,
|
|
water: document.getElementById('colorWater').value,
|
|
roads: document.getElementById('colorRoads').value,
|
|
buildings: document.getElementById('colorBuildings').value,
|
|
parks: document.getElementById('colorParks').value,
|
|
buildings3d: document.getElementById('buildings3d').checked
|
|
};
|
|
}
|
|
|
|
// Set colors to inputs
|
|
function setColors(colors) {
|
|
document.getElementById('colorLand').value = colors.land;
|
|
document.getElementById('colorWater').value = colors.water;
|
|
document.getElementById('colorRoads').value = colors.roads;
|
|
document.getElementById('colorBuildings').value = colors.buildings;
|
|
document.getElementById('colorParks').value = colors.parks;
|
|
if (colors.buildings3d !== undefined) {
|
|
document.getElementById('buildings3d').checked = colors.buildings3d;
|
|
}
|
|
updateHexDisplays();
|
|
applyStyle();
|
|
}
|
|
|
|
// Update hex value displays
|
|
function updateHexDisplays() {
|
|
document.getElementById('hexLand').textContent = document.getElementById('colorLand').value;
|
|
document.getElementById('hexWater').textContent = document.getElementById('colorWater').value;
|
|
document.getElementById('hexRoads').textContent = document.getElementById('colorRoads').value;
|
|
document.getElementById('hexBuildings').textContent = document.getElementById('colorBuildings').value;
|
|
document.getElementById('hexParks').textContent = document.getElementById('colorParks').value;
|
|
}
|
|
|
|
// Render saved themes list
|
|
function renderSavedThemes() {
|
|
const container = document.getElementById('savedThemes');
|
|
const themes = loadSavedThemes();
|
|
const themeNames = Object.keys(themes);
|
|
|
|
if (themeNames.length === 0) {
|
|
container.innerHTML = '<div class="no-themes">No saved themes yet</div>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = themeNames.map(name => {
|
|
const t = themes[name];
|
|
return `
|
|
<div class="theme-item" data-theme="${name}">
|
|
<div class="theme-colors">
|
|
<span class="color-dot" style="background:${t.land}"></span>
|
|
<span class="color-dot" style="background:${t.water}"></span>
|
|
<span class="color-dot" style="background:${t.roads}"></span>
|
|
<span class="color-dot" style="background:${t.buildings}"></span>
|
|
<span class="color-dot" style="background:${t.parks}"></span>
|
|
</div>
|
|
<span class="theme-name">${name}</span>
|
|
<button class="delete-btn" data-delete="${name}">×</button>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
// Add click handlers
|
|
container.querySelectorAll('.theme-item').forEach(item => {
|
|
item.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('delete-btn')) return;
|
|
const themeName = item.dataset.theme;
|
|
const themes = loadSavedThemes();
|
|
if (themes[themeName]) {
|
|
setColors(themes[themeName]);
|
|
document.getElementById('themeName').value = themeName;
|
|
// Update active state
|
|
container.querySelectorAll('.theme-item').forEach(i => i.classList.remove('active'));
|
|
item.classList.add('active');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Delete handlers
|
|
container.querySelectorAll('.delete-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const themeName = btn.dataset.delete;
|
|
if (confirm(`Delete theme "${themeName}"?`)) {
|
|
const themes = loadSavedThemes();
|
|
delete themes[themeName];
|
|
saveSavedThemes(themes);
|
|
renderSavedThemes();
|
|
showSuccess(`Deleted "${themeName}"`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Show success message
|
|
function showSuccess(msg) {
|
|
const el = document.getElementById('successMsg');
|
|
el.textContent = msg;
|
|
el.style.display = 'block';
|
|
setTimeout(() => { el.style.display = 'none'; }, 3000);
|
|
}
|
|
|
|
// Build and apply the map style
|
|
function applyStyle() {
|
|
const colors = getCurrentColors();
|
|
|
|
const style = {
|
|
version: 8,
|
|
name: 'HikeMap Theme',
|
|
sources: {
|
|
'openmaptiles': {
|
|
type: 'vector',
|
|
url: 'https://tiles.openfreemap.org/planet'
|
|
}
|
|
},
|
|
glyphs: 'https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf',
|
|
layers: [
|
|
{ id: 'background', type: 'background', paint: { 'background-color': colors.land } },
|
|
{ id: 'park', type: 'fill', source: 'openmaptiles', 'source-layer': 'park',
|
|
paint: { 'fill-color': colors.parks, 'fill-opacity': 0.7 } },
|
|
{ id: 'landcover_wood', type: 'fill', source: 'openmaptiles', 'source-layer': 'landcover',
|
|
filter: ['==', ['get', 'class'], 'wood'],
|
|
paint: { 'fill-color': colors.parks, 'fill-opacity': 0.5 } },
|
|
{ id: 'landcover_grass', type: 'fill', source: 'openmaptiles', 'source-layer': 'landcover',
|
|
filter: ['==', ['get', 'class'], 'grass'],
|
|
paint: { 'fill-color': colors.parks, 'fill-opacity': 0.4 } },
|
|
{ id: 'water', type: 'fill', source: 'openmaptiles', 'source-layer': 'water',
|
|
paint: { 'fill-color': colors.water } },
|
|
{ id: 'waterway', type: 'line', source: 'openmaptiles', 'source-layer': 'waterway',
|
|
paint: { 'line-color': colors.water, 'line-width': ['interpolate', ['linear'], ['zoom'], 8, 1, 14, 3] } },
|
|
colors.buildings3d ? {
|
|
id: 'buildings-3d', type: 'fill-extrusion', source: 'openmaptiles', 'source-layer': 'building', minzoom: 13,
|
|
paint: {
|
|
'fill-extrusion-color': colors.buildings,
|
|
'fill-extrusion-height': ['*', 2, ['get', 'render_height']],
|
|
'fill-extrusion-base': ['*', 2, ['get', 'render_min_height']],
|
|
'fill-extrusion-opacity': 0.85
|
|
}
|
|
} : {
|
|
id: 'buildings', type: 'fill', source: 'openmaptiles', 'source-layer': 'building', minzoom: 13,
|
|
paint: { 'fill-color': colors.buildings, 'fill-opacity': 0.8 }
|
|
},
|
|
{ id: 'roads-service', type: 'line', source: 'openmaptiles', 'source-layer': 'transportation',
|
|
filter: ['match', ['get', 'class'], ['service', 'track'], true, false],
|
|
paint: { 'line-color': colors.roads, 'line-width': 1, 'line-opacity': 0.4 } },
|
|
{ id: 'roads-path', type: 'line', source: 'openmaptiles', 'source-layer': 'transportation',
|
|
filter: ['match', ['get', 'class'], ['path', 'pedestrian'], true, false],
|
|
paint: { 'line-color': colors.roads, 'line-width': 1, 'line-dasharray': [2, 1], 'line-opacity': 0.6 } },
|
|
{ id: 'roads-minor', type: 'line', source: 'openmaptiles', 'source-layer': 'transportation',
|
|
filter: ['==', ['get', 'class'], 'minor'],
|
|
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
paint: { 'line-color': colors.roads, 'line-width': ['interpolate', ['exponential', 1.2], ['zoom'], 13, 1, 20, 10], 'line-opacity': 0.8 } },
|
|
{ id: 'roads-secondary', type: 'line', source: 'openmaptiles', 'source-layer': 'transportation',
|
|
filter: ['match', ['get', 'class'], ['secondary', 'tertiary'], true, false],
|
|
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
paint: { 'line-color': colors.roads, 'line-width': ['interpolate', ['exponential', 1.2], ['zoom'], 8, 0.5, 20, 13] } },
|
|
{ id: 'roads-primary', type: 'line', source: 'openmaptiles', 'source-layer': 'transportation',
|
|
filter: ['match', ['get', 'class'], ['primary', 'trunk'], true, false],
|
|
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
paint: { 'line-color': colors.roads, 'line-width': ['interpolate', ['exponential', 1.2], ['zoom'], 5, 0.5, 20, 18] } },
|
|
{ id: 'roads-motorway', type: 'line', source: 'openmaptiles', 'source-layer': 'transportation',
|
|
filter: ['==', ['get', 'class'], 'motorway'],
|
|
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
paint: { 'line-color': colors.roads, 'line-width': ['interpolate', ['exponential', 1.2], ['zoom'], 5, 1, 20, 20] } },
|
|
{ id: 'rail', type: 'line', source: 'openmaptiles', 'source-layer': 'transportation',
|
|
filter: ['==', ['get', 'class'], 'rail'],
|
|
paint: { 'line-color': '#888888', 'line-width': 2, 'line-dasharray': [3, 3] } },
|
|
{ id: 'boundary', type: 'line', source: 'openmaptiles', 'source-layer': 'boundary',
|
|
filter: ['==', ['get', 'admin_level'], 2],
|
|
paint: { 'line-color': '#888888', 'line-width': 1, 'line-dasharray': [4, 2] } },
|
|
{ id: 'road-labels', type: 'symbol', source: 'openmaptiles', 'source-layer': 'transportation_name', minzoom: 14,
|
|
layout: { 'text-field': ['coalesce', ['get', 'name_en'], ['get', 'name']], 'text-size': 10, 'symbol-placement': 'line', 'text-font': ['Noto Sans Regular'] },
|
|
paint: { 'text-color': '#ffffff', 'text-halo-color': colors.land, 'text-halo-width': 1 } },
|
|
{ id: 'place-labels', type: 'symbol', source: 'openmaptiles', 'source-layer': 'place',
|
|
layout: { 'text-field': ['coalesce', ['get', 'name_en'], ['get', 'name']], 'text-size': ['interpolate', ['linear'], ['zoom'], 10, 12, 14, 16], 'text-font': ['Noto Sans Bold'] },
|
|
paint: { 'text-color': '#ffffff', 'text-halo-color': colors.land, 'text-halo-width': 2 } }
|
|
]
|
|
};
|
|
|
|
map.setStyle(style);
|
|
}
|
|
|
|
// Initialize map
|
|
const map = new maplibregl.Map({
|
|
container: 'map',
|
|
style: { version: 8, sources: {}, layers: [{ id: 'background', type: 'background', paint: { 'background-color': '#1a1a2e' } }] },
|
|
center: [-97.84, 30.49],
|
|
zoom: 15,
|
|
pitch: 45,
|
|
bearing: 0
|
|
});
|
|
|
|
map.addControl(new maplibregl.NavigationControl());
|
|
|
|
// Apply initial style after map loads
|
|
map.on('load', () => {
|
|
applyStyle();
|
|
renderSavedThemes();
|
|
|
|
// Load active game theme if exists
|
|
const activeTheme = localStorage.getItem('hikemap_active_theme');
|
|
if (activeTheme) {
|
|
try {
|
|
const colors = JSON.parse(activeTheme);
|
|
setColors(colors);
|
|
} catch (e) {}
|
|
}
|
|
});
|
|
|
|
// Color input handlers
|
|
['colorLand', 'colorWater', 'colorRoads', 'colorBuildings', 'colorParks'].forEach(id => {
|
|
document.getElementById(id).addEventListener('input', () => {
|
|
updateHexDisplays();
|
|
applyStyle();
|
|
});
|
|
});
|
|
|
|
document.getElementById('buildings3d').addEventListener('change', applyStyle);
|
|
|
|
// Preset handlers
|
|
document.querySelectorAll('.preset-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const presetName = btn.dataset.preset;
|
|
if (presets[presetName]) {
|
|
setColors({ ...presets[presetName], buildings3d: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
// Save theme
|
|
document.getElementById('saveTheme').addEventListener('click', () => {
|
|
const name = document.getElementById('themeName').value.trim();
|
|
if (!name) {
|
|
alert('Please enter a theme name');
|
|
return;
|
|
}
|
|
const themes = loadSavedThemes();
|
|
themes[name] = getCurrentColors();
|
|
saveSavedThemes(themes);
|
|
renderSavedThemes();
|
|
showSuccess(`Saved "${name}"`);
|
|
});
|
|
|
|
// Apply to game
|
|
document.getElementById('applyToGame').addEventListener('click', () => {
|
|
const colors = getCurrentColors();
|
|
localStorage.setItem('hikemap_active_theme', JSON.stringify(colors));
|
|
showSuccess('Theme applied to game! Refresh HikeMap to see changes.');
|
|
});
|
|
|
|
// Camera controls
|
|
document.getElementById('pitch').addEventListener('input', (e) => {
|
|
const val = parseInt(e.target.value);
|
|
map.setPitch(val);
|
|
document.getElementById('pitchVal').textContent = val + '°';
|
|
});
|
|
|
|
document.getElementById('bearing').addEventListener('input', (e) => {
|
|
const val = parseInt(e.target.value);
|
|
map.setBearing(val);
|
|
document.getElementById('bearingVal').textContent = val + '°';
|
|
});
|
|
|
|
map.on('move', () => {
|
|
document.getElementById('pitch').value = Math.round(map.getPitch());
|
|
document.getElementById('pitchVal').textContent = Math.round(map.getPitch()) + '°';
|
|
document.getElementById('bearing').value = Math.round(map.getBearing());
|
|
document.getElementById('bearingVal').textContent = Math.round(map.getBearing()) + '°';
|
|
});
|
|
|
|
// Initial hex display update
|
|
updateHexDisplays();
|
|
</script>
|
|
</body>
|
|
</html>
|