WEBLEB
Home
Editor
Login
Pro
English
English
Français
Español
Português
Deutsch
Italiano
हिंदी
CodePen Home Valentine's
586
zegarkidawida
Open In Editor
Publish Your Code
Recommended
17 March 2025
CodePen Home Cipher Quest: Codebreaker
28 March 2025
CodePen Home Outlined Mobile Cards
4 December 2024
CodePen Home ThreeJS 'Portal Planes' with Matcaps
HTML
Copy
Valentine's
Valentine's
Amorous Symphony
❤ Start the Magic ❤
♥
♥
♥
♥
♥
♥
♥
♥
♥
♥
♥
♥
♥
♥
♥
♥
♥
♥
♥
♥
CODE by zegarkidawida
Happy Valentine's Day
Initializing Cosmic Love...
CSS
Copy
html, body { margin: 0; padding: 0; overflow: hidden; width: 100%; height: 100%; } .scene { position: fixed; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; opacity: 0; transition: opacity 8s ease-in-out; } .valentine-text { position: fixed; top: 20%; width: 100%; text-align: center; color: #ff69b4; text-shadow: 0 0 15px rgba(255, 105, 180, 0.5); opacity: 0; transition: opacity 2s; z-index: 1000; } .main-text { font-family: "Great Vibes", cursive; font-size: 4em; margin-bottom: 0.2em; } .sub-text { font-size: 1.5em; color: #ffffff; text-shadow: 0 0 10px rgba(255, 255, 255, 0.7); } .loading-screen { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.9); color: #ff69b4; display: flex; justify-content: center; align-items: center; font-size: 2em; font-family: "Great Vibes", cursive; z-index: 10000; transition: opacity 1s; } .start-screen { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient( circle at 50% 50%, rgba(50, 0, 20, 0.95) 0%, rgba(20, 0, 30, 0.95) 70%, rgba(0, 0, 0, 0.95) 100% ); display: flex; justify-content: center; align-items: center; z-index: 100000; transition: opacity 1s; backdrop-filter: blur(10px); } .title-container { text-align: center; transform: translateY(-20%); position: relative; perspective: 1000px; } .title-wrapper { position: relative; display: inline-block; } .title-overlay { display: none; } .main-title { font-family: "Great Vibes", cursive; font-size: 5em; color: #ff69b4; text-shadow: 0 0 20px rgba(255, 105, 180, 0.5); margin: 0; position: relative; perspective: 300px; transform-style: preserve-3d; transition: all 0.5s ease; animation: titleEntrance 2s cubic-bezier(0.19, 1, 0.22, 1) forwards, titleGlow 3s ease-in-out infinite alternate; transform-origin: center bottom; z-index: 2; } .main-title:hover { transform: rotateX(5deg) rotateY(10deg) translateZ(50px) scale(1.05); text-shadow: 0 0 10px rgba(255, 105, 180, 0.5), 0 0 20px rgba(255, 20, 147, 0.5), 0 0 30px rgba(255, 105, 180, 0.3); } .main-title::before { content: "Valentine's"; position: absolute; top: -5px; left: -5px; color: transparent; background: linear-gradient( 45deg, rgba(255, 105, 180, 0.2) 0%, rgba(255, 20, 147, 0.4) 50%, rgba(255, 105, 180, 0.2) 100% ); -webkit-background-clip: text; background-clip: text; z-index: -1; filter: blur(10px); opacity: 0.5; transition: all 0.5s ease; } .main-title:hover::before { transform: translateZ(-10px); opacity: 0.7; } .sub-title { font-family: "Arial", sans-serif; font-size: 1.5em; color: #fff; text-transform: uppercase; letter-spacing: 4px; margin: 20px 0 40px; } .start-button { background: transparent; border: 2px solid #ff69b4; color: #ff69b4; padding: 15px 40px; font-size: 1.5em; border-radius: 30px; cursor: pointer; transition: all 0.3s ease; backdrop-filter: blur(5px); position: relative; overflow: hidden; transform: translateY(20px); animation: buttonEntrance 1.5s 1s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; } .start-button:hover { background: rgba(255, 105, 180, 0.2); transform: scale(1.1); box-shadow: 0 0 20px rgba(255, 105, 180, 0.5), 0 0 40px rgba(255, 20, 147, 0.3), inset 0 0 15px rgba(255, 105, 180, 0.3); } .start-button:hover::before { left: 100%; } .start-button::before { content: ""; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient( 90deg, transparent, rgba(255, 255, 255, 0.2), transparent ); transition: 0.5s; } .floating-hearts { position: absolute; width: 100%; height: 100%; top: 0; left: 0; pointer-events: none; animation: backgroundTwinkle 20s linear infinite; overflow: hidden; filter: blur(1px); } .heart { position: absolute; font-size: 1.5em; color: rgba(255, 105, 180, 0.6); opacity: 0; animation: float 12s linear infinite; text-shadow: 0 0 10px rgba(255, 105, 180, 0.3); will-change: transform; } .heart:nth-child(1) { left: 10%; top: 120%; animation-delay: 0s; } .heart:nth-child(2) { left: 30%; top: 130%; animation-delay: 1.2s; } .heart:nth-child(3) { left: 50%; top: 110%; animation-delay: 2.4s; } .heart:nth-child(4) { left: 70%; top: 140%; animation-delay: 3.6s; } .heart:nth-child(5) { left: 90%; top: 125%; animation-delay: 4.8s; } .heart:nth-child(6) { left: 20%; top: 135%; animation-delay: 6s; } .heart:nth-child(7) { left: 40%; top: 115%; animation-delay: 7.2s; } .heart:nth-child(8) { left: 60%; top: 145%; animation-delay: 8.4s; } .heart:nth-child(9) { left: 80%; top: 120%; animation-delay: 9.6s; } .heart:nth-child(10) { left: 15%; top: 130%; animation-delay: 10.8s; } .heart:nth-child(11) { left: 25%; top: 125%; animation-delay: 12s; } .heart:nth-child(12) { left: 55%; top: 135%; animation-delay: 13.2s; } .heart:nth-child(13) { left: 85%; top: 140%; animation-delay: 14.4s; } .heart:nth-child(14) { left: 35%; top: 145%; animation-delay: 15.6s; } .heart:nth-child(15) { left: 65%; top: 115%; animation-delay: 16.8s; } .heart:nth-child(16) { left: 5%; top: 125%; animation-delay: 18s; } .heart:nth-child(17) { left: 45%; top: 135%; animation-delay: 19.2s; } .heart:nth-child(18) { left: 75%; top: 130%; animation-delay: 20.4s; } .heart:nth-child(19) { left: 95%; top: 140%; animation-delay: 21.6s; } .heart:nth-child(20) { left: 25%; top: 120%; animation-delay: 22.8s; } .heart::before { content: "❀"; position: absolute; opacity: 0.2; filter: blur(3px); transform: scale(1.3); color: rgba(255, 20, 147, 0.4); } @keyframes titleEntrance { 0% { transform: translateY(100vh) rotateX(90deg); opacity: 0; } 100% { transform: translateY(0) rotateX(0); opacity: 1; } } @keyframes titleGlow { 0%, 100% { text-shadow: 0 0 30px rgba(255, 105, 180, 0.3), 0 0 60px rgba(255, 50, 150, 0.2), 0 0 90px rgba(255, 0, 100, 0.1); } 50% { text-shadow: 0 0 50px rgba(255, 105, 180, 0.6), 0 0 100px rgba(255, 50, 150, 0.4), 0 0 150px rgba(255, 0, 100, 0.2); } } @keyframes buttonEntrance { 0% { opacity: 0; transform: translateY(20px) scale(0.8); } 100% { opacity: 1; transform: translateY(0) scale(1); } } @keyframes backgroundTwinkle { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(1deg); } 75% { transform: rotate(-1deg); } } @keyframes float { 0% { transform: translateY(120%) translateX(-50%) scale(0); opacity: 0; } 5% { opacity: 0.7; transform: translateY(100%) translateX(0%) scale(0.8); } 95% { opacity: 0.4; transform: translateY(-20%) translateX(5%) scale(1.2); } 100% { transform: translateY(-40%) translateX(10%) scale(1.5); opacity: 0; } } .crystal-overlay { display: none; }
JS
Copy
import * as THREE from "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.module.js"; import { Howl, Howler } from "https://cdn.jsdelivr.net/npm/howler@2.2.3/+esm"; class DiamondHeart { constructor() { this.preloadShaderTextures(); this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, powerPreference: "high-performance" }); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.setClearColor(0x000000, 0); this.renderer.outputEncoding = THREE.sRGBEncoding; this.renderer.shadowMap.enabled = true; const existingCanvas = document.querySelector(".scene canvas"); if (existingCanvas) { existingCanvas.remove(); } document.querySelector(".scene").appendChild(this.renderer.domElement); this.createHeart(); this.createRings(); this.setupLights(); this.setupEnvironment(); this.createDolphinPendant(); this.createHeartPendant(); this.initParticles(); this.initEventListeners(); this.lastUpdate = Date.now(); this.initAudioReactivity(); this.animate(); } async preloadShaderTextures() { const textureLoader = new THREE.TextureLoader(); try { this.particleTexture = await textureLoader.loadAsync( "https://cdn.jsdelivr.net/npm/three@0.128.0/examples/textures/sprites/disc.png" ); console.log("Shader textures preloaded successfully"); } catch (error) { console.error("Error preloading shader textures:", error); } } createHeart() { const heartShape = new THREE.Shape(); heartShape.moveTo(0, 0); heartShape.bezierCurveTo(0, 0, 0, -50, 50, -50); heartShape.bezierCurveTo(100, -50, 100, 0, 100, 0); heartShape.bezierCurveTo(100, 50, 50, 100, 0, 150); heartShape.bezierCurveTo(-50, 100, -100, 50, -100, 0); heartShape.bezierCurveTo(-100, -50, -50, -50, 0, 0); const extrudeSettings = { depth: 35, bevelEnabled: true, bevelSegments: 20, steps: 7, bevelSize: 10, bevelThickness: 10 }; const geometry = new THREE.ExtrudeGeometry(heartShape, extrudeSettings); const material = new THREE.MeshPhysicalMaterial({ color: new THREE.Color(0xff6666), transmission: 0.98, opacity: 1, reflectivity: 1, roughness: 0, metalness: 0.2, clearcoat: 1, clearcoatRoughness: 0, ior: 2.7, transparent: true, side: THREE.DoubleSide, emissive: new THREE.Color(0xff2222), emissiveIntensity: 0.05 }); geometry.computeVertexNormals(); this.heart = new THREE.Mesh(geometry, material); this.heart.position.set(0, 0, -200); this.heart.rotation.x = Math.PI / 4; this.heart.castShadow = true; this.heart.receiveShadow = true; this.scene.add(this.heart); this.camera.position.z = 500; } createRings() { const outerRadius = 70; const innerRadius = 60; const ringHeight = 10; const chamferSize = 2; const ringShape = new THREE.Shape(); ringShape.moveTo(outerRadius, 0); ringShape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false); const holeShape = new THREE.Shape(); holeShape.moveTo(innerRadius, 0); holeShape.absarc(0, 0, innerRadius, 0, Math.PI * 2, false); ringShape.holes.push(holeShape); const extrudeSettings = { depth: ringHeight, steps: 3, bevelEnabled: true, bevelSize: chamferSize, bevelThickness: chamferSize, bevelSegments: 10 }; const ringGeometry = new THREE.ExtrudeGeometry(ringShape, extrudeSettings); const ringMaterial = new THREE.MeshPhysicalMaterial({ color: 0xffd700, metalness: 0.95, roughness: 0.05, clearcoat: 1, clearcoatRoughness: 0, reflectivity: 1, transmission: 0.05, opacity: 0.98, ior: 1.8, iridescence: 0.3, iridescenceIOR: 1.6, specularIntensity: 1, specularColor: new THREE.Color(0xffc125), side: THREE.DoubleSide }); this.rings = []; const orbitRadius = 220; const verticalSpread = 80; for (let i = 0; i < 3; i++) { const ring = new THREE.Mesh(ringGeometry, ringMaterial); ring.position.set( Math.cos((i * Math.PI * 2) / 3) * orbitRadius, Math.sin((i * Math.PI * 2) / 3) * orbitRadius * 0.6, -200 + (i - 1) * verticalSpread ); ring.rotation.x = Math.PI / 2; this.scene.add(ring); this.rings.push(ring); } } setupLights() { const ambientLight = new THREE.AmbientLight(0xffffff, 0.3); this.scene.add(ambientLight); const lights = [ [1, 1, 1, 0xff4444], [-1, -1, -1, 0xffffff], [1, -1, 1, 0xff2222], [-1, 1, -1, 0xffffff] ]; lights.forEach(([x, y, z, color]) => { const light = new THREE.DirectionalLight(color, 0.7); light.position.set(x * 200, y * 200, z * 200); this.scene.add(light); }); const pointLight = new THREE.PointLight(0xff3333, 1.5, 700); pointLight.position.set(0, 0, 300); this.scene.add(pointLight); } setupEnvironment() { const solidColor = new THREE.Color(0x4b0082); this.scene.background = solidColor; this.scene.environment = null; this.renderer.toneMappingExposure = 1.3; this.renderer.toneMapping = THREE.ACESFilmicToneMapping; } createDolphinPendant() { const dolphinShape = new THREE.Shape(); dolphinShape.moveTo(0, 0); dolphinShape.quadraticCurveTo(-20, 10, -40, 20); dolphinShape.quadraticCurveTo(-60, 40, -50, 60); dolphinShape.quadraticCurveTo(-40, 80, -20, 70); dolphinShape.lineTo(0, 50); dolphinShape.lineTo(20, 70); dolphinShape.quadraticCurveTo(40, 80, 50, 60); dolphinShape.quadraticCurveTo(60, 40, 40, 20); dolphinShape.quadraticCurveTo(20, 10, 0, 0); const extrudeSettings = { depth: 20, bevelEnabled: true, bevelSegments: 10, steps: 4, bevelSize: 5, bevelThickness: 5 }; const geometry = new THREE.ExtrudeGeometry(dolphinShape, extrudeSettings); const material = new THREE.MeshPhysicalMaterial({ color: new THREE.Color(0x6a5acd), metalness: 1, roughness: 0.2, reflectivity: 1, clearcoat: 1, clearcoatRoughness: 0.1, transmission: 0.1, opacity: 0.95, ior: 1.7, iridescence: 1, iridescenceIOR: 1.5, specularIntensity: 1, specularColor: new THREE.Color(0x9370db), side: THREE.DoubleSide }); this.dolphinPendant = new THREE.Mesh(geometry, material); this.dolphinPendant.position.set(0, 0, -300); this.dolphinPendant.rotation.z = Math.PI / 2; this.dolphinPendant.scale.set(0.8, 0.8, 0.8); this.dolphinPendant.castShadow = true; this.scene.add(this.dolphinPendant); } createHeartPendant() { const heartShape = new THREE.Shape(); heartShape.moveTo(0, 0); heartShape.bezierCurveTo(0, 0, 0, -50, 50, -50); heartShape.bezierCurveTo(100, -50, 100, 0, 100, 0); heartShape.bezierCurveTo(100, 50, 50, 100, 0, 150); heartShape.bezierCurveTo(-50, 100, -100, 50, -100, 0); heartShape.bezierCurveTo(-100, -50, -50, -50, 0, 0); const extrudeSettings = { depth: 20, bevelEnabled: true, bevelSegments: 10, steps: 4, bevelSize: 5, bevelThickness: 5 }; const geometry = new THREE.ExtrudeGeometry(heartShape, extrudeSettings); const material = new THREE.MeshPhysicalMaterial({ color: new THREE.Color(0x8a4fff), metalness: 1, roughness: 0.15, reflectivity: 1, clearcoat: 1, clearcoatRoughness: 0.1, transmission: 0.1, opacity: 0.95, ior: 1.7, iridescence: 1, iridescenceIOR: 1.5, specularIntensity: 1, specularColor: new THREE.Color(0x7b68ee), side: THREE.DoubleSide }); this.heartPendant = new THREE.Mesh(geometry, material); this.heartPendant.position.set(0, 0, -300); this.heartPendant.rotation.z = Math.PI; this.heartPendant.scale.set(0.5, 0.5, 0.5); this.heartPendant.castShadow = true; this.scene.add(this.heartPendant); } initParticles() { this.particles = []; const geometry = new THREE.BufferGeometry(); const positions = []; const colors = []; const sizes = []; const velocities = []; for (let i = 0; i < 10000; i++) { const radius = Math.random() * 1000; const theta = Math.random() * Math.PI * 2; const phi = Math.acos(Math.random() * 2 - 1); const x = radius * Math.sin(phi) * Math.cos(theta); const y = radius * Math.sin(phi) * Math.sin(theta); const z = radius * Math.cos(phi); positions.push(x, y, z); const hue = Math.random(); const saturation = 0.5 + Math.random() * 0.5; const lightness = 0.5 + Math.random() * 0.5; colors.push( Math.cos(hue * Math.PI * 2) * 0.5 + 0.5, Math.cos(hue * Math.PI * 2 + 2) * 0.5 + 0.5, Math.cos(hue * Math.PI * 2 + 4) * 0.5 + 0.5 ); sizes.push(1 + Math.random() * 3); velocities.push( (Math.random() - 0.5) * 0.5, (Math.random() - 0.5) * 0.5, (Math.random() - 0.5) * 0.5 ); } geometry.setAttribute( "position", new THREE.Float32BufferAttribute(positions, 3) ); geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3)); geometry.setAttribute("size", new THREE.Float32BufferAttribute(sizes, 1)); geometry.setAttribute( "velocity", new THREE.Float32BufferAttribute(velocities, 3) ); const material = new THREE.ShaderMaterial({ uniforms: { time: { value: 0 }, musicEnergy: { value: 0 }, pointTexture: { value: this.particleTexture || new THREE.TextureLoader().load( "https://cdn.jsdelivr.net/npm/three@0.128.0/examples/textures/sprites/disc.png" ) } }, vertexShader: ` attribute float size; attribute vec3 velocity; uniform float time; uniform float musicEnergy; varying vec3 vColor; void main() { vColor = color; vec3 newPosition = position; // Music-synchronized movement float movementIntensity = musicEnergy * 50.0; newPosition += velocity * movementIntensity * (sin(time * 2.0 + length(position) * 0.01) + 1.0) * 0.5; vec4 mvPosition = modelViewMatrix * vec4(newPosition, 1.0); gl_PointSize = size * (300.0 / -mvPosition.z) * (1.0 + musicEnergy * 2.0); gl_Position = projectionMatrix * mvPosition; } `, fragmentShader: ` uniform sampler2D pointTexture; uniform float musicEnergy; varying vec3 vColor; void main() { vec2 coord = gl_PointCoord - vec2(0.5); float dist = length(coord); float alpha = smoothstep(0.5, 0.0, dist); vec3 dynamicColor = vColor * (1.0 + musicEnergy * 2.0); gl_FragColor = vec4(dynamicColor, alpha * (0.7 + musicEnergy * 0.3)); } `, blending: THREE.AdditiveBlending, depthTest: false, transparent: true, vertexColors: true }); this.particleSystem = new THREE.Points(geometry, material); this.scene.add(this.particleSystem); } initEventListeners() { document.addEventListener("mousemove", (e) => { const mouseX = (e.clientX / window.innerWidth) * 2 - 1; const mouseY = (e.clientY / window.innerHeight) * 2 - 1; this.heart.rotation.y = mouseX * 0.5; this.heart.rotation.x = mouseY * 0.3; this.camera.position.z = 500 + mouseY * 50; this.camera.position.x = mouseX * 50; this.camera.lookAt(this.heart.position); }); } initAudioReactivity() { this.sound = new Howl({ src: [ "https://www.amigaremix.com/listen/3595/tony_fluke73_wiren_-_planets_live_performance_-_amigaremix_03595.mp3" ], html5: true, pool: 1, onplay: () => { document.querySelector(".valentine-text").style.opacity = "1"; document.querySelector(".loading-screen").style.opacity = "0"; }, onload: () => { document.querySelector(".loading-screen").style.opacity = "0"; } }); this.analyser = Howler.ctx.createAnalyser(); this.analyser.fftSize = 2048; Howler.masterGain.connect(this.analyser); this.audioData = new Uint8Array(this.analyser.frequencyBinCount); this.energyHistory = new Array(30).fill(0.0001); this.beatCutoff = 0.25; this.beatThreshold = 1.4; } getFrequencyEnergy(startBin, endBin) { return ( Array.from(this.audioData.slice(startBin, endBin)).reduce( (acc, val) => acc + val / 255, 0 ) / (endBin - startBin) ); } detectBeat(energy, history) { const averageEnergy = history.reduce((a, b) => a + b) / history.length; const isBeat = energy > averageEnergy * this.beatThreshold && energy > this.beatCutoff; this.energyHistory = [...this.energyHistory.slice(1), energy]; return isBeat; } animate() { const now = Date.now(); const delta = (now - this.lastUpdate) / 1000; this.lastUpdate = now; if (this.analyser) { this.analyser.getByteFrequencyData(this.audioData); const subBass = this.getFrequencyEnergy(0, 10); const bass = this.getFrequencyEnergy(10, 40); const lowMid = this.getFrequencyEnergy(40, 150); const mid = this.getFrequencyEnergy(150, 400); const highMid = this.getFrequencyEnergy(400, 900); const presence = this.getFrequencyEnergy(900, 1800); const beat = this.detectBeat(subBass + bass, this.energyHistory); const rotationIntensity = { heart: bass * 0.0005 + highMid * 0.0003, rings: mid * 0.001, dolphin: lowMid * 0.0007, heartPendant: presence * 0.0004 }; this.heart.rotation.y += rotationIntensity.heart * (beat ? 1.5 : 1); this.heart.rotation.x += rotationIntensity.heart * 0.6 * (beat ? 1.2 : 1); this.heart.rotation.z += subBass * 0.0003 * Math.sin(now * 0.001); this.rings.forEach((ring, i) => { const rhythmFactor = 1 + (beat ? mid * 0.02 : 0); ring.rotation.x += rotationIntensity.rings * rhythmFactor; ring.rotation.y += rotationIntensity.rings * (i % 2 ? 0.8 : 1.2) * rhythmFactor; ring.rotation.z += highMid * 0.0002 * Math.cos(now * 0.0005 + i); }); if (this.dolphinPendant) { this.dolphinPendant.rotation.x += rotationIntensity.dolphin * Math.sin(now * 0.0007); this.dolphinPendant.rotation.y += rotationIntensity.dolphin * Math.cos(now * 0.0008); this.dolphinPendant.rotation.z += lowMid * 0.0004 * Math.sin(now * 0.0006); } if (this.heartPendant) { this.heartPendant.rotation.x += rotationIntensity.heartPendant * Math.sin(now * 0.0009); this.heartPendant.rotation.y += rotationIntensity.heartPendant * Math.cos(now * 0.001); this.heartPendant.rotation.z += presence * 0.0003 * Math.sin(now * 0.0007); } this.heart.material.color.setHSL( (Math.cos(now * 0.0007) * 0.05 + 0.9) % 1, 0.7 + mid * 0.4, 0.5 + presence * 0.3 ); this.rings.forEach((ring, i) => { if (beat) { ring.scale.set(1.3, 1.3, 1.3); } else { ring.scale.lerp(new THREE.Vector3(1, 1, 1), 0.1); } }); const positions = this.particleSystem.geometry.attributes.position.array; for (let i = 0; i < positions.length; i += 3) { positions[i + 1] += Math.sin(now * 0.001 + i) * (presence * 0.7); } this.particleSystem.geometry.attributes.position.needsUpdate = true; const baseScale = 1 + subBass * 0.4; const beatScale = beat ? 1.15 : 1; this.heart.scale.set( baseScale * beatScale, baseScale * beatScale, baseScale * beatScale ); } this.heart.rotation.y += 0.007; this.heart.rotation.x += 0.004; this.heart.rotation.z += 0.002; const scale = 1 + Math.sin(Date.now() * 0.001) * 0.05; this.heart.scale.set(scale, scale, scale); this.rings.forEach((ring, i) => { const angle = Date.now() * 0.0005 + (i * 2 * Math.PI) / 3; const orbitRadius = 220 + Math.sin(Date.now() * 0.001 + i) * 20; ring.position.x = Math.cos(angle) * orbitRadius; ring.position.y = Math.sin(angle * 1.2) * (orbitRadius * 0.6); ring.position.z = -200 + (i - 1) * 80 + Math.cos(angle * 0.8) * 30; // Maintain minimum distance between rings this.rings.forEach((otherRing, j) => { if (i !== j) { const distance = ring.position.distanceTo(otherRing.position); if (distance < 100) { const pushForce = (100 - distance) * 0.1; const direction = new THREE.Vector3() .subVectors(ring.position, otherRing.position) .normalize(); ring.position.addScaledVector(direction, pushForce); } } }); }); if (this.dolphinPendant) { const time = Date.now() * 0.0005; const orbitRadius = 350; this.dolphinPendant.position.x = Math.cos(time) * orbitRadius; this.dolphinPendant.position.y = Math.sin(time * 1.5) * (orbitRadius * 0.4); this.dolphinPendant.position.z = -300 + Math.sin(time * 0.9) * 50; } if (this.heartPendant) { const time = Date.now() * 0.0005 + Math.PI; const orbitRadius = 300; this.heartPendant.position.x = Math.cos(time) * orbitRadius; this.heartPendant.position.y = Math.sin(time * 1.7) * (orbitRadius * 0.3); this.heartPendant.position.z = -250 + Math.cos(time * 1.1) * 60; } if (Date.now() % 300 < delta * 1000) { this.createFloatingHeart(); } if (this.particleSystem) { this.particleSystem.material.uniforms.time.value = Date.now() * 0.001; } const subBass = this.getFrequencyEnergy(0, 10); const bass = this.getFrequencyEnergy(10, 40); const musicEnergy = subBass + bass; if (this.particleSystem) { this.particleSystem.material.uniforms.musicEnergy.value = musicEnergy; } this.renderer.render(this.scene, this.camera); requestAnimationFrame(() => this.animate()); } createFloatingHeart() { const heartGeometry = new THREE.SphereGeometry(2, 8, 8); const heartMaterial = new THREE.MeshBasicMaterial({ color: new THREE.Color().setHSL(Math.random(), 0.7, 0.5), transparent: true, opacity: 0.7 }); const heart = new THREE.Mesh(heartGeometry, heartMaterial); heart.position.set( (Math.random() - 0.5) * 100, (Math.random() - 0.5) * 100, -200 ); this.scene.add(heart); const speed = 0.1 + Math.random() * 0.2; const angle = Math.random() * Math.PI * 2; const radius = 50 + Math.random() * 100; const animate = () => { heart.position.x += Math.cos(angle) * speed; heart.position.y += Math.sin(angle) * speed; heart.position.z += speed * 2; heart.scale.multiplyScalar(0.99); heart.material.opacity *= 0.99; if (heart.material.opacity < 0.1) { this.scene.remove(heart); return; } requestAnimationFrame(animate); }; animate(); } } let resizeTimeout; window.addEventListener("resize", () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { new DiamondHeart(); }, 250); }); function launchExperience() { document.querySelector(".start-screen").style.opacity = "0"; document.querySelector(".scene").style.opacity = "1"; document.body.style.cursor = "none"; const diamondHeart = new DiamondHeart(); diamondHeart.sound.play(); if (document.documentElement.requestFullscreen) { document.documentElement.requestFullscreen(); } setTimeout(() => { document.querySelector(".start-screen").remove(); }, 1000); } window.launchExperience = launchExperience;