1// Rotating Earth ornament for the Gallery section's theme toggle. 2// A WebGL take on the three.js webgpu_tsl_earth example: a day-textured 3// globe whose dark side lights up with city lights, wrapped in a thin 4// cloud layer and a soft atmospheric rim. Rendered into the .theme-earth 5// canvas inside the theme button, so clicking it still toggles the theme 6// (handled by theme.js). Runs only while Gallery is the active section. 7import * as THREE from "https://esm.sh/[email protected]"; 8 9const canvas = document.querySelector(".theme-earth"); 10 11// Served from the three.js repo (the npm package omits example textures). 12const TEX = 13 "https://cdn.jsdelivr.net/gh/mrdoob/three.js@r169/examples/textures/planets/"; 14 15let renderer, scene, camera, earth, clouds; 16let started = false; 17let running = false; 18 19// Day/night shader: blend the daytime map into the night-lights map 20// across the terminator, driven by the sun direction. 21const earthVertex = /* glsl */ ` 22 varying vec2 vUv; 23 varying vec3 vNormal; 24 void main() { 25 vUv = uv; 26 vNormal = normalize( normalMatrix * normal ); 27 gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 28 } 29`; 30 31const earthFragment = /* glsl */ ` 32 uniform sampler2D dayMap; 33 uniform sampler2D nightMap; 34 uniform vec3 sunDirection; 35 varying vec2 vUv; 36 varying vec3 vNormal; 37 void main() { 38 float intensity = dot( normalize( vNormal ), normalize( sunDirection ) ); 39 float mixAmount = smoothstep( -0.3, 0.2, intensity ); 40 vec3 day = texture2D( dayMap, vUv ).rgb * 1.2; 41 vec3 night = texture2D( nightMap, vUv ).rgb * 1.7; 42 vec3 color = mix( night, day, mixAmount ); 43 color += 0.06; // gentle overall lift 44 // Subtle rim / atmosphere glow toward the edges. 45 float rim = pow( 1.0 - max( dot( normalize( vNormal ), vec3( 0.0, 0.0, 1.0 ) ), 0.0 ), 3.0 ); 46 color += vec3( 0.25, 0.4, 0.7 ) * rim * 0.6; 47 gl_FragColor = vec4( color, 1.0 ); 48 } 49`; 50 51function build() { 52 renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, canvas }); 53 renderer.setPixelRatio(window.devicePixelRatio); 54 renderer.setSize(canvas.width, canvas.height, false); 55 56 scene = new THREE.Scene(); 57 58 camera = new THREE.PerspectiveCamera(40, 1, 0.1, 100); 59 camera.position.set(0, 0, 3); 60 camera.lookAt(0, 0, 0); 61 62 const loader = new THREE.TextureLoader(); 63 loader.setCrossOrigin("anonymous"); 64 const dayMap = loader.load(`${TEX}earth_atmos_2048.jpg`); 65 const nightMap = loader.load(`${TEX}earth_lights_2048.png`); 66 const cloudMap = loader.load(`${TEX}earth_clouds_1024.png`); 67 dayMap.colorSpace = THREE.SRGBColorSpace; 68 nightMap.colorSpace = THREE.SRGBColorSpace; 69 70 const geometry = new THREE.SphereGeometry(1, 48, 48); 71 72 const material = new THREE.ShaderMaterial({ 73 fragmentShader: earthFragment, 74 uniforms: { 75 dayMap: { value: dayMap }, 76 nightMap: { value: nightMap }, 77 sunDirection: { value: new THREE.Vector3(1, 0.3, 1).normalize() }, 78 }, 79 vertexShader: earthVertex, 80 }); 81 82 earth = new THREE.Mesh(geometry, material); 83 earth.rotation.z = 0.41; // ~23.5° axial tilt 84 scene.add(earth); 85 86 clouds = new THREE.Mesh( 87 new THREE.SphereGeometry(1.015, 48, 48), 88 new THREE.MeshBasicMaterial({ 89 depthWrite: false, 90 map: cloudMap, 91 opacity: 0.55, 92 transparent: true, 93 }) 94 ); 95 earth.add(clouds); 96} 97 98function tick() { 99 if (!running) { 100 return; 101 } 102 earth.rotation.y += 0.0025; 103 clouds.rotation.y += 0.0006; 104 renderer.render(scene, camera); 105 requestAnimationFrame(tick); 106} 107 108function start() { 109 if (!started) { 110 build(); 111 started = true; 112 } 113 if (running) { 114 return; 115 } 116 running = true; 117 requestAnimationFrame(tick); 118} 119 120function stop() { 121 running = false; 122} 123 124// Run the WebGL loop only while the Gallery section is showing. 125function sync() { 126 if (document.documentElement.getAttribute("data-section") === "gallery") { 127 start(); 128 } else { 129 stop(); 130 } 131} 132 133if (canvas) { 134 const observer = new MutationObserver(sync); 135 observer.observe(document.documentElement, { 136 attributeFilter: ["data-section"], 137 attributes: true, 138 }); 139 sync(); 140}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.