earth / index.html
Ravisil's picture
Add 2 files
95eceac verified
<!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"></span>
</div>
<div class="info-row">
<span class="info-label">Longitude</span>
<span class="info-value" id="longitude"></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">&times;</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>
// Dados de localizações - expandido com mais detalhes
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"
}
];
// Inicializar cena Three.js
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...");
// Criar cena
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
// Criar câmera
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 15;
// Criar renderizador com configurações de melhor qualidade
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);
// Adicionar iluminação
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...");
// Criar Terra com materiais melhorados
createEarth();
// Adicionar fundo de estrelas com mais densidade
createStars();
// Configurar interações da UI
setupUI();
// Iniciar loop de animação
animate();
// Esconder tela de carregamento quando tudo estiver pronto
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);
// Usar texturas de melhor qualidade para a Terra
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);
// Criar efeito de atmosfera/nuvens
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);
// Adicionar efeito de água
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() {
// Funcionalidade de busca aprimorada
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('');
// Adicionar eventos de clique aos resultados da busca
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);
});
// Fechar resultados da busca ao clicar fora
document.addEventListener('click', function(e) {
if (!searchBox.contains(e.target) && !searchResults.contains(e.target)) {
searchResults.style.display = 'none';
}
});
// Botões de modo
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;
});
// Controles de zoom com animação
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);
});
// Controles de camada com feedback visual
document.querySelectorAll('.layer-option').forEach(option => {
option.addEventListener('click', function() {
document.querySelectorAll('.layer-option').forEach(opt => opt.classList.remove('active'));
this.classList.add('active');
// Simular mudança de camada (em um app real isso mudaria as texturas)
console.log('Camada alterada para:', this.dataset.layer);
// Mostrar toast de confirmação
showToast(`Mudou para visualização ${this.dataset.layer}`);
});
});
// Controle deslizante de tempo com transições suaves
const timeSlider = document.getElementById('time-slider');
timeSlider.addEventListener('input', function() {
updateTimeOfDay(this.value);
});
// Botão de fechar detalhes do local
document.getElementById('close-details').addEventListener('click', function() {
document.getElementById('overlay').style.display = 'none';
document.getElementById('location-details').style.display = 'none';
});
// Lidar com redimensionamento da janela
window.addEventListener('resize', function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Manipulador de clique na Terra
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);
}
});
// Botão de favoritos
document.getElementById('bookmark-btn').addEventListener('click', function() {
if (selectedLocation) {
showToast(`${selectedLocation.name} salvo nos favoritos`);
} else {
showToast(`Nenhum local selecionado para favoritar`);
}
});
// Botão de medição
document.getElementById('measure-btn').addEventListener('click', function() {
showToast(`Ferramenta de medição ativada - clique em dois pontos no globo`);
// Em um app real, isso ativaria o modo de medição
});
// Inicializar hora do dia
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) {
// Converter lat/lng para coordenadas esféricas
const phi = (90 - lat) * (Math.PI / 180);
const theta = (180 - lng) * (Math.PI / 180);
// Calcular posição alvo
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)
);
// Armazenar posição atual
const startPosition = camera.position.clone();
const startTime = Date.now();
// Função de animação
function animateFlight() {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
// Função ease in-out
const easedProgress = progress < 0.5 ?
2 * progress * progress :
1 - Math.pow(-2 * progress + 2, 2) / 2;
// Interpolar posição
camera.position.lerpVectors(startPosition, targetPosition, easedProgress);
camera.lookAt(0, 0, 0);
// Continuar até que a duração seja alcançada
if (progress < 1) {
requestAnimationFrame(animateFlight);
} else {
// Atualizar informações da localização atual
currentLat = lat;
currentLng = lng;
updateInfoPanel();
}
}
// Iniciar animação
animateFlight();
// Encontrar e selecionar a localização
selectedLocation = locations.find(loc => loc.lat === lat && loc.lng === lng);
// Mostrar notificação toast
if (selectedLocation) {
setTimeout(() => {
showToast(`Chegou em ${selectedLocation.name}`);
}, duration);
}
// Habilitar amortecimento suave após o voo
controls.enableDamping = true;
}
function animateCameraPosition(targetZ) {
const startZ = camera.position.z;
const startTime = Date.now();
const duration = 500; // milissegundos
function animateZoom() {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
// Função quadratic ease-out
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;
// Atualizar painel de detalhes
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;
// Mostrar sobreposição e painel
document.getElementById('overlay').style.display = 'block';
document.getElementById('location-details').style.display = 'block';
selectedLocation = location;
}
function updateTimeOfDay(hour) {
// Atualizar a exibição do tempo
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}`;
// Simular ciclo dia/noite girando a Terra e ajustando a iluminação
const normalizedHour = parseFloat(hour) / 24;
earthMesh.rotation.y = normalizedHour * Math.PI * 2;
// Ajustar luz ambiente baseado no tempo
const ambientIntensity = Math.abs((normalizedHour - 0.25) % 1 - 0.5) * 2; // Picos ao meio-dia
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);
// Calcular latitude/longitude
const lat = 90 - (spherical.phi * 180 / Math.PI);
const lng = -180 + (spherical.theta * 180 / Math.PI);
// Atualizar coordenadas
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'}`;
// Calcular altitude (distância do centro menos raio da Terra)
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;
// Calcular ângulo de visão
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)}°`;
// Atualizar bússola
updateCompass(lng);
currentLat = lat;
currentLng = lng;
currentZoom = alt;
}
function updateCompass(degree) {
const arrow = document.querySelector('.compass-arrow');
const compassDegrees = document.getElementById('compass-degrees');
// Normalizar grau para 0-360
degree = (degree % 360 + 360) % 360;
// Girar seta
arrow.style.transform = `rotateZ(${-degree}deg)`;
// Atualizar direção da bússola
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;
}
// Iniciar a aplicação
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>