前端嘛 Logo
前端嘛
奇点的一生

奇点的一生

2026-07-08
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>奇点的一生</title>
  </head>

  <body>
    <style>
      body {
        margin: 0;
        overflow: hidden;
        background-color: #010103;
        font-family:
          "Inter",
          -apple-system,
          sans-serif;
        color: #fff;
      }
      canvas {
        display: block;
      }
      #overlay {
        position: absolute;
        inset: 0;
        pointer-events: none;
        display: flex;
        flex-direction: column;
        justify-content: space-between;
        padding: 40px;
        background: radial-gradient(
          circle at center,
          transparent 30%,
          rgba(0, 0, 0, 0.5) 100%
        );
        z-index: 10;
      }
      .header {
        text-align: center;
      }
      .title {
        font-size: 1.2rem;
        letter-spacing: 0.8em;
        text-transform: uppercase;
        color: #fff;
        margin-bottom: 12px;
        font-weight: 300;
        opacity: 0.9;
      }
      .status-pill {
        display: inline-block;
        padding: 6px 20px;
        background: rgba(255, 255, 255, 0.03);
        border: 1px solid rgba(255, 255, 255, 0.15);
        border-radius: 30px;
        font-size: 0.65rem;
        letter-spacing: 0.25em;
        text-transform: uppercase;
        transition: all 1.5s cubic-bezier(0.4, 0, 0.2, 1);
      }
      .hud-bottom {
        display: flex;
        justify-content: space-between;
        align-items: flex-end;
        font-family: "JetBrains Mono", monospace;
        font-size: 0.7rem;
        opacity: 0.6;
        letter-spacing: 1px;
      }
      .metric {
        margin-bottom: 6px;
      }
      .val {
        color: #00f3ff;
        font-weight: bold;
        transition: color 1.5s ease;
      }
      #vignette {
        position: fixed;
        inset: 0;
        background: radial-gradient(circle, transparent 50%, black 150%);
        pointer-events: none;
        z-index: 5;
      }
    </style>

    <script
      async
      src="https://unpkg.com/es-module-shims@1.8.0/dist/es-module-shims.js"
    ></script>
    <script type="importmap">
      {
        "imports": {
          "three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
          "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/",
          "gsap": "https://unpkg.com/gsap@3.12.5/index.js"
        }
      }
    </script>

    <div id="vignette"></div>
    <div id="overlay">
      <div class="header">
        <div class="title" id="main-title">稳定奇点</div>
        <div class="status-pill" id="status-text">拓扑结构:正常</div>
      </div>
      <div class="hud-bottom">
        <div>
          <div class="metric">质量指数: <span class="val">4.2M SOL</span></div>
          <div class="metric">
            引力透镜: <span class="val" id="lensing-val">SCHWARZSCHILD</span>
          </div>
        </div>
        <div style="text-align: right">
          <div class="metric">
            相对速度: <span class="val" id="vel-val">0.45c</span>
          </div>
          <div class="metric">辐射探测: <span class="val">已开启</span></div>
        </div>
      </div>
    </div>

    <script type="module">
      import * as THREE from "three";
      import { OrbitControls } from "three/addons/controls/OrbitControls.js";
      import gsap from "gsap";

      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(
        40,
        window.innerWidth / window.innerHeight,
        0.1,
        1000,
      );
      camera.position.set(60, 30, 60);

      const renderer = new THREE.WebGLRenderer({
        antialias: true,
        powerPreference: "high-performance",
      });
      renderer.setSize(window.innerWidth, window.innerHeight);
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
      renderer.toneMapping = THREE.ACESFilmicToneMapping;
      renderer.toneMappingExposure = 1.6;
      document.body.appendChild(renderer.domElement);

      const controls = new OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.03;
      controls.autoRotate = true;
      controls.autoRotateSpeed = 0.4;

      const noiseChunk = `
        vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
        vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
        vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
        vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
        float snoise(vec3 v) {
            const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;
            const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);
            vec3 i  = floor(v + dot(v, C.yyy) );
            vec3 x0 = v - i + dot(i, C.xxx) ;
            vec3 g = step(x0.yzx, x0.xyz);
            vec3 l = 1.0 - g;
            vec3 i1 = min( g.xyz, l.zxy );
            vec3 i2 = max( g.xyz, l.zxy );
            vec3 x1 = x0 - i1 + C.xxx;
            vec3 x2 = x0 - i2 + C.yyy;
            vec3 x3 = x0 - D.yyy;
            i = mod289(i);
            vec4 p = permute( permute( permute( i.z + vec4(0.0, i1.z, i2.z, 1.0 )) + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
            float n_ = 0.142857142857;
            vec3  ns = n_ * D.wyz - D.xzx;
            vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
            vec4 x_ = floor(j * ns.z);
            vec4 y_ = floor(j - 7.0 * x_ );
            vec4 x = x_ *ns.x + ns.yyyy;
            vec4 y = y_ *ns.x + ns.yyyy;
            vec4 h = 1.0 - abs(x) - abs(y);
            vec4 b0 = vec4( x.xy, y.xy );
            vec4 b1 = vec4( x.zw, y.zw );
            vec4 s0 = floor(b0)*2.0 + 1.0;
            vec4 s1 = floor(b1)*2.0 + 1.0;
            vec4 sh = -step(h, vec4(0.0));
            vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
            vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
            vec3 p0 = vec3(a0.xy,h.x);
            vec3 p1 = vec3(a0.zw,h.y);
            vec3 p2 = vec3(a1.xy,h.z);
            vec3 p3 = vec3(a1.zw,h.w);
            vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
            p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
            vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
            m = m * m;
            return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3) ) );
        }
    `;

      const coreGroup = new THREE.Group();
      scene.add(coreGroup);

      const bhMat = new THREE.MeshBasicMaterial({ color: 0x000000 });
      const bhGeo = new THREE.SphereGeometry(4, 64, 64);
      coreGroup.add(new THREE.Mesh(bhGeo, bhMat));

      const auraMat = new THREE.ShaderMaterial({
        uniforms: { uTime: { value: 0 }, uIntensity: { value: 1.0 } },
        vertexShader: `
            varying vec3 vNormal;
            varying vec3 vView;
            void main() {
                vNormal = normalize(normalMatrix * normal);
                vView = normalize(-(modelViewMatrix * vec4(position, 1.0)).xyz);
                gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
            }
        `,
        fragmentShader: `
            uniform float uIntensity;
            varying vec3 vNormal;
            varying vec3 vView;
            void main() {
                float rim = pow(1.0 - max(dot(vNormal, vView), 0.0), 4.0);
                gl_FragColor = vec4(vec3(1.0, 0.45, 0.1) * rim * uIntensity * 5.0, 1.0);
            }
        `,
        side: THREE.BackSide,
        transparent: true,
        blending: THREE.AdditiveBlending,
      });
      coreGroup.add(
        new THREE.Mesh(new THREE.SphereGeometry(4.25, 64, 64), auraMat),
      );

      const instanceCount = 5000;
      const streakGeo = new THREE.CylinderGeometry(0.01, 0.12, 2.2, 3);
      streakGeo.rotateX(Math.PI / 2);

      const diskMaterial = new THREE.ShaderMaterial({
        uniforms: {
          uTime: { value: 0 },
          uMorph: { value: 0.1 },
          uCompression: { value: 1.0 },
          uIntensity: { value: 1.0 },
          uOrbitScale: { value: 1.0 },
        },
        vertexShader: `
            ${noiseChunk}
            uniform float uTime;
            uniform float uMorph;
            uniform float uCompression;
            uniform float uIntensity;
            uniform float uOrbitScale;
            varying vec3 vColor;
            varying float vOpacity;
            void main() {
                vec4 instPos = instanceMatrix * vec4(0.0, 0.0, 0.0, 1.0);
                float rOriginal = length(instPos.xz);
                float r = rOriginal * uCompression;
                float initialAngle = atan(instPos.z, instPos.x);
                float orbitalVelocity = (1.5 / sqrt(rOriginal)) * uOrbitScale;
                float currentAngle = initialAngle + (uTime * orbitalVelocity);
                vec3 morphedWorldPos = vec3(cos(currentAngle) * r, instPos.y, sin(currentAngle) * r);
                float noise = snoise(vec3(morphedWorldPos.x * 0.08, morphedWorldPos.z * 0.08, uTime * 0.3));
                morphedWorldPos.y += noise * uMorph * 4.0;
                vec3 viewDir = normalize(cameraPosition - morphedWorldPos);
                vec3 orbitDir = normalize(vec3(-sin(currentAngle), 0.0, cos(currentAngle)));
                float doppler = dot(orbitDir, viewDir);
                vec3 hot = vec3(1.0, 0.95, 0.9);
                vec3 warm = vec3(1.0, 0.45, 0.1);
                vec3 cool = vec3(0.1, 0.35, 1.0);
                vec3 color = mix(cool, warm, smoothstep(45.0, 12.0, r));
                color = mix(color, hot, smoothstep(10.0, 4.0, r));
                vColor = color * (1.3 + doppler * 0.7) * uIntensity;
                vOpacity = (smoothstep(3.8, 5.5, r) * (1.0 - smoothstep(38.0, 48.0, r))) * 0.8;
                float deltaAngle = currentAngle - initialAngle;
                float c = cos(deltaAngle);
                float s = sin(deltaAngle);
                mat3 rotY = mat3(
                    c, 0, s,
                    0, 1, 0,
                   -s, 0, c
                );
                vec3 localPos = (instanceMatrix * vec4(position, 0.0)).xyz;
                vec3 rotatedLocalPos = rotY * localPos;
                gl_Position = projectionMatrix * viewMatrix * vec4(morphedWorldPos + rotatedLocalPos, 1.0);
            }
        `,
        fragmentShader: `
            varying vec3 vColor;
            varying float vOpacity;
            void main() {
                gl_FragColor = vec4(vColor, vOpacity);
            }
        `,
        transparent: true,
        blending: THREE.AdditiveBlending,
        depthWrite: false,
      });

      const instancedDisk = new THREE.InstancedMesh(
        streakGeo,
        diskMaterial,
        instanceCount,
      );
      const dummy = new THREE.Object3D();

      for (let i = 0; i < instanceCount; i++) {
        const r = 5 + Math.pow(Math.random(), 1.3) * 40;
        const angle = Math.random() * Math.PI * 2;
        dummy.position.set(
          Math.cos(angle) * r,
          (Math.random() - 0.5) * (8 / r),
          Math.sin(angle) * r,
        );
        dummy.lookAt(
          dummy.position.x + Math.sin(angle),
          dummy.position.y,
          dummy.position.z - Math.cos(angle),
        );
        dummy.updateMatrix();
        instancedDisk.setMatrixAt(i, dummy.matrix);
      }
      scene.add(instancedDisk);

      const config = [
        {
          title: "稳定奇点",
          status: "拓扑结构:正常",
          morph: 0.1,
          compress: 1.0,
          intensity: 1.0,
          rotate: 0.4,
          camY: 25,
          camDist: 85,
          orbit: 1.0,
          color: "#00f3ff",
          vel: "0.45c",
        },
        {
          title: "吸积湍流",
          status: "拓扑结构:波动",
          morph: 4.5,
          compress: 1.15,
          intensity: 1.4,
          rotate: 1.5,
          camY: 45,
          camDist: 95,
          orbit: 1.8,
          color: "#ffaa00",
          vel: "0.78c",
        },
        {
          title: "相对论坍缩",
          status: "拓扑结构:临界",
          morph: 0.8,
          compress: 0.38,
          intensity: 3.5,
          rotate: 5.0,
          camY: 12,
          camDist: 55,
          orbit: 4.5,
          color: "#ff0044",
          vel: "0.99c",
        },
      ];

      let stateIdx = 0;
      const mainTitle = document.getElementById("main-title");
      const statusText = document.getElementById("status-text");
      const velVal = document.getElementById("vel-val");
      const camControl = { distance: 85 };

      function transition() {
        stateIdx = (stateIdx + 1) % config.length;
        const s = config[stateIdx];
        const tl = gsap.timeline({
          defaults: { duration: 4.0, ease: "power2.inOut" },
        });
        tl.to(diskMaterial.uniforms.uMorph, { value: s.morph }, 0);
        tl.to(diskMaterial.uniforms.uCompression, { value: s.compress }, 0);
        tl.to(diskMaterial.uniforms.uIntensity, { value: s.intensity }, 0);
        tl.to(diskMaterial.uniforms.uOrbitScale, { value: s.orbit }, 0);
        tl.to(auraMat.uniforms.uIntensity, { value: s.intensity }, 0);
        tl.to(controls, { autoRotateSpeed: s.rotate }, 0);
        tl.to(camera.position, { y: s.camY }, 0);
        tl.to(camControl, { distance: s.camDist }, 0);
        gsap.to([mainTitle, statusText, ".val"], {
          opacity: 0,
          duration: 0.8,
          onComplete: () => {
            mainTitle.innerText = s.title;
            statusText.innerText = s.status;
            statusText.style.color = s.color;
            statusText.style.borderColor = s.color;
            velVal.innerText = s.vel;
            velVal.style.color = s.color;
            gsap.to([mainTitle, statusText, ".val"], {
              opacity: 1,
              duration: 1.2,
            });
          },
        });
      }

      setInterval(transition, 10000);

      const clock = new THREE.Clock();
      function animate() {
        const time = clock.getElapsedTime();
        diskMaterial.uniforms.uTime.value = time;
        auraMat.uniforms.uTime.value = time;
        instancedDisk.rotation.y += 0.0005;
        const currentDir = new THREE.Vector3()
          .subVectors(camera.position, controls.target)
          .normalize();
        camera.position.x =
          controls.target.x + currentDir.x * camControl.distance;
        camera.position.z =
          controls.target.z + currentDir.z * camControl.distance;
        controls.update();
        renderer.render(scene, camera);
        requestAnimationFrame(animate);
      }

      window.addEventListener("resize", () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      });

      animate();
    </script>
  </body>
</html>