← back to Model Arena
data/artifacts/1fd68f0d37b7/grok.html
521 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mic Audio Visualizer</title>
<style>
:root, [data-theme="dark"] {
--bg: #0d1117;
--surface: #161b22;
--text: #e6edf3;
--muted: #8b949e;
--accent: #58a6ff;
--bar-hue-start: 200;
--wave: #58a6ff;
--grid: rgba(88, 166, 255, 0.08);
--btn-bg: #21262d;
--btn-hover: #30363d;
--border: #30363d;
--error: #f85149;
}
[data-theme="light"] {
--bg: #f6f8fa;
--surface: #ffffff;
--text: #1f2328;
--muted: #656d76;
--accent: #0969da;
--bar-hue-start: 210;
--wave: #0969da;
--grid: rgba(9, 105, 218, 0.08);
--btn-bg: #f3f4f6;
--btn-hover: #eaeef2;
--border: #d0d7de;
--error: #cf222e;
}
[data-theme="neon"] {
--bg: #0a0014;
--surface: #12001f;
--text: #f0e6ff;
--muted: #a78bfa;
--accent: #ff00aa;
--bar-hue-start: 280;
--wave: #00ffcc;
--grid: rgba(255, 0, 170, 0.1);
--btn-bg: #1a0030;
--btn-hover: #2a0050;
--border: #4a0080;
--error: #ff4466;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
height: 100%;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--text);
transition: background 0.3s, color 0.3s;
}
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100%;
padding: 16px;
}
.app {
width: min(920px, 100%);
display: flex;
flex-direction: column;
gap: 16px;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
h1 {
font-size: 1.25rem;
font-weight: 600;
letter-spacing: -0.02em;
}
.controls {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
button {
appearance: none;
border: 1px solid var(--border);
background: var(--btn-bg);
color: var(--text);
padding: 8px 14px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: background 0.15s, border-color 0.15s, transform 0.1s;
}
button:hover { background: var(--btn-hover); }
button:active { transform: scale(0.97); }
button.primary {
background: var(--accent);
border-color: transparent;
color: #fff;
}
[data-theme="light"] button.primary { color: #fff; }
[data-theme="neon"] button.primary {
background: linear-gradient(135deg, #ff00aa, #7c3aed);
color: #fff;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.stage {
position: relative;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 16px;
overflow: hidden;
aspect-ratio: 16 / 9;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
}
canvas {
display: block;
width: 100%;
height: 100%;
}
.overlay {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
padding: 24px;
text-align: center;
background: color-mix(in srgb, var(--surface) 92%, transparent);
backdrop-filter: blur(6px);
transition: opacity 0.3s, visibility 0.3s;
}
.overlay.hidden {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.overlay p {
color: var(--muted);
font-size: 0.95rem;
max-width: 360px;
line-height: 1.5;
}
.overlay .icon {
font-size: 2.5rem;
line-height: 1;
margin-bottom: 4px;
}
.overlay.error .icon { filter: grayscale(0); }
.status {
font-size: 0.8rem;
color: var(--muted);
display: flex;
align-items: center;
gap: 8px;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--muted);
flex-shrink: 0;
}
.dot.live {
background: #3fb950;
box-shadow: 0 0 0 0 rgba(63, 185, 80, 0.5);
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(63, 185, 80, 0.45); }
70% { box-shadow: 0 0 0 8px rgba(63, 185, 80, 0); }
100% { box-shadow: 0 0 0 0 rgba(63, 185, 80, 0); }
}
.theme-label { font-size: 0.8rem; color: var(--muted); }
</style>
</head>
<body>
<div class="app">
<header>
<h1>Microphone Visualizer</h1>
<div class="controls">
<span class="theme-label">Theme</span>
<button type="button" id="themeBtn" title="Cycle color theme">Dark</button>
<button type="button" id="startBtn" class="primary">Start Microphone</button>
<button type="button" id="stopBtn" disabled>Stop</button>
</div>
</header>
<div class="stage">
<canvas id="canvas"></canvas>
<div class="overlay" id="overlay">
<div class="icon" id="overlayIcon">🎙️</div>
<p id="overlayMsg">Click “Start Microphone” and allow access to see a live frequency-bar and waveform visualization.</p>
</div>
</div>
<div class="status">
<span class="dot" id="dot"></span>
<span id="statusText">Idle</span>
</div>
</div>
<script>
(function () {
const themes = ['dark', 'light', 'neon'];
const themeLabels = { dark: 'Dark', light: 'Light', neon: 'Neon' };
let themeIndex = 0;
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('overlay');
const overlayMsg = document.getElementById('overlayMsg');
const overlayIcon = document.getElementById('overlayIcon');
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const themeBtn = document.getElementById('themeBtn');
const statusText = document.getElementById('statusText');
const dot = document.getElementById('dot');
let audioCtx = null;
let analyser = null;
let micStream = null;
let sourceNode = null;
let rafId = null;
let freqData = null;
let timeData = null;
let running = false;
function cssVar(name) {
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
}
function applyTheme(name) {
document.documentElement.setAttribute('data-theme', name);
themeBtn.textContent = themeLabels[name];
}
themeBtn.addEventListener('click', function () {
themeIndex = (themeIndex + 1) % themes.length;
applyTheme(themes[themeIndex]);
});
applyTheme(themes[0]);
function resize() {
const rect = canvas.parentElement.getBoundingClientRect();
const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.floor(rect.width * dpr);
canvas.height = Math.floor(rect.height * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
window.addEventListener('resize', resize);
resize();
function setStatus(text, live) {
statusText.textContent = text;
dot.classList.toggle('live', !!live);
}
function showOverlay(msg, isError) {
overlayMsg.textContent = msg;
overlayIcon.textContent = isError ? '🚫' : '🎙️';
overlay.classList.toggle('error', !!isError);
overlay.classList.remove('hidden');
}
function hideOverlay() {
overlay.classList.add('hidden');
}
function drawIdle() {
const w = canvas.clientWidth;
const h = canvas.clientHeight;
ctx.clearRect(0, 0, w, h);
const bg = cssVar('--surface') || '#161b22';
ctx.fillStyle = bg;
ctx.fillRect(0, 0, w, h);
const grid = cssVar('--grid');
ctx.strokeStyle = grid;
ctx.lineWidth = 1;
const step = 40;
ctx.beginPath();
for (let x = 0; x <= w; x += step) {
ctx.moveTo(x + 0.5, 0);
ctx.lineTo(x + 0.5, h);
}
for (let y = 0; y <= h; y += step) {
ctx.moveTo(0, y + 0.5);
ctx.lineTo(w, y + 0.5);
}
ctx.stroke();
}
function draw() {
if (!running || !analyser) return;
const w = canvas.clientWidth;
const h = canvas.clientHeight;
analyser.getByteFrequencyData(freqData);
analyser.getByteTimeDomainData(timeData);
const bg = cssVar('--bg') || '#0d1117';
ctx.fillStyle = bg;
ctx.fillRect(0, 0, w, h);
const grid = cssVar('--grid');
ctx.strokeStyle = grid;
ctx.lineWidth = 1;
ctx.beginPath();
const midY = h * 0.42;
ctx.moveTo(0, midY + 0.5);
ctx.lineTo(w, midY + 0.5);
ctx.stroke();
const barRegionTop = h * 0.52;
const barRegionH = h * 0.42;
const binCount = freqData.length;
const usable = Math.floor(binCount * 0.75);
const barCount = Math.min(96, usable);
const gap = 2;
const totalGap = gap * (barCount - 1);
const barW = Math.max(2, (w - totalGap) / barCount);
const hueStart = parseFloat(cssVar('--bar-hue-start')) || 200;
const theme = document.documentElement.getAttribute('data-theme');
for (let i = 0; i < barCount; i++) {
const dataIndex = Math.floor(i * (usable / barCount));
const v = freqData[dataIndex] / 255;
const barH = Math.max(2, v * barRegionH);
const x = i * (barW + gap);
const y = barRegionTop + barRegionH - barH;
let grad;
if (theme === 'neon') {
grad = ctx.createLinearGradient(x, y + barH, x, y);
grad.addColorStop(0, 'hsla(' + (hueStart + i * 1.2) + ',100%,50%,0.3)');
grad.addColorStop(0.5, 'hsla(' + (hueStart + i * 1.2) + ',100%,55%,0.85)');
grad.addColorStop(1, 'hsla(' + ((hueStart + 60 + i) % 360) + ',100%,70%,1)');
} else if (theme === 'light') {
grad = ctx.createLinearGradient(x, y + barH, x, y);
grad.addColorStop(0, 'hsla(' + (hueStart + i * 0.8) + ',70%,55%,0.35)');
grad.addColorStop(1, 'hsla(' + (hueStart + i * 0.8) + ',80%,40%,0.95)');
} else {
grad = ctx.createLinearGradient(x, y + barH, x, y);
grad.addColorStop(0, 'hsla(' + (hueStart + i * 0.9) + ',85%,45%,0.25)');
grad.addColorStop(0.6, 'hsla(' + (hueStart + i * 0.9) + ',90%,55%,0.85)');
grad.addColorStop(1, 'hsla(' + ((hueStart + 40 + i * 0.5) % 360) + ',95%,65%,1)');
}
ctx.fillStyle = grad;
const radius = Math.min(4, barW / 2);
roundRect(ctx, x, y, barW, barH, radius);
ctx.fill();
}
// Waveform
const waveColor = cssVar('--wave') || '#58a6ff';
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = waveColor;
ctx.shadowColor = waveColor;
ctx.shadowBlur = theme === 'neon' ? 12 : 0;
const slice = w / timeData.length;
const waveH = h * 0.35;
const waveMid = h * 0.28;
for (let i = 0; i < timeData.length; i++) {
const v = (timeData[i] - 128) / 128;
const x = i * slice;
const y = waveMid + v * waveH;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
ctx.shadowBlur = 0;
// Soft fill under waveform
ctx.lineTo(w, waveMid);
ctx.lineTo(0, waveMid);
ctx.closePath();
const fillGrad = ctx.createLinearGradient(0, waveMid - waveH, 0, waveMid + waveH);
if (theme === 'neon') {
fillGrad.addColorStop(0, 'rgba(0,255,204,0.12)');
fillGrad.addColorStop(1, 'rgba(255,0,170,0.05)');
} else if (theme === 'light') {
fillGrad.addColorStop(0, 'rgba(9,105,218,0.1)');
fillGrad.addColorStop(1, 'rgba(9,105,218,0.02)');
} else {
fillGrad.addColorStop(0, 'rgba(88,166,255,0.12)');
fillGrad.addColorStop(1, 'rgba(88,166,255,0.02)');
}
ctx.fillStyle = fillGrad;
ctx.fill();
rafId = requestAnimationFrame(draw);
}
function roundRect(c, x, y, w, h, r) {
r = Math.min(r, w / 2, h / 2);
c.beginPath();
c.moveTo(x + r, y);
c.arcTo(x + w, y, x + w, y + h, r);
c.arcTo(x + w, y + h, x, y + h, r);
c.arcTo(x, y + h, x, y, r);
c.arcTo(x, y, x + w, y, r);
c.closePath();
}
async function start() {
if (running) return;
startBtn.disabled = true;
setStatus('Requesting microphone…', false);
try {
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
throw new Error('getUserMedia not supported in this browser.');
}
micStream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false
},
video: false
});
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') {
await audioCtx.resume();
}
analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
analyser.smoothingTimeConstant = 0.78;
analyser.minDecibels = -90;
analyser.maxDecibels = -25;
sourceNode = audioCtx.createMediaStreamSource(micStream);
sourceNode.connect(analyser);
freqData = new Uint8Array(analyser.frequencyBinCount);
timeData = new Uint8Array(analyser.fftSize);
running = true;
hideOverlay();
stopBtn.disabled = false;
startBtn.disabled = true;
setStatus('Live — speak or play audio near your mic', true);
resize();
draw();
} catch (err) {
running = false;
startBtn.disabled = false;
stopBtn.disabled = true;
const name = err && err.name ? err.name : '';
let msg;
if (name === 'NotAllowedError' || name === 'PermissionDeniedError') {
msg = 'Microphone access was denied. Please allow microphone permission in your browser settings and try again.';
} else if (name === 'NotFoundError' || name === 'DevicesNotFoundError') {
msg = 'No microphone was found. Connect a mic and try again.';
} else if (name === 'NotReadableError') {
msg = 'The microphone is already in use by another application.';
} else {
msg = 'Could not access the microphone: ' + (err.message || String(err));
}
showOverlay(msg, true);
setStatus('Microphone unavailable', false);
drawIdle();
}
}
function stop() {
running = false;
if (rafId) {
cancelAnimationFrame(rafId);
rafId = null;
}
if (sourceNode) {
try { sourceNode.disconnect(); } catch (e) {}
sourceNode = null;
}
if (analyser) {
try { analyser.disconnect(); } catch (e) {}
analyser = null;
}
if (micStream) {
micStream.getTracks().forEach(function (t) { t.stop(); });
micStream = null;
}
if (audioCtx) {
audioCtx.close().catch(function () {});
audioCtx = null;
}
freqData = null;
timeData = null;
startBtn.disabled = false;
stopBtn.disabled = true;
setStatus('Stopped', false);
showOverlay('Microphone stopped. Click “Start Microphone” to visualize again.', false);
drawIdle();
}
startBtn.addEventListener('click', start);
stopBtn.addEventListener('click', stop);
drawIdle();
})();
</script>
</body>
</html>