Spaces:
Running
Running
Create create an animated. Visualization of twith multiple. Lines repeating the same text. move in a wave, multiple controls for wave form, color, speed, etc - Initial Deployment
a9f7415 verified | <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Text Wave Visualizer</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script> | |
| <style> | |
| .wave-container { | |
| perspective: 1000px; | |
| transform-style: preserve-3d; | |
| } | |
| .wave-line { | |
| position: relative; | |
| transform-origin: center; | |
| will-change: transform; | |
| } | |
| .controls-panel { | |
| backdrop-filter: blur(10px); | |
| background-color: rgba(255, 255, 255, 0.15); | |
| } | |
| @media (max-width: 768px) { | |
| .controls-panel { | |
| position: static; | |
| width: 100%; | |
| margin-top: 20px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gradient-to-br from-gray-900 to-indigo-900 min-h-screen text-white overflow-x-hidden"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="text-center mb-8"> | |
| <h1 class="text-4xl md:text-5xl font-bold mb-2 bg-clip-text text-transparent bg-gradient-to-r from-purple-400 to-pink-600"> | |
| Text Wave Visualizer | |
| </h1> | |
| <p class="text-gray-300 max-w-2xl mx-auto"> | |
| Create mesmerizing wave patterns with customizable text, colors, and motion parameters | |
| </p> | |
| </header> | |
| <div class="flex flex-col lg:flex-row gap-8"> | |
| <!-- Main Visualization Area --> | |
| <div class="flex-1 relative h-[500px] overflow-hidden rounded-xl border border-gray-700 shadow-2xl bg-gray-800/30"> | |
| <div id="waveContainer" class="wave-container w-full h-full flex flex-col justify-center items-center p-4"></div> | |
| </div> | |
| <!-- Controls Panel --> | |
| <div class="controls-panel lg:w-80 p-6 rounded-xl border border-gray-700 shadow-lg"> | |
| <h2 class="text-xl font-semibold mb-4 flex items-center gap-2"> | |
| <i class="fas fa-sliders-h"></i> Wave Controls | |
| </h2> | |
| <div class="space-y-6"> | |
| <!-- Text Input --> | |
| <div> | |
| <label class="block text-sm font-medium mb-1">Text Content</label> | |
| <input type="text" id="textInput" value="WAVE" | |
| class="w-full px-3 py-2 bg-gray-800/50 border border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"> | |
| </div> | |
| <!-- Color Controls --> | |
| <div> | |
| <label class="block text-sm font-medium mb-1">Color Scheme</label> | |
| <div class="flex gap-2"> | |
| <button data-color="gradient-purple" class="color-btn flex-1 h-8 rounded bg-gradient-to-r from-purple-500 to-pink-500"></button> | |
| <button data-color="gradient-blue" class="color-btn flex-1 h-8 rounded bg-gradient-to-r from-blue-400 to-teal-400"></button> | |
| <button data-color="gradient-orange" class="color-btn flex-1 h-8 rounded bg-gradient-to-r from-orange-400 to-red-500"></button> | |
| <button data-color="gradient-green" class="color-btn flex-1 h-8 rounded bg-gradient-to-r from-green-400 to-cyan-400"></button> | |
| </div> | |
| </div> | |
| <!-- Wave Type --> | |
| <div> | |
| <label class="block text-sm font-medium mb-1">Wave Type</label> | |
| <select id="waveType" class="w-full px-3 py-2 bg-gray-800/50 border border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-purple-500"> | |
| <option value="sine">Sine Wave</option> | |
| <option value="square">Square Wave</option> | |
| <option value="triangle">Triangle Wave</option> | |
| <option value="sawtooth">Sawtooth Wave</option> | |
| </select> | |
| </div> | |
| <!-- Numerical Controls --> | |
| <div class="grid grid-cols-2 gap-4"> | |
| <div> | |
| <label for="amplitude" class="block text-sm font-medium mb-1">Amplitude</label> | |
| <input type="range" id="amplitude" min="5" max="100" value="30" | |
| class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"> | |
| <span id="amplitudeValue" class="text-xs text-gray-400">30</span> | |
| </div> | |
| <div> | |
| <label for="frequency" class="block text-sm font-medium mb-1">Frequency</label> | |
| <input type="range" id="frequency" min="0.1" max="2" step="0.1" value="0.5" | |
| class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"> | |
| <span id="frequencyValue" class="text-xs text-gray-400">0.5</span> | |
| </div> | |
| <div> | |
| <label for="speed" class="block text-sm font-medium mb-1">Speed</label> | |
| <input type="range" id="speed" min="1" max="10" value="3" | |
| class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"> | |
| <span id="speedValue" class="text-xs text-gray-400">3</span> | |
| </div> | |
| <div> | |
| <label for="lineCount" class="block text-sm font-medium mb-1">Lines</label> | |
| <input type="range" id="lineCount" min="3" max="20" value="8" | |
| class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"> | |
| <span id="lineCountValue" class="text-xs text-gray-400">8</span> | |
| </div> | |
| </div> | |
| <!-- Advanced Toggles --> | |
| <div class="space-y-2"> | |
| <div class="flex items-center"> | |
| <input type="checkbox" id="randomizePhase" class="w-4 h-4 text-purple-600 bg-gray-800 border-gray-700 rounded focus:ring-purple-500"> | |
| <label for="randomizePhase" class="ml-2 text-sm">Random Phase</label> | |
| </div> | |
| <div class="flex items-center"> | |
| <input type="checkbox" id="show3D" checked class="w-4 h-4 text-purple-600 bg-gray-800 border-gray-700 rounded focus:ring-purple-500"> | |
| <label for="show3D" class="ml-2 text-sm">3D Perspective</label> | |
| </div> | |
| </div> | |
| <!-- Preset Buttons --> | |
| <div class="flex gap-2"> | |
| <button id="resetBtn" class="flex-1 py-2 px-3 bg-gray-700 hover:bg-gray-600 rounded-md text-sm transition"> | |
| <i class="fas fa-redo mr-1"></i> Reset | |
| </button> | |
| <button id="randomBtn" class="flex-1 py-2 px-3 bg-purple-700 hover:bg-purple-600 rounded-md text-sm transition"> | |
| <i class="fas fa-random mr-1"></i> Randomize | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-8 text-center text-gray-400 text-sm"> | |
| <p>Click and drag to rotate the wave in 3D space</p> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // DOM Elements | |
| const waveContainer = document.getElementById('waveContainer'); | |
| const textInput = document.getElementById('textInput'); | |
| const waveType = document.getElementById('waveType'); | |
| const amplitude = document.getElementById('amplitude'); | |
| const frequency = document.getElementById('frequency'); | |
| const speed = document.getElementById('speed'); | |
| const lineCount = document.getElementById('lineCount'); | |
| const randomizePhase = document.getElementById('randomizePhase'); | |
| const show3D = document.getElementById('show3D'); | |
| const resetBtn = document.getElementById('resetBtn'); | |
| const randomBtn = document.getElementById('randomBtn'); | |
| const colorBtns = document.querySelectorAll('.color-btn'); | |
| // Display values | |
| const amplitudeValue = document.getElementById('amplitudeValue'); | |
| const frequencyValue = document.getElementById('frequencyValue'); | |
| const speedValue = document.getElementById('speedValue'); | |
| const lineCountValue = document.getElementById('lineCountValue'); | |
| // State | |
| let animationId; | |
| let time = 0; | |
| let lines = []; | |
| let isDragging = false; | |
| let lastX = 0; | |
| let lastY = 0; | |
| let rotateX = 0; | |
| let rotateY = 0; | |
| let currentColorScheme = 'gradient-purple'; | |
| // Initialize | |
| createWaveLines(); | |
| updateDisplayValues(); | |
| setupEventListeners(); | |
| animate(); | |
| // Functions | |
| function createWaveLines() { | |
| waveContainer.innerHTML = ''; | |
| lines = []; | |
| const count = parseInt(lineCount.value); | |
| const text = textInput.value || 'WAVE'; | |
| for (let i = 0; i < count; i++) { | |
| const line = document.createElement('div'); | |
| line.className = 'wave-line text-center mb-1'; | |
| line.style.fontSize = `${Math.max(12, 24 - (i * 0.5))}px`; | |
| line.textContent = text.repeat(15); | |
| // Set initial color based on position | |
| updateLineColor(line, i, count); | |
| waveContainer.appendChild(line); | |
| lines.push({ | |
| element: line, | |
| phase: randomizePhase.checked ? Math.random() * Math.PI * 2 : 0 | |
| }); | |
| } | |
| } | |
| function updateLineColor(line, index, total) { | |
| const ratio = index / total; | |
| if (currentColorScheme === 'gradient-purple') { | |
| line.style.background = `linear-gradient(90deg, rgba(192, 132, 252, ${1 - ratio * 0.7}), rgba(236, 72, 153, ${1 - ratio * 0.7}))`; | |
| line.style.webkitBackgroundClip = 'text'; | |
| line.style.backgroundClip = 'text'; | |
| line.style.color = 'transparent'; | |
| } | |
| else if (currentColorScheme === 'gradient-blue') { | |
| line.style.background = `linear-gradient(90deg, rgba(96, 165, 250, ${1 - ratio * 0.7}), rgba(45, 212, 191, ${1 - ratio * 0.7}))`; | |
| line.style.webkitBackgroundClip = 'text'; | |
| line.style.backgroundClip = 'text'; | |
| line.style.color = 'transparent'; | |
| } | |
| else if (currentColorScheme === 'gradient-orange') { | |
| line.style.background = `linear-gradient(90deg, rgba(251, 146, 60, ${1 - ratio * 0.7}), rgba(239, 68, 68, ${1 - ratio * 0.7}))`; | |
| line.style.webkitBackgroundClip = 'text'; | |
| line.style.backgroundClip = 'text'; | |
| line.style.color = 'transparent'; | |
| } | |
| else if (currentColorScheme === 'gradient-green') { | |
| line.style.background = `linear-gradient(90deg, rgba(74, 222, 128, ${1 - ratio * 0.7}), rgba(34, 211, 238, ${1 - ratio * 0.7}))`; | |
| line.style.webkitBackgroundClip = 'text'; | |
| line.style.backgroundClip = 'text'; | |
| line.style.color = 'transparent'; | |
| } | |
| } | |
| function updateWave() { | |
| const amp = parseInt(amplitude.value); | |
| const freq = parseFloat(frequency.value); | |
| const spd = parseInt(speed.value); | |
| const type = waveType.value; | |
| lines.forEach((line, i) => { | |
| const element = line.element; | |
| const phase = line.phase; | |
| const yOffset = i * 5 - (parseInt(lineCount.value) * 5) / 2; | |
| let offset = 0; | |
| const timeFactor = time * spd * 0.01; | |
| if (type === 'sine') { | |
| offset = Math.sin(timeFactor * freq + phase) * amp; | |
| } else if (type === 'square') { | |
| offset = Math.sign(Math.sin(timeFactor * freq + phase)) * amp; | |
| } else if (type === 'triangle') { | |
| offset = (2 * amp / Math.PI) * Math.asin(Math.sin(timeFactor * freq + phase)); | |
| } else if (type === 'sawtooth') { | |
| offset = (2 * amp / Math.PI) * Math.atan(Math.tan((timeFactor * freq + phase) / 2)); | |
| } | |
| element.style.transform = `translateX(${offset}px) translateY(${yOffset}px)`; | |
| element.style.opacity = 0.7 + (0.3 * (i / lines.length)); | |
| }); | |
| if (show3D.checked) { | |
| waveContainer.style.transform = `rotateX(${rotateY}deg) rotateY(${rotateX}deg)`; | |
| } else { | |
| waveContainer.style.transform = 'none'; | |
| } | |
| } | |
| function animate() { | |
| time++; | |
| updateWave(); | |
| animationId = requestAnimationFrame(animate); | |
| } | |
| function updateDisplayValues() { | |
| amplitudeValue.textContent = amplitude.value; | |
| frequencyValue.textContent = frequency.value; | |
| speedValue.textContent = speed.value; | |
| lineCountValue.textContent = lineCount.value; | |
| } | |
| function setupEventListeners() { | |
| // Input event listeners | |
| textInput.addEventListener('input', createWaveLines); | |
| waveType.addEventListener('change', () => time = 0); | |
| amplitude.addEventListener('input', updateDisplayValues); | |
| frequency.addEventListener('input', updateDisplayValues); | |
| speed.addEventListener('input', updateDisplayValues); | |
| lineCount.addEventListener('input', function() { | |
| updateDisplayValues(); | |
| createWaveLines(); | |
| }); | |
| // Checkbox listeners | |
| randomizePhase.addEventListener('change', function() { | |
| lines.forEach(line => { | |
| line.phase = this.checked ? Math.random() * Math.PI * 2 : 0; | |
| }); | |
| }); | |
| show3D.addEventListener('change', function() { | |
| waveContainer.style.transform = this.checked ? | |
| `rotateX(${rotateY}deg) rotateY(${rotateX}deg)` : 'none'; | |
| }); | |
| // Button listeners | |
| resetBtn.addEventListener('click', function() { | |
| textInput.value = 'WAVE'; | |
| waveType.value = 'sine'; | |
| amplitude.value = 30; | |
| frequency.value = 0.5; | |
| speed.value = 3; | |
| lineCount.value = 8; | |
| randomizePhase.checked = false; | |
| show3D.checked = true; | |
| rotateX = 0; | |
| rotateY = 0; | |
| currentColorScheme = 'gradient-purple'; | |
| updateDisplayValues(); | |
| createWaveLines(); | |
| // Reset color buttons active state | |
| colorBtns.forEach(btn => { | |
| btn.classList.remove('ring-2', 'ring-white'); | |
| }); | |
| document.querySelector(`[data-color="gradient-purple"]`).classList.add('ring-2', 'ring-white'); | |
| }); | |
| randomBtn.addEventListener('click', function() { | |
| waveType.value = ['sine', 'square', 'triangle', 'sawtooth'][Math.floor(Math.random() * 4)]; | |
| amplitude.value = Math.floor(Math.random() * 96) + 5; | |
| frequency.value = (Math.random() * 1.9 + 0.1).toFixed(1); | |
| speed.value = Math.floor(Math.random() * 9) + 1; | |
| lineCount.value = Math.floor(Math.random() * 17) + 3; | |
| randomizePhase.checked = Math.random() > 0.5; | |
| show3D.checked = Math.random() > 0.3; | |
| // Random color scheme | |
| const colors = ['gradient-purple', 'gradient-blue', 'gradient-orange', 'gradient-green']; | |
| currentColorScheme = colors[Math.floor(Math.random() * colors.length)]; | |
| // Update active color button | |
| colorBtns.forEach(btn => { | |
| btn.classList.remove('ring-2', 'ring-white'); | |
| if (btn.dataset.color === currentColorScheme) { | |
| btn.classList.add('ring-2', 'ring-white'); | |
| } | |
| }); | |
| updateDisplayValues(); | |
| createWaveLines(); | |
| }); | |
| // Color buttons | |
| colorBtns.forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| currentColorScheme = this.dataset.color; | |
| colorBtns.forEach(b => b.classList.remove('ring-2', 'ring-white')); | |
| this.classList.add('ring-2', 'ring-white'); | |
| createWaveLines(); | |
| }); | |
| // Set initial active button | |
| if (btn.dataset.color === currentColorScheme) { | |
| btn.classList.add('ring-2', 'ring-white'); | |
| } | |
| }); | |
| // Mouse interaction for 3D rotation | |
| waveContainer.addEventListener('mousedown', function(e) { | |
| if (!show3D.checked) return; | |
| isDragging = true; | |
| lastX = e.clientX; | |
| lastY = e.clientY; | |
| }); | |
| document.addEventListener('mousemove', function(e) { | |
| if (!isDragging || !show3D.checked) return; | |
| const deltaX = e.clientX - lastX; | |
| const deltaY = e.clientY - lastY; | |
| rotateX += deltaX * 0.5; | |
| rotateY -= deltaY * 0.5; | |
| lastX = e.clientX; | |
| lastY = e.clientY; | |
| }); | |
| document.addEventListener('mouseup', function() { | |
| isDragging = false; | |
| }); | |
| // Touch support | |
| waveContainer.addEventListener('touchstart', function(e) { | |
| if (!show3D.checked) return; | |
| isDragging = true; | |
| lastX = e.touches[0].clientX; | |
| lastY = e.touches[0].clientY; | |
| e.preventDefault(); | |
| }); | |
| document.addEventListener('touchmove', function(e) { | |
| if (!isDragging || !show3D.checked) return; | |
| const deltaX = e.touches[0].clientX - lastX; | |
| const deltaY = e.touches[0].clientY - lastY; | |
| rotateX += deltaX * 0.5; | |
| rotateY -= deltaY * 0.5; | |
| lastX = e.touches[0].clientX; | |
| lastY = e.touches[0].clientY; | |
| e.preventDefault(); | |
| }); | |
| document.addEventListener('touchend', function() { | |
| isDragging = false; | |
| }); | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MarkTheArtist/text-wave" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |