From 96217eb39af47c343b983a8fa1f1d131ca7e5e4b Mon Sep 17 00:00:00 2001 From: HikeMap User Date: Sun, 11 Jan 2026 14:00:16 -0600 Subject: [PATCH] Add global map theme system, player icon, and combat UI improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add global map theme stored in game_settings (admin-only to set) - Add map theme editor with visibility toggles for map elements - Replace player GPS marker and combat icon with runner.png - Restructure player combat UI to mirror monster entry layout - Increase player and home base marker sizes (80px vs 70px monsters) - Move navigation control to top-left, remove zoom buttons - Add skill icons and cache icons 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- database.js | 30 ++ index.html | 336 +++++++++------ mapgameimgs/cacheicons/cacheIcon100-01.png | Bin 0 -> 7867 bytes .../cacheicons/cacheIcon100-01_shadow.png | Bin 0 -> 2473 bytes mapgameimgs/player/runner.png | Bin 0 -> 12077 bytes mapgameimgs/skills/basic_attack.png | Bin 0 -> 3662 bytes mapgameimgs/skills/defend.png | Bin 0 -> 3184 bytes mapgameimgs/skills/double_attack.png | Bin 0 -> 4361 bytes mapgameimgs/skills/focus.png | Bin 0 -> 3184 bytes mapgameimgs/skills/full_restore.png | Bin 0 -> 3184 bytes mapgameimgs/skills/heal.png | Bin 0 -> 3183 bytes mapgameimgs/skills/heavy_blow.png | Bin 0 -> 3180 bytes mapgameimgs/skills/power_strike.png | Bin 0 -> 3180 bytes mapgameimgs/skills/quick_heal.png | Bin 0 -> 3183 bytes mapgameimgs/skills/quick_strike.png | Bin 0 -> 3180 bytes mapgameimgs/skills/second_wind.png | Bin 0 -> 3184 bytes mapgameimgs/skills/triple_strike.png | Bin 0 -> 3180 bytes mapgameimgs/skills/whirlwind.png | Bin 0 -> 3180 bytes maplibre-test.html | 383 +++++++++++++----- server.js | 29 ++ 20 files changed, 556 insertions(+), 222 deletions(-) create mode 100755 mapgameimgs/cacheicons/cacheIcon100-01.png create mode 100755 mapgameimgs/cacheicons/cacheIcon100-01_shadow.png create mode 100755 mapgameimgs/player/runner.png create mode 100644 mapgameimgs/skills/basic_attack.png create mode 100644 mapgameimgs/skills/defend.png create mode 100644 mapgameimgs/skills/double_attack.png create mode 100644 mapgameimgs/skills/focus.png create mode 100644 mapgameimgs/skills/full_restore.png create mode 100644 mapgameimgs/skills/heal.png create mode 100644 mapgameimgs/skills/heavy_blow.png create mode 100644 mapgameimgs/skills/power_strike.png create mode 100644 mapgameimgs/skills/quick_heal.png create mode 100644 mapgameimgs/skills/quick_strike.png create mode 100644 mapgameimgs/skills/second_wind.png create mode 100644 mapgameimgs/skills/triple_strike.png create mode 100644 mapgameimgs/skills/whirlwind.png diff --git a/database.js b/database.js index bdf260c..c893e82 100644 --- a/database.js +++ b/database.js @@ -286,6 +286,11 @@ class HikeMapDB { this.db.exec(`ALTER TABLE rpg_stats ADD COLUMN reveal_radius INTEGER DEFAULT 800`); } catch (e) { /* Column already exists */ } + // Migration: Add map_theme column to store user's custom map theme + try { + this.db.exec(`ALTER TABLE rpg_stats ADD COLUMN map_theme TEXT`); + } catch (e) { /* Column already exists */ } + // Migration: Add animation overrides to monster_types try { this.db.exec(`ALTER TABLE monster_types ADD COLUMN attack_animation TEXT DEFAULT 'attack'`); @@ -811,6 +816,31 @@ class HikeMapDB { return stmt.run(iconId, userId); } + // Get user's map theme + getMapTheme(userId) { + const stmt = this.db.prepare(`SELECT map_theme FROM rpg_stats WHERE user_id = ?`); + const result = stmt.get(userId); + if (result && result.map_theme) { + try { + return JSON.parse(result.map_theme); + } catch (e) { + return null; + } + } + return null; + } + + // Set user's map theme + setMapTheme(userId, theme) { + const stmt = this.db.prepare(` + UPDATE rpg_stats SET + map_theme = ?, + updated_at = datetime('now') + WHERE user_id = ? + `); + return stmt.run(JSON.stringify(theme), userId); + } + // Check if user can set home base (once per day) canSetHomeBase(userId) { const stmt = this.db.prepare(` diff --git a/index.html b/index.html index 9cc6a85..40b30cf 100644 --- a/index.html +++ b/index.html @@ -2632,7 +2632,64 @@ min-height: 180px; } .player-side { - flex: 0 0 140px; + flex: 1; + max-width: 200px; + } + /* Player entry - mirrors monster-entry exactly */ + .player-entry { + background: rgba(0, 0, 0, 0.4); + border: 2px solid #4285f4; + border-radius: 10px; + padding: 10px; + } + .player-entry-header { + display: flex; + align-items: center; + margin-bottom: 6px; + } + .player-entry-icon { + width: var(--combat-icon-size); + height: var(--combat-icon-size); + object-fit: contain; + } + .player-entry .sprite-container { + position: relative; + width: var(--combat-icon-size); + height: var(--combat-icon-size); + margin-right: 10px; + flex-shrink: 0; + } + .player-entry-name { + font-size: 12px; + font-weight: bold; + flex: 1; + } + .player-entry-hp { + margin-top: 4px; + } + .player-entry-hp .hp-bar { + height: 10px; + } + .player-entry-hp .stat-text { + font-size: 10px; + margin-top: 2px; + } + .player-entry-mp { + margin-top: 2px; + } + .player-entry-mp .mp-bar { + height: 8px; + } + .player-entry-mp .stat-text { + font-size: 9px; + margin-top: 1px; + color: #4ecdc4; + } + .player-entry .status-overlay img { + width: var(--combat-status-size); + height: var(--combat-status-size); + top: 0; + left: 0; } .monster-side { flex: 1; @@ -2814,21 +2871,6 @@ top: -4px; left: -4px; } - .player-side .sprite-container { - width: 80px; - height: 80px; - display: flex; - justify-content: center; - align-items: center; - margin-bottom: 8px; - } - .player-side .combatant-icon { - font-size: 48px; - } - .player-side .status-overlay img { - width: 28px; - height: 28px; - } .monster-entry .status-overlay img { width: var(--combat-status-size); height: var(--combat-status-size); @@ -3007,15 +3049,15 @@ /* Home Base Marker */ .home-base-marker { - width: 50px; - height: 50px; + width: 80px; + height: 80px; display: flex; align-items: center; justify-content: center; } .home-base-marker img { - width: 50px; - height: 50px; + width: 80px; + height: 80px; object-fit: contain; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5)); } @@ -3689,25 +3731,24 @@
⚡ Your Turn
-
-
-
🏃
-
-
-
Trail Runner
-
-
-
HP
+
+
+
+
+ Player +
+
+ Trail Runner +
+
+
HP: 100/100
-
-
MP
+
+
MP: 50/50
-
- HP: 100/100 | MP: 50/50 -
VS
@@ -4375,118 +4416,173 @@ // ===================== // FANTASY MAP STYLE // ===================== - // Default fantasy colors (can be customized via Theme Editor) - const defaultMapColors = { + // Default theme (colors + visibility toggles) + const defaultMapTheme = { land: '#1a1a2e', water: '#0f3460', roads: '#e94560', buildings: '#16213e', - parks: '#1b4332' + parks: '#1b4332', + buildings3d: true, + showRoads: true, + showBuildings: true, + showParks: true, + showWater: true, + showRoadLabels: true, + showPlaceLabels: true }; - // Load active theme from localStorage (set by Theme Editor) - let mapColors = { ...defaultMapColors }; - try { - const savedTheme = localStorage.getItem('hikemap_active_theme'); - if (savedTheme) { - const parsed = JSON.parse(savedTheme); - mapColors = { ...defaultMapColors, ...parsed }; - console.log('Loaded custom map theme:', mapColors); + // Current map theme (will be loaded from server after login) + let mapTheme = { ...defaultMapTheme }; + + // Legacy: also support mapColors for backwards compatibility + let mapColors = mapTheme; + + // Load global theme from server (public endpoint, no auth needed) + async function loadMapThemeFromServer() { + try { + const response = await fetch('/api/map-theme'); + + if (response.ok) { + const data = await response.json(); + if (data.theme) { + console.log('Loaded map theme from server:', data.theme); + return data.theme; + } + } + } catch (err) { + console.error('Error loading map theme:', err); } - } catch (e) { - console.log('Using default map colors'); + return null; } - // Build the fantasy map style - function buildFantasyStyle(colors, use3dBuildings = true) { - return { - version: 8, - name: 'HikeMap Fantasy', - sources: { - 'openmaptiles': { - type: 'vector', - url: 'https://tiles.openfreemap.org/planet' - } - }, - glyphs: 'https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf', - layers: [ - // Background - { id: 'background', type: 'background', paint: { 'background-color': colors.land } }, - // Parks + // Apply a new theme to the map + function applyMapTheme(theme) { + mapTheme = { ...defaultMapTheme, ...theme }; + mapColors = mapTheme; // Keep backwards compat + if (map) { + map.setStyle(buildFantasyStyle(mapTheme)); + } + } + + // Build the fantasy map style with visibility toggles + function buildFantasyStyle(theme) { + const layers = [ + // Background (always shown) + { id: 'background', type: 'background', paint: { 'background-color': theme.land } } + ]; + + // Parks + if (theme.showParks !== false) { + layers.push( { id: 'park', type: 'fill', source: 'openmaptiles', 'source-layer': 'park', - paint: { 'fill-color': colors.parks, 'fill-opacity': 0.7 } }, - // Landcover - wood + paint: { 'fill-color': theme.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 } }, - // Landcover - grass + paint: { 'fill-color': theme.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 } }, - // Water + paint: { 'fill-color': theme.parks, 'fill-opacity': 0.4 } } + ); + } + + // Water + if (theme.showWater !== false) { + layers.push( { id: 'water', type: 'fill', source: 'openmaptiles', 'source-layer': 'water', - paint: { 'fill-color': colors.water } }, - // Waterways + paint: { 'fill-color': theme.water } }, { id: 'waterway', type: 'line', source: 'openmaptiles', 'source-layer': 'waterway', - paint: { 'line-color': colors.water, 'line-width': ['interpolate', ['linear'], ['zoom'], 8, 1, 14, 3] } }, - // Buildings (3D or 2D) - use3dBuildings ? { + paint: { 'line-color': theme.water, 'line-width': ['interpolate', ['linear'], ['zoom'], 8, 1, 14, 3] } } + ); + } + + // Buildings + if (theme.showBuildings !== false) { + if (theme.buildings3d !== false) { + layers.push({ id: 'buildings-3d', type: 'fill-extrusion', source: 'openmaptiles', 'source-layer': 'building', minzoom: 13, paint: { - 'fill-extrusion-color': colors.buildings, + 'fill-extrusion-color': theme.buildings, 'fill-extrusion-height': ['*', 2, ['get', 'render_height']], 'fill-extrusion-base': ['*', 2, ['get', 'render_min_height']], 'fill-extrusion-opacity': 0.85 } - } : { + }); + } else { + layers.push({ id: 'buildings', type: 'fill', source: 'openmaptiles', 'source-layer': 'building', minzoom: 13, - paint: { 'fill-color': colors.buildings, 'fill-opacity': 0.8 } - }, - // Roads - service/track + paint: { 'fill-color': theme.buildings, 'fill-opacity': 0.8 } + }); + } + } + + // Roads + if (theme.showRoads !== false) { + layers.push( { 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 } }, - // Roads - path/pedestrian + paint: { 'line-color': theme.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 } }, - // Roads - minor + paint: { 'line-color': theme.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 } }, - // Roads - secondary/tertiary + paint: { 'line-color': theme.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] } }, - // Roads - primary/trunk + paint: { 'line-color': theme.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] } }, - // Roads - motorway + paint: { 'line-color': theme.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] } }, - // Rail - { id: 'rail', type: 'line', source: 'openmaptiles', 'source-layer': 'transportation', - filter: ['==', ['get', 'class'], 'rail'], - paint: { 'line-color': '#888888', 'line-width': 2, 'line-dasharray': [3, 3] } }, - // Boundaries - { 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] } }, - // Road labels - { 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 } }, - // Place labels - { 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 } } - ] + paint: { 'line-color': theme.roads, 'line-width': ['interpolate', ['exponential', 1.2], ['zoom'], 5, 1, 20, 20] } } + ); + } + + // Rail and boundaries (always show) + layers.push( + { 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] } } + ); + + // Road labels + if (theme.showRoadLabels !== false) { + layers.push({ + 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': theme.land, 'text-halo-width': 1 } + }); + } + + // Place labels + if (theme.showPlaceLabels !== false) { + layers.push({ + 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': theme.land, 'text-halo-width': 2 } + }); + } + + return { + version: 8, + name: 'HikeMap Fantasy', + sources: { + 'openmaptiles': { + type: 'vector', + url: 'https://tiles.openfreemap.org/planet' + } + }, + glyphs: 'https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf', + layers: layers }; } @@ -4495,7 +4591,7 @@ // ===================== const map = new maplibregl.Map({ container: 'map', - style: buildFantasyStyle(mapColors, true), + style: buildFantasyStyle(mapTheme), center: [-97.84, 30.49], // Note: [lng, lat] for MapLibre zoom: 13, maxZoom: 22, @@ -4506,7 +4602,15 @@ }); // Add navigation controls - map.addControl(new maplibregl.NavigationControl(), 'top-right'); + map.addControl(new maplibregl.NavigationControl({ showZoom: false }), 'top-left'); + + // Load global theme from server on startup + (async function loadGlobalTheme() { + const savedTheme = await loadMapThemeFromServer(); + if (savedTheme) { + applyMapTheme(savedTheme); + } + })(); // ===================== // COORDINATE HELPER FUNCTIONS @@ -6639,9 +6743,9 @@ if (!gpsMarker) { const el = document.createElement('div'); el.className = 'custom-div-icon gps-marker-icon'; - el.innerHTML = ''; - el.style.width = '36px'; - el.style.height = '36px'; + el.innerHTML = ''; + el.style.width = '80px'; + el.style.height = '80px'; gpsMarker = new maplibregl.Marker({ element: el, anchor: 'center' }) .setLngLat([lng, lat]) .addTo(map); @@ -6650,7 +6754,7 @@ // Update rotation if we have heading if (currentHeading !== null) { const el = gpsMarker.getElement(); - const icon = el.querySelector('.mdi-navigation'); + const icon = el.querySelector('.player-marker-img'); if (icon) { icon.style.transform = 'rotate(' + currentHeading + 'deg)'; } @@ -12829,9 +12933,9 @@ // Recreate the marker const el = document.createElement('div'); el.className = 'custom-div-icon gps-marker-icon'; - el.innerHTML = ''; - el.style.width = '36px'; - el.style.height = '36px'; + el.innerHTML = ''; + el.style.width = '80px'; + el.style.height = '80px'; gpsMarker = new maplibregl.Marker({ element: el, anchor: 'center' }) .setLngLat([lng, lat]) .addTo(map); diff --git a/mapgameimgs/cacheicons/cacheIcon100-01.png b/mapgameimgs/cacheicons/cacheIcon100-01.png new file mode 100755 index 0000000000000000000000000000000000000000..a66e5c55a5e8c76a8cc5eea73358ee4078e47323 GIT binary patch literal 7867 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 literal 0 HcmV?d00001 diff --git a/mapgameimgs/skills/basic_attack.png b/mapgameimgs/skills/basic_attack.png new file mode 100644 index 0000000000000000000000000000000000000000..8ee46e5e30da95a0ae3e9ae78bffefa7c86c6264 GIT binary patch literal 3662 zcmcIn3s@6Z7LL|Og@OoHDT19wU8OQiCYd3bDFJ~XWJ?esD=9v3GBXK*BomWBfYxri zf=DTD?Ybz!g0iwksk-WlRkRr2j{*fne6+G!wIBGZ?z%wLc5iq@w<~J9+kD?-?o9r3 z&VT=N?zuOG3Gp!=Zhme&9?wG_D@z1-q4RoiDEMA6|J7^YHp&$HmW9W2_i-2&NcL%?4*1OaRT}g@$LFC_00+LTXm4GfMfl>+1QCj*;?{afLu( ziel4rvAJe8DK}n8=Vs6Z!w(OGLbFNWz`$B5DBGYnTF7iEzt=Ab#?IFWAL@lzGo<_o zXFy1$NPwa^GYjFtAux?%5=cx0qam0;6cPjp1t@_CBnT>k1t=*9A#pL(|KWpZW=2CM z%4YV*0#8zYn$>C|5yWP*1=~cyoLP&Y1Oa>q5up$U2-uQiv{Koy(K2bkgN(J%W}V5Z zr8)>6ZxzhGbJF1PvBAr_>8%Xcx|uY1a1!XJ~}gvj)~^ zwE!&Y!UB~P3K#r|ZVd((+G35)0*w0S_C&NrnPXy+MApJ(nrSvV3q+hWK+IxIWFJZM z9OeM-GB%}gR?d>f4I+Z;XO|QvGRn+SR?e*CIQ>AD69&9MLLeD5Em>z|IGZKVNwMF6 zl~Gn!$`=ZRAt2eXfKZ}1i3&&&4uixC1bt8iC?bZkQvU@O3P_wFvHt{fj82pDOejN> z8qRE>fW$fjrDYM5QQI3$p&;c(i}4FC=}f=VzHcQk91VQ9bR#tw6@p=61}0D(gJ~91!)ggjz#4+V zQ6a?=m{>jFZI)RF79*v9+^drn<76eGg(3-y!5UV=z^DdDK@kERW>GQ0U?FNsq+$8c zqqtF=o-->r252sFE`{EbBx7|Jkh+}y#g)V^>L2NKP;V8I6zyCnQa2);a ztU@Hq$n-a4S6xF^4tlhT2NjKB&AP{G;PEIf3ef*C-ZLxi`I^cg75B>}332X-eQf~w zafj%8mh~w))0vyzorZi;FL>S>1X~XYI;5*-{wtCdsLv7d^oYI4$Brrk#^C(p9J&r(;K6ltV~wD5n8Cs9 z!?AZ2Jl-%bxhz7dI(W16!YqEW|Iv{S_h~g}?t3V{^2LuFx?a{CKBIDRTlfL-51tQw zQZAkrpV~5OuB|nC*Mg6{c3qB?yW8LUvb3XssPkD>z4^`AdSU%X$M=^L--z1Vua=8^ z8=IP{we-}fQ?u&+^yfmP!%B`TS`_rgnT7^M=)@*QNCd40_nS8j=e7kS$nbMhcP?}r zHnb^XvyA_K%F8V~uz)v@wrsNo1=LK|yl$|4P{nLF3rppbPi<7roIQETYh5`M-j=fI z{)!zPOPSKFhHyk=~;zVO(qkF5LpI>@L zM$M*8alcB6i5WTVI3x6ZFv)vk=ZzZ=lD8+S^AFxBDJcQyb?eqS9FEe`(xM{w)81a& zgC@O}a-hqa9{BM;9X^t3BJC*0RaIMCX|(zk4+x!>A63e*xS?Dy8QfuJ0i3q zwz52@z5ae_tM{#p^z`kjpn`&elqH3Qqh?(1?96@pZI73aY)F{yFZU-eP7DaxSz8MT zddu(KyO)%dl(rSlQMeINg*N)PcRM@NTlw(xIfhh}7EW>VC;efy-Zzw$k? zH}3F|!|LNFYyRG`b!%XJadB}&LqlHAv17+tT3Q~Kbh!^X(F~O}c;DacR)b~k@tS#Gy4Phh4@KbiNh znD(6)dPd}*4Vs;|r>?GU&z>`FZMe43$FVY|w!S_!Dd`~c<*~f=V~aD#N3H&1sT;^()^7ET9*+w6(<)FO8F- zDtrEjloF{ae_8B{P5-ce7Z^>hkbT{~A$?fMGTSyuL}X+|>ayHRtLEwMzx180DbU}~ zEx$Z|!)@DdM_oI#Ha+JBdHb6AsrISiEy~TYT^rPkEA4cLiY!@PO+Wl0wXV=(TlZ-D z4eb3HGxj#2=Wb?Y;g`eLZl@h%3uR#&54XfUDC&BrynFl`VUc^cV8eTy|M=w5@v<$E H3xD@-^FX#tT=?GiAkf_4izlv+vIX&YWSnp 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 F<=@h2oGbtU literal 0 HcmV?d00001 diff --git a/mapgameimgs/skills/focus.png b/mapgameimgs/skills/focus.png new file mode 100644 index 0000000000000000000000000000000000000000..97fc9c466009b7b3ad205da999927e161a0ef434 GIT binary patch literal 3184 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 g+WYRoY1cFX#tT=?GiAkf_4izlv+vIX&YWSnp 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 g+WYRoY1c @@ -334,7 +343,12 @@

