| <!DOCTYPE html> |
| <html lang="pt-BR"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Explorador Terrestre | Visualização 3D do Planeta</title> |
| <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet"> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| font-family: 'Roboto', sans-serif; |
| } |
| body { |
| overflow: hidden; |
| background: #121212; |
| color: #fff; |
| } |
| #earth-container { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| z-index: 1; |
| } |
| .ui-panel { |
| position: fixed; |
| background: rgba(0, 0, 0, 0.7); |
| border-radius: 10px; |
| padding: 15px; |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); |
| backdrop-filter: blur(10px); |
| z-index: 100; |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| #search-panel { |
| top: 20px; |
| left: 50%; |
| transform: translateX(-50%); |
| width: 450px; |
| max-width: 90%; |
| transition: all 0.3s ease; |
| } |
| #search-panel:focus-within { |
| transform: translateX(-50%) scale(1.02); |
| box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4); |
| } |
| #search-box { |
| width: 100%; |
| padding: 12px 20px; |
| border-radius: 30px; |
| border: none; |
| background: rgba(255, 255, 255, 0.95); |
| font-size: 16px; |
| box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); |
| color: #333; |
| transition: all 0.3s ease; |
| } |
| #search-box:focus { |
| outline: none; |
| background: white; |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); |
| } |
| #search-results { |
| max-height: 300px; |
| overflow-y: auto; |
| margin-top: 15px; |
| display: none; |
| border-radius: 8px; |
| background: rgba(30, 30, 30, 0.9); |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); |
| } |
| .search-item { |
| padding: 12px 15px; |
| border-radius: 8px; |
| margin: 5px; |
| cursor: pointer; |
| transition: all 0.2s; |
| display: flex; |
| align-items: center; |
| background: rgba(255, 255, 255, 0.05); |
| } |
| .search-item:hover { |
| background: rgba(65, 105, 225, 0.3); |
| transform: translateX(2px); |
| } |
| .search-item i { |
| margin-right: 12px; |
| min-width: 20px; |
| text-align: center; |
| color: #4CAF50; |
| } |
| #controls-panel { |
| bottom: 25px; |
| left: 25px; |
| display: flex; |
| gap: 8px; |
| } |
| .control-btn { |
| background: rgba(255, 255, 255, 0.1); |
| border: none; |
| color: white; |
| width: 48px; |
| height: 48px; |
| border-radius: 50%; |
| cursor: pointer; |
| transition: all 0.2s; |
| font-size: 18px; |
| display: inline-flex; |
| align-items: center; |
| justify-content: center; |
| box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); |
| } |
| .control-btn:hover { |
| background: rgba(255, 255, 255, 0.25); |
| transform: scale(1.1); |
| } |
| .control-btn.active { |
| background: #4CAF50; |
| transform: scale(1.1); |
| } |
| .control-btn i { |
| pointer-events: none; |
| } |
| #info-panel { |
| bottom: 25px; |
| right: 25px; |
| width: 300px; |
| padding: 20px; |
| } |
| .info-row { |
| display: flex; |
| justify-content: space-between; |
| padding: 10px 0; |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| .info-row:last-child { |
| border-bottom: none; |
| } |
| .info-label { |
| color: #aaa; |
| font-size: 14px; |
| } |
| .info-value { |
| font-weight: 500; |
| color: #fff; |
| font-size: 15px; |
| } |
| #loading-screen { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: #121212; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| z-index: 1000; |
| transition: opacity 0.5s; |
| } |
| .loading-spinner { |
| width: 60px; |
| height: 60px; |
| border: 6px solid rgba(255, 255, 255, 0.1); |
| border-radius: 50%; |
| border-top-color: #4CAF50; |
| animation: spin 1.2s linear infinite; |
| margin-bottom: 25px; |
| } |
| @keyframes spin { |
| to { transform: rotate(360deg); } |
| } |
| #loading-text { |
| color: #fff; |
| margin-top: 20px; |
| font-size: 18px; |
| text-align: center; |
| max-width: 80%; |
| } |
| #layer-panel { |
| top: 25px; |
| right: 25px; |
| width: 220px; |
| } |
| .panel-title { |
| margin-bottom: 15px; |
| color: #4CAF50; |
| font-size: 16px; |
| font-weight: 500; |
| display: flex; |
| align-items: center; |
| } |
| .panel-title i { |
| margin-right: 10px; |
| } |
| .layer-option { |
| display: flex; |
| align-items: center; |
| padding: 12px 15px; |
| cursor: pointer; |
| border-radius: 8px; |
| margin: 5px 0; |
| transition: all 0.2s; |
| background: rgba(255, 255, 255, 0.05); |
| } |
| .layer-option:hover { |
| background: rgba(255, 255, 255, 0.15); |
| } |
| .layer-option.active { |
| background: rgba(76, 175, 80, 0.2); |
| } |
| .layer-option i { |
| margin-right: 12px; |
| width: 20px; |
| text-align: center; |
| color: #4CAF50; |
| } |
| #overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: rgba(0, 0, 0, 0.7); |
| display: none; |
| z-index: 500; |
| backdrop-filter: blur(5px); |
| } |
| #location-details { |
| position: fixed; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| background: rgba(20, 20, 20, 0.95); |
| border-radius: 12px; |
| padding: 25px; |
| width: 550px; |
| max-width: 90%; |
| max-height: 80vh; |
| overflow-y: auto; |
| z-index: 600; |
| display: none; |
| box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| .details-header { |
| display: flex; |
| justify-content: space-between; |
| margin-bottom: 20px; |
| padding-bottom: 15px; |
| border-bottom: 1px solid rgba(255, 255, 255, 0.2); |
| align-items: center; |
| } |
| .details-header h3 { |
| font-size: 22px; |
| font-weight: 500; |
| } |
| .close-btn { |
| background: none; |
| border: none; |
| color: #aaa; |
| font-size: 24px; |
| cursor: pointer; |
| transition: color 0.2s; |
| padding: 5px; |
| } |
| .close-btn:hover { |
| color: white; |
| transform: scale(1.1); |
| } |
| .details-image { |
| width: 100%; |
| height: 220px; |
| background-size: cover; |
| background-position: center; |
| border-radius: 8px; |
| margin-bottom: 20px; |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); |
| } |
| #location-description { |
| color: #ddd; |
| line-height: 1.6; |
| margin-bottom: 20px; |
| } |
| .location-stats { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 15px; |
| } |
| .location-stat { |
| background: rgba(255, 255, 255, 0.05); |
| padding: 12px; |
| border-radius: 8px; |
| } |
| .stat-label { |
| color: #aaa; |
| font-size: 13px; |
| margin-bottom: 5px; |
| } |
| .stat-value { |
| font-weight: 500; |
| font-size: 15px; |
| } |
| #compass { |
| position: fixed; |
| top: 25px; |
| left: 25px; |
| width: 65px; |
| height: 65px; |
| background: rgba(0, 0, 0, 0.7); |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| z-index: 100; |
| border: 2px solid rgba(255, 255, 255, 0.1); |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); |
| } |
| .compass-arrow { |
| font-size: 28px; |
| transition: transform 0.2s; |
| color: #4CAF50; |
| } |
| #zoom-controls { |
| position: fixed; |
| right: 25px; |
| bottom: 160px; |
| display: flex; |
| flex-direction: column; |
| background: rgba(0, 0, 0, 0.7); |
| border-radius: 30px; |
| padding: 10px 6px; |
| z-index: 100; |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| .zoom-btn { |
| background: rgba(255, 255, 255, 0.1); |
| border: none; |
| color: white; |
| width: 45px; |
| height: 45px; |
| border-radius: 50%; |
| margin: 5px; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 20px; |
| transition: all 0.2s; |
| } |
| .zoom-btn:hover { |
| background: rgba(255, 255, 255, 0.2); |
| transform: scale(1.1); |
| } |
| #time-controls { |
| position: fixed; |
| top: 25px; |
| left: 120px; |
| width: 280px; |
| z-index: 100; |
| } |
| #time-slider { |
| width: 100%; |
| margin-top: 15px; |
| -webkit-appearance: none; |
| height: 6px; |
| background: rgba(255, 255, 255, 0.1); |
| border-radius: 3px; |
| outline: none; |
| } |
| #time-slider::-webkit-slider-thumb { |
| -webkit-appearance: none; |
| width: 18px; |
| height: 18px; |
| background: #4CAF50; |
| border-radius: 50%; |
| cursor: pointer; |
| } |
| .time-display-container { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 8px; |
| } |
| .time-label { |
| display: flex; |
| align-items: center; |
| font-size: 14px; |
| color: #aaa; |
| } |
| .time-label i { |
| margin-right: 8px; |
| color: #4CAF50; |
| } |
| #time-display { |
| font-weight: 500; |
| } |
| @media (max-width: 768px) { |
| #search-panel { |
| width: 90%; |
| } |
| |
| #layer-panel, #time-controls { |
| top: 80px; |
| left: 50%; |
| transform: translateX(-50%); |
| width: 90%; |
| } |
| |
| #controls-panel { |
| bottom: 80px; |
| left: 50%; |
| transform: translateX(-50%); |
| } |
| |
| #compass { |
| top: auto; |
| bottom: 180px; |
| left: 50%; |
| transform: translateX(-50%); |
| } |
| |
| #zoom-controls { |
| right: 50%; |
| transform: translateX(50%); |
| bottom: 300px; |
| flex-direction: row; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <div id="loading-screen"> |
| <div class="loading-spinner"></div> |
| <h2 style="color: #fff; margin-bottom: 10px;">Explorador Terrestre</h2> |
| <p id="loading-text">Inicializando renderizador 3D...</p> |
| </div> |
|
|
| <div id="earth-container"></div> |
|
|
| <div id="compass"> |
| <i class="compass-arrow fas fa-arrow-up"></i> |
| <div id="compass-degrees">N</div> |
| </div> |
|
|
| <div class="ui-panel" id="search-panel"> |
| <input type="text" id="search-box" placeholder="Busque por cidades, pontos turísticos, coordenadas..." autocomplete="off"> |
| <div id="search-results"></div> |
| </div> |
|
|
| <div class="ui-panel" id="layer-panel"> |
| <div class="panel-title"><i class="fas fa-layer-group"></i> Camadas do Mapa</div> |
| <div class="layer-option active" data-layer="satellite"> |
| <i class="fas fa-satellite-dish"></i> Satélite |
| </div> |
| <div class="layer-option" data-layer="terrain"> |
| <i class="fas fa-mountain"></i> Terreno |
| </div> |
| <div class="layer-option" data-layer="roads"> |
| <i class="fas fa-road"></i> Estradas |
| </div> |
| <div class="layer-option" data-layer="night"> |
| <i class="fas fa-moon"></i> Luzes Noturnas |
| </div> |
| </div> |
|
|
| <div class="ui-panel" id="time-controls"> |
| <div class="time-display-container"> |
| <span class="time-label"><i class="fas fa-clock"></i> Hora do Dia</span> |
| <span id="time-display">12:00 PM</span> |
| </div> |
| <input type="range" id="time-slider" min="0" max="24" step="0.1" value="12"> |
| </div> |
|
|
| <div class="ui-panel" id="controls-panel"> |
| <button class="control-btn active" id="rotate-btn" title="Rotacionar"> |
| <i class="fas fa-sync-alt"></i> |
| </button> |
| <button class="control-btn" id="fly-btn" title="Modo Voo"> |
| <i class="fas fa-paper-plane"></i> |
| </button> |
| <button class="control-btn" id="measure-btn" title="Medir Distância"> |
| <i class="fas fa-ruler"></i> |
| </button> |
| <button class="control-btn" id="bookmark-btn" title="Salvar Local"> |
| <i class="fas fa-bookmark"></i> |
| </button> |
| </div> |
|
|
| <div id="zoom-controls"> |
| <button class="zoom-btn" id="zoom-in" title="Aproximar"> |
| <i class="fas fa-plus"></i> |
| </button> |
| <button class="zoom-btn" id="zoom-out" title="Afastar"> |
| <i class="fas fa-minus"></i> |
| </button> |
| </div> |
|
|
| <div class="ui-panel" id="info-panel"> |
| <div class="info-row"> |
| <span class="info-label">Latitude</span> |
| <span class="info-value" id="latitude">0°</span> |
| </div> |
| <div class="info-row"> |
| <span class="info-label">Longitude</span> |
| <span class="info-value" id="longitude">0°</span> |
| </div> |
| <div class="info-row"> |
| <span class="info-label">Altitude</span> |
| <span class="info-value" id="altitude">35,786 km</span> |
| </div> |
| <div class="info-row"> |
| <span class="info-label">Ângulo de Visão</span> |
| <span class="info-value" id="view-angle">10°</span> |
| </div> |
| </div> |
|
|
| <div id="overlay"></div> |
|
|
| <div id="location-details"> |
| <div class="details-header"> |
| <h3 id="location-title">Detalhes do Local</h3> |
| <button class="close-btn" id="close-details">×</button> |
| </div> |
| <div class="details-image" id="location-image"></div> |
| <p id="location-description">Esta é uma descrição do local selecionado.</p> |
| <div class="location-stats"> |
| <div class="location-stat"> |
| <div class="stat-label">Coordenadas</div> |
| <div class="stat-value" id="location-coords">0°, 0°</div> |
| </div> |
| <div class="location-stat"> |
| <div class="stat-label">Elevação</div> |
| <div class="stat-value" id="location-elevation">0 m</div> |
| </div> |
| <div class="location-stat"> |
| <div class="stat-label">Tipo</div> |
| <div class="stat-value" id="location-type">Ponto Turístico</div> |
| </div> |
| <div class="location-stat"> |
| <div class="stat-label">População</div> |
| <div class="stat-value" id="location-population">-</div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| const locations = [ |
| { |
| name: "Torre Eiffel", |
| lat: 48.8584, |
| lng: 2.2945, |
| type: "landmark", |
| description: "A Torre Eiffel é uma torre de treliça de ferro forjado no Champ de Mars em Paris, França. Ela recebeu o nome do engenheiro Gustave Eiffel, cuja empresa projetou e construiu a torre. Construída em 1889 como o arco de entrada para a Feira Mundial de 1889, tornou-se um ícone cultural global da França e uma das estruturas mais reconhecíveis do mundo.", |
| image: "https://images.unsplash.com/photo-1431274172761-fca41d930114?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80", |
| elevation: "300 m", |
| population: "N/A", |
| country: "França" |
| }, |
| { |
| name: "Estátua da Liberdade", |
| lat: 40.6892, |
| lng: -74.0445, |
| type: "landmark", |
| description: "A Estátua da Liberdade é uma colossal escultura neoclássica na Ilha da Liberdade no porto de Nova York. A estátua foi projetada pelo escultor francês Frédéric Auguste Bartholdi e sua estrutura metálica foi construída por Gustave Eiffel. A estátua foi dedicada em 28 de outubro de 1886 como um presente dos Estados Unidos para o povo da França.", |
| image: "https://images.unsplash.com/photo-1499856871958-5b9627545d1a?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80", |
| elevation: "93 m", |
| population: "N/A", |
| country: "Estados Unidos" |
| }, |
| { |
| name: "Grande Pirâmide de Gizé", |
| lat: 29.9792, |
| lng: 31.1342, |
| type: "landmark", |
| description: "A Grande Pirâmide de Gizé é a mais antiga e maior das pirâmides no complexo de pirâmides de Gizé, no Egito. É a mais antiga das Sete Maravilhas do Mundo Antigo e a única que permanece em grande parte intacta. Egiptólogos acreditam que foi construída como um túmulo para o faraó Khufu da Quarta Dinastia Egípcia ao longo de um período de 20 anos, concluído por volta de 2560 AEC.", |
| image: "https://images.unsplash.com/photo-1503177119275-0ee32b63fb8a?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80", |
| elevation: "138 m", |
| population: "N/A", |
| country: "Egito" |
| }, |
| { |
| name: "Monte Everest", |
| lat: 27.9881, |
| lng: 86.9250, |
| type: "mountain", |
| description: "O Monte Everest é a montanha mais alta acima do nível do mar, localizada na sub-cordilheira Mahalangur Himal dos Himalaias. A fronteira China-Nepal atravessa seu ponto de cume. Sua elevação de 8.848,86 m foi estabelecida mais recentemente em 2020 pelas autoridades chinesas e nepalesas. O Everest atrai muitos alpinistas, incluindo montanhistas altamente experientes.", |
| image: "https://images.unsplash.com/photo-1587135991091-082eebf57ffc?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80", |
| elevation: "8.848 m", |
| population: "N/A", |
| country: "Nepal/China" |
| }, |
| { |
| name: "Casa da Ópera de Sydney", |
| lat: -33.8568, |
| lng: 151.2153, |
| type: "landmark", |
| description: "A Casa da Ópera de Sydney é um centro de artes cênicas de múltiplos locais no porto de Sydney, Nova Gales do Sul, Austrália. É um dos edifícios mais famosos e distintos do século XX. Projetado pelo arquiteto dinamarquês Jørn Utzon, o local foi formalmente inaugurado em 20 de outubro de 1973 após um período de gestação começando com a seleção de Utzon como vencedor de uma competição internacional de design em 1957.", |
| image: "https://images.unsplash.com/photo-1524820197278-540916411e20?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80", |
| elevation: "20 m", |
| population: "N/A", |
| country: "Austrália" |
| }, |
| { |
| name: "Nova York", |
| lat: 40.7128, |
| lng: -74.0060, |
| type: "city", |
| description: "A cidade de Nova York compreende 5 distritos situados onde o rio Hudson encontra o Oceano Atlântico. Em seu núcleo está Manhattan, um distrito densamente povoado que está entre os principais centros comerciais, financeiros e culturais do mundo. Seus locais icônicos incluem arranha-céus como o Empire State Building e o vasto Central Park. O teatro da Broadway é encenado na Times Square iluminada por neon.", |
| image: "https://images.unsplash.com/photo-1485871981521-5b1fd3805eee?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80", |
| elevation: "10 m", |
| population: "8,4 milhões", |
| country: "Estados Unidos" |
| }, |
| { |
| name: "Tóquio", |
| lat: 35.6762, |
| lng: 139.6503, |
| type: "city", |
| description: "Tóquio, a movimentada capital do Japão, mistura o ultramoderno e o tradicional, desde arranha-céus iluminados por neon até templos históricos. O opulento Santuário Xintoísta Meiji é conhecido por seu portão imponente e pelas florestas ao redor. O Palácio Imperial fica em meio a grandes jardins públicos. Os muitos museus da cidade oferecem exposições que vão desde arte clássica até um teatro kabuki reconstruído.", |
| image: "https://images.unsplash.com/photo-1542051841857-5f90071e7989?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80", |
| elevation: "40 m", |
| population: "13,9 milhões", |
| country: "Japão" |
| }, |
| { |
| name: "Grand Canyon", |
| lat: 36.1069, |
| lng: -112.1129, |
| type: "natural", |
| description: "O Grand Canyon no Arizona é uma formação natural distinguida por faixas estratificadas de rocha vermelha, revelando milhões de anos de história geológica em corte transversal. Vasto em escala, o cânion tem em média 16 km de largura e 1,6 km de profundidade ao longo de seus 446 km de comprimento. Grande parte da área é um parque nacional, com corredeiras do rio Colorado e vistas panorâmicas.", |
| image: "https://images.unsplash.com/photo-1509316785289-025107a2dc08?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80", |
| elevation: "2.600 m", |
| population: "N/A", |
| country: "Estados Unidos" |
| }, |
| { |
| name: "Floresta Amazônica", |
| lat: -3.4653, |
| lng: -62.2159, |
| type: "natural", |
| description: "A Floresta Amazônica, cobrindo grande parte do noroeste do Brasil e estendendo-se para Colômbia, Peru e outros países sul-americanos, é a maior floresta tropical do mundo, famosa por sua biodiversidade. Ela é cruzada por milhares de rios, incluindo o poderoso Amazonas. Cidades ribeirinhas, com arquitetura do século XIX dos dias de boom da borracha, incluem Manaus e Belém no Brasil e Leticia na Colômbia.", |
| image: "https://images.unsplash.com/photo-1584696049838-8e39294120e5?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80", |
| elevation: "100 m", |
| population: "N/A", |
| country: "Brasil" |
| }, |
| { |
| name: "Grande Barreira de Corais", |
| lat: -18.2871, |
| lng: 147.6992, |
| type: "natural", |
| description: "A Grande Barreira de Corais é o maior sistema de recifes de coral do mundo, composto por mais de 2.900 recifes individuais e 900 ilhas se estendendo por mais de 2.300 quilômetros em uma área de aproximadamente 344.400 quilômetros quadrados. O recife está localizado no Mar de Coral, na costa de Queensland, Austrália.", |
| image: "https://images.unsplash.com/photo-1544551763-46a013bb70d5?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80", |
| elevation: "Nível do mar", |
| population: "N/A", |
| country: "Austrália" |
| } |
| ]; |
| |
| let scene, camera, renderer, earthMesh, cloudsMesh; |
| let controls, pointOfInterest, isRotating = true; |
| let currentLat = 0, currentLng = 0, currentZoom = 35.786; |
| let selectedLocation = null; |
| |
| function init() { |
| updateLoadingText("Criando cena 3D..."); |
| |
| |
| scene = new THREE.Scene(); |
| scene.background = new THREE.Color(0x000000); |
| |
| |
| camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); |
| camera.position.z = 15; |
| |
| |
| renderer = new THREE.WebGLRenderer({ |
| antialias: true, |
| powerPreference: "high-performance", |
| logarithmicDepthBuffer: true |
| }); |
| renderer.setPixelRatio(window.devicePixelRatio || 1); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| renderer.shadowMap.enabled = true; |
| renderer.shadowMap.type = THREE.PCFSoftShadowMap; |
| renderer.gammaOutput = true; |
| renderer.gammaFactor = 2.2; |
| document.getElementById('earth-container').appendChild(renderer.domElement); |
| |
| |
| const ambientLight = new THREE.AmbientLight(0x404040, 1.5); |
| scene.add(ambientLight); |
| |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2); |
| directionalLight.position.set(5, 3, 5); |
| directionalLight.castShadow = true; |
| directionalLight.shadow.mapSize.width = 2048; |
| directionalLight.shadow.mapSize.height = 2048; |
| scene.add(directionalLight); |
| |
| const light = new THREE.PointLight(0xffffff, 0.2); |
| light.position.set(0, 20, 0); |
| scene.add(light); |
| |
| controls = new THREE.OrbitControls(camera, renderer.domElement); |
| controls.enableDamping = true; |
| controls.dampingFactor = 0.05; |
| controls.rotateSpeed = 0.5; |
| controls.minDistance = 5; |
| controls.maxDistance = 50; |
| controls.maxPolarAngle = Math.PI; |
| controls.minPolarAngle = 0; |
| |
| updateLoadingText("Carregando texturas da Terra..."); |
| |
| |
| createEarth(); |
| |
| |
| createStars(); |
| |
| |
| setupUI(); |
| |
| |
| animate(); |
| |
| |
| setTimeout(() => { |
| document.getElementById('loading-screen').style.opacity = '0'; |
| setTimeout(() => { |
| document.getElementById('loading-screen').style.display = 'none'; |
| }, 500); |
| }, 1500); |
| } |
| |
| function createEarth() { |
| const radius = 5; |
| const geometry = new THREE.SphereGeometry(radius, 128, 128); |
| |
| |
| const textureLoader = new THREE.TextureLoader(); |
| |
| const earthMaterial = new THREE.MeshStandardMaterial({ |
| map: textureLoader.load('https://threejs.org/examples/textures/planets/earth_atmos_2048.jpg'), |
| bumpMap: textureLoader.load('https://threejs.org/examples/textures/planets/earth_normal_2048.jpg'), |
| bumpScale: 0.05, |
| roughnessMap: textureLoader.load('https://threejs.org/examples/textures/planets/earth_specular_2048.jpg'), |
| metalness: 0.1, |
| roughness: 0.8, |
| displacementScale: 0.1 |
| }); |
| |
| earthMesh = new THREE.Mesh(geometry, earthMaterial); |
| earthMesh.castShadow = true; |
| earthMesh.receiveShadow = true; |
| scene.add(earthMesh); |
| |
| |
| const cloudsGeometry = new THREE.SphereGeometry(radius * 1.005, 128, 128); |
| const cloudsMaterial = new THREE.MeshStandardMaterial({ |
| map: textureLoader.load('https://threejs.org/examples/textures/planets/earth_clouds_1024.png'), |
| transparent: true, |
| opacity: 0.3, |
| alphaMap: textureLoader.load('https://threejs.org/examples/textures/planets/earth_clouds_1024.png'), |
| side: THREE.DoubleSide, |
| blending: THREE.AdditiveBlending |
| }); |
| |
| cloudsMesh = new THREE.Mesh(cloudsGeometry, cloudsMaterial); |
| scene.add(cloudsMesh); |
| |
| |
| const waterGeometry = new THREE.SphereGeometry(radius, 128, 128); |
| const waterMaterial = new THREE.MeshStandardMaterial({ |
| color: 0x44aadd, |
| transparent: true, |
| opacity: 0.2, |
| metalness: 0.7, |
| roughness: 0.1, |
| side: THREE.DoubleSide |
| }); |
| |
| const waterMesh = new THREE.Mesh(waterGeometry, waterMaterial); |
| scene.add(waterMesh); |
| } |
| |
| function createStars() { |
| const starsGeometry = new THREE.BufferGeometry(); |
| const starsMaterial = new THREE.PointsMaterial({ |
| color: 0xffffff, |
| size: 0.15, |
| sizeAttenuation: true, |
| transparent: true |
| }); |
| |
| const starsVertices = []; |
| for (let i = 0; i < 10000; i++) { |
| const x = (Math.random() - 0.5) * 2000; |
| const y = (Math.random() - 0.5) * 2000; |
| const z = (Math.random() - 0.5) * 2000; |
| starsVertices.push(x, y, z); |
| } |
| |
| starsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starsVertices, 3)); |
| const stars = new THREE.Points(starsGeometry, starsMaterial); |
| scene.add(stars); |
| } |
| |
| function setupUI() { |
| |
| const searchBox = document.getElementById('search-box'); |
| const searchResults = document.getElementById('search-results'); |
| |
| let searchDebounce; |
| searchBox.addEventListener('input', function() { |
| clearTimeout(searchDebounce); |
| const query = this.value.toLowerCase().trim(); |
| |
| if (query.length < 2) { |
| searchResults.style.display = 'none'; |
| return; |
| } |
| |
| searchDebounce = setTimeout(() => { |
| const results = locations.filter(loc => |
| loc.name.toLowerCase().includes(query) || |
| loc.description.toLowerCase().includes(query) || |
| loc.country.toLowerCase().includes(query) || |
| loc.type.toLowerCase().includes(query) |
| ); |
| |
| if (results.length > 0) { |
| searchResults.innerHTML = results.map(loc => ` |
| <div class="search-item" data-lat="${loc.lat}" data-lng="${loc.lng}" data-name="${loc.name}"> |
| <i class="fas fa-${getLocationIcon(loc.type)}"></i> |
| <div> |
| <div style="font-weight:500;">${loc.name}</div> |
| <div style="font-size:12px;color:#aaa;">${loc.country} • ${loc.type.charAt(0).toUpperCase() + loc.type.slice(1)}</div> |
| </div> |
| </div> |
| `).join(''); |
| |
| |
| document.querySelectorAll('.search-item').forEach(item => { |
| item.addEventListener('click', function() { |
| const lat = parseFloat(this.getAttribute('data-lat')); |
| const lng = parseFloat(this.getAttribute('data-lng')); |
| const name = this.getAttribute('data-name'); |
| |
| flyToLocation(lat, lng); |
| showLocationDetails(name); |
| |
| searchResults.style.display = 'none'; |
| searchBox.value = ''; |
| }); |
| }); |
| |
| searchResults.style.display = 'block'; |
| } else { |
| searchResults.innerHTML = '<div style="padding:15px;color:#aaa;text-align:center;">Nenhum resultado encontrado<br><small>Tente um termo diferente</small></div>'; |
| searchResults.style.display = 'block'; |
| } |
| }, 300); |
| }); |
| |
| |
| document.addEventListener('click', function(e) { |
| if (!searchBox.contains(e.target) && !searchResults.contains(e.target)) { |
| searchResults.style.display = 'none'; |
| } |
| }); |
| |
| |
| document.getElementById('rotate-btn').addEventListener('click', function() { |
| isRotating = true; |
| this.classList.add('active'); |
| document.getElementById('fly-btn').classList.remove('active'); |
| controls.enableRotate = true; |
| controls.autoRotate = true; |
| controls.autoRotateSpeed = 0.5; |
| }); |
| |
| document.getElementById('fly-btn').addEventListener('click', function() { |
| isRotating = false; |
| this.classList.add('active'); |
| document.getElementById('rotate-btn').classList.remove('active'); |
| controls.autoRotate = false; |
| }); |
| |
| |
| document.getElementById('zoom-in').addEventListener('click', function() { |
| animateCameraPosition(camera.position.z * 0.8); |
| }); |
| |
| document.getElementById('zoom-out').addEventListener('click', function() { |
| animateCameraPosition(camera.position.z * 1.2); |
| }); |
| |
| |
| document.querySelectorAll('.layer-option').forEach(option => { |
| option.addEventListener('click', function() { |
| document.querySelectorAll('.layer-option').forEach(opt => opt.classList.remove('active')); |
| this.classList.add('active'); |
| |
| |
| console.log('Camada alterada para:', this.dataset.layer); |
| |
| |
| showToast(`Mudou para visualização ${this.dataset.layer}`); |
| }); |
| }); |
| |
| |
| const timeSlider = document.getElementById('time-slider'); |
| timeSlider.addEventListener('input', function() { |
| updateTimeOfDay(this.value); |
| }); |
| |
| |
| document.getElementById('close-details').addEventListener('click', function() { |
| document.getElementById('overlay').style.display = 'none'; |
| document.getElementById('location-details').style.display = 'none'; |
| }); |
| |
| |
| window.addEventListener('resize', function() { |
| camera.aspect = window.innerWidth / window.innerHeight; |
| camera.updateProjectionMatrix(); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| }); |
| |
| |
| renderer.domElement.addEventListener('click', function(event) { |
| if (event.target === renderer.domElement) { |
| const randomLoc = locations[Math.floor(Math.random() * locations.length)]; |
| flyToLocation(randomLoc.lat, randomLoc.lng); |
| setTimeout(() => { |
| showLocationDetails(randomLoc.name); |
| }, 1000); |
| } |
| }); |
| |
| |
| document.getElementById('bookmark-btn').addEventListener('click', function() { |
| if (selectedLocation) { |
| showToast(`${selectedLocation.name} salvo nos favoritos`); |
| } else { |
| showToast(`Nenhum local selecionado para favoritar`); |
| } |
| }); |
| |
| |
| document.getElementById('measure-btn').addEventListener('click', function() { |
| showToast(`Ferramenta de medição ativada - clique em dois pontos no globo`); |
| |
| }); |
| |
| |
| updateTimeOfDay(timeSlider.value); |
| } |
| |
| function getLocationIcon(type) { |
| switch(type) { |
| case 'city': return 'city'; |
| case 'mountain': return 'mountain'; |
| case 'natural': return 'tree'; |
| default: return 'landmark'; |
| } |
| } |
| |
| function flyToLocation(lat, lng, duration = 1000) { |
| |
| const phi = (90 - lat) * (Math.PI / 180); |
| const theta = (180 - lng) * (Math.PI / 180); |
| |
| |
| const radius = camera.position.distanceTo(new THREE.Vector3(0, 0, 0)); |
| const targetPosition = new THREE.Vector3( |
| radius * Math.sin(phi) * Math.cos(theta), |
| radius * Math.cos(phi), |
| radius * Math.sin(phi) * Math.sin(theta) |
| ); |
| |
| |
| const startPosition = camera.position.clone(); |
| const startTime = Date.now(); |
| |
| |
| function animateFlight() { |
| const elapsed = Date.now() - startTime; |
| const progress = Math.min(elapsed / duration, 1); |
| |
| |
| const easedProgress = progress < 0.5 ? |
| 2 * progress * progress : |
| 1 - Math.pow(-2 * progress + 2, 2) / 2; |
| |
| |
| camera.position.lerpVectors(startPosition, targetPosition, easedProgress); |
| camera.lookAt(0, 0, 0); |
| |
| |
| if (progress < 1) { |
| requestAnimationFrame(animateFlight); |
| } else { |
| |
| currentLat = lat; |
| currentLng = lng; |
| updateInfoPanel(); |
| } |
| } |
| |
| |
| animateFlight(); |
| |
| |
| selectedLocation = locations.find(loc => loc.lat === lat && loc.lng === lng); |
| |
| |
| if (selectedLocation) { |
| setTimeout(() => { |
| showToast(`Chegou em ${selectedLocation.name}`); |
| }, duration); |
| } |
| |
| |
| controls.enableDamping = true; |
| } |
| |
| function animateCameraPosition(targetZ) { |
| const startZ = camera.position.z; |
| const startTime = Date.now(); |
| const duration = 500; |
| |
| function animateZoom() { |
| const elapsed = Date.now() - startTime; |
| const progress = Math.min(elapsed / duration, 1); |
| |
| |
| const easedProgress = -1 * progress * (progress - 2); |
| |
| camera.position.z = startZ + (targetZ - startZ) * easedProgress; |
| |
| if (progress < 1) { |
| requestAnimationFrame(animateZoom); |
| } |
| } |
| |
| animateZoom(); |
| } |
| |
| function showLocationDetails(name) { |
| const location = locations.find(loc => loc.name === name); |
| if (!location) return; |
| |
| |
| document.getElementById('location-title').textContent = location.name; |
| document.getElementById('location-description').textContent = location.description; |
| document.getElementById('location-image').style.backgroundImage = `url(${location.image})`; |
| document.getElementById('location-coords').textContent = |
| `${Math.abs(location.lat).toFixed(4)}° ${location.lat > 0 ? 'N' : 'S'}, ` + |
| `${Math.abs(location.lng).toFixed(4)}° ${location.lng > 0 ? 'E' : 'W'}`; |
| document.getElementById('location-elevation').textContent = location.elevation; |
| document.getElementById('location-type').textContent = |
| location.type.charAt(0).toUpperCase() + location.type.slice(1); |
| document.getElementById('location-population').textContent = location.population; |
| |
| |
| document.getElementById('overlay').style.display = 'block'; |
| document.getElementById('location-details').style.display = 'block'; |
| |
| selectedLocation = location; |
| } |
| |
| function updateTimeOfDay(hour) { |
| |
| const period = hour >= 12 ? 'PM' : 'AM'; |
| const displayHour = hour % 12 === 0 ? 12 : hour % 12; |
| const minutes = Math.floor((hour % 1) * 60).toString().padStart(2, '0'); |
| document.getElementById('time-display').textContent = `${displayHour}:${minutes} ${period}`; |
| |
| |
| const normalizedHour = parseFloat(hour) / 24; |
| earthMesh.rotation.y = normalizedHour * Math.PI * 2; |
| |
| |
| const ambientIntensity = Math.abs((normalizedHour - 0.25) % 1 - 0.5) * 2; |
| scene.children.forEach(child => { |
| if (child instanceof THREE.AmbientLight) { |
| child.intensity = ambientIntensity * 0.8 + 0.5; |
| } |
| }); |
| } |
| |
| function updateInfoPanel() { |
| const spherical = new THREE.Spherical(); |
| spherical.setFromVector3(camera.position); |
| |
| |
| const lat = 90 - (spherical.phi * 180 / Math.PI); |
| const lng = -180 + (spherical.theta * 180 / Math.PI); |
| |
| |
| document.getElementById('latitude').textContent = `${Math.abs(lat).toFixed(2)}° ${lat > 0 ? 'N' : 'S'}`; |
| document.getElementById('longitude').textContent = `${Math.abs(lng).toFixed(2)}° ${lng > 0 ? 'E' : 'W'}`; |
| |
| |
| const alt = (camera.position.distanceTo(new THREE.Vector3(0, 0, 0)) - 5) * 6371; |
| let altitudeText; |
| |
| if (alt < 0.1) { |
| altitudeText = `${(alt).toFixed(0)} m`; |
| } else if (alt < 1000) { |
| altitudeText = `${(alt).toFixed(2)} km`; |
| } else { |
| altitudeText = `${(alt).toFixed(0)} km`; |
| } |
| document.getElementById('altitude').textContent = altitudeText; |
| |
| |
| const viewAngle = Math.atan(5 / camera.position.distanceTo(new THREE.Vector3(0, 0, 0))) * 180 / Math.PI; |
| document.getElementById('view-angle').textContent = `${viewAngle.toFixed(1)}°`; |
| |
| |
| updateCompass(lng); |
| |
| currentLat = lat; |
| currentLng = lng; |
| currentZoom = alt; |
| } |
| |
| function updateCompass(degree) { |
| const arrow = document.querySelector('.compass-arrow'); |
| const compassDegrees = document.getElementById('compass-degrees'); |
| |
| |
| degree = (degree % 360 + 360) % 360; |
| |
| |
| arrow.style.transform = `rotateZ(${-degree}deg)`; |
| |
| |
| const directions = ['N', 'NE', 'L', 'SE', 'S', 'SO', 'O', 'NO']; |
| const dirIndex = Math.round(((degree % 360) / 45)) % 8; |
| compassDegrees.textContent = directions[dirIndex]; |
| } |
| |
| function showToast(message) { |
| const toast = document.createElement('div'); |
| toast.style.position = 'fixed'; |
| toast.style.bottom = '20px'; |
| toast.style.left = '50%'; |
| toast.style.transform = 'translateX(-50%)'; |
| toast.style.backgroundColor = 'rgba(0,0,0,0.8)'; |
| toast.style.color = 'white'; |
| toast.style.padding = '10px 20px'; |
| toast.style.borderRadius = '20px'; |
| toast.style.zIndex = '1000'; |
| toast.style.opacity = '0'; |
| toast.style.transition = 'opacity 0.3s'; |
| toast.textContent = message; |
| |
| document.body.appendChild(toast); |
| |
| setTimeout(() => { |
| toast.style.opacity = '1'; |
| }, 10); |
| |
| setTimeout(() => { |
| toast.style.opacity = '0'; |
| setTimeout(() => { |
| toast.remove(); |
| }, 300); |
| }, 3000); |
| } |
| |
| function animate() { |
| requestAnimationFrame(animate); |
| |
| if (isRotating) { |
| earthMesh.rotation.y += 0.0005; |
| cloudsMesh.rotation.y += 0.0006; |
| } |
| |
| controls.update(); |
| updateInfoPanel(); |
| renderer.render(scene, camera); |
| } |
| |
| function updateLoadingText(text) { |
| document.getElementById('loading-text').textContent = text; |
| } |
| |
| |
| window.onload = init; |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Feito com <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p><p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=Ravisil/earth" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> |
| </html> |