← back to Model Arena

data/artifacts/1fd68f0d37b7/kimi.html

314 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Microphone Audio Visualizer</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  
  :root {
    --bg-color: #0a0a0a;
    --text-color: #ffffff;
    --accent-color: #00ff88;
    --bar-color: #00ccff;
    --wave-color: #ffffff;
    --error-bg: rgba(255, 50, 50, 0.1);
    --error-border: rgba(255, 50, 50, 0.4);
  }
  
  body.light {
    --bg-color: #f5f5f5;
    --text-color: #1a1a1a;
    --accent-color: #0066cc;
    --bar-color: #6600cc;
    --wave-color: #000000;
    --error-bg: rgba(200, 0, 0, 0.1);
    --error-border: rgba(200, 0, 0, 0.3);
  }
  
  body {
    background: var(--bg-color);
    color: var(--text-color);
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    overflow: hidden;
    transition: background 0.3s ease, color 0.3s ease;
  }
  
  #container {
    position: relative;
    width: 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
  }
  
  canvas {
    flex: 1;
    width: 100%;
    height: 100%;
    display: block;
  }
  
  #controls {
    position: absolute;
    top: 20px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 12px;
    z-index: 10;
  }
  
  button {
    padding: 12px 24px;
    border: 2px solid var(--accent-color);
    background: var(--bg-color);
    color: var(--accent-color);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    border-radius: 30px;
    transition: all 0.3s ease;
    text-transform: uppercase;
    letter-spacing: 1px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  }
  
  button:hover {
    background: var(--accent-color);
    color: var(--bg-color);
    transform: translateY(-2px);
    box-shadow: 0 4px 16px rgba(0,0,0,0.2);
  }
  
  button:active {
    transform: translateY(0);
  }
  
  #error {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: var(--error-bg);
    border: 1px solid var(--error-border);
    color: var(--text-color);
    padding: 24px;
    border-radius: 12px;
    text-align: center;
    max-width: 400px;
    width: 90%;
    display: none;
    font-size: 16px;
    line-height: 1.5;
    backdrop-filter: blur(10px);
  }
  
  #status {
    position: absolute;
    bottom: 30px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 13px;
    opacity: 0.6;
    pointer-events: none;
    letter-spacing: 0.5px;
  }
</style>
</head>
<body>
<div id="container">
  <div id="controls">
    <button id="startBtn">Start Microphone</button>
    <button id="themeBtn">Toggle Theme</button>
  </div>
  <div id="error"></div>
  <div id="status">Click 'Start Microphone' to begin visualization</div>
  <canvas id="canvas"></canvas>
</div>

