← back to Model Arena
data/artifacts/047c3b8392fa/qwen3-14b.html
366 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Audiovisual Instrument</title>
<style>
:root {
--bg-color: #f9f9f9;
--text-color: #333;
--accent-color: #ff6b6b;
--border-color: #ddd;
--particle-color: #ff6b6b;
--safe-height: 100vh;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
display: flex;
flex-direction: column;
min-height: var(--safe-height);
overflow: hidden;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
header {
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border-color);
}
h1 {
margin: 0;
font-size: 1.5rem;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 1rem;
padding: 1rem;
background-color: #fff;
border: 1px solid var(--border-color);
border-radius: 8px;
flex: 1;
min-height: 200px;
}
.control-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
flex: 1 1 200px;
}
.control-group label {
font-size: 0.9rem;
margin-bottom: 0.25rem;
display: block;
}
.control-group input[type="range"],
.control-group input[type="checkbox"] {
width: 100%;
padding: 0.25rem;
border: 1px solid var(--border-color);
border-radius: 4px;
background-color: #fff;
-webkit-appearance: none;
appearance: none;
outline: none;
}
.control-group input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
background-color: var(--accent-color);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.control-group input[type="range"]::-moz-range-thumb {
width: 16px;
height: 16px;
background-color: var(--accent-color);
border-radius: 50%;
cursor: pointer;
}
.particle-canvas {
flex: 1;
position: relative;
overflow: hidden;
background-color: #fff;
border: 1px solid var(--border-color);
border-radius: 8px;
min-height: 400px;
}
.status {
padding: 1rem;
text-align: center;
font-size: 1rem;
color: #555;
}
.error {
color: #d32f2f;
}
.complete {
color: #2e7d32;
}
@media (max-width: 600px) {
.controls {
flex-direction: column;
}
.control-group {
flex: 1 1 100%;
}
}
/* Reduced motion */
@media (prefers-reduced-motion: no-preference) {
.particle-canvas {
animation: pulse 5s infinite ease-in-out;
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.8;
}
}
</style>
</head>
<body>
<header>
<h1>Audiovisual Instrument</h1>
<button id="toggleMute" aria-label="Toggle mute">🔊</button>
</header>
<div class="controls">
<div class="control-group">
<label for="tone">Tone</label>
<input type="range" id="tone" min="20" max="1000" value="440" step="1" />
</div>
<div class="control-group">
<label for="volume">Volume</label>
<input type="range" id="volume" min="0" max="1" step="0.01" value="0.5" />
</div>
<div class="control-group">
<label for="particleCount">Particles</label>
<input type="range" id="particleCount" min="0" max="100" value="30" step="1" />
</div>
<div class="control-group">
<label for="speed">Speed</label>
<input type="range" id="speed" min="0.1" max="5" value="1" step="0.1" />
</div>
<div class="control-group">
<label for="mute">Mute</label>
<input type="checkbox" id="mute" />
</div>
</div>
<div class="particle-canvas" id="canvas"></div>
<div class="status" id="status">Start playing with the tone slider to see reactive particles.</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const status = document.getElementById('status');
const toggleMuteBtn = document.getElementById('toggleMute');
const muteCheckbox = document.getElementById('mute');
const toneSlider = document.getElementById('tone');
const volumeSlider = document.getElementById('volume');
const particleCountSlider = document.getElementById('particleCount');
const speedSlider = document.getElementById('speed');
let isMuted = muteCheckbox.checked;
// Resize canvas
function resizeCanvas() {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Audio setup
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
let oscillator = audioCtx.createOscillator();
let gainNode = audioCtx.createGain();
oscillator.type = 'sine';
oscillator.frequency.value = parseFloat(toneSlider.value);
gainNode.gain.value = parseFloat(volumeSlider.value);
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.start();
oscillator.stop(audioCtx.currentTime + 1);
function updateAudio() {
oscillator.frequency.value = parseFloat(toneSlider.value);
gainNode.gain.value = parseFloat(volumeSlider.value);
}
toneSlider.addEventListener('input', updateAudio);
volumeSlider.addEventListener('input', updateAudio);
muteCheckbox.addEventListener('change', () => {
isMuted = muteCheckbox.checked;
updateAudio();
toggleMuteBtn.textContent = isMuted ? '🔊' : '🔇';
});
toggleMuteBtn.addEventListener('click', () => {
muteCheckbox.checked = !isMuted;
isMuted = !isMuted;
toggleMuteBtn.textContent = isMuted ? '🔊' : '🔇';
updateAudio();
});
// Particle system
class Particle {
constructor(x, y, radius, color, speed) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.speed = speed;
this.angle = Math.random() * Math.PI * 2;
this.vx = Math.cos(this.angle) * this.speed;
this.vy = Math.sin(this.angle) * this.speed;
}
update() {
this.x += this.vx;
this.y += this.vy;
if (this.x < 0 || this.x > canvas.width || this.y < 0 || this.y > canvas.height) {
this.reset();
}
}
reset() {
this.x = canvas.width / 2;
this.y = canvas.height / 2;
this.angle = Math.random() * Math.PI * 2;
this.vx = Math.cos(this.angle) * this.speed;
this.vy = Math.sin(this.angle) * this.speed;
}
draw(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
}
const particles = [];
const particleCount = parseInt(particleCountSlider.value);
const particleSpeed = parseFloat(speedSlider.value);
for (let i = 0; i < particleCount; i++) {
const radius = 3 + Math.random() * 3;
const color = `hsl(${Math.random() * 360}, 100%, 60%)`;
particles.push(new Particle(
canvas.width / 2,
canvas.height / 2,
radius,
color,
particleSpeed
));
}
function drawParticles() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const particle of particles) {
particle.update();
particle.draw(ctx);
}
}
function animate() {
drawParticles();
requestAnimationFrame(animate);
}
animate();
// Handle user interaction
let isPlaying = false;
function startAudio() {
if (isPlaying) return;
oscillator.stop();
oscillator = audioCtx.createOscillator();
oscillator.type = 'sine';
oscillator.frequency.value = parseFloat(toneSlider.value);
gainNode.gain.value = parseFloat(volumeSlider.value);
oscillator.connect(gainNode);
oscillator.start();
isPlaying = true;
}
function stopAudio() {
oscillator.stop();
isPlaying = false;
}
toneSlider.addEventListener('input', () => {
if (isPlaying) return;
startAudio();
});
particleCountSlider.addEventListener('input', () => {
const count = parseInt(particleCountSlider.value);
particles.length = 0;
for (let i = 0; i < count; i++) {
const radius = 3 + Math.random() * 3;
const color = `hsl(${Math.random() * 360}, 100%, 60%)`;
particles.push(new Particle(
canvas.width / 2,
canvas.height / 2,
radius,
color,
particleSpeed
));
}
});
speedSlider.addEventListener('input', () => {
const speed = parseFloat(speedSlider.value);
for (const particle of particles) {
particle.speed = speed;
}
});
// Status messages
function setStatus(message, type = 'normal') {
status.textContent = message;
status.className = 'status';
if (type === 'error') status.classList.add('error');
else if (type === 'complete') status.classList.add('complete');
}
setStatus("Start playing with the tone slider to see reactive particles.");
</script>
</body>
</html>