|
|
<!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> |