|
|
/**
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TERRA - 전 세계 실시간 탐사 시스템</title>
<meta property="og:title" content="TERRA: 지구 탐사 시스템">
<meta property="og:description" content="클릭 한 번으로 전 세계 어디든 거리뷰로 착륙하세요.">
<meta property="og:image" content="https://images.unsplash.com/photo-1451187580459-43490279c0fa?auto=format&fit=crop&w=1200">
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<style>
body { margin: 0; background: #000; color: #fff; font-family: 'Inter', 'Noto Sans KR', sans-serif; overflow: hidden; }
#canvas-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; }
.ui-layer { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 100; pointer-events: none; display: flex; flex-direction: column; align-items: center; }
.interactive { pointer-events: auto; }
.search-area { margin-top: 30px; width: 90%; max-width: 500px; }
.search-box { display: flex; background: rgba(0,0,0,0.85); border: 2px solid #4ade80; border-radius: 50px; padding: 5px; box-shadow: 0 0 30px rgba(74, 222, 128, 0.4); }
input { background: transparent; border: none; color: white; padding: 12px 20px; flex-grow: 1; outline: none; font-size: 1rem; width: 100px; }
button#scanBtn { background: #4ade80; color: #000; border: none; padding: 10px 25px; border-radius: 50px; font-weight: bold; cursor: pointer; transition: 0.3s; }
button#scanBtn:hover { background: #fff; transform: scale(1.05); }
/* 상태 메시지 */
#status-msg { margin-top: 15px; font-family: monospace; color: #4ade80; font-size: 0.9rem; opacity: 0; text-align: center; }
#scanner-overlay {
position: fixed; top: 55%; left: 50%; transform: translate(-50%, -50%) scale(0.95);
width: 90%; height: 75%; border: 2px solid #4ade80; border-radius: 20px;
overflow: hidden; opacity: 0; visibility: hidden; transition: 0.5s cubic-bezier(0.19, 1, 0.22, 1);
z-index: 50; background: #000; box-shadow: 0 0 100px rgba(0,0,0,1);
}
#scanner-overlay.active { opacity: 1; visibility: visible; transform: translate(-50%, -50%) scale(1); }
.view-controls { position: absolute; top: 15px; left: 15px; display: flex; gap: 8px; z-index: 60; }
.view-btn { background: rgba(0,0,0,0.8); color: #fff; border: 1px solid #4ade80; padding: 8px 15px; border-radius: 8px; cursor: pointer; font-size: 0.8rem; font-weight: bold; }
.view-btn.active { background: #4ade80; color: #000; }
iframe { width: 100%; height: 100%; border: none; background: #111; }
.close-btn { position: absolute; top: 15px; right: 15px; background: #ff4757; color: white; border: none; padding: 8px 15px; border-radius: 8px; cursor: pointer; z-index: 60; font-weight: bold; }
@media (max-width: 600px) {
.search-box { width: 100%; }
#scanner-overlay { width: 95%; height: 80%; top: 58%; }
.view-btn { padding: 6px 10px; font-size: 0.7rem; }
}
</style>
</head>
<body>
<div id="canvas-container"></div>
<div id="scanner-overlay">
<div class="view-controls">
<button class="view-btn active" id="btnStreet" xxonclick="updateFrame('street')">STREET</button>
<button class="view-btn" id="btnSat" xxonclick="updateFrame('satellite')">AERIAL</button>
</div>
<button class="close-btn" xxonclick="closeScanner()">CLOSE</button>
<iframe id="viewerFrame" allow="fullscreen; geolocation;"></iframe>
</div>
<div class="ui-layer">
<div class="search-area interactive">
<div class="search-box">
<input type="text" id="cityInput" placeholder="탐사 장소 입력..." xxonkeypress="if(event.keyCode==13) initiateScan()">
<button id="scanBtn" xxonclick="initiateScan()">SCAN</button>
</div>
<div id="status-msg">TARGET ACQUIRING...</div>
</div>
</div>
<script>
let scene, camera, renderer, earth, isRotating = true;
let currentLat, currentLon, currentAddress;
let timeline;
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('canvas-container').appendChild(renderer.domElement);
scene.add(new THREE.AmbientLight(0xffffff, 1.2));
const loader = new THREE.TextureLoader();
const texture = loader.load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/planets/earth_atmos_2048.jpg');
earth = new THREE.Mesh(new THREE.SphereGeometry(2, 64, 64), new THREE.MeshPhongMaterial({ map: texture }));
earth.rotation.y = -Math.PI / 2;
scene.add(earth);
camera.position.z = 7;
animate();
}
function animate() {
requestAnimationFrame(animate);
if (isRotating) earth.rotation.y += 0.0015;
renderer.render(scene, camera);
}
async function initiateScan() {
const query = document.getElementById('cityInput').value;
if(!query) return;
const msg = document.getElementById('status-msg');
msg.style.opacity = 1;
msg.innerText = "COORDINATING TARGET...";
const res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${query}&addressdetails=1`);
const data = await res.json();
if (data && data.length > 0) {
currentLat = parseFloat(data[0].lat);
currentLon = parseFloat(data[0].lon);
currentAddress = data[0].address;
isRotating = false;
if(timeline) timeline.kill();
const overlay = document.getElementById('scanner-overlay');
if(overlay.classList.contains('active')) gsap.to(overlay, { opacity: 0.1, duration: 0.3 });
const targetX = currentLat * (Math.PI / 180);
const targetY = -(currentLon * (Math.PI / 180)) - (Math.PI / 2);
timeline = gsap.timeline();
timeline.to(earth.rotation, { x: targetX, y: targetY, duration: 1.8, ease: "power3.inOut" })
.to(camera.position, { z: 2.8, duration: 1.2, ease: "power2.inOut" }, "-=1.5")
.add(() => {
updateFrame('street');
overlay.classList.add('active');
gsap.to(overlay, { opacity: 1, duration: 0.5 });
msg.innerText = "SCAN COMPLETE.";
setTimeout(() => msg.style.opacity = 0, 2000);
});
} else {
msg.innerText = "TARGET NOT FOUND.";
}
}
function updateFrame(type) {
const frame = document.getElementById('viewerFrame');
const isKorea = currentAddress && currentAddress.country_code === 'kr';
document.getElementById('btnStreet').classList.toggle('active', type === 'street');
document.getElementById('btnSat').classList.toggle('active', type === 'satellite');
if (type === 'street') {
if (isKorea) {
frame.src = `https://map.kakao.com/link/roadview/${currentLat},${currentLon}`;
} else {
frame.src = `https://www.google.com/maps/embed/v1/streetview?key=YOUR_API_KEY_OPTIONAL&location=${currentLat},${currentLon}&heading=210&pitch=10&fov=90`.replace('key=YOUR_API_KEY_OPTIONAL&', '');
if(!frame.src || frame.src.includes('key=')) {
frame.src = `https://maps.google.com/maps?q=${currentLat},${currentLon}&layer=c&cbll=${currentLat},${currentLon}&output=svembed`;
}
}
} else {
frame.src = `https://maps.google.com/maps?q=${currentLat},${currentLon}&t=k&z=19&output=embed`;
}
}
function closeScanner() {
document.getElementById('scanner-overlay').classList.remove('active');
document.getElementById('viewerFrame').src = "";
isRotating = true;
gsap.to(camera.position, { z: 7, duration: 1.5 });
gsap.to(earth.rotation, { x: 0, duration: 1.5 });
}
init();
</script>
</body>
</html>
**/
