22// MAP
33// ═══════════════════════════════════════
44function _styleUrl ( ) {
5- if ( _mapStyle === 'satellite' ) return STYLE_SAT ;
5+ if ( _mapStyle === 'satellite' || _mapStyle === 'satellite3d' ) return STYLE_SAT ;
66 if ( _mapStyle === 'dark' ) return STYLE_DARK ;
77 if ( _mapStyle === 'bright' ) return STYLE_BRIGHT ;
88 return STYLE_STREET ; // light, enriched, terrain3d, globe all use Liberty as base
@@ -236,7 +236,7 @@ function _scaledTextSize(orig) {
236236}
237237
238238function applyLabelScale ( ) {
239- if ( _mapStyle === 'satellite' ) return ;
239+ if ( _mapStyle === 'satellite' || _mapStyle === 'satellite3d' ) return ;
240240 const style = map . getStyle ( ) ;
241241 if ( ! style || ! style . layers ) return ;
242242 for ( const layer of style . layers ) {
@@ -251,7 +251,7 @@ function applyLabelScale() {
251251}
252252
253253function applyLabelVisibility ( ) {
254- if ( _mapStyle === 'satellite' ) return ;
254+ if ( _mapStyle === 'satellite' || _mapStyle === 'satellite3d' ) return ;
255255 const vis = labelsVisible ? 'visible' : 'none' ;
256256 const style = map . getStyle ( ) ;
257257 if ( ! style || ! style . layers ) return ;
@@ -276,7 +276,7 @@ function toggleLabels() {
276276// Apply vivid parks, airport runways, mountain peaks, and optional 3D buildings
277277function applyExtraLayers ( ) {
278278 const hasOmt = map . getSource && map . getSource ( 'openmaptiles' ) ;
279- if ( ! hasOmt || [ 'satellite' , 'globe' ] . includes ( _mapStyle ) ) return ;
279+ if ( ! hasOmt || [ 'satellite' , 'satellite3d' , ' globe'] . includes ( _mapStyle ) ) return ;
280280
281281 // Vivid park fill — more saturated green over the base park layer
282282 if ( ! map . getLayer ( 'matrix-park-vivid' ) ) {
@@ -357,7 +357,7 @@ function applyExtraLayers() {
357357
358358function apply3DBuildings ( ) {
359359 if ( ! map . getSource || ! map . getSource ( 'openmaptiles' ) ) return ;
360- const shouldShow = buildings3DVisible && ! [ 'satellite' , 'terrain3d' , 'globe' ] . includes ( _mapStyle ) ;
360+ const shouldShow = buildings3DVisible && ! [ 'satellite' , 'satellite3d' , ' terrain3d', 'globe' ] . includes ( _mapStyle ) ;
361361 if ( shouldShow && ! map . getLayer ( 'matrix-buildings-3d' ) ) {
362362 // Insert below the first symbol layer so road/POI labels render on top of buildings
363363 const firstSymbol = map . getStyle ( ) ?. layers ?. find ( l => l . type === 'symbol' ) ;
@@ -418,7 +418,7 @@ function addPinLayers() {
418418 if ( ! alreadyThere ) {
419419 // In 3D Terrain (pitched view), offset the focal point downward in screen space
420420 // so the pin appears in the visible ground area rather than drifting off-screen
421- const pitchOffset = _mapStyle === 'terrain3d' ? [ 0 , Math . round ( map . transform ?. height * 0.15 || 100 ) ] : [ 0 , 0 ] ;
421+ const pitchOffset = ( _mapStyle === 'terrain3d' || _mapStyle === 'satellite3d' ) ? [ 0 , Math . round ( map . transform ?. height * 0.15 || 100 ) ] : [ 0 , 0 ] ;
422422 map . flyTo ( { center : [ lng , lat ] , zoom : targetZoom , speed : 0.8 , curve : 1.0 , essential : true ,
423423 offset : pitchOffset ,
424424 easing : t => t < 0.5 ? 4 * t * t * t : 1 - Math . pow ( - 2 * t + 2 , 3 ) / 2 } ) ;
@@ -446,7 +446,7 @@ function applyTheme() {
446446 _tileTemplatesCache = null ;
447447 // Set initial button label
448448 const btn = document . getElementById ( 'tb-style-btn' ) ;
449- const labels = { light : 'Light Map' , bright : 'Bright Map' , enriched : 'Terrain' , dark : 'Dark Map' , satellite : 'Satellite' , terrain3d : '3D Terrain' , globe : 'Globe' } ;
449+ const labels = { light : 'Light Map' , bright : 'Bright Map' , enriched : 'Terrain' , dark : 'Dark Map' , satellite : 'Satellite' , satellite3d : '3D Satellite' , terrain3d : '3D Terrain' , globe : 'Globe' } ;
450450 if ( btn ) btn . textContent = ( labels [ _mapStyle ] || _mapStyle ) + ' ▾' ;
451451
452452 // Set initial active state in menu
@@ -624,7 +624,7 @@ async function initMap() {
624624 map . getCanvas ( ) . addEventListener ( 'mouseout' , ( ) => { coordsEl . style . display = 'none' ; } ) ;
625625 const updateZoom = ( ) => {
626626 zoomEl . textContent = 'z' + map . getZoom ( ) . toFixed ( 2 ) ;
627- const is3D = _mapStyle === 'terrain3d' ;
627+ const is3D = _mapStyle === 'terrain3d' || _mapStyle === 'satellite3d' ;
628628 if ( is3D ) {
629629 pitchWrap . style . display = 'flex' ;
630630 pitchEl . textContent = `p${ map . getPitch ( ) . toFixed ( 0 ) } ° b${ map . getBearing ( ) . toFixed ( 0 ) } °` ;
@@ -676,7 +676,7 @@ async function initMap() {
676676 bldgBtn . innerHTML = '<span style="font-size:11px;font-weight:700;line-height:29px;display:block;color:var(--text);opacity:.7;font-family:var(--font)">3D</span>' ;
677677 bldgBtn . addEventListener ( 'click' , toggle3DBuildings ) ;
678678 bldgWrap . appendChild ( bldgBtn ) ;
679- if ( [ 'satellite' , 'terrain3d' , 'globe' ] . includes ( _mapStyle ) ) bldgWrap . style . display = 'none' ;
679+ if ( [ 'satellite' , 'satellite3d' , ' terrain3d', 'globe' ] . includes ( _mapStyle ) ) bldgWrap . style . display = 'none' ;
680680 navGroup . before ( bldgWrap ) ;
681681 }
682682 applyLabelScale ( ) ;
@@ -701,7 +701,7 @@ async function initMap() {
701701 // Water clicks allowed at any zoom; land clicks require zoom >= 5.
702702 // Satellite/terrain3d/globe have no vector layers for water detection.
703703 const allHits = map . queryRenderedFeatures ( e . point ) ;
704- const noVectorLayers = [ 'satellite' , 'terrain3d' , 'globe' ] . includes ( _mapStyle ) ;
704+ const noVectorLayers = [ 'satellite' , 'satellite3d' , ' terrain3d', 'globe' ] . includes ( _mapStyle ) ;
705705 const isWater = ! noVectorLayers && allHits . some ( f => f . layer . type === 'fill' && / ^ ( w a t e r | o c e a n ) / . test ( f . layer . id ) ) ;
706706 // If style is mid-transition (queryRenderedFeatures returns nothing), treat as land
707707 if ( ! isWater && map . getZoom ( ) < 5 ) return ;
@@ -765,7 +765,7 @@ async function initMap() {
765765
766766 // Elevation — only in 3D Terrain mode
767767 let elevationStr = '' ;
768- if ( _mapStyle === 'terrain3d' ) {
768+ if ( _mapStyle === 'terrain3d' || _mapStyle === 'satellite3d' ) {
769769 try {
770770 const elev = map . queryTerrainElevation ( [ lng , lat ] ) ;
771771 if ( elev !== null && elev !== undefined ) {
@@ -871,21 +871,21 @@ function toggleStyleMenu(e) {
871871function setMapStyle ( mode ) {
872872 const wasDark = _mapStyle === 'dark' ;
873873 _mapStyle = mode ;
874- // Persist style preference (satellite resets to previous on reload)
875- if ( mode !== 'satellite' ) localStorage . setItem ( 'matrix-theme' , mode ) ;
874+ // Persist style preference (satellite/satellite3d reset to previous on reload)
875+ if ( mode !== 'satellite' && mode !== 'satellite3d' ) localStorage . setItem ( 'matrix-theme' , mode ) ;
876876
877877 // Defer dark-map CSS class removal until pin icons are re-added with correct
878878 // compensation (otherwise pre-darkened images render without the CSS filter)
879879 const mapEl = document . getElementById ( 'map' ) ;
880880 if ( _mapStyle === 'dark' ) mapEl . classList . add ( 'dark-map' ) ;
881- mapEl . classList . toggle ( 'sat-mode' , [ 'satellite' , 'terrain3d' , 'globe' ] . includes ( _mapStyle ) ) ;
881+ mapEl . classList . toggle ( 'sat-mode' , [ 'satellite' , 'satellite3d' , ' terrain3d', 'globe' ] . includes ( _mapStyle ) ) ;
882882
883883 // Labels toggle visibility
884884 const labelsWrap = document . getElementById ( 'labels-toggle-wrap' ) ;
885- if ( labelsWrap ) labelsWrap . style . visibility = _mapStyle === 'satellite' ? 'hidden' : 'visible' ;
885+ if ( labelsWrap ) labelsWrap . style . visibility = ( _mapStyle === 'satellite' || _mapStyle === 'satellite3d' ) ? 'hidden' : 'visible' ;
886886 // Hide 3D buildings toggle in modes where buildings don't make sense
887887 const bldgWrap = document . getElementById ( 'buildings-toggle-wrap' ) ;
888- if ( bldgWrap ) bldgWrap . style . display = [ 'satellite' , 'terrain3d' , 'globe' ] . includes ( _mapStyle ) ? 'none' : '' ;
888+ if ( bldgWrap ) bldgWrap . style . display = [ 'satellite' , 'satellite3d' , ' terrain3d', 'globe' ] . includes ( _mapStyle ) ? 'none' : '' ;
889889
890890 // Disable Export Video in Globe mode (flyTo animation doesn't translate to globe projection)
891891 const exportBtn = document . getElementById ( 'tb-export-video' ) ;
@@ -895,7 +895,7 @@ function setMapStyle(mode) {
895895 }
896896
897897 // Update button label
898- const labels = { light : 'Light Map' , bright : 'Bright Map' , enriched : 'Terrain' , dark : 'Dark Map' , satellite : 'Satellite' , terrain3d : '3D Terrain' , globe : 'Globe' } ;
898+ const labels = { light : 'Light Map' , bright : 'Bright Map' , enriched : 'Terrain' , dark : 'Dark Map' , satellite : 'Satellite' , satellite3d : '3D Satellite' , terrain3d : '3D Terrain' , globe : 'Globe' } ;
899899 const btn = document . getElementById ( 'tb-style-btn' ) ;
900900 if ( btn ) btn . textContent = ( labels [ mode ] || mode ) + ' ▾' ;
901901
@@ -914,7 +914,7 @@ function setMapStyle(mode) {
914914}
915915
916916function _applyTerrainAndProjection ( ) {
917- const is3D = _mapStyle === 'terrain3d' ;
917+ const is3D = _mapStyle === 'terrain3d' || _mapStyle === 'satellite3d' ;
918918 const isGlobe = _mapStyle === 'globe' ;
919919
920920 // Set projection FIRST — before any camera moves — so easeTo never runs under the wrong projection
@@ -956,7 +956,7 @@ function _applyTerrainAndProjection() {
956956 if ( map . getLayer ( 'sky' ) === undefined ) {
957957 map . addLayer ( { id : 'sky' , type : 'sky' , paint : { 'sky-type' : 'gradient' , 'sky-gradient' : [ 'interpolate' , [ 'linear' ] , [ 'sky-radial-progress' ] , 0 , 'rgba(255,255,255,0)' , 0.5 , '#87cefa' , 1 , '#4682b4' ] } } ) ;
958958 }
959- if ( ! map . getLayer ( 'terrain-hillshade' ) ) {
959+ if ( _mapStyle !== 'satellite3d' && ! map . getLayer ( 'terrain-hillshade' ) ) {
960960 // Insert below the first road/label layer so hillshading shows through
961961 const firstSymbol = map . getStyle ( ) . layers . find ( l => l . type === 'line' || l . type === 'symbol' ) ;
962962 map . addLayer ( {
0 commit comments