替换官网品牌Logo并完善WebGL资源
This commit is contained in:
+852
@@ -0,0 +1,852 @@
|
||||
import * as THREE from './vendor/three.module.min.js';
|
||||
|
||||
const root = document.getElementById('heroWebgl');
|
||||
const canvas = document.getElementById('heroWebglCanvas');
|
||||
|
||||
if (root && canvas) {
|
||||
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
||||
const lowPower = window.innerWidth < 640 || Boolean(connection && connection.saveData) || (navigator.deviceMemory && navigator.deviceMemory <= 2);
|
||||
const staticMode = reducedMotion || lowPower;
|
||||
let renderer;
|
||||
|
||||
try {
|
||||
renderer = new THREE.WebGLRenderer({
|
||||
canvas,
|
||||
alpha: false,
|
||||
antialias: !lowPower,
|
||||
depth: true,
|
||||
stencil: false,
|
||||
powerPreference: staticMode ? 'low-power' : 'high-performance'
|
||||
});
|
||||
} catch (error) {
|
||||
root.classList.add('has-error');
|
||||
}
|
||||
|
||||
if (renderer) {
|
||||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||
renderer.toneMappingExposure = 0.96;
|
||||
renderer.shadowMap.enabled = !lowPower;
|
||||
renderer.shadowMap.type = THREE.PCFShadowMap;
|
||||
renderer.autoClear = false;
|
||||
|
||||
const resolvedTheme = document.documentElement.dataset.theme || 'light';
|
||||
const initialDark = resolvedTheme === 'dark' ? 1 : 0;
|
||||
const uniforms = {
|
||||
uTime: { value: 0 },
|
||||
uDark: { value: initialDark },
|
||||
uResolution: { value: new THREE.Vector2(1, 1) },
|
||||
uMouse: { value: new THREE.Vector2(0.72, 0.4) }
|
||||
};
|
||||
|
||||
const backgroundScene = new THREE.Scene();
|
||||
const backgroundCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
|
||||
const backgroundMaterial = new THREE.ShaderMaterial({
|
||||
uniforms,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
toneMapped: false,
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
precision highp float;
|
||||
varying vec2 vUv;
|
||||
uniform float uTime;
|
||||
uniform float uDark;
|
||||
uniform vec2 uResolution;
|
||||
uniform vec2 uMouse;
|
||||
|
||||
float hash21(vec2 p) {
|
||||
p = fract(p * vec2(123.34, 456.21));
|
||||
p += dot(p, p + 45.32);
|
||||
return fract(p.x * p.y);
|
||||
}
|
||||
|
||||
float segmentDistance(vec2 p, vec2 a, vec2 b) {
|
||||
vec2 pa = p - a;
|
||||
vec2 ba = b - a;
|
||||
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
|
||||
return length(pa - ba * h);
|
||||
}
|
||||
|
||||
float beam(vec2 p, vec2 a, vec2 b, float width) {
|
||||
return exp(-segmentDistance(p, a, b) / width);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = vUv;
|
||||
float aspect = uResolution.x / max(uResolution.y, 1.0);
|
||||
vec2 p = uv - 0.5;
|
||||
p.x *= aspect;
|
||||
float t = uTime;
|
||||
|
||||
vec3 lightTop = vec3(0.992, 0.997, 1.0);
|
||||
vec3 lightBottom = vec3(0.88, 0.93, 1.0);
|
||||
vec3 darkTop = vec3(0.016, 0.027, 0.044);
|
||||
vec3 darkBottom = vec3(0.025, 0.055, 0.08);
|
||||
vec3 color = mix(mix(lightBottom, lightTop, uv.y), mix(darkBottom, darkTop, uv.y), uDark);
|
||||
|
||||
vec2 focus = vec2(aspect * 0.18 + (uMouse.x - 0.5) * 0.035, -0.12 + (0.5 - uMouse.y) * 0.025);
|
||||
float halo = exp(-length((p - focus) / vec2(0.48, 0.42)) * 2.2);
|
||||
color += mix(vec3(0.19, 0.52, 1.0), vec3(0.06, 0.42, 0.72), uDark) * halo * mix(0.12, 0.23, uDark);
|
||||
|
||||
float skyWash = exp(-length((p - vec2(aspect * 0.37, 0.39)) / vec2(0.52, 0.34)) * 2.0);
|
||||
color += mix(vec3(0.2, 0.43, 0.78), vec3(0.04, 0.18, 0.36), uDark) * skyWash * mix(0.11, 0.14, uDark);
|
||||
|
||||
vec2 leftSource = vec2(-aspect * 0.58, -0.38);
|
||||
vec2 rightTarget = vec2(aspect * 0.62, -0.19);
|
||||
float cyanBeam = beam(p, leftSource, focus + vec2(-0.06, -0.08), 0.009);
|
||||
float cyanGlow = beam(p, leftSource, focus + vec2(-0.06, -0.08), 0.055);
|
||||
float warmBeam = beam(p, focus + vec2(0.04, 0.02), rightTarget, 0.008);
|
||||
float warmGlow = beam(p, focus + vec2(0.04, 0.02), rightTarget, 0.052);
|
||||
color += vec3(0.18, 0.88, 1.0) * (cyanBeam * 1.48 + cyanGlow * 0.34);
|
||||
color += vec3(1.0, 0.58, 0.18) * (warmBeam * 2.05 + warmGlow * 0.4);
|
||||
|
||||
float spectrumA = beam(p, leftSource + vec2(0.0, 0.025), focus + vec2(0.1, -0.025), 0.0045);
|
||||
float spectrumB = beam(p, focus + vec2(-0.02, -0.03), rightTarget + vec2(0.0, -0.045), 0.0045);
|
||||
color += vec3(0.55, 0.42, 1.0) * spectrumA * 0.24;
|
||||
color += vec3(0.96, 0.35, 0.72) * spectrumB * 0.18;
|
||||
|
||||
float fiberFlow = 0.0;
|
||||
for (int index = 0; index < 7; index++) {
|
||||
float offset = (float(index) - 3.0) * 0.012;
|
||||
fiberFlow += beam(
|
||||
p,
|
||||
leftSource + vec2(0.0, offset),
|
||||
focus + vec2(-0.07, -0.075 + offset * 0.45),
|
||||
0.0022
|
||||
);
|
||||
}
|
||||
color += vec3(0.22, 0.92, 1.0) * fiberFlow * mix(0.12, 0.2, uDark);
|
||||
|
||||
vec2 causticOrigin = focus + vec2(-0.055, -0.16);
|
||||
vec2 causticVector = p - causticOrigin;
|
||||
float causticDistance = length(causticVector);
|
||||
float causticAngle = atan(causticVector.y, causticVector.x);
|
||||
float causticBands = pow(max(0.0, sin(causticAngle * 18.0 + causticDistance * 5.0 - t * 0.18)), 18.0);
|
||||
float causticMask = smoothstep(0.05, 0.22, causticDistance) * exp(-causticDistance * 1.75) * smoothstep(0.1, -0.25, p.y);
|
||||
color += mix(vec3(0.36, 0.78, 1.0), vec3(0.68, 0.34, 1.0), smoothstep(-1.2, 0.5, causticAngle)) * causticBands * causticMask * mix(0.34, 0.44, uDark);
|
||||
|
||||
float floorMask = smoothstep(0.06, -0.28, p.y);
|
||||
vec2 gp = p - vec2(0.0, -0.24);
|
||||
float horizontal = 1.0 - smoothstep(0.0, 0.018, abs(fract((gp.y + 0.55) * 18.0) - 0.5));
|
||||
float perspectiveX = gp.x / max(0.12, abs(gp.y - 0.34));
|
||||
float vertical = 1.0 - smoothstep(0.0, 0.025, abs(fract(perspectiveX * 3.2) - 0.5));
|
||||
float grid = (horizontal * 0.48 + vertical * 0.35) * floorMask;
|
||||
color += mix(vec3(0.12, 0.34, 0.68), vec3(0.08, 0.48, 0.58), uDark) * grid * mix(0.2, 0.22, uDark);
|
||||
|
||||
float dust = step(0.993, hash21(floor(p * 190.0 + t * 0.4))) * floorMask;
|
||||
color += vec3(0.35, 0.9, 1.0) * dust * 0.28;
|
||||
color += (hash21(p * 210.0) - 0.5) * mix(0.008, 0.014, uDark);
|
||||
|
||||
float vignette = smoothstep(1.25, 0.2, length((uv - 0.5) * vec2(0.82, 1.0)));
|
||||
color *= mix(0.95, 1.0, vignette);
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
}
|
||||
`
|
||||
});
|
||||
const backgroundGeometry = new THREE.PlaneGeometry(2, 2);
|
||||
backgroundScene.add(new THREE.Mesh(backgroundGeometry, backgroundMaterial));
|
||||
|
||||
const worldScene = new THREE.Scene();
|
||||
const worldCamera = new THREE.PerspectiveCamera(33, 1, 0.1, 50);
|
||||
worldCamera.position.set(0, 0.12, 9.4);
|
||||
|
||||
const hemisphereLight = new THREE.HemisphereLight(0xe8f8ff, 0x0c1830, 1.8);
|
||||
const keyLight = new THREE.DirectionalLight(0xffffff, 5.2);
|
||||
keyLight.position.set(-3.8, 5.6, 5.8);
|
||||
keyLight.castShadow = !lowPower;
|
||||
keyLight.shadow.mapSize.set(lowPower ? 512 : 1024, lowPower ? 512 : 1024);
|
||||
keyLight.shadow.camera.near = 0.5;
|
||||
keyLight.shadow.camera.far = 20;
|
||||
keyLight.shadow.camera.left = -5;
|
||||
keyLight.shadow.camera.right = 5;
|
||||
keyLight.shadow.camera.top = 5;
|
||||
keyLight.shadow.camera.bottom = -5;
|
||||
|
||||
const cyanLight = new THREE.PointLight(0x62dfff, 22, 15, 2);
|
||||
cyanLight.position.set(-3.2, -0.5, 4.4);
|
||||
const blueLight = new THREE.PointLight(0x4e78ff, 16, 13, 2);
|
||||
blueLight.position.set(3.5, 2.1, 3.2);
|
||||
const warmLight = new THREE.PointLight(0xffb75e, 12, 12, 2);
|
||||
warmLight.position.set(3.2, -1.0, 4.8);
|
||||
worldScene.add(hemisphereLight, keyLight, cyanLight, blueLight, warmLight);
|
||||
|
||||
const disposableGeometries = [];
|
||||
const disposableMaterials = [];
|
||||
const trackGeometry = function (geometry) {
|
||||
disposableGeometries.push(geometry);
|
||||
return geometry;
|
||||
};
|
||||
const trackMaterial = function (material) {
|
||||
disposableMaterials.push(material);
|
||||
return material;
|
||||
};
|
||||
|
||||
const environmentScene = new THREE.Scene();
|
||||
environmentScene.background = new THREE.Color(0x06162d);
|
||||
|
||||
function addEnvironmentPanel(color, position, rotation, scale, intensity) {
|
||||
const panelGeometry = trackGeometry(new THREE.PlaneGeometry(scale[0], scale[1]));
|
||||
const panelMaterial = trackMaterial(new THREE.MeshBasicMaterial({
|
||||
color: new THREE.Color(color).multiplyScalar(intensity || 1),
|
||||
side: THREE.DoubleSide
|
||||
}));
|
||||
const panel = new THREE.Mesh(panelGeometry, panelMaterial);
|
||||
panel.position.set(position[0], position[1], position[2]);
|
||||
panel.rotation.set(rotation[0], rotation[1], rotation[2]);
|
||||
environmentScene.add(panel);
|
||||
}
|
||||
|
||||
addEnvironmentPanel(0xffffff, [0, 5.6, 0], [Math.PI / 2, 0, 0], [14, 14], 1.15);
|
||||
addEnvironmentPanel(0x3ceaff, [-5.4, -0.6, 0], [0, Math.PI / 2, 0], [4.2, 13], 1.55);
|
||||
addEnvironmentPanel(0x1e55cf, [5.2, 0.8, -0.8], [0, -Math.PI / 2, 0], [5, 12], 1.45);
|
||||
addEnvironmentPanel(0xffb663, [2.6, -1.2, 5], [0, 0, 0], [3.2, 9], 1.5);
|
||||
addEnvironmentPanel(0x7b62ff, [-2.2, 0.2, -5], [0, 0, 0], [4, 10], 1.35);
|
||||
addEnvironmentPanel(0xffffff, [-1.8, 1.2, 4.8], [0, 0, 0], [0.8, 9], 1.65);
|
||||
const pmremGenerator = new THREE.PMREMGenerator(renderer);
|
||||
pmremGenerator.compileCubemapShader();
|
||||
const environmentTarget = pmremGenerator.fromScene(environmentScene, lowPower ? 0.035 : 0.025, 0.1, 30);
|
||||
pmremGenerator.dispose();
|
||||
worldScene.environment = environmentTarget.texture;
|
||||
|
||||
const crystalShape = new THREE.Shape();
|
||||
crystalShape.moveTo(-1.43, -1.48);
|
||||
crystalShape.lineTo(1.36, -1.48);
|
||||
crystalShape.lineTo(0.06, 2.08);
|
||||
crystalShape.closePath();
|
||||
|
||||
const crystalGeometry = trackGeometry(new THREE.ExtrudeGeometry(crystalShape, {
|
||||
depth: 1.02,
|
||||
bevelEnabled: true,
|
||||
bevelSegments: lowPower ? 1 : 4,
|
||||
bevelSize: 0.065,
|
||||
bevelThickness: 0.1,
|
||||
curveSegments: 1,
|
||||
steps: 1
|
||||
}));
|
||||
crystalGeometry.translate(0, -0.22, -0.51);
|
||||
|
||||
const glassFrontMaterial = trackMaterial(new THREE.MeshPhysicalMaterial({
|
||||
color: 0xc8eaff,
|
||||
roughness: 0.008,
|
||||
metalness: 0,
|
||||
transmission: 0,
|
||||
thickness: 2.8,
|
||||
ior: 1.54,
|
||||
dispersion: lowPower ? 0.06 : 0.19,
|
||||
attenuationColor: new THREE.Color(0x62b8ef),
|
||||
attenuationDistance: 2.6,
|
||||
clearcoat: 1,
|
||||
clearcoatRoughness: 0.006,
|
||||
iridescence: lowPower ? 0.08 : 0.24,
|
||||
iridescenceIOR: 1.34,
|
||||
envMapIntensity: 0.65,
|
||||
specularIntensity: 1,
|
||||
specularColor: new THREE.Color(0xffffff),
|
||||
transparent: true,
|
||||
opacity: 0.055,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false
|
||||
}));
|
||||
glassFrontMaterial.userData.lightColor = new THREE.Color(0xc8eaff);
|
||||
glassFrontMaterial.userData.darkColor = new THREE.Color(0x8ed2f4);
|
||||
glassFrontMaterial.userData.lightOpacity = 0.055;
|
||||
glassFrontMaterial.userData.darkOpacity = 0.1;
|
||||
glassFrontMaterial.userData.lightEnv = 0.65;
|
||||
glassFrontMaterial.userData.darkEnv = 1.1;
|
||||
|
||||
const glassSideMaterial = trackMaterial(new THREE.MeshPhysicalMaterial({
|
||||
color: 0x2766ad,
|
||||
roughness: 0.015,
|
||||
metalness: 0.06,
|
||||
transmission: 0.4,
|
||||
thickness: 3.2,
|
||||
ior: 1.52,
|
||||
dispersion: lowPower ? 0.04 : 0.13,
|
||||
attenuationColor: new THREE.Color(0x164998),
|
||||
attenuationDistance: 1.45,
|
||||
clearcoat: 1,
|
||||
clearcoatRoughness: 0.008,
|
||||
iridescence: lowPower ? 0.04 : 0.16,
|
||||
iridescenceIOR: 1.3,
|
||||
envMapIntensity: 4.5,
|
||||
emissive: new THREE.Color(0x0d2e78),
|
||||
emissiveIntensity: 0.12,
|
||||
transparent: true,
|
||||
opacity: 0.94,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false
|
||||
}));
|
||||
glassSideMaterial.userData.lightColor = new THREE.Color(0x2766ad);
|
||||
glassSideMaterial.userData.darkColor = new THREE.Color(0x1d57a6);
|
||||
glassSideMaterial.userData.lightOpacity = 0.94;
|
||||
glassSideMaterial.userData.darkOpacity = 0.97;
|
||||
glassSideMaterial.userData.lightEnv = 4.5;
|
||||
glassSideMaterial.userData.darkEnv = 5.3;
|
||||
const crystalMaterials = [glassFrontMaterial, glassSideMaterial];
|
||||
|
||||
const crystalMesh = new THREE.Mesh(crystalGeometry, crystalMaterials);
|
||||
crystalMesh.castShadow = !lowPower;
|
||||
crystalMesh.receiveShadow = true;
|
||||
crystalMesh.renderOrder = 1;
|
||||
|
||||
function createCrystalFacet(points, color, opacity, additive, z) {
|
||||
const shape = new THREE.Shape();
|
||||
shape.moveTo(points[0][0], points[0][1]);
|
||||
points.slice(1).forEach(function (point) { shape.lineTo(point[0], point[1]); });
|
||||
shape.closePath();
|
||||
const geometry = trackGeometry(new THREE.ShapeGeometry(shape));
|
||||
const material = trackMaterial(new THREE.MeshBasicMaterial({
|
||||
color,
|
||||
transparent: true,
|
||||
opacity,
|
||||
side: THREE.DoubleSide,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
toneMapped: false,
|
||||
blending: additive ? THREE.AdditiveBlending : THREE.NormalBlending
|
||||
}));
|
||||
material.userData.lightOpacity = opacity;
|
||||
material.userData.darkOpacity = Math.min(0.68, opacity * 1.28);
|
||||
const facet = new THREE.Mesh(geometry, material);
|
||||
facet.position.z = z || 0.625;
|
||||
facet.renderOrder = additive ? 4 : 3;
|
||||
return { facet, material };
|
||||
}
|
||||
|
||||
const crystalFacets = [
|
||||
createCrystalFacet([[-1.39, -1.65], [1.31, -1.65], [0.05, 1.86]], 0x93d7f1, 0.23, false, 0.615),
|
||||
createCrystalFacet([[-1.36, -1.68], [-0.9, -1.68], [0.05, 1.83], [-0.17, 1.44]], 0x129fd8, 0.42, false),
|
||||
createCrystalFacet([[0.05, 1.83], [0.3, 1.32], [1.29, -1.68], [0.84, -1.68]], 0x082f76, 0.6, false),
|
||||
createCrystalFacet([[-1.36, -1.68], [1.29, -1.68], [0.92, -1.23], [-1.02, -1.23]], 0x2d6fae, 0.38, false),
|
||||
createCrystalFacet([[0.02, 1.68], [0.25, 1.22], [-0.1, 0.84], [-0.25, 1.27]], 0xf8fdff, 0.18, true, 0.64),
|
||||
createCrystalFacet([[-0.25, 1.27], [-1.2, -1.34], [-0.56, -0.61], [-0.1, 0.84]], 0x17a9df, 0.48, false),
|
||||
createCrystalFacet([[-1.23, -1.36], [-0.56, -0.61], [0.18, -1.38]], 0x168bc9, 0.44, false, 0.646),
|
||||
createCrystalFacet([[-0.1, 0.84], [-0.56, -0.61], [0.2, -1.39], [0.46, -0.02]], 0x4ba9dd, 0.46, false),
|
||||
createCrystalFacet([[-0.56, -0.61], [0.46, -0.02], [0.18, -1.38]], 0xbfefff, 0.28, false, 0.648),
|
||||
createCrystalFacet([[-0.72, -0.82], [0.04, 0.74], [0.38, 0.04], [-0.02, -0.72]], 0xd5f5ff, 0.34, false, 0.638),
|
||||
createCrystalFacet([[0.25, 1.22], [0.64, 0.55], [0.46, -0.02], [-0.1, 0.84]], 0xfff8e8, 0.18, true, 0.64),
|
||||
createCrystalFacet([[0.46, -0.02], [0.64, 0.55], [1.12, -1.34], [0.2, -1.39]], 0x0a376e, 0.54, false),
|
||||
createCrystalFacet([[-1.2, -1.34], [0.2, -1.39], [1.12, -1.34], [0.82, -1.04], [-0.86, -1.02]], 0x5acff4, 0.25, true, 0.64),
|
||||
createCrystalFacet([[0.3, 1.32], [0.64, 0.55], [1.12, -1.34], [1.29, -1.68]], 0x06265b, 0.44, false),
|
||||
createCrystalFacet([[0.38, 0.04], [0.72, -0.36], [1.04, -1.2], [0.18, -1.32]], 0xffc982, 0.13, true, 0.644),
|
||||
createCrystalFacet([[-0.17, 1.44], [-0.1, 0.84], [-0.56, -0.61], [-0.9, -1.68]], 0xa7edff, 0.16, true, 0.64),
|
||||
createCrystalFacet([[0.02, 1.68], [0.3, 1.32], [0.25, 1.22]], 0xffffff, 0.25, true, 0.645),
|
||||
createCrystalFacet([[0.02, 1.7], [0.29, 1.31], [0.13, 1.04], [-0.06, 1.35]], 0xff82c8, 0.16, true, 0.65),
|
||||
createCrystalFacet([[0.38, -0.03], [0.83, -0.54], [1.03, -1.16], [0.64, -0.8]], 0xffb867, 0.13, true, 0.651)
|
||||
];
|
||||
const reflectionMaterials = crystalFacets.map(function (entry) { return entry.material; });
|
||||
|
||||
const spectrumGeometry = trackGeometry(new THREE.ShapeGeometry(crystalShape));
|
||||
spectrumGeometry.translate(0, -0.22, 0);
|
||||
const spectrumMaterial = trackMaterial(new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
uTime: uniforms.uTime,
|
||||
uDark: uniforms.uDark
|
||||
},
|
||||
transparent: true,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
blending: THREE.AdditiveBlending,
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
varying vec3 vLocal;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
vLocal = position;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
precision highp float;
|
||||
varying vec2 vUv;
|
||||
varying vec3 vLocal;
|
||||
uniform float uTime;
|
||||
uniform float uDark;
|
||||
|
||||
vec3 spectrum(float t) {
|
||||
return 0.56 + 0.44 * cos(6.28318 * (vec3(0.02, 0.35, 0.68) + t));
|
||||
}
|
||||
|
||||
void main() {
|
||||
float diagonalA = exp(-abs(vLocal.y - (0.37 * vLocal.x - 0.64)) * 24.0);
|
||||
float diagonalB = exp(-abs(vLocal.y - (-0.22 * vLocal.x - 0.22)) * 34.0);
|
||||
float spectralShard = pow(max(0.0, sin(vLocal.x * 8.4 - vLocal.y * 5.7 + uTime * 0.42)), 44.0);
|
||||
float strength = diagonalA * 0.2 + diagonalB * 0.12 + spectralShard * 0.075;
|
||||
vec3 color = spectrum(vLocal.x * 0.16 + vLocal.y * 0.08 + uTime * 0.018);
|
||||
color += vec3(0.76, 0.94, 1.0) * diagonalA * 0.42;
|
||||
gl_FragColor = vec4(color * strength, strength * mix(0.66, 0.9, uDark));
|
||||
}
|
||||
`
|
||||
}));
|
||||
const spectrumMesh = new THREE.Mesh(spectrumGeometry, spectrumMaterial);
|
||||
spectrumMesh.position.z = 0.655;
|
||||
spectrumMesh.renderOrder = 5;
|
||||
spectrumMesh.userData.noReflection = true;
|
||||
|
||||
function createLightStreak(start, end, color, width, opacity, z) {
|
||||
const deltaX = end[0] - start[0];
|
||||
const deltaY = end[1] - start[1];
|
||||
const length = Math.hypot(deltaX, deltaY);
|
||||
const group = new THREE.Group();
|
||||
[
|
||||
{ scale: 7.2, alpha: opacity * 0.12, z: (z || 0.67) - 0.006 },
|
||||
{ scale: 2.8, alpha: opacity * 0.28, z: (z || 0.67) - 0.002 },
|
||||
{ scale: 1, alpha: opacity, z: z || 0.67 }
|
||||
].forEach(function (layer) {
|
||||
const geometry = trackGeometry(new THREE.PlaneGeometry(length, width * layer.scale));
|
||||
const material = trackMaterial(new THREE.MeshBasicMaterial({
|
||||
color,
|
||||
transparent: true,
|
||||
opacity: layer.alpha,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
blending: THREE.AdditiveBlending,
|
||||
side: THREE.DoubleSide
|
||||
}));
|
||||
const beam = new THREE.Mesh(geometry, material);
|
||||
beam.position.set((start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, layer.z);
|
||||
beam.rotation.z = Math.atan2(deltaY, deltaX);
|
||||
beam.renderOrder = 6;
|
||||
group.add(beam);
|
||||
});
|
||||
group.userData.noReflection = true;
|
||||
return group;
|
||||
}
|
||||
|
||||
const cyanStreak = createLightStreak([-1.42, -1.03], [1.32, -0.16], 0x8df6ff, 0.011, 0.92);
|
||||
const warmStreak = createLightStreak([0.24, -0.13], [1.47, -0.4], 0xffc36c, 0.012, 0.82, 0.69);
|
||||
const violetStreak = createLightStreak([-0.38, -0.52], [0.64, -0.75], 0xb478ff, 0.009, 0.68, 0.69);
|
||||
const rainbowStreak = createLightStreak([-0.08, 0.42], [0.62, 0.04], 0xff78d7, 0.007, 0.48, 0.695);
|
||||
|
||||
const sparklePositions = [];
|
||||
const sparkleCount = lowPower ? 26 : 74;
|
||||
for (let index = 0; index < sparkleCount; index += 1) {
|
||||
const seedA = Math.abs(Math.sin(index * 91.73 + 0.47));
|
||||
const seedB = Math.abs(Math.sin(index * 37.19 + 1.21));
|
||||
const y = -1.57 + seedA * 2.28;
|
||||
const widthAtY = Math.max(0.08, 1.18 * (1 - (y + 1.57) / 3.4));
|
||||
sparklePositions.push((seedB * 2 - 1) * widthAtY, y, 0.69 + (index % 5) * 0.001);
|
||||
}
|
||||
const sparkleGeometry = trackGeometry(new THREE.BufferGeometry());
|
||||
sparkleGeometry.setAttribute('position', new THREE.Float32BufferAttribute(sparklePositions, 3));
|
||||
const sparkleMaterial = trackMaterial(new THREE.ShaderMaterial({
|
||||
uniforms: { uTime: uniforms.uTime, uDark: uniforms.uDark },
|
||||
transparent: true,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
blending: THREE.AdditiveBlending,
|
||||
vertexShader: `
|
||||
uniform float uTime;
|
||||
varying float vPulse;
|
||||
void main() {
|
||||
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
||||
vPulse = 0.55 + 0.45 * sin(uTime * 1.9 + position.x * 11.0 + position.y * 7.0);
|
||||
gl_PointSize = (0.075 + vPulse * 0.085) * (260.0 / -mvPosition.z);
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
precision highp float;
|
||||
varying float vPulse;
|
||||
uniform float uDark;
|
||||
void main() {
|
||||
float d = length(gl_PointCoord - 0.5);
|
||||
float alpha = smoothstep(0.5, 0.06, d) * vPulse * mix(0.34, 0.72, uDark);
|
||||
vec3 color = mix(vec3(0.48, 0.88, 1.0), vec3(1.0, 0.72, 0.48), vPulse * 0.34);
|
||||
gl_FragColor = vec4(color, alpha);
|
||||
}
|
||||
`
|
||||
}));
|
||||
const crystalSparkles = new THREE.Points(sparkleGeometry, sparkleMaterial);
|
||||
crystalSparkles.renderOrder = 7;
|
||||
crystalSparkles.userData.noReflection = true;
|
||||
|
||||
const edgeGeometry = trackGeometry(new THREE.EdgesGeometry(crystalGeometry, 16));
|
||||
const edgeMaterial = trackMaterial(new THREE.LineBasicMaterial({
|
||||
color: 0xc5f3ff,
|
||||
transparent: true,
|
||||
depthTest: false,
|
||||
opacity: 0.72
|
||||
}));
|
||||
const crystalEdges = new THREE.LineSegments(edgeGeometry, edgeMaterial);
|
||||
crystalEdges.renderOrder = 7;
|
||||
|
||||
const glintGeometry = trackGeometry(new THREE.PlaneGeometry(1.15, 1.15));
|
||||
const glintMaterial = trackMaterial(new THREE.ShaderMaterial({
|
||||
uniforms: { uTime: uniforms.uTime, uDark: uniforms.uDark },
|
||||
transparent: true,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
blending: THREE.AdditiveBlending,
|
||||
vertexShader: `varying vec2 vUv; void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}`,
|
||||
fragmentShader: `
|
||||
precision highp float;
|
||||
varying vec2 vUv;
|
||||
uniform float uTime;
|
||||
uniform float uDark;
|
||||
void main(){
|
||||
vec2 p=vUv-0.5;
|
||||
float core=exp(-length(p)*24.0);
|
||||
float cross=exp(-abs(p.x)*95.0)*exp(-abs(p.y)*4.5)+exp(-abs(p.y)*95.0)*exp(-abs(p.x)*4.5);
|
||||
float pulse=0.86+0.14*sin(uTime*1.6);
|
||||
vec3 color=mix(vec3(0.42,0.86,1.0),vec3(1.0,0.42,0.92),smoothstep(-0.2,0.18,p.x));
|
||||
gl_FragColor=vec4(color*(core*1.7+cross*0.72)*pulse,(core+cross*0.54)*mix(0.72,1.0,uDark));
|
||||
}
|
||||
`
|
||||
}));
|
||||
const baseGlint = new THREE.Mesh(glintGeometry, glintMaterial);
|
||||
baseGlint.position.set(-0.18, -1.6, 0.86);
|
||||
baseGlint.scale.setScalar(0.94);
|
||||
baseGlint.renderOrder = 8;
|
||||
|
||||
const crystalGroup = new THREE.Group();
|
||||
crystalGroup.add(
|
||||
crystalMesh,
|
||||
...crystalFacets.map(function (entry) { return entry.facet; }),
|
||||
spectrumMesh,
|
||||
cyanStreak,
|
||||
warmStreak,
|
||||
violetStreak,
|
||||
rainbowStreak,
|
||||
crystalSparkles,
|
||||
crystalEdges,
|
||||
baseGlint
|
||||
);
|
||||
crystalGroup.rotation.set(0.045, -0.44, -0.018);
|
||||
|
||||
// Preserve the authored, high-frequency refraction from the approved visual
|
||||
// while keeping it inside the live Three.js composition. The procedural
|
||||
// crystal underneath supplies parallax, glow, reflection and the dark theme.
|
||||
let referenceTexture = null;
|
||||
const referenceGeometry = trackGeometry(new THREE.PlaneGeometry(3.29, 3.7));
|
||||
const referenceMaterial = trackMaterial(new THREE.MeshBasicMaterial({
|
||||
transparent: true,
|
||||
opacity: initialDark ? 0.92 : 0.97,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
toneMapped: false
|
||||
}));
|
||||
const referenceCrystal = new THREE.Mesh(referenceGeometry, referenceMaterial);
|
||||
referenceCrystal.position.set(-0.29, 0.05, 1.28);
|
||||
referenceCrystal.renderOrder = 24;
|
||||
referenceCrystal.userData.noReflection = true;
|
||||
|
||||
const referenceReflectionMaterial = trackMaterial(new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
uMap: { value: null },
|
||||
uOpacity: { value: initialDark ? 0.14 : 0.22 }
|
||||
},
|
||||
transparent: true,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
toneMapped: false,
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
precision highp float;
|
||||
varying vec2 vUv;
|
||||
uniform sampler2D uMap;
|
||||
uniform float uOpacity;
|
||||
void main() {
|
||||
vec4 sampleColor = texture2D(uMap, vUv);
|
||||
float fade = pow(clamp(1.0 - vUv.y, 0.0, 1.0), 2.1);
|
||||
vec3 reflected = mix(sampleColor.rgb, vec3(0.56, 0.76, 1.0), 0.38);
|
||||
gl_FragColor = vec4(reflected, sampleColor.a * fade * uOpacity);
|
||||
}
|
||||
`
|
||||
}));
|
||||
const referenceReflection = new THREE.Mesh(referenceGeometry, referenceReflectionMaterial);
|
||||
referenceReflection.position.set(-0.29, -3.06, 0.96);
|
||||
referenceReflection.scale.set(1, -0.68, 1);
|
||||
referenceReflection.renderOrder = 2;
|
||||
referenceReflection.visible = false;
|
||||
|
||||
new THREE.TextureLoader().load(
|
||||
'./assets/prism-reference-light.png',
|
||||
function (texture) {
|
||||
referenceTexture = texture;
|
||||
texture.colorSpace = THREE.SRGBColorSpace;
|
||||
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
||||
texture.magFilter = THREE.LinearFilter;
|
||||
referenceMaterial.map = texture;
|
||||
referenceMaterial.needsUpdate = true;
|
||||
referenceReflectionMaterial.uniforms.uMap.value = texture;
|
||||
referenceReflection.visible = true;
|
||||
root.dataset.referenceCrystal = 'loaded';
|
||||
renderFrame(performance.now());
|
||||
},
|
||||
undefined,
|
||||
function () {
|
||||
referenceCrystal.visible = false;
|
||||
root.dataset.referenceCrystal = 'error';
|
||||
}
|
||||
);
|
||||
|
||||
const crystalReflection = crystalGroup.clone(true);
|
||||
crystalReflection.traverse(function (object) {
|
||||
if (object.userData.noReflection) {
|
||||
object.visible = false;
|
||||
return;
|
||||
}
|
||||
if (!object.material) return;
|
||||
const materials = Array.isArray(object.material) ? object.material : [object.material];
|
||||
const clonedMaterials = materials.map(function (material) {
|
||||
const clone = trackMaterial(material.clone());
|
||||
clone.transparent = true;
|
||||
clone.depthWrite = false;
|
||||
clone.depthTest = false;
|
||||
clone.opacity = material.isLineBasicMaterial ? 0.08 : Math.min(0.14, (material.opacity || 1) * 0.16);
|
||||
if (clone.isMeshPhysicalMaterial) {
|
||||
clone.transmission = 0;
|
||||
clone.roughness = 0.24;
|
||||
clone.metalness = 0.08;
|
||||
clone.color = new THREE.Color(0x5ba8ff);
|
||||
}
|
||||
return clone;
|
||||
});
|
||||
object.material = Array.isArray(object.material) ? clonedMaterials : clonedMaterials[0];
|
||||
object.renderOrder = 0;
|
||||
});
|
||||
crystalReflection.scale.set(1, -1, 1);
|
||||
crystalReflection.position.y = -3.42;
|
||||
|
||||
const floorEffects = new THREE.Group();
|
||||
function createFloorRay(points, color, opacity) {
|
||||
const geometry = trackGeometry(new THREE.BufferGeometry());
|
||||
const positions = [];
|
||||
points.forEach(function (point) { positions.push(point[0], -1.705, point[1]); });
|
||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
||||
geometry.computeVertexNormals();
|
||||
const material = trackMaterial(new THREE.MeshBasicMaterial({
|
||||
color,
|
||||
transparent: true,
|
||||
opacity,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false,
|
||||
blending: THREE.AdditiveBlending
|
||||
}));
|
||||
material.userData.lightOpacity = opacity;
|
||||
material.userData.darkOpacity = opacity * 1.35;
|
||||
const ray = new THREE.Mesh(geometry, material);
|
||||
floorEffects.add(ray);
|
||||
return material;
|
||||
}
|
||||
const floorRayMaterials = [
|
||||
createFloorRay([[-0.95, 0.2], [-5.6, 4.8], [-2.7, 4.8]], 0x6cecff, 0.2),
|
||||
createFloorRay([[-0.2, 0.3], [-2.2, 5.4], [-0.55, 5.4]], 0x7f6dff, 0.14),
|
||||
createFloorRay([[0.54, 0.14], [1.2, 5.5], [3.6, 5.5]], 0xffd58b, 0.16),
|
||||
createFloorRay([[0.72, 0.12], [3.4, 4.6], [5.4, 4.6]], 0x4f83ff, 0.12)
|
||||
];
|
||||
|
||||
const worldRoot = new THREE.Group();
|
||||
worldRoot.add(floorEffects, crystalReflection, referenceReflection, crystalGroup, referenceCrystal);
|
||||
worldScene.add(worldRoot);
|
||||
|
||||
const floorGeometry = trackGeometry(new THREE.PlaneGeometry(10, 10));
|
||||
const floorMaterial = trackMaterial(new THREE.MeshPhysicalMaterial({
|
||||
color: 0xd8e8ff,
|
||||
roughness: 0.2,
|
||||
metalness: 0.08,
|
||||
envMapIntensity: 1.6,
|
||||
transparent: true,
|
||||
opacity: 0.22,
|
||||
depthWrite: false,
|
||||
side: THREE.DoubleSide
|
||||
}));
|
||||
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
|
||||
floor.rotation.x = -Math.PI / 2;
|
||||
floor.position.set(0, -1.71, 0);
|
||||
floor.receiveShadow = !lowPower;
|
||||
worldScene.add(floor);
|
||||
|
||||
const floorShadowMaterial = trackMaterial(new THREE.ShadowMaterial({ color: 0x0b3472, opacity: 0.18 }));
|
||||
const floorShadow = new THREE.Mesh(floorGeometry, floorShadowMaterial);
|
||||
floorShadow.rotation.x = -Math.PI / 2;
|
||||
floorShadow.position.set(0, -1.695, 0);
|
||||
floorShadow.receiveShadow = !lowPower;
|
||||
worldScene.add(floorShadow);
|
||||
|
||||
const restPointer = new THREE.Vector2(0.58, 0.44);
|
||||
const targetMouse = restPointer.clone();
|
||||
const currentMouse = targetMouse.clone();
|
||||
const lightEdgeColor = new THREE.Color(0xbfeeff);
|
||||
const darkEdgeColor = new THREE.Color(0x73e9ff);
|
||||
const lightFloorColor = new THREE.Color(0xd9eaff);
|
||||
const darkFloorColor = new THREE.Color(0x0b2746);
|
||||
const lightReferenceColor = new THREE.Color(0xffffff);
|
||||
const darkReferenceColor = new THREE.Color(0xb7dbff);
|
||||
const currentColor = new THREE.Color();
|
||||
let targetDark = initialDark;
|
||||
let heroVisible = true;
|
||||
let lastFrame = 0;
|
||||
let frameInterval = staticMode ? Infinity : (window.innerWidth < 900 ? 1000 / 30 : 1000 / 45);
|
||||
const startTime = performance.now();
|
||||
|
||||
function updateWorld(elapsed) {
|
||||
const dark = uniforms.uDark.value;
|
||||
const floatOffset = staticMode ? 0 : Math.sin(elapsed * 0.48) * 0.075;
|
||||
crystalGroup.position.y = floatOffset;
|
||||
crystalGroup.rotation.x = 0.02 + Math.sin(elapsed * 0.28) * 0.025 + (currentMouse.y - 0.5) * 0.1;
|
||||
crystalGroup.rotation.y = -0.44 + Math.sin(elapsed * 0.18) * 0.035 + (currentMouse.x - 0.5) * 0.1;
|
||||
crystalGroup.rotation.z = -0.018 + Math.sin(elapsed * 0.22) * 0.012;
|
||||
const pointerTiltX = (0.5 - currentMouse.y) * 0.16;
|
||||
const pointerTiltY = (currentMouse.x - 0.5) * 0.28;
|
||||
const pointerShiftX = (currentMouse.x - 0.5) * 0.11;
|
||||
const pointerShiftY = (0.5 - currentMouse.y) * 0.06;
|
||||
referenceCrystal.position.x = -0.29 + pointerShiftX;
|
||||
referenceCrystal.position.y = 0.05 + floatOffset * 0.72 + pointerShiftY;
|
||||
referenceCrystal.rotation.x = pointerTiltX;
|
||||
referenceCrystal.rotation.y = pointerTiltY;
|
||||
referenceCrystal.rotation.z = staticMode ? 0 : Math.sin(elapsed * 0.22) * 0.0035;
|
||||
referenceReflection.position.x = -0.29 + pointerShiftX * 0.7;
|
||||
referenceReflection.position.y = -3.06 - floatOffset * 0.46;
|
||||
referenceReflection.rotation.x = -pointerTiltX * 0.48;
|
||||
referenceReflection.rotation.y = pointerTiltY * 0.62;
|
||||
referenceReflection.rotation.z = -referenceCrystal.rotation.z * 0.5;
|
||||
referenceMaterial.opacity = THREE.MathUtils.lerp(0.97, 0.92, dark);
|
||||
referenceMaterial.color.copy(currentColor.copy(lightReferenceColor).lerp(darkReferenceColor, dark));
|
||||
referenceCrystal.visible = Boolean(referenceMaterial.map);
|
||||
referenceReflectionMaterial.uniforms.uOpacity.value = THREE.MathUtils.lerp(0.22, 0.14, dark);
|
||||
referenceReflection.visible = Boolean(referenceMaterial.map);
|
||||
crystalGroup.visible = false;
|
||||
crystalReflection.visible = false;
|
||||
crystalReflection.rotation.copy(crystalGroup.rotation);
|
||||
crystalReflection.position.y = -3.42 - floatOffset;
|
||||
worldRoot.rotation.y = (currentMouse.x - 0.5) * 0.03;
|
||||
worldRoot.rotation.x = (0.5 - currentMouse.y) * 0.02;
|
||||
|
||||
crystalMaterials.forEach(function (material) {
|
||||
material.color.copy(currentColor.copy(material.userData.lightColor).lerp(material.userData.darkColor, dark));
|
||||
material.opacity = THREE.MathUtils.lerp(material.userData.lightOpacity, material.userData.darkOpacity, dark);
|
||||
material.envMapIntensity = THREE.MathUtils.lerp(material.userData.lightEnv, material.userData.darkEnv, dark);
|
||||
});
|
||||
glassSideMaterial.emissiveIntensity = THREE.MathUtils.lerp(0.12, 0.2, dark);
|
||||
reflectionMaterials.forEach(function (material) {
|
||||
material.opacity = THREE.MathUtils.lerp(material.userData.lightOpacity, material.userData.darkOpacity, dark);
|
||||
});
|
||||
floorRayMaterials.forEach(function (material) {
|
||||
material.opacity = THREE.MathUtils.lerp(material.userData.lightOpacity, material.userData.darkOpacity, dark);
|
||||
});
|
||||
edgeMaterial.color.copy(currentColor.copy(lightEdgeColor).lerp(darkEdgeColor, dark));
|
||||
edgeMaterial.opacity = THREE.MathUtils.lerp(0.72, 0.9, dark);
|
||||
floorMaterial.color.copy(currentColor.copy(lightFloorColor).lerp(darkFloorColor, dark));
|
||||
floorMaterial.opacity = THREE.MathUtils.lerp(0.22, 0.13, dark);
|
||||
floorMaterial.envMapIntensity = THREE.MathUtils.lerp(1.6, 2.4, dark);
|
||||
floorShadowMaterial.opacity = THREE.MathUtils.lerp(0.16, 0.3, dark);
|
||||
hemisphereLight.intensity = THREE.MathUtils.lerp(1.8, 1.25, dark);
|
||||
keyLight.intensity = THREE.MathUtils.lerp(2.4, 2.6, dark);
|
||||
cyanLight.intensity = THREE.MathUtils.lerp(18, 26, dark);
|
||||
warmLight.intensity = THREE.MathUtils.lerp(13, 20, dark);
|
||||
}
|
||||
|
||||
function renderFrame(now) {
|
||||
if (!staticMode && (!heroVisible || document.hidden || now - lastFrame < frameInterval)) return;
|
||||
lastFrame = now;
|
||||
const elapsed = staticMode ? 1.4 : (now - startTime) / 1000;
|
||||
if (staticMode) {
|
||||
uniforms.uDark.value = targetDark;
|
||||
uniforms.uTime.value = 1.4;
|
||||
} else {
|
||||
uniforms.uDark.value += (targetDark - uniforms.uDark.value) * 0.085;
|
||||
currentMouse.lerp(targetMouse, 0.055);
|
||||
uniforms.uMouse.value.copy(currentMouse);
|
||||
uniforms.uTime.value = elapsed;
|
||||
}
|
||||
updateWorld(elapsed);
|
||||
renderer.clear(true, true, true);
|
||||
renderer.render(backgroundScene, backgroundCamera);
|
||||
renderer.clearDepth();
|
||||
renderer.render(worldScene, worldCamera);
|
||||
}
|
||||
|
||||
function resize() {
|
||||
const rect = root.getBoundingClientRect();
|
||||
const width = Math.max(1, Math.round(rect.width));
|
||||
const height = Math.max(1, Math.round(rect.height));
|
||||
const maxDpr = window.innerWidth < 900 ? 1 : 1.5;
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, maxDpr));
|
||||
renderer.setSize(width, height, false);
|
||||
uniforms.uResolution.value.set(width * renderer.getPixelRatio(), height * renderer.getPixelRatio());
|
||||
|
||||
const aspect = width / height;
|
||||
worldCamera.aspect = aspect;
|
||||
worldCamera.fov = aspect < 0.75 ? 38 : 33;
|
||||
worldCamera.updateProjectionMatrix();
|
||||
if (aspect > 1.1) {
|
||||
worldRoot.position.set(Math.min(1.92, aspect * 1.15), -0.02, 0);
|
||||
worldRoot.scale.setScalar(0.9);
|
||||
} else {
|
||||
worldRoot.position.set(0.08, -1.55, 0);
|
||||
worldRoot.scale.setScalar(0.56);
|
||||
}
|
||||
floor.position.set(worldRoot.position.x, worldRoot.position.y - 1.71 * worldRoot.scale.x, 0);
|
||||
floorShadow.position.set(worldRoot.position.x, worldRoot.position.y - 1.695 * worldRoot.scale.x, 0);
|
||||
frameInterval = staticMode ? Infinity : (width < 900 ? 1000 / 30 : 1000 / 45);
|
||||
renderFrame(performance.now());
|
||||
}
|
||||
|
||||
const resizeObserver = new ResizeObserver(resize);
|
||||
resizeObserver.observe(root);
|
||||
|
||||
const hero = document.getElementById('home');
|
||||
if (hero) {
|
||||
hero.addEventListener('pointermove', function (event) {
|
||||
if (staticMode) return;
|
||||
const rect = hero.getBoundingClientRect();
|
||||
targetMouse.set(
|
||||
THREE.MathUtils.clamp((event.clientX - rect.left) / rect.width, 0, 1),
|
||||
THREE.MathUtils.clamp((event.clientY - rect.top) / rect.height, 0, 1)
|
||||
);
|
||||
}, { passive: true });
|
||||
hero.addEventListener('pointerleave', function () { targetMouse.copy(restPointer); }, { passive: true });
|
||||
|
||||
const visibilityObserver = new IntersectionObserver(function (entries) {
|
||||
heroVisible = entries[0] ? entries[0].isIntersecting : true;
|
||||
if (heroVisible && staticMode) renderFrame(performance.now());
|
||||
}, { rootMargin: '120px 0px' });
|
||||
visibilityObserver.observe(hero);
|
||||
}
|
||||
|
||||
window.addEventListener('roncarve:themechange', function (event) {
|
||||
targetDark = event.detail.resolved === 'dark' ? 1 : 0;
|
||||
if (staticMode) renderFrame(performance.now());
|
||||
});
|
||||
|
||||
canvas.addEventListener('webglcontextlost', function (event) {
|
||||
event.preventDefault();
|
||||
root.classList.remove('is-ready');
|
||||
root.classList.add('has-error');
|
||||
renderer.setAnimationLoop(null);
|
||||
});
|
||||
|
||||
resize();
|
||||
renderFrame(performance.now());
|
||||
root.classList.add('is-ready');
|
||||
if (!staticMode) renderer.setAnimationLoop(renderFrame);
|
||||
|
||||
window.addEventListener('beforeunload', function () {
|
||||
renderer.setAnimationLoop(null);
|
||||
resizeObserver.disconnect();
|
||||
backgroundGeometry.dispose();
|
||||
backgroundMaterial.dispose();
|
||||
disposableGeometries.forEach(function (geometry) { geometry.dispose(); });
|
||||
disposableMaterials.forEach(function (material) { material.dispose(); });
|
||||
if (referenceTexture) referenceTexture.dispose();
|
||||
environmentTarget.dispose();
|
||||
renderer.dispose();
|
||||
}, { once: true });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user