| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>3D Map Generator</title> |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> |
| <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> |
| <style> |
| :root { |
| --primary-color: #4361ee; |
| --secondary-color: #3f37c9; |
| --accent-color: #4895ef; |
| --text-color: #f8f9fa; |
| --bg-color: #121212; |
| --panel-bg: #1e1e1e; |
| --border-color: #333333; |
| --hover-color: #2a2a2a; |
| --success-color: #4cc9f0; |
| --warning-color: #f72585; |
| } |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| background-color: var(--bg-color); |
| color: var(--text-color); |
| height: 100vh; |
| overflow: hidden; |
| } |
| |
| .app-container { |
| display: grid; |
| grid-template-columns: 1fr 350px; |
| grid-template-rows: 60px 1fr 200px; |
| grid-template-areas: |
| "header header" |
| "map viewer" |
| "controls controls"; |
| height: 100vh; |
| gap: 10px; |
| padding: 10px; |
| } |
| |
| header { |
| grid-area: header; |
| background-color: var(--panel-bg); |
| border-radius: 8px; |
| padding: 0 20px; |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); |
| border: 1px solid var(--border-color); |
| } |
| |
| .logo { |
| font-size: 1.8rem; |
| font-weight: 700; |
| background: linear-gradient(90deg, var(--accent-color), var(--success-color)); |
| -webkit-background-clip: text; |
| background-clip: text; |
| color: transparent; |
| } |
| |
| .search-container { |
| display: flex; |
| gap: 10px; |
| width: 50%; |
| } |
| |
| #search-input { |
| flex: 1; |
| padding: 12px 15px; |
| border-radius: 6px; |
| border: 1px solid var(--border-color); |
| background-color: #2d2d2d; |
| color: var(--text-color); |
| font-size: 1rem; |
| } |
| |
| #search-btn { |
| padding: 12px 20px; |
| background-color: var(--primary-color); |
| color: white; |
| border: none; |
| border-radius: 6px; |
| cursor: pointer; |
| font-weight: 600; |
| transition: background-color 0.3s; |
| } |
| |
| #search-btn:hover { |
| background-color: var(--secondary-color); |
| } |
| |
| .map-container { |
| grid-area: map; |
| position: relative; |
| border-radius: 8px; |
| overflow: hidden; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); |
| border: 1px solid var(--border-color); |
| } |
| |
| #map { |
| height: 100%; |
| width: 100%; |
| } |
| |
| .viewer-container { |
| grid-area: viewer; |
| background-color: var(--panel-bg); |
| border-radius: 8px; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); |
| border: 1px solid var(--border-color); |
| } |
| |
| .viewer-header { |
| padding: 15px; |
| background-color: var(--hover-color); |
| font-weight: 600; |
| border-bottom: 1px solid var(--border-color); |
| } |
| |
| #model-viewer { |
| flex: 1; |
| background-color: #000; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: #999; |
| font-size: 1.2rem; |
| } |
| |
| .controls-container { |
| grid-area: controls; |
| background-color: var(--panel-bg); |
| border-radius: 8px; |
| padding: 20px; |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); |
| gap: 20px; |
| overflow-y: auto; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); |
| border: 1px solid var(--border-color); |
| } |
| |
| .control-group { |
| background-color: var(--hover-color); |
| border-radius: 6px; |
| padding: 15px; |
| border: 1px solid var(--border-color); |
| } |
| |
| .control-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 15px; |
| } |
| |
| .control-title { |
| font-size: 1.1rem; |
| font-weight: 600; |
| } |
| |
| .toggle-switch { |
| position: relative; |
| display: inline-block; |
| width: 50px; |
| height: 24px; |
| } |
| |
| .toggle-switch input { |
| opacity: 0; |
| width: 0; |
| height: 0; |
| } |
| |
| .slider { |
| position: absolute; |
| cursor: pointer; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background-color: #555; |
| transition: .4s; |
| border-radius: 24px; |
| } |
| |
| .slider:before { |
| position: absolute; |
| content: ""; |
| height: 16px; |
| width: 16px; |
| left: 4px; |
| bottom: 4px; |
| background-color: white; |
| transition: .4s; |
| border-radius: 50%; |
| } |
| |
| input:checked + .slider { |
| background-color: var(--primary-color); |
| } |
| |
| input:checked + .slider:before { |
| transform: translateX(26px); |
| } |
| |
| .slider-content { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 10px; |
| } |
| |
| .control-item { |
| display: flex; |
| flex-direction: column; |
| gap: 8px; |
| } |
| |
| label { |
| font-size: 0.9rem; |
| opacity: 0.8; |
| } |
| |
| select, input[type="color"], input[type="range"] { |
| padding: 8px; |
| border-radius: 4px; |
| border: 1px solid var(--border-color); |
| background-color: #2d2d2d; |
| color: var(--text-color); |
| } |
| |
| input[type="range"] { |
| padding: 0; |
| } |
| |
| .range-value { |
| text-align: center; |
| font-size: 0.9rem; |
| opacity: 0.8; |
| } |
| |
| .export-container { |
| display: flex; |
| gap: 15px; |
| margin-top: 20px; |
| } |
| |
| .export-btn { |
| flex: 1; |
| padding: 12px; |
| background-color: var(--success-color); |
| color: white; |
| border: none; |
| border-radius: 6px; |
| cursor: pointer; |
| font-weight: 600; |
| transition: all 0.3s; |
| } |
| |
| .export-btn:hover { |
| background-color: #3aaed8; |
| transform: translateY(-2px); |
| } |
| |
| .export-btn:active { |
| transform: translateY(0); |
| } |
| |
| .bbox-controls { |
| position: absolute; |
| top: 10px; |
| right: 10px; |
| z-index: 1000; |
| background-color: var(--panel-bg); |
| border-radius: 6px; |
| padding: 10px; |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); |
| border: 1px solid var(--border-color); |
| } |
| |
| .bbox-btn { |
| padding: 8px 12px; |
| background-color: var(--primary-color); |
| color: white; |
| border: none; |
| border-radius: 4px; |
| cursor: pointer; |
| margin: 5px; |
| font-size: 0.9rem; |
| } |
| |
| .bbox-btn:hover { |
| background-color: var(--secondary-color); |
| } |
| |
| #draw-bbox { |
| background-color: var(--warning-color); |
| } |
| |
| #draw-bbox:hover { |
| background-color: #d3166a; |
| } |
| |
| .instructions { |
| position: absolute; |
| bottom: 10px; |
| left: 10px; |
| z-index: 1000; |
| background-color: rgba(30, 30, 30, 0.8); |
| border-radius: 6px; |
| padding: 10px; |
| font-size: 0.8rem; |
| max-width: 300px; |
| } |
| |
| @media (max-width: 1024px) { |
| .app-container { |
| grid-template-columns: 1fr; |
| grid-template-rows: 60px 1fr 1fr 250px; |
| grid-template-areas: |
| "header" |
| "map" |
| "viewer" |
| "controls"; |
| } |
| } |
| |
| @media (max-width: 768px) { |
| header { |
| flex-direction: column; |
| align-items: flex-start; |
| padding: 10px; |
| } |
| |
| .search-container { |
| width: 100%; |
| margin-top: 10px; |
| } |
| |
| .controls-container { |
| grid-template-columns: 1fr; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="app-container"> |
| <header> |
| <div class="logo">3D Map Generator</div> |
| <div class="search-container"> |
| <input type="text" id="search-input" placeholder="Search for a location (e.g. Sydney, Australia)"> |
| <button id="search-btn">Search</button> |
| </div> |
| </header> |
|
|
| <div class="map-container"> |
| <div id="map"></div> |
| <div class="bbox-controls"> |
| <button id="draw-bbox" class="bbox-btn">Draw Bounding Box</button> |
| <button id="clear-bbox" class="bbox-btn">Clear Box</button> |
| </div> |
| <div class="instructions"> |
| <p>1. Search for a location or pan/zoom to your area of interest</p> |
| <p>2. Click "Draw Bounding Box" and drag on the map to select an area</p> |
| <p>3. Adjust the box by dragging its corners or edges</p> |
| <p>4. Configure your 3D model settings in the control panel</p> |
| <p>5. Click OBJ or GLB export to download your model</p> |
| </div> |
| </div> |
|
|
| <div class="viewer-container"> |
| <div class="viewer-header">3D Model Viewer</div> |
| <div id="model-viewer">3D Model will appear here after generation</div> |
| </div> |
|
|
| <div class="controls-container"> |
| <div class="control-group"> |
| <div class="control-header"> |
| <div class="control-title">Imagery Source</div> |
| </div> |
| <div class="slider-content"> |
| <div class="control-item"> |
| <label for="imagery-source">Source</label> |
| <select id="imagery-source"> |
| <option value="maptiler">MapTiler</option> |
| <option value="mapbox">Mapbox</option> |
| <option value="google">Google Earth (API Required)</option> |
| </select> |
| </div> |
| <div class="control-item"> |
| <label for="imagery-type">Type</label> |
| <select id="imagery-type"> |
| <option value="clean">Clean (No Labels)</option> |
| <option value="labeled">With Labels</option> |
| </select> |
| </div> |
| <div class="control-item"> |
| <label for="maptiler-key">MapTiler API Key</label> |
| <input type="text" id="maptiler-key" value="K05sQSujRJ1QvWTl5Nt1"> |
| </div> |
| <div class="control-item"> |
| <label for="mapbox-key">Mapbox API Key</label> |
| <input type="text" id="mapbox-key" value="pk.eyJ1IjoidzhpbjRhbTgiLCJhIjoiY21jdDczMm82MDBmYzJrcjN0a3hoaHZuNSJ9.NJXk-4YN0odvrf3yA5VPjQ"> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="control-group"> |
| <div class="control-header"> |
| <div class="control-title">Elevation Settings</div> |
| <label class="toggle-switch"> |
| <input type="checkbox" id="elevation-toggle" checked> |
| <span class="slider"></span> |
| </label> |
| </div> |
| <div class="slider-content"> |
| <div class="control-item"> |
| <label for="elevation-exaggeration">Exaggeration</label> |
| <input type="range" id="elevation-exaggeration" min="1" max="5" step="0.1" value="1.5"> |
| </div> |
| <div class="control-item"> |
| <div class="range-value" id="elevation-value">1.5x</div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="control-group"> |
| <div class="control-header"> |
| <div class="control-title">Buildings</div> |
| <label class="toggle-switch"> |
| <input type="checkbox" id="buildings-toggle"> |
| <span class="slider"></span> |
| </label> |
| </div> |
| <div class="slider-content"> |
| <div class="control-item"> |
| <label for="buildings-height">Height Exaggeration</label> |
| <input type="range" id="buildings-height" min="1" max="3" step="0.1" value="1"> |
| </div> |
| <div class="control-item"> |
| <div class="range-value" id="buildings-height-value">1.0x</div> |
| </div> |
| <div class="control-item"> |
| <label for="buildings-color">Color</label> |
| <input type="color" id="buildings-color" value="#4361ee"> |
| </div> |
| <div class="control-item"> |
| <label> </label> |
| <button class="export-btn" id="generate-btn">Generate 3D Mesh</button> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="control-group"> |
| <div class="control-header"> |
| <div class="control-title">Trees</div> |
| <label class="toggle-switch"> |
| <input type="checkbox" id="trees-toggle"> |
| <span class="slider"></span> |
| </label> |
| </div> |
| <div class="slider-content"> |
| <div class="control-item"> |
| <label for="trees-density">Density (Bushland)</label> |
| <input type="range" id="trees-density" min="1" max="10" step="1" value="5"> |
| </div> |
| <div class="control-item"> |
| <div class="range-value" id="trees-density-value">1 tree/100m</div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="control-group"> |
| <div class="control-header"> |
| <div class="control-title">Roads</div> |
| <label class="toggle-switch"> |
| <input type="checkbox" id="roads-toggle"> |
| <span class="slider"></span> |
| </label> |
| </div> |
| <div class="slider-content"> |
| <div class="control-item"> |
| <label for="roads-height">Height Exaggeration</label> |
| <input type="range" id="roads-height" min="1" max="3" step="0.1" value="1"> |
| </div> |
| <div class="control-item"> |
| <div class="range-value" id="roads-height-value">1.0x</div> |
| </div> |
| <div class="control-item"> |
| <label for="roads-width">Width Exaggeration</label> |
| <input type="range" id="roads-width" min="1" max="3" step="0.1" value="1"> |
| </div> |
| <div class="control-item"> |
| <div class="range-value" id="roads-width-value">1.0x</div> |
| </div> |
| <div class="control-item"> |
| <label for="roads-color">Color</label> |
| <input type="color" id="roads-color" value="#f72585"> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="control-group"> |
| <div class="control-header"> |
| <div class="control-title">Pathways</div> |
| <label class="toggle-switch"> |
| <input type="checkbox" id="paths-toggle"> |
| <span class="slider"></span> |
| </label> |
| </div> |
| <div class="slider-content"> |
| <div class="control-item"> |
| <label for="paths-height">Height Exaggeration</label> |
| <input type="range" id="paths-height" min="1" max="3" step="0.1" value="1"> |
| </div> |
| <div class="control-item"> |
| <div class="range-value" id="paths-height-value">1.0x</div> |
| </div> |
| <div class="control-item"> |
| <label for="paths-width">Width Exaggeration</label> |
| <input type="range" id="paths-width" min="1" max="3" step="0.1" value="1"> |
| </div> |
| <div class="control-item"> |
| <div class="range-value" id="paths-width-value">1.0x</div> |
| </div> |
| <div class="control-item"> |
| <label for="paths-color">Color</label> |
| <input type="color" id="paths-color" value="#4cc9f0"> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="export-container"> |
| <button class="export-btn" id="export-obj">Export as OBJ</button> |
| <button class="export-btn" id="export-glb">Export as GLB</button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| const map = L.map('map').setView([0, 0], 2); |
| |
| |
| const baseLayer = L.tileLayer('https://api.maptiler.com/tiles/satellite/{z}/{x}/{y}.jpg?key=K05sQSujRJ1QvWTl5Nt1', { |
| attribution: '© MapTiler', |
| maxZoom: 18 |
| }).addTo(map); |
| |
| |
| let boundingBox = null; |
| let isDrawing = false; |
| |
| |
| document.getElementById('draw-bbox').addEventListener('click', () => { |
| if (boundingBox) { |
| map.removeLayer(boundingBox); |
| } |
| |
| |
| const southWest = map.getBounds().getSouthWest(); |
| const northEast = map.getBounds().getNorthEast(); |
| const bounds = [[southWest.lat, southWest.lng], [northEast.lat, northEast.lng]]; |
| |
| boundingBox = L.rectangle(bounds, { |
| color: "#ff7800", |
| weight: 2, |
| fillOpacity: 0.1 |
| }).addTo(map); |
| |
| |
| boundingBox.editing.enable(); |
| }); |
| |
| |
| document.getElementById('clear-bbox').addEventListener('click', () => { |
| if (boundingBox) { |
| map.removeLayer(boundingBox); |
| boundingBox = null; |
| } |
| }); |
| |
| |
| document.getElementById('search-btn').addEventListener('click', () => { |
| const query = document.getElementById('search-input').value; |
| if (query) { |
| fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}`) |
| .then(response => response.json()) |
| .then(data => { |
| if (data && data[0]) { |
| const lat = parseFloat(data[0].lat); |
| const lon = parseFloat(data[0].lon); |
| map.setView([lat, lon], 15); |
| } |
| }) |
| .catch(error => console.error('Search error:', error)); |
| } |
| }); |
| |
| |
| document.getElementById('search-input').addEventListener('keypress', (e) => { |
| if (e.key === 'Enter') { |
| document.getElementById('search-btn').click(); |
| } |
| }); |
| |
| |
| const elevationSlider = document.getElementById('elevation-exaggeration'); |
| const elevationValue = document.getElementById('elevation-value'); |
| |
| elevationSlider.addEventListener('input', () => { |
| elevationValue.textContent = elevationSlider.value + 'x'; |
| }); |
| |
| |
| const buildingsHeightSlider = document.getElementById('buildings-height'); |
| const buildingsHeightValue = document.getElementById('buildings-height-value'); |
| |
| buildingsHeightSlider.addEventListener('input', () => { |
| buildingsHeightValue.textContent = buildingsHeightSlider.value + 'x'; |
| }); |
| |
| |
| const treesDensitySlider = document.getElementById('trees-density'); |
| const treesDensityValue = document.getElementById('trees-density-value'); |
| |
| treesDensitySlider.addEventListener('input', () => { |
| treesDensityValue.textContent = `1 tree/${100/treesDensitySlider.value}m`; |
| }); |
| |
| |
| const roadsHeightSlider = document.getElementById('roads-height'); |
| const roadsHeightValue = document.getElementById('roads-height-value'); |
| |
| roadsHeightSlider.addEventListener('input', () => { |
| roadsHeightValue.textContent = roadsHeightSlider.value + 'x'; |
| }); |
| |
| |
| const roadsWidthSlider = document.getElementById('roads-width'); |
| const roadsWidthValue = document.getElementById('roads-width-value'); |
| |
| roadsWidthSlider.addEventListener('input', () => { |
| roadsWidthValue.textContent = roadsWidthSlider.value + 'x'; |
| }); |
| |
| |
| const pathsHeightSlider = document.getElementById('paths-height'); |
| const pathsHeightValue = document.getElementById('paths-height-value'); |
| |
| pathsHeightSlider.addEventListener('input', () => { |
| pathsHeightValue.textContent = pathsHeightSlider.value + 'x'; |
| }); |
| |
| |
| const pathsWidthSlider = document.getElementById('paths-width'); |
| const pathsWidthValue = document.getElementById('paths-width-value'); |
| |
| pathsWidthSlider.addEventListener('input', () => { |
| pathsWidthValue.textContent = pathsWidthSlider.value + 'x'; |
| }); |
| |
| |
| document.getElementById('imagery-source').addEventListener('change', (e) => { |
| const source = e.target.value; |
| const key = source === 'maptiler' ? |
| document.getElementById('maptiler-key').value : |
| document.getElementById('mapbox-key').value; |
| |
| map.eachLayer(layer => { |
| if (layer instanceof L.TileLayer) { |
| map.removeLayer(layer); |
| } |
| }); |
| |
| let url; |
| if (source === 'maptiler') { |
| url = `https://api.maptiler.com/maps/${document.getElementById('imagery-type').value === 'clean' ? 'satellite' : 'hybrid'}/{z}/{x}/{y}.jpg?key=${key}`; |
| } else if (source === 'mapbox') { |
| url = `https://api.mapbox.com/styles/v1/mapbox/${document.getElementById('imagery-type').value === 'clean' ? 'satellite-v9' : 'satellite-streets-v12'}/tiles/{z}/{x}/{y}?access_token=${key}`; |
| } else { |
| |
| url = 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}'; |
| } |
| |
| L.tileLayer(url, { |
| attribution: `© ${source}`, |
| maxZoom: 18 |
| }).addTo(map); |
| }); |
| |
| |
| document.getElementById('imagery-type').addEventListener('change', () => { |
| document.getElementById('imagery-source').dispatchEvent(new Event('change')); |
| }); |
| |
| |
| document.getElementById('generate-btn').addEventListener('click', () => { |
| const viewer = document.getElementById('model-viewer'); |
| viewer.innerHTML = ` |
| <div style="text-align:center"> |
| <div style="font-size:3rem;margin-bottom:10px">🌍</div> |
| <div>Generating 3D mesh...</div> |
| <div style="margin-top:10px;font-size:0.9rem;opacity:0.8">This would normally download elevation data and satellite imagery</div> |
| </div> |
| `; |
| |
| |
| setTimeout(() => { |
| viewer.innerHTML = ` |
| <div style="text-align:center"> |
| <div style="font-size:3rem;margin-bottom:10px">✅</div> |
| <div>3D mesh generated successfully!</div> |
| <div style="margin-top:10px;font-size:0.9rem;opacity:0.8">Adjust settings and click export to download</div> |
| </div> |
| `; |
| }, 1500); |
| }); |
| |
| |
| document.getElementById('export-obj').addEventListener('click', () => { |
| exportModel('OBJ'); |
| }); |
| |
| document.getElementById('export-glb').addEventListener('click', () => { |
| exportModel('GLB'); |
| }); |
| |
| function exportModel(format) { |
| if (!boundingBox) { |
| alert('Please draw a bounding box first'); |
| return; |
| } |
| |
| const viewer = document.getElementById('model-viewer'); |
| viewer.innerHTML = ` |
| <div style="text-align:center"> |
| <div style="font-size:3rem;margin-bottom:10px">📦</div> |
| <div>Exporting as ${format}...</div> |
| <div style="margin-top:10px;font-size:0.9rem;opacity:0.8">Preparing download</div> |
| </div> |
| `; |
| |
| |
| setTimeout(() => { |
| |
| const blob = new Blob([`3D Model data in ${format} format`], {type: 'application/octet-stream'}); |
| const url = window.URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = `map-3d-model.${format.toLowerCase()}`; |
| document.body.appendChild(a); |
| a.click(); |
| |
| |
| setTimeout(() => { |
| document.body.removeChild(a); |
| window.URL.revokeObjectURL(url); |
| }, 100); |
| |
| viewer.innerHTML = ` |
| <div style="text-align:center"> |
| <div style="font-size:3rem;margin-bottom:10px">💾</div> |
| <div>${format} file downloaded!</div> |
| <div style="margin-top:10px;font-size:0.9rem;opacity:0.8">Your 3D model is ready for use</div> |
| </div> |
| `; |
| }, 2000); |
| } |
| |
| |
| document.getElementById('elevation-toggle').addEventListener('change', function() { |
| if (this.checked) { |
| document.getElementById('elevation-exaggeration').parentElement.parentElement.style.opacity = '1'; |
| document.getElementById('elevation-exaggeration').disabled = false; |
| } else { |
| document.getElementById('elevation-exaggeration').parentElement.parentElement.style.opacity = '0.5'; |
| document.getElementById('elevation-exaggeration').disabled = true; |
| } |
| }); |
| |
| document.getElementById('buildings-toggle').addEventListener('change', function() { |
| if (this.checked) { |
| document.getElementById('buildings-height').parentElement.parentElement.style.opacity = '1'; |
| document.getElementById('buildings-color').parentElement.parentElement.style.opacity = '1'; |
| document.getElementById('buildings-height').disabled = false; |
| document.getElementById('buildings-color').disabled = false; |
| } else { |
| document.getElementById('buildings-height').parentElement.parentElement.style.opacity = '0.5'; |
| document.getElementById('buildings-color').parentElement.parentElement.style.opacity = '0.5'; |
| document.getElementById('buildings-height').disabled = true; |
| document.getElementById('buildings-color').disabled = true; |
| } |
| }); |
| |
| document.getElementById('trees-toggle').addEventListener('change', function() { |
| if (this.checked) { |
| document.getElementById('trees-density').parentElement.parentElement.style.opacity = '1'; |
| document.getElementById('trees-density').disabled = false; |
| } else { |
| document.getElementById('trees-density').parentElement.parentElement.style.opacity = '0.5'; |
| document.getElementById('trees-density').disabled = true; |
| } |
| }); |
| |
| document.getElementById('roads-toggle').addEventListener('change', function() { |
| if (this.checked) { |
| document.querySelectorAll('#roads-height, #roads-width, #roads-color').forEach(el => { |
| el.parentElement.parentElement.style.opacity = '1'; |
| el.disabled = false; |
| }); |
| } else { |
| document.querySelectorAll('#roads-height, #roads-width, #roads-color').forEach(el => { |
| el.parentElement.parentElement.style.opacity = '0.5'; |
| el.disabled = true; |
| }); |
| } |
| }); |
| |
| document.getElementById('paths-toggle').addEventListener('change', function() { |
| if (this.checked) { |
| document.querySelectorAll('#paths-height, #paths-width, #paths-color').forEach(el => { |
| el.parentElement.parentElement.style.opacity = '1'; |
| el.disabled = false; |
| }); |
| } else { |
| document.querySelectorAll('#paths-height, #paths-width, #paths-color').forEach(el => { |
| el.parentElement.parentElement.style.opacity = '0.5'; |
| el.disabled = true; |
| }); |
| } |
| }); |
| </script> |
| </body> |
| </html> |