//#region assets/images/home/earth_light_gray_white.png var earth_light_gray_white_default = "" + new URL("../images/earth_light_gray_white-D7Fpf71y.png", import.meta.url).href; //#endregion //#region assets/images/about/location.svg var location_default = "data:image/svg+xml,%3csvg%20viewBox='0%200%2030%2041.1846'%20xmlns='http://www.w3.org/2000/svg'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='30.000000'%20height='41.184570'%20fill='none'%20customFrame='%23000000'%3e%3cpath%20id='减去顶层'%20d='M14.898%2041.1845C14.898%2041.1845%2030%2031.7422%2030%2014.8666C30%206.6297%2023.2653%200%2015.102%200C6.73469%200%200%206.6297%200%2014.8666C0%2030.5368%2014.898%2041.1845%2014.898%2041.1845ZM20.2041%2015.0225C20.2041%2017.7964%2017.9198%2020.045%2015.102%2020.045C12.2843%2020.045%2010%2017.7964%2010%2015.0225C10%2012.2487%2012.2843%2010%2015.102%2010C17.9198%2010%2020.2041%2012.2487%2020.2041%2015.0225Z'%20fill='rgb(239,67,53)'%20fill-rule='evenodd'%20/%3e%3c/svg%3e"; //#endregion //#region js/earth.js (function() { const getUiLang = window.getUiLang || (() => (location.pathname.split("/")[1] === "en" ? "en" : "zh")); function markerName(data) { return getUiLang() === "en" ? (data.nameEn || data.nameZh) : (data.nameZh || data.nameEn); } const canvas = document.getElementById("earth-canvas"); if (!canvas || !window.THREE) return; const container = canvas.parentElement; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(40, container.clientWidth / container.clientHeight, .1, 1e3); camera.position.z = 1.5; const renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true }); renderer.setSize(container.clientWidth, container.clientHeight); renderer.setPixelRatio(window.devicePixelRatio); const controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = .05; controls.enablePan = true; controls.enableZoom = false; controls.autoRotate = false; controls.rotateSpeed = .1; controls.minPolarAngle = Math.PI / 2; controls.maxPolarAngle = Math.PI / 2; const geometry = new THREE.SphereGeometry(.5, 64, 64); const hemiLight = new THREE.HemisphereLight("#e0e0e0", "#6a8aaa", .5); scene.add(hemiLight); const bounceLight = new THREE.DirectionalLight("#000000", .1); bounceLight.position.set(0, -5, 0); scene.add(bounceLight); const ambientLight = new THREE.AmbientLight("#e0e0e0", .9); scene.add(ambientLight); const textureLoader = new THREE.TextureLoader(); const material = new THREE.MeshPhongMaterial({ color: "#efefef", emissive: "#000000", shininess: 20, transparent: true, opacity: .9 }); const earth = new THREE.Mesh(geometry, material); earth.renderOrder = 0; textureLoader.load(earth_light_gray_white_default, function(texture) { material.map = texture; material.color.setHex(16777215); material.needsUpdate = true; }, void 0, function(err) { console.warn("Earth texture failed to load, using default color.", err); }); scene.add(earth); const markersData = [ { nameZh: "上海", nameEn: "Shanghai", lat: 31.2304, lon: 121.4737 }, { nameZh: "东京", nameEn: "Tokyo", lat: 35.6762, lon: 139.6503 }, { nameZh: "伊斯坦布尔", nameEn: "Istanbul", lat: 41.0082, lon: 28.9784 }, { nameZh: "伦敦", nameEn: "London", lat: 51.5074, lon: -.1278 }, { nameZh: "加尔各答", nameEn: "Kolkata", lat: 22.5726, lon: 88.3639 }, { nameZh: "北京", nameEn: "Beijing", lat: 39.9042, lon: 116.4074 }, { nameZh: "华沙", nameEn: "Warsaw", lat: 52.2297, lon: 21.0122 }, { nameZh: "南京", nameEn: "Nanjing", lat: 32.0603, lon: 118.7969 }, { nameZh: "吉隆坡", nameEn: "Kuala Lumpur", lat: 3.139, lon: 101.6869 }, { nameZh: "圣保罗", nameEn: "São Paulo", lat: -23.5505, lon: -46.6333 }, { nameZh: "圣安东尼奥", nameEn: "San Antonio", lat: 29.4241, lon: -98.4936 }, { nameZh: "墨西哥城", nameEn: "Mexico City", lat: 19.4326, lon: -99.1332 }, { nameZh: "多伦多", nameEn: "Toronto", lat: 43.6532, lon: -79.3832 }, { nameZh: "奥克兰", nameEn: "Auckland", lat: -36.8485, lon: 174.7633 }, { nameZh: "孟买", nameEn: "Mumbai", lat: 19.076, lon: 72.8777 }, { nameZh: "安地比斯", nameEn: "Antibes", lat: 43.5804, lon: 7.1251 }, { nameZh: "山景城", nameEn: "Mountain View", lat: 37.3861, lon: -122.0839 }, { nameZh: "巴黎", nameEn: "Paris", lat: 48.8566, lon: 2.3522 }, { nameZh: "布宜诺斯艾利斯", nameEn: "Buenos Aires", lat: -34.6037, lon: -58.3816 }, { nameZh: "开普敦", nameEn: "Cape Town", lat: -33.9249, lon: 18.4241 }, { nameZh: "悉尼", nameEn: "Sydney", lat: -33.8688, lon: 151.2093 }, { nameZh: "新加坡", nameEn: "Singapore", lat: 1.3521, lon: 103.8198 }, { nameZh: "新德里", nameEn: "New Delhi", lat: 28.6139, lon: 77.209 }, { nameZh: "曼谷", nameEn: "Bangkok", lat: 13.7563, lon: 100.5018 }, { nameZh: "法兰克福", nameEn: "Frankfurt", lat: 50.1109, lon: 8.6821 }, { nameZh: "波哥大", nameEn: "Bogotá", lat: 4.711, lon: -74.0721 }, { nameZh: "深圳", nameEn: "Shenzhen", lat: 22.5431, lon: 114.0579 }, { nameZh: "牛津", nameEn: "Oxford", lat: 51.752, lon: -1.2577 }, { nameZh: "特拉维夫", nameEn: "Tel Aviv", lat: 32.0853, lon: 34.7818 }, { nameZh: "科伦坡", nameEn: "Colombo", lat: 6.9271, lon: 79.8612 }, { nameZh: "米兰", nameEn: "Milan", lat: 45.4642, lon: 9.19 }, { nameZh: "纽约", nameEn: "New York", lat: 40.7128, lon: -74.006 }, { nameZh: "莫斯科", nameEn: "Moscow", lat: 55.7558, lon: 37.6173 }, { nameZh: "达卡", nameEn: "Dhaka", lat: 23.8103, lon: 90.4125 }, { nameZh: "迪拜", nameEn: "Dubai", lat: 25.2048, lon: 55.2708 }, { nameZh: "邦加罗尔", nameEn: "Bengaluru", lat: 12.9716, lon: 77.5946 }, { nameZh: "金奈", nameEn: "Chennai", lat: 13.0827, lon: 80.2707 }, { nameZh: "雅加达", nameEn: "Jakarta", lat: -6.2088, lon: 106.8456 }, { nameZh: "首尔", nameEn: "Seoul", lat: 37.5665, lon: 126.978 }, { nameZh: "香港", nameEn: "Hong Kong", lat: 22.3193, lon: 114.1694 }, { nameZh: "全球总部福州", nameEn: "Global HQ Fuzhou", lat: 26.0745, lon: 119.2965 } ]; const markerMeshes = []; let fuzhouMarkerMesh = null; function latLonToVector3(lat, lon, radius = 1) { const phi = (90 - lat) * (Math.PI / 180); const theta = (lon + 180) * (Math.PI / 180); const x = -radius * Math.sin(phi) * Math.cos(theta); const y = radius * Math.cos(phi); const z = radius * Math.sin(phi) * Math.sin(theta); return new THREE.Vector3(x, y, z); } const defaultMarkerGeometry = new THREE.CircleGeometry(.012, 24); const defaultMarkerMaterial = new THREE.MeshBasicMaterial({ color: 15680309, side: THREE.FrontSide, depthWrite: false, transparent: true, opacity: 1 }); const svgWidth = .03; const svgHeight = .04; const locationTexture = textureLoader.load(location_default); const fuzhouMarkerGeometry = new THREE.PlaneGeometry(svgWidth, svgHeight); fuzhouMarkerGeometry.translate(0, svgHeight / 2, 0); const fuzhouMarkerMaterial = new THREE.MeshBasicMaterial({ map: locationTexture, color: 16777215, side: THREE.FrontSide, depthWrite: false, transparent: true, opacity: 1 }); markersData.forEach((data) => { const position = latLonToVector3(data.lat, data.lon, .5); let marker; if (data.nameZh === "全球总部福州") { marker = new THREE.Mesh(fuzhouMarkerGeometry, fuzhouMarkerMaterial); fuzhouMarkerMesh = marker; } else marker = new THREE.Mesh(defaultMarkerGeometry, defaultMarkerMaterial); marker.position.copy(position); const normal = position.clone().normalize(); marker.position.add(normal.multiplyScalar(.01)); marker.renderOrder = 1; marker.userData = { name: markerName(data), nameZh: data.nameZh }; earth.add(marker); markerMeshes.push(marker); }); const targetLon = 9.6869; earth.rotation.y = -(targetLon * Math.PI / 180) + Math.PI; const tooltip = document.createElement("div"); tooltip.style.position = "absolute"; tooltip.style.padding = "8px"; tooltip.style.color = "#2E353A"; tooltip.style.fontSize = "16px"; tooltip.style.fontFamily = "MiSans VF"; tooltip.style.textAlign = "center"; tooltip.style.lineHeight = "20px"; tooltip.style.whiteSpace = "nowrap"; tooltip.style.pointerEvents = "none"; tooltip.style.display = "none"; tooltip.style.zIndex = "1000"; tooltip.style.transform = "translate(-50%, 15px)"; tooltip.classList.add("tooltip-arrow"); container.appendChild(tooltip); const fuzhouTooltip = tooltip.cloneNode(true); fuzhouTooltip.innerHTML = (window.t || ((k) => k))("earth.hq_fuzhou_html"); container.appendChild(fuzhouTooltip); const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); canvas.addEventListener("mousemove", (e) => { const rect = canvas.getBoundingClientRect(); mouse.x = (e.clientX - rect.left) / rect.width * 2 - 1; mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(markerMeshes); if (intersects.length > 0) { const object = intersects[0].object; const name = object.userData.name; if (object.userData.nameZh !== "全球总部福州") { tooltip.textContent = name; tooltip.style.display = "block"; const vector = object.position.clone(); object.updateWorldMatrix(true, false); vector.setFromMatrixPosition(object.matrixWorld); vector.project(camera); const containerRect = container.getBoundingClientRect(); const x = (vector.x * .5 + .5) * rect.width + (rect.left - containerRect.left); const y = (-(vector.y * .5) + .5) * rect.height + (rect.top - containerRect.top); tooltip.style.left = `${x}px`; tooltip.style.top = `${y - 10}px`; } else tooltip.style.display = "none"; document.body.style.cursor = "pointer"; } else { tooltip.style.display = "none"; document.body.style.cursor = "default"; } }); function animate() { requestAnimationFrame(animate); earth.rotation.y += 5e-4; markerMeshes.forEach((marker) => { marker.lookAt(camera.position); const worldPosition = new THREE.Vector3(); marker.getWorldPosition(worldPosition); const viewDirection = new THREE.Vector3().subVectors(camera.position, worldPosition).normalize(); const dot = worldPosition.clone().normalize().dot(viewDirection); let scaleValue = Math.max(0, dot); scaleValue = Math.pow(scaleValue, .5); marker.scale.setScalar(scaleValue); }); if (fuzhouMarkerMesh) { const worldPosition = new THREE.Vector3(); fuzhouMarkerMesh.getWorldPosition(worldPosition); const viewDirection = new THREE.Vector3().subVectors(camera.position, worldPosition).normalize(); if (worldPosition.clone().normalize().dot(viewDirection) > 0) { fuzhouTooltip.style.display = "block"; const vector = fuzhouMarkerMesh.position.clone(); fuzhouMarkerMesh.updateWorldMatrix(true, false); vector.setFromMatrixPosition(fuzhouMarkerMesh.matrixWorld); vector.project(camera); const rect = canvas.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); const x = (vector.x * .5 + .5) * rect.width + (rect.left - containerRect.left); const y = (-(vector.y * .5) + .5) * rect.height + (rect.top - containerRect.top); fuzhouTooltip.style.left = `${x}px`; fuzhouTooltip.style.top = `${y - 10}px`; } else fuzhouTooltip.style.display = "none"; } controls.update(); renderer.render(scene, camera); } animate(); window.addEventListener("resize", () => { const width = container.clientWidth; const height = container.clientHeight; camera.aspect = width / height; camera.updateProjectionMatrix(); renderer.setSize(width, height); }); })(); //#endregion