// gif player document.querySelectorAll('.paused-animation').forEach(el => { el.addEventListener('click', () => { el.classList.toggle('active'); }); }); // track players const players = document.querySelectorAll('.track-player'); function updateAllWaveformWidths() { players.forEach(player => { const container = player.querySelector('.track-container'); if (container) update_player_waveform_widths(container); }); } document.addEventListener('DOMContentLoaded', updateAllWaveformWidths); window.addEventListener('resize', updateAllWaveformWidths); players.forEach(player => { const trackName = player.getAttribute('data-track'); const button = document.createElement('div'); button.className = 'blurgreen play-button play'; // start as play player.appendChild(button); const wrapper = document.createElement('div'); wrapper.className = 'track'; wrapper.innerHTML = `
`; player.appendChild(wrapper); const playercontainer = player.querySelector('.track-container'); const played = player.querySelector('.track-played'); const unplayed = player.querySelector('.track-unplayed'); played.style.backgroundImage = `url('tracks/${trackName}-played.webp')`; unplayed.style.backgroundImage = `url('tracks/${trackName}.webp')`; const audio = document.createElement('audio'); audio.style.display = 'none'; player.appendChild(audio); button.addEventListener('click', () => { // Pause other players immediately players.forEach(p => { const otherAudio = p.querySelector('audio'); const otherBtn = p.querySelector('.play-button'); if (otherAudio !== audio) { if (!otherAudio.paused) otherAudio.pause(); if (otherBtn) { otherBtn.classList.add('play'); otherBtn.classList.remove('pause'); } } }); // Then lazy-load and play/pause current audio as before... if (!audio.src) { audio.src = 'tracks/' + trackName + '.mp3'; audio.currentTime = 0; audio.load(); audio.addEventListener('canplay', () => { audio.play().then(() => { button.classList.remove('play'); button.classList.add('pause'); }).catch(() => { button.classList.add('play'); button.classList.remove('pause'); }); }, { once: true }); return; } if (audio.paused) { audio.play().then(() => { button.classList.remove('play'); button.classList.add('pause'); }).catch(() => { button.classList.add('play'); button.classList.remove('pause'); }); } else { audio.pause(); button.classList.add('play'); button.classList.remove('pause'); } }); audio.addEventListener('ended', () => { button.classList.add('play'); button.classList.remove('pause'); }); playercontainer.addEventListener('click', e => { const rect = playercontainer.getBoundingClientRect(); // Use container rect! const clickX = e.clientX - rect.left; const width = rect.width; const portion = Math.min(Math.max(clickX / width, 0), 1); audio.currentTime = portion * audio.duration; }); audio.addEventListener('timeupdate', () => { const portion = audio.duration ? audio.currentTime / audio.duration : 0; update_play_position(played, unplayed, portion); }); }); function update_play_position(played_element, unplayed_element, portion) { const playedPercent = portion * 100; const unplayedPercent = 100 - playedPercent; played_element.style.width = playedPercent + "%"; unplayed_element.style.width = unplayedPercent + "%"; } function update_player_waveform_widths(player_container_element) { const trackWidth = player_container_element.offsetWidth; const played_element = player_container_element.querySelector('.track-played'); const unplayed_element = player_container_element.querySelector('.track-unplayed'); played_element.style.backgroundSize = trackWidth + "px 80px"; unplayed_element.style.backgroundSize = trackWidth + "px 80px"; } // icosahedron // Wait for everything to load window.onload = function() { // Make sure THREE is available if (typeof THREE === 'undefined') { console.error('THREE.js is not loaded from CDN'); // Provide visual feedfront in the container const container = document.getElementById('icosahedron-container'); container.innerHTML = '
THREE.js not loaded
'; return; } // Initialize the scene const container = document.getElementById('icosahedron-container'); const containerWidth = container.clientWidth; const containerHeight = container.clientHeight; // Setup renderer const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setSize(containerWidth, containerHeight); renderer.setPixelRatio(window.devicePixelRatio); renderer.setClearColor(0x000000, 0); // Transparent frontground container.appendChild(renderer.domElement); // Setup scene const scene = new THREE.Scene(); // Setup camera const camera = new THREE.PerspectiveCamera(26, containerWidth / containerHeight, 0.1, 100); camera.position.z = 4; // Create icosahedron with manually defined vertices function createIcosahedron(radius) { // Golden ratio for icosahedron vertices const t = (1 + Math.sqrt(5)) / 2; // Normalize radius const normRadius = radius / Math.sqrt(1 + t * t); // Create vertices const vertices = [ [-1, t, 0], [1, t, 0], [-1, -t, 0], [1, -t, 0], [0, -1, t], [0, 1, t], [0, -1, -t], [0, 1, -t], [t, 0, -1], [t, 0, 1], [-t, 0, -1], [-t, 0, 1] ].map(v => new THREE.Vector3(v[0] * normRadius, v[1] * normRadius, v[2] * normRadius)); // Define edges (pairs of vertex indices) const edges = [ [0, 11], [0, 5], [0, 1], [0, 7], [0, 10], [1, 5], [1, 7], [1, 8], [1, 9], [2, 3], [2, 4], [2, 6], [2, 10], [2, 11], [3, 4], [3, 6], [3, 8], [3, 9], [4, 5], [4, 9], [4, 11], [5, 9], [5, 11], [6, 7], [6, 8], [6, 10], [7, 8], [7, 10], [8, 9], /*[8, 10],*/ /*[9, 11],*/ [10, 11] ]; return { vertices: vertices, edges: edges }; } // Convert vertex indices to actual vertices and create a line function createLine(icosa, edgeIndex, material) { const startIndex = icosa.edges[edgeIndex][0]; const endIndex = icosa.edges[edgeIndex][1]; const geometry = new THREE.BufferGeometry(); const positions = new Float32Array([ icosa.vertices[startIndex].x, icosa.vertices[startIndex].y, icosa.vertices[startIndex].z, icosa.vertices[endIndex].x, icosa.vertices[endIndex].y, icosa.vertices[endIndex].z ]); geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); return new THREE.Line(geometry, material); } // Function to create the dual line rendering (thin back lines, thick front lines) function createDualLineRendering(radius) { const group = new THREE.Group(); const icosa = createIcosahedron(radius); // Create materials const backMaterial = new THREE.MeshBasicMaterial({ color: 0x63a8b8, // green for back lines depthTest: false, // Don't test depth for back lines transparent: true, opacity: 0.4, side: THREE.DoubleSide }); const frontMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff, // white for front lines transparent: true, depthTest: true, // Use depth test for front lines side: THREE.DoubleSide }); // For each edge, create: // 1. A thin cylindrical tube with back material that ignores depth // 2. A cylindrical tube with front material that uses depth testing const backRadius = 0.025; // thin for back lines const frontRadius = 0.035; // thick for front lines icosa.edges.forEach((edge, index) => { const start = icosa.vertices[edge[0]]; const end = icosa.vertices[edge[1]]; // Create back line (thick) const backLine = createCylinderBetweenPoints(start, end, backRadius, 1, backMaterial); backLine.renderOrder = 1; // Render after front lines group.add(backLine); // Create front line (thin) const frontLine = createCylinderBetweenPoints(start, end, frontRadius, 0.95, frontMaterial); frontLine.renderOrder = 3; // Render before back lines group.add(frontLine); }); // Create faces for depth testing (invisible) const faceMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0, depthWrite: true, side: THREE.DoubleSide }); // Create faces for internal icosahedron const internalMaterial = new THREE.MeshBasicMaterial({ color: 0x63a8b8, depthTest: false, transparent: true, opacity: 0.2, side: THREE.DoubleSide }); // Define faces of icosahedron (each is a triangle of vertex indices) const faces = [ [0, 11, 5], [0, 5, 1], [0, 1, 7], [0, 7, 10], [0, 10, 11], [1, 5, 9], [5, 11, 4], [11, 10, 2], [10, 7, 6], [7, 1, 8], [3, 9, 4], [3, 4, 2], [3, 2, 6], [3, 6, 8], [3, 8, 9], [4, 9, 5], [2, 4, 11], [6, 2, 10], [8, 6, 7], [9, 8, 1] ]; // Create invisible faces for depth testing faces.forEach(face => { const geometry = new THREE.BufferGeometry(); const positions = new Float32Array([ icosa.vertices[face[0]].x, icosa.vertices[face[0]].y, icosa.vertices[face[0]].z, icosa.vertices[face[1]].x, icosa.vertices[face[1]].y, icosa.vertices[face[1]].z, icosa.vertices[face[2]].x, icosa.vertices[face[2]].y, icosa.vertices[face[2]].z ]); geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); const faceMesh = new THREE.Mesh(geometry, faceMaterial); faceMesh.scale.set(0.975, 0.975, 0.975); faceMesh.renderOrder = 0; // Render first for depth buffer group.add(faceMesh); const internalMesh1 = new THREE.Mesh(geometry, internalMaterial); const internalMesh2 = new THREE.Mesh(geometry, internalMaterial); const internalMesh3 = new THREE.Mesh(geometry, internalMaterial); const internalMesh4 = new THREE.Mesh(geometry, internalMaterial); const internalMesh5 = new THREE.Mesh(geometry, internalMaterial); internalMesh1.scale.set(0.85, 0.85, 0.85); internalMesh1.renderOrder = 1; // Render between front and back group.add(internalMesh1); internalMesh2.scale.set(0.65, 0.65, 0.65); internalMesh2.renderOrder = 1; // Render between front and back group.add(internalMesh2); internalMesh3.scale.set(0.5, 0.5, 0.5); internalMesh3.renderOrder = 1; // Render between front and back group.add(internalMesh3); internalMesh4.scale.set(0.4, 0.4, 0.4); internalMesh4.renderOrder = 1; // Render between front and back group.add(internalMesh4); internalMesh5.scale.set(0.3, 0.3, 0.3); internalMesh5.renderOrder = 1; // Render between front and back group.add(internalMesh5); }); return group; } // Function to create a cylinder between two points function createCylinderBetweenPoints(pointX, pointY, radius, lengthmultiplier, material) { // Direction from pointX to pointY const direction = new THREE.Vector3().subVectors(pointY, pointX); const length = direction.length(); // Create cylinder const geometry = new THREE.CylinderGeometry(radius, radius, length * lengthmultiplier, 4, 1); // By default, cylinder is along Y-axis, so rotate it geometry.rotateX(Math.PI / 2); // Create mesh const cylinder = new THREE.Mesh(geometry, material); // Position and orient cylinder const midpoint = new THREE.Vector3().addVectors(pointX, pointY).multiplyScalar(0.5); cylinder.position.copy(midpoint); cylinder.lookAt(pointY); return cylinder; } // Create our icosahedron const icosahedronGroup = createDualLineRendering(0.85); scene.add(icosahedronGroup); // Animation state let animating = true; let lastFrameTime = 0; const targetFPS = 20; const animMultiplier = 30 / targetFPS; const frameDuration = 1000 / targetFPS; // Animation function function animate(now) { requestAnimationFrame(animate); const delta = now - lastFrameTime; if (delta < frameDuration) return; lastFrameTime = now; // Rotate if animation is enabled if (animating) { icosahedronGroup.rotation.x -= 0.002 * animMultiplier; icosahedronGroup.rotation.y += 0.004 * animMultiplier; icosahedronGroup.rotation.z -= 0.001 * animMultiplier; } renderer.render(scene, camera); } // Start animation animate(); // Handle window resize window.addEventListener('resize', function() { // Only update if container dimensions change const newWidth = container.clientWidth; const newHeight = container.clientHeight; if (newWidth !== containerWidth || newHeight !== containerHeight) { camera.aspect = newWidth / newHeight; camera.updateProjectionMatrix(); renderer.setSize(newWidth, newHeight); } }); console.log('Icosahedron wireframe created successfully'); };