<script>
(function() {
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  const startBtn = document.getElementById('startBtn');
  const themeBtn = document.getElementById('themeBtn');
  const errorDiv = document.getElementById('error');
  const statusDiv = document.getElementById('status');
  
  let audioContext;
  let analyser;
  let microphone;
  let freqArray;
  let timeArray;
  let animationId;
  let isRunning = false;
  
  // Theme toggle
  themeBtn.addEventListener('click', () => {
    document.body.classList.toggle('light');
  });
  
  // Handle canvas resolution
  function resize() {
    const dpr = window.devicePixelRatio || 1;
    const rect = canvas.getBoundingClientRect();
    canvas.width = rect.width * dpr;
    canvas.height = rect.height * dpr;
    ctx.scale(dpr, dpr);
  }
  
  window.addEventListener('resize', resize);
  resize();
  
  async function initAudio() {
    try {
      audioContext = new (window.AudioContext || window.webkitAudioContext)();
      
      const stream = await navigator.mediaDevices.getUserMedia({ 
        audio: {
          echoCancellation: false,
          noiseSuppression: false,
          autoGainControl: false
        } 
      });
      
      microphone = audioContext.createMediaStreamSource(stream);
      analyser = audioContext.createAnalyser();
      
      analyser.fftSize = 2048;
      const bufferLength = analyser.frequencyBinCount;
      
      // Frequency data uses half the FFT size
      freqArray = new Uint8Array(bufferLength);
      // Time domain data uses full FFT size
      timeArray = new Uint8Array(analyser.fftSize);
      
      microphone.connect(analyser);
      
      isRunning = true;
      startBtn.textContent = 'Stop Microphone';
      statusDiv.textContent = 'Visualizing live audio...';
      errorDiv.style.display = 'none';
      
      draw();
      
    } catch (err) {
      console.error('Microphone access error:', err);
      let message = 'Unable to access microphone. ';
      if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
        message += 'Permission was denied. Please allow microphone access in your browser settings and try again.';
      } else if (err.name === 'NotFoundError') {
        message += 'No microphone found. Please ensure your device has a microphone connected.';
      } else {
        message += 'Please check your browser permissions and ensure a microphone is available.';
      }
      errorDiv.textContent = message;
      errorDiv.style.display = 'block';
      statusDiv.textContent = 'Error accessing microphone';
    }
  }
  
  function stopAudio() {
    if (animationId) {
      cancelAnimationFrame(animationId);
    }
    if (microphone && microphone.mediaStream) {
      microphone.mediaStream.getTracks().forEach(track => track.stop());
    }
    if (audioContext && audioContext.state !== 'closed') {
      audioContext.close();
    }
    isRunning = false;
    startBtn.textContent = 'Start Microphone';
    statusDiv.textContent = "Click 'Start Microphone' to begin visualization";
    
    // Clear canvas
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    ctx.clearRect(0, 0, width, height);
  }
  
  startBtn.addEventListener('click', () => {
    if (isRunning) {
      stopAudio();
    } else {
      initAudio();
    }
  });
  
  function draw() {
    if (!isRunning) return;
    
    animationId = requestAnimationFrame(draw);
    
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    
    analyser.getByteFrequencyData(freqArray);
    analyser.getByteTimeDomainData(timeArray);
    
    ctx.clearRect(0, 0, width, height);
    
    const isLight = document.body.classList.contains('light');
    const accent = isLight ? '#0066cc' : '#00ff88';
    const barBase = isLight ? '102, 0, 204' : '0, 204, 255';
    
    // Draw Frequency Bars (bottom)
    const barWidth = (width / freqArray.length) * 2.5;
    let x = 0;
    
    for (let i = 0; i < freqArray.length; i++) {
      const percent = freqArray[i] / 255;
      const barHeight = percent * (height * 0.45);
      
      const gradient = ctx.createLinearGradient(0, height, 0, height - barHeight);
      gradient.addColorStop(0, `rgba(${barBase}, 0.4)`);
      gradient.addColorStop(0.5, `rgba(${barBase}, 0.7)`);
      gradient.addColorStop(1, accent);
      
      ctx.fillStyle = gradient;
      ctx.fillRect(x, height - barHeight, barWidth, barHeight);
      
      x += barWidth + 1;
      if (x > width) break;
    }
    
    // Draw Waveform (center)
    ctx.lineWidth = 3;
    ctx.strokeStyle = isLight ? '#000000' : '#ffffff';
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.beginPath();
    
    const sliceWidth = width / timeArray.length;
    x = 0;
    
    for (let i = 0; i < timeArray.length; i++) {
      const v = timeArray[i] / 128.0;
      const y = (v * height * 0.4) + (height * 0.3);
      
      if (i === 0) {
        ctx.moveTo(x, y);
      } else {
        ctx.lineTo(x, y);
      }
      
      x += sliceWidth;
    }
    
    ctx.stroke();
    
    // Subtle center line
    ctx.lineWidth = 1;
    ctx.strokeStyle = isLight ? 'rgba(0,0,0,0.08)' : 'rgba(255,255,255,0.08)';
    ctx.beginPath();
    ctx.moveTo(0, height / 2);
    ctx.lineTo(width, height / 2);
    ctx.stroke();
  }
})();
</script>
</body>
</html>