Map Theme Editor

+ +
+
Color Palette
@@ -364,8 +378,37 @@ #1b4332
+
-
+
+
Map Elements
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
@@ -441,7 +484,7 @@
Apply to Game
-

This will save the current colors as the active theme used in HikeMap.

+

Saves to your account. Refresh HikeMap to see changes.

@@ -482,7 +525,16 @@ } }; - // Load saved themes from localStorage + // Check if user is logged in + function getToken() { + return localStorage.getItem('accessToken'); + } + + function isLoggedIn() { + return !!getToken(); + } + + // Load saved themes from localStorage (local storage for theme library) function loadSavedThemes() { const themes = JSON.parse(localStorage.getItem('hikemap_themes') || '{}'); return themes; @@ -493,28 +545,40 @@ localStorage.setItem('hikemap_themes', JSON.stringify(themes)); } - // Get current colors from inputs - function getCurrentColors() { + // Get current theme from inputs + function getCurrentTheme() { 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 + buildings3d: document.getElementById('buildings3d').checked, + showRoads: document.getElementById('showRoads').checked, + showBuildings: document.getElementById('showBuildings').checked, + showParks: document.getElementById('showParks').checked, + showWater: document.getElementById('showWater').checked, + showRoadLabels: document.getElementById('showRoadLabels').checked, + showPlaceLabels: document.getElementById('showPlaceLabels').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; - } + // Set theme to inputs + function setTheme(theme) { + if (theme.land) document.getElementById('colorLand').value = theme.land; + if (theme.water) document.getElementById('colorWater').value = theme.water; + if (theme.roads) document.getElementById('colorRoads').value = theme.roads; + if (theme.buildings) document.getElementById('colorBuildings').value = theme.buildings; + if (theme.parks) document.getElementById('colorParks').value = theme.parks; + + document.getElementById('buildings3d').checked = theme.buildings3d !== false; + document.getElementById('showRoads').checked = theme.showRoads !== false; + document.getElementById('showBuildings').checked = theme.showBuildings !== false; + document.getElementById('showParks').checked = theme.showParks !== false; + document.getElementById('showWater').checked = theme.showWater !== false; + document.getElementById('showRoadLabels').checked = theme.showRoadLabels !== false; + document.getElementById('showPlaceLabels').checked = theme.showPlaceLabels !== false; + updateHexDisplays(); applyStyle(); } @@ -563,9 +627,8 @@ const themeName = item.dataset.theme; const themes = loadSavedThemes(); if (themes[themeName]) { - setColors(themes[themeName]); + setTheme(themes[themeName]); document.getElementById('themeName').value = themeName; - // Update active state container.querySelectorAll('.theme-item').forEach(i => i.classList.remove('active')); item.classList.add('active'); } @@ -591,91 +654,196 @@ // Show success message function showSuccess(msg) { const el = document.getElementById('successMsg'); + document.getElementById('errorMsg').style.display = 'none'; el.textContent = msg; el.style.display = 'block'; setTimeout(() => { el.style.display = 'none'; }, 3000); } + // Show error message + function showError(msg) { + const el = document.getElementById('errorMsg'); + document.getElementById('successMsg').style.display = 'none'; + el.textContent = msg; + el.style.display = 'block'; + setTimeout(() => { el.style.display = 'none'; }, 5000); + } + // Build and apply the map style function applyStyle() { - const colors = getCurrentColors(); + const theme = getCurrentTheme(); - 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 } }, + const layers = [ + { id: 'background', type: 'background', paint: { 'background-color': theme.land } } + ]; + + // Parks + if (theme.showParks) { + layers.push( { id: 'park', type: 'fill', source: 'openmaptiles', 'source-layer': 'park', - paint: { 'fill-color': colors.parks, 'fill-opacity': 0.7 } }, + paint: { 'fill-color': theme.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 } }, + paint: { 'fill-color': theme.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 } }, + paint: { 'fill-color': theme.parks, 'fill-opacity': 0.4 } } + ); + } + + // Water + if (theme.showWater) { + layers.push( { id: 'water', type: 'fill', source: 'openmaptiles', 'source-layer': 'water', - paint: { 'fill-color': colors.water } }, + paint: { 'fill-color': theme.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 ? { + paint: { 'line-color': theme.water, 'line-width': ['interpolate', ['linear'], ['zoom'], 8, 1, 14, 3] } } + ); + } + + // Buildings + if (theme.showBuildings) { + if (theme.buildings3d) { + layers.push({ id: 'buildings-3d', type: 'fill-extrusion', source: 'openmaptiles', 'source-layer': 'building', minzoom: 13, paint: { - 'fill-extrusion-color': colors.buildings, + 'fill-extrusion-color': theme.buildings, 'fill-extrusion-height': ['*', 2, ['get', 'render_height']], 'fill-extrusion-base': ['*', 2, ['get', 'render_min_height']], 'fill-extrusion-opacity': 0.85 } - } : { + }); + } else { + layers.push({ id: 'buildings', type: 'fill', source: 'openmaptiles', 'source-layer': 'building', minzoom: 13, - paint: { 'fill-color': colors.buildings, 'fill-opacity': 0.8 } - }, + paint: { 'fill-color': theme.buildings, 'fill-opacity': 0.8 } + }); + } + } + + // Roads + if (theme.showRoads) { + layers.push( { 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 } }, + paint: { 'line-color': theme.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 } }, + paint: { 'line-color': theme.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 } }, + paint: { 'line-color': theme.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] } }, + paint: { 'line-color': theme.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] } }, + paint: { 'line-color': theme.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 } } - ] + paint: { 'line-color': theme.roads, 'line-width': ['interpolate', ['exponential', 1.2], ['zoom'], 5, 1, 20, 20] } } + ); + } + + // Rail and boundary (always show) + layers.push( + { 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] } } + ); + + // Road labels + if (theme.showRoadLabels) { + layers.push({ + 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': theme.land, 'text-halo-width': 1 } + }); + } + + // Place labels + if (theme.showPlaceLabels) { + layers.push({ + 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': theme.land, 'text-halo-width': 2 } + }); + } + + 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: layers }; map.setStyle(style); } + // Load active theme from server (public endpoint) + async function loadActiveTheme() { + try { + const response = await fetch('/api/map-theme'); + + if (response.ok) { + const data = await response.json(); + return data.theme; + } + } catch (err) { + console.error('Error loading theme from server:', err); + } + + return null; + } + + // Save active theme to server (admin only) + async function saveActiveTheme(theme) { + const token = getToken(); + if (!token) { + showError('Please log in as admin to HikeMap first'); + return false; + } + + try { + const response = await fetch('/api/admin/map-theme', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ theme }) + }); + + if (response.ok) { + return true; + } else if (response.status === 403) { + showError('Admin access required to set game theme'); + return false; + } else { + const err = await response.json(); + showError(err.error || 'Failed to save theme'); + return false; + } + } catch (err) { + console.error('Error saving theme to server:', err); + showError('Failed to save theme to server'); + return false; + } + } + // Initialize map const map = new maplibregl.Map({ container: 'map', @@ -689,18 +857,16 @@ 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) {} + map.on('load', async () => { + // Try to load active theme from server + const serverTheme = await loadActiveTheme(); + if (serverTheme) { + setTheme(serverTheme); + } else { + applyStyle(); } + + renderSavedThemes(); }); // Color input handlers @@ -711,19 +877,22 @@ }); }); - document.getElementById('buildings3d').addEventListener('change', applyStyle); + // Toggle handlers + ['buildings3d', 'showRoads', 'showBuildings', 'showParks', 'showWater', 'showRoadLabels', 'showPlaceLabels'].forEach(id => { + document.getElementById(id).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 }); + setTheme({ ...presets[presetName], buildings3d: true, showRoads: true, showBuildings: true, showParks: true, showWater: true, showRoadLabels: true, showPlaceLabels: true }); } }); }); - // Save theme + // Save theme to local library document.getElementById('saveTheme').addEventListener('click', () => { const name = document.getElementById('themeName').value.trim(); if (!name) { @@ -731,17 +900,19 @@ return; } const themes = loadSavedThemes(); - themes[name] = getCurrentColors(); + themes[name] = getCurrentTheme(); saveSavedThemes(themes); renderSavedThemes(); - showSuccess(`Saved "${name}"`); + showSuccess(`Saved "${name}" to library`); }); - // 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.'); + // Apply to game (save to server) + document.getElementById('applyToGame').addEventListener('click', async () => { + const theme = getCurrentTheme(); + const success = await saveActiveTheme(theme); + if (success) { + showSuccess('Theme applied! Refresh HikeMap to see changes.'); + } }); // Camera controls diff --git a/server.js b/server.js index feb01a2..1a1d5da 100644 --- a/server.js +++ b/server.js @@ -1145,6 +1145,35 @@ app.put('/api/user/home-base/icon', authenticateToken, (req, res) => { } }); +// Get global map theme (public - no auth required) +app.get('/api/map-theme', (req, res) => { + try { + const themeJson = db.getSetting('mapTheme'); + const theme = themeJson ? JSON.parse(themeJson) : null; + res.json({ theme }); + } catch (err) { + console.error('Get map theme error:', err); + res.status(500).json({ error: 'Failed to get map theme' }); + } +}); + +// Set global map theme (admin only) +app.put('/api/admin/map-theme', adminOnly, (req, res) => { + try { + const { theme } = req.body; + + if (!theme) { + return res.status(400).json({ error: 'Theme data is required' }); + } + + db.setSetting('mapTheme', JSON.stringify(theme)); + res.json({ success: true }); + } catch (err) { + console.error('Set map theme error:', err); + res.status(500).json({ error: 'Failed to set map theme' }); + } +}); + // Handle player death app.post('/api/user/death', authenticateToken, (req, res) => { try {