From e0d5deb9c920b01c6c787900d7470f3358df7e4b Mon Sep 17 00:00:00 2001 From: Alina Marquardt Date: Fri, 16 May 2025 10:09:05 +0200 Subject: [PATCH 1/2] added mastodon verification --- public_html/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public_html/index.html b/public_html/index.html index 2ee9cc6..3021a10 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -12,6 +12,8 @@ + + From 445f42cce8fbac226fa6dbe64e0fe2bfb4e575b8 Mon Sep 17 00:00:00 2001 From: Alina Marquardt Date: Fri, 16 May 2025 10:46:31 +0200 Subject: [PATCH 2/2] lazyloading and js cleanup --- public_html/functions.js | 131 ++++++++++++++++----------------------- public_html/index.html | 2 +- 2 files changed, 54 insertions(+), 79 deletions(-) diff --git a/public_html/functions.js b/public_html/functions.js index af561bf..7044e6b 100644 --- a/public_html/functions.js +++ b/public_html/functions.js @@ -8,13 +8,6 @@ document.querySelectorAll('.paused-animation').forEach(el => { // 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); @@ -22,7 +15,7 @@ players.forEach(player => { const trackName = player.getAttribute('data-track'); const button = document.createElement('div'); - button.className = 'play-button play'; // start as play + button.className = 'play-button play'; player.appendChild(button); const wrapper = document.createElement('div'); @@ -38,7 +31,6 @@ players.forEach(player => { 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.png')`; unplayed.style.backgroundImage = `url('tracks/${trackName}.png')`; const audio = document.createElement('audio'); @@ -46,7 +38,7 @@ players.forEach(player => { player.appendChild(audio); button.addEventListener('click', () => { - // Pause other players immediately + // pause other players immediately players.forEach(p => { const otherAudio = p.querySelector('audio'); const otherBtn = p.querySelector('.play-button'); @@ -59,8 +51,9 @@ players.forEach(player => { } }); - // Then lazy-load and play/pause current audio as before... + // player needs mp3 url (and played waveform image to lazyload) if (!audio.src) { + played.style.backgroundImage = `url('tracks/${trackName}-played.png')`; audio.src = 'tracks/' + trackName + '.mp3'; audio.currentTime = 0; audio.load(); @@ -93,15 +86,14 @@ players.forEach(player => { } }); - audio.addEventListener('ended', () => { button.classList.add('play'); button.classList.remove('pause'); }); - + // skip to playback position on waveform interaction playercontainer.addEventListener('click', e => { - const rect = playercontainer.getBoundingClientRect(); // Use container rect! + const rect = playercontainer.getBoundingClientRect(); const clickX = e.clientX - rect.left; const width = rect.width; @@ -112,18 +104,18 @@ players.forEach(player => { audio.addEventListener('timeupdate', () => { const portion = audio.duration ? audio.currentTime / audio.duration : 0; - update_play_position(played, unplayed, portion); + updatePlayPosition(played, unplayed, portion); }); }); -function update_play_position(played_element, unplayed_element, portion) { +function updatePlayPosition(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) { +function updatePlayerWaveformWidths(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'); @@ -131,51 +123,49 @@ function update_player_waveform_widths(player_container_element) { unplayed_element.style.backgroundSize = trackWidth + "px 80px"; } +function updateAllWaveformWidths() { // required for proper display of played/unplayed waveforms + players.forEach(player => { + const container = player.querySelector('.track-container'); + if (container) updatePlayerWaveformWidths(container); + }); +} + // icosahedron -// Wait for everything to load window.onload = function() { - // Make sure THREE is available + // Make sure three.js 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 + // init scene const container = document.getElementById('icosahedron-container'); const containerWidth = container.clientWidth; const containerHeight = container.clientHeight; - // Setup renderer + // 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 + renderer.setClearColor(0x000000, 0); // transparent container.appendChild(renderer.domElement); - // Setup scene + // setup scene and camera 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 + // icosahedron with manually defined vertices function createIcosahedron(radius) { - // Golden ratio for icosahedron vertices + // golden ratio const t = (1 + Math.sqrt(5)) / 2; - - // Normalize radius + // normalize radius const normRadius = radius / Math.sqrt(1 + t * t); - // Create vertices const vertices = [ [-1, t, 0], [1, t, 0], @@ -191,7 +181,7 @@ window.onload = function() { [-t, 0, 1] ].map(v => new THREE.Vector3(v[0] * normRadius, v[1] * normRadius, v[2] * normRadius)); - // Define edges (pairs of vertex indices) + // edges (pairs of vertex indices) const edges = [ [0, 11], [0, 5], @@ -221,8 +211,7 @@ window.onload = function() { [6, 10], [7, 8], [7, 10], - [8, 9], /*[8, 10],*/ - /*[9, 11],*/ + [8, 9], [10, 11] ]; @@ -232,7 +221,7 @@ window.onload = function() { }; } - // Convert vertex indices to actual vertices and create a line + // 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]; @@ -248,49 +237,44 @@ window.onload = function() { return new THREE.Line(geometry, material); } - // Function to create the dual line rendering (thin back lines, thick front lines) + // create 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 + color: 0x63a8b8, + 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 + color: 0xffffff, + depthTest: true, // depth test 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 + // cylinders for each edge + const backRadius = 0.025; + const frontRadius = 0.035; 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 + backLine.renderOrder = 1; group.add(backLine); - // Create front line (thin) const frontLine = createCylinderBetweenPoints(start, end, frontRadius, 0.95, frontMaterial); - frontLine.renderOrder = 3; // Render before back lines + frontLine.renderOrder = 3; group.add(frontLine); }); - // Create faces for depth testing (invisible) + // invisible face material for depth testing const faceMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, @@ -299,7 +283,7 @@ window.onload = function() { side: THREE.DoubleSide }); - // Create faces for internal icosahedron + // internal icosahedra material const internalMaterial = new THREE.MeshBasicMaterial({ color: 0x63a8b8, depthTest: false, @@ -308,7 +292,7 @@ window.onload = function() { side: THREE.DoubleSide }); - // Define faces of icosahedron (each is a triangle of vertex indices) + // triangles of vertex indices const faces = [ [0, 11, 5], [0, 5, 1], @@ -332,7 +316,7 @@ window.onload = function() { [9, 8, 1] ]; - // Create invisible faces for depth testing + // create depth testing and internal icosahedra faces.forEach(face => { const geometry = new THREE.BufferGeometry(); const positions = new Float32Array([ @@ -345,50 +329,46 @@ window.onload = function() { const faceMesh = new THREE.Mesh(geometry, faceMaterial); faceMesh.scale.set(0.975, 0.975, 0.975); - faceMesh.renderOrder = 0; // Render first for depth buffer + faceMesh.renderOrder = 0; group.add(faceMesh); + // stack internal icosahedra for volumetric effect 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 + internalMesh1.renderOrder = 1; group.add(internalMesh1); internalMesh2.scale.set(0.65, 0.65, 0.65); - internalMesh2.renderOrder = 1; // Render between front and back + internalMesh2.renderOrder = 1; group.add(internalMesh2); internalMesh3.scale.set(0.5, 0.5, 0.5); - internalMesh3.renderOrder = 1; // Render between front and back + internalMesh3.renderOrder = 1; group.add(internalMesh3); internalMesh4.scale.set(0.4, 0.4, 0.4); - internalMesh4.renderOrder = 1; // Render between front and back + internalMesh4.renderOrder = 1; group.add(internalMesh4); internalMesh5.scale.set(0.3, 0.3, 0.3); - internalMesh5.renderOrder = 1; // Render between front and back + internalMesh5.renderOrder = 1; 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 + // default cylinder is along y-axis, rotate it geometry.rotateX(Math.PI / 2); - // Create mesh const cylinder = new THREE.Mesh(geometry, material); - // Position and orient cylinder + // position and orient cylinder const midpoint = new THREE.Vector3().addVectors(pointX, pointY).multiplyScalar(0.5); cylinder.position.copy(midpoint); cylinder.lookAt(pointY); @@ -396,11 +376,11 @@ window.onload = function() { return cylinder; } - // Create our icosahedron + // create icosahedron const icosahedronGroup = createDualLineRendering(0.85); scene.add(icosahedronGroup); - // Animation state + // animation state let animating = true; let lastFrameTime = 0; @@ -408,7 +388,6 @@ window.onload = function() { const animMultiplier = 30 / targetFPS; const frameDuration = 1000 / targetFPS; - // Animation function function animate(now) { requestAnimationFrame(animate); @@ -417,7 +396,6 @@ window.onload = function() { lastFrameTime = now; - // Rotate if animation is enabled if (animating) { icosahedronGroup.rotation.x -= 0.002 * animMultiplier; icosahedronGroup.rotation.y += 0.004 * animMultiplier; @@ -427,12 +405,9 @@ window.onload = function() { 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; @@ -443,5 +418,5 @@ window.onload = function() { } }); - console.log('Icosahedron wireframe created successfully'); + // console.log('icosahedron wireframe created successfully'); }; diff --git a/public_html/index.html b/public_html/index.html index 3021a10..aa866d1 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -143,7 +143,7 @@
LF 3d animation poster-frame - LF 3d animation + LF 3d animation

a specific animation style I had in mind for my music persona lastfuture required objects with transparent flat faces, integer snapping, controlled image noise and visual artefacts. to solve this I wrote the tiny 3d engine 3d simple that can create this animation style then render it out as a gif.