@grok like this?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tone & Flash Generator</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background:
#1a1a2e;
color:
#eaeaea;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.flash-overlay {
display: none;
position: fixed;
top: 0; left: 0;
width: 100vw;
height: 100vh;
z-index: 9999;
pointer-events: none;
background:
#ffffff;
opacity: 0;
}
.flash-overlay.flashing {
display: block;
animation: screenFlash infinite alternate linear;
}
@keyframes screenFlash {
from { opacity: 0; }
to { opacity: 0.92; }
}
.container {
background:
#16213e;
border-radius: 16px;
padding: 40px;
width: 520px;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
}
h1 { text-align: center; font-size: 1.8rem; margin-bottom: 8px; color:
#e94560; }
.subtitle { text-align: center; font-size: 0.85rem; color: #888; margin-bottom: 32px; }
.freq-display { text-align: center; font-size: 3.2rem; font-weight: bold; color:
#e94560; margin-bottom: 4px; }
.freq-label { text-align: center; font-size: 0.9rem; color: #888; margin-bottom: 24px; }
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 6px;
border-radius: 3px;
background:
#0f3460;
outline: none;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 22px; height: 22px;
border-radius: 50%;
background:
#e94560;
cursor: pointer;
box-shadow: 0 0 12px rgba(233,69,96,0.8);
}
.manual-input, .row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.manual-input label, .row label {
width: 80px;
font-size: 0.95rem;
color:
#aaa;
white-space: nowrap;
}
.manual-input input[type="number"], .timer-input {
flex: 1;
background:
#0f3460;
border: 1px solid
#e94560;
border-radius: 8px;
color:
#eaeaea;
padding: 10px;
font-size: 1.1rem;
}
.section-title {
font-size: 0.85rem;
color:
#e94560;
text-transform: uppercase;
letter-spacing: 1px;
margin: 24px 0 12px;
border-bottom: 1px solid
#0f3460;
padding-bottom: 6px;
}
.wave-btn, .unit-btn, .preset-btn, .toggle-btn {
padding: 8px 12px;
border: 1px solid
#0f3460;
border-radius: 8px;
background:
#0f3460;
color:
#aaa;
cursor: pointer;
transition: all 0.2s;
}
.wave-btn.active, .unit-btn.active, .toggle-btn.active {
background:
#e94560;
border-color:
#e94560;
color: white;
box-shadow: 0 0 12px rgba(233,69,96,0.6);
}
.waveform-buttons, .preset-buttons, .color-swatches {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.swatch {
width: 32px; height: 32px;
border-radius: 50%;
cursor: pointer;
border: 2px solid transparent;
}
.swatch.active { border-color:
#fff; transform: scale(1.15); }
.play-btn {
width: 100%;
padding: 16px;
border: none;
border-radius: 12px;
background:
#e94560;
color: white;
font-size: 1.2rem;
font-weight: bold;
cursor: pointer;
margin: 16px 0;
}
.play-btn.playing { background:
#0f3460; }
canvas {
width: 100%;
height: 90px;
background:
#0a0a1a;
border-radius: 8px;
display: block;
}
.warning {
font-size: 0.75rem;
color:
#ffaa00;
text-align: center;
margin-top: 12px;
}
</style>
</head>
<body>
<div class="flash-overlay" id="flashOverlay"></div>
<div class="container">
<h1>🎵 Tone & Flash Generator</h1>
<p class="subtitle">1 Hz – 5000 Hz Audio & Visual Frequency Generator</p>
<div class="freq-display" id="freqDisplay">440 Hz</div>
<div class="freq-label">Current Frequency</div>
<input type="range" id="freqSlider" min="1" max="5000" value="440" step="1">
<div class="manual-input">
<label for="freqInput">Set Hz:</label>
<input type="number" id="freqInput" min="1" max="5000" value="440">
</div>
<div class="preset-buttons">
<button class="preset-btn" data-freq="1">1</button>
<button class="preset-btn" data-freq="10">10</button>
<button class="preset-btn" data-freq="40">40</button>
<button class="preset-btn" data-freq="110">110</button>
<button class="preset-btn" data-freq="220">220</button>
<button class="preset-btn" data-freq="440">440</button>
<button class="preset-btn" data-freq="528">528</button>
<button class="preset-btn" data-freq="1000">1k</button>
<button class="preset-btn" data-freq="5000">5k</button>
</div>
<div class="row">
<label>Volume</label>
<input type="range" id="volumeSlider" min="0" max="100" value="60" step="1">
<span id="volumeDisplay">60%</span>
</div>
<div class="row">
<label>Wave</label>
<div class="waveform-buttons">
<button class="wave-btn active" data-wave="sine">Sine</button>
<button class="wave-btn" data-wave="square">Square</button>
<button class="wave-btn" data-wave="sawtooth">Saw</button>
<button class="wave-btn" data-wave="triangle">Triangle</button>
</div>
</div>
<div class="section-title">⚡ Screen Flash</div>
<div class="row">
<label>Enabled</label>
<button class="toggle-btn" id="flashToggle">OFF</button>
</div>
<div class="row">
<label>Color</label>
<div class="color-swatches">
<div class="swatch active" data-color="
#ffffff" style="background:
#ffffff"></div>
<div class="swatch" data-color="
#ff0000" style="background:
#ff0000"></div>
<div class="swatch" data-color="
#00ff00" style="background:
#00ff00"></div>
<div class="swatch" data-color="
#0000ff" style="background:
#0000ff"></div>
<div class="swatch" data-color="
#ffff00" style="background:
#ffff00"></div>
<input type="color" id="customColor" value="
#ffffff">
</div>
</div>
<div class="section-title">⏱ Auto Stop Timer</div>
<div class="row">
<label>Duration</label>
<input type="number" id="timerInput" min="1" max="9999" value="5" style="width:80px">
<div class="timer-unit-btns">
<button class="unit-btn active" data-unit="seconds">Sec</button>
<button class="unit-btn" data-unit="minutes">Min</button>
</div>
</div>
<div class="row">
<label>Timer</label>
<button class="toggle-btn" id="timerToggle">DISABLED</button>
</div>
<div class="timer-display" id="timerDisplay" style="text-align:center; font-size:1.4rem; color:
#e94560; min-height:32px;"></div>
<button class="play-btn" id="playBtn">▶ PLAY</button>
<canvas id="visualizer"></canvas>
<div class="warning">⚠️ Flashing lights may trigger seizures in some people. Use responsibly.</div>
</div>
<script>
const freqSlider = document.getElementById('freqSlider');
const freqInput = document.getElementById('freqInput');
const freqDisplay = document.getElementById('freqDisplay');
const volumeSlider = document.getElementById('volumeSlider');
const volumeDisplay = document.getElementById('volumeDisplay');
const playBtn = document.getElementById('playBtn');
const canvas = document.getElementById('visualizer');
const ctx = canvas.getContext('2d');
const flashOverlay = document.getElementById('flashOverlay');
const flashToggle = document.getElementById('flashToggle');
const customColor = document.getElementById('customColor');
const timerToggle = document.getElementById('timerToggle');
const timerInput = document.getElementById('timerInput');
const timerDisplay = document.getElementById('timerDisplay');
let audioCtx = null, oscillator = null, gainNode = null, analyser = null;
let animationId = null;
let isPlaying = false;
let currentWave = 'sine';
let currentFreq = 440;
let flashEnabled = false;
let flashColor = '
#ffffff';
let timerEnabled = false;
let timerUnit = 'seconds';
let timerRemaining = 0;
let timerInterval = null;
function resizeCanvas() {
canvas.width = canvas.offsetWidth * 2;
canvas.height = 180;
}
window.addEventListener('resize', resizeCanvas);
setTimeout(resizeCanvas, 100);
// Frequency
freqSlider.addEventListener('input', () => {
currentFreq = parseInt(freqSlider.value);
freqInput.value = currentFreq;
freqDisplay.textContent = currentFreq ' Hz';
if (isPlaying && oscillator) oscillator.frequency.setTargetAtTime(currentFreq, audioCtx.currentTime, 0.02);
if (flashEnabled && isPlaying) startFlash();
});
freqInput.addEventListener('input', () => {
let val = parseInt(freqInput.value);
if (isNaN(val)) return;
val = Math.max(1, Math.min(5000, val));
currentFreq = val;
freqSlider.value = val;
freqDisplay.textContent = val ' Hz';
if (isPlaying && oscillator) oscillator.frequency.setTargetAtTime(val, audioCtx.currentTime, 0.02);
if (flashEnabled && isPlaying) startFlash();
});
// Volume
volumeSlider.addEventListener('input', () => {
const vol = parseInt(volumeSlider.value);
volumeDisplay.textContent = vol '%';
if (gainNode) gainNode.gain.setTargetAtTime(vol / 100, audioCtx?.currentTime || 0, 0.1);
});
// Waveform
document.querySelectorAll('.wave-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.wave-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentWave = btn.dataset.wave;
if (isPlaying && oscillator) oscillator.type = currentWave;
});
});
// Presets
document.querySelectorAll('.preset-btn').forEach(btn => {
btn.addEventListener('click', () => {
currentFreq = parseInt(btn.dataset.freq);
freqSlider.value = currentFreq;
freqInput.value = currentFreq;
freqDisplay.textContent = currentFreq ' Hz';
if (isPlaying && oscillator) oscillator.frequency.setTargetAtTime(currentFreq, audioCtx.currentTime, 0.02);
if (flashEnabled && isPlaying) startFlash();
});
});
// Flash
flashToggle.addEventListener('click', () => {
flashEnabled = !flashEnabled;
flashToggle.textContent = flashEnabled ? 'ON' : 'OFF';
flashToggle.classList.toggle('active', flashEnabled);
if (flashEnabled && isPlaying) startFlash();
else stopFlash();
});
document.querySelectorAll('.swatch').forEach(swatch => {
swatch.addEventListener('click', () => {
document.querySelectorAll('.swatch').forEach(s => s.classList.remove('active'));
swatch.classList.add('active');
flashColor = swatch.dataset.color;
if (flashEnabled)
flashOverlay.style.background = flashColor;
});
});
customColor.addEventListener('input', () => {
flashColor = customColor.value;
document.querySelectorAll('.swatch').forEach(s => s.classList.remove('active'));
if (flashEnabled)
flashOverlay.style.background = flashColor;
});
// Timer
document.querySelectorAll('.unit-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.unit-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
timerUnit = btn.dataset.unit;
});
});
timerToggle.addEventListener('click', () => {
timerEnabled = !timerEnabled;
timerToggle.textContent = timerEnabled ? 'ENABLED' : 'DISABLED';
timerToggle.classList.toggle('active', timerEnabled);
});
function startTimer() {
clearInterval(timerInterval);
let val = parseInt(timerInput.value);
if (isNaN(val) || val <= 0) return;
timerRemaining = (timerUnit === 'minutes') ? val * 60 : val;
updateTimerDisplay();
timerInterval = setInterval(() => {
timerRemaining--;
updateTimerDisplay();
if (timerRemaining <= 0) stopAll();
}, 1000);
}
function updateTimerDisplay() {
const m = Math.floor(timerRemaining / 60);
const s = timerRemaining % 60;
timerDisplay.textContent = `${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;
}
// Play / Stop
playBtn.addEventListener('click', () => isPlaying ? stopAll() : startAll());
function startAll() {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume();
analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
gainNode = audioCtx.createGain();
gainNode.gain.value = parseInt(volumeSlider.value) / 100;
oscillator = audioCtx.createOscillator();
oscillator.type = currentWave;
oscillator.frequency.value = currentFreq;
oscillator.connect(gainNode);
gainNode.connect(analyser);
analyser.connect(audioCtx.destination);
oscillator.start();
isPlaying = true;
playBtn.textContent = '⏹ STOP';
playBtn.classList.add('playing');
if (flashEnabled) startFlash();
if (timerEnabled) startTimer();
drawVisualizer();
}
function stopAll() {
if (oscillator) { oscillator.stop(); oscillator.disconnect(); }
if (audioCtx) audioCtx.close();
stopFlash();
clearInterval(timerInterval);
cancelAnimationFrame(animationId);
ctx.clearRect(0, 0, canvas.width, canvas.height);
isPlaying = false;
playBtn.textContent = '▶ PLAY';
playBtn.classList.remove('playing');
timerDisplay.textContent = '';
}
// Improved Flash using CSS Animation (this fixes the stopping issue)
function startFlash() {
stopFlash();
const safeFreq = Math.min(currentFreq, 25); // safety cap
const halfCycleMs = Math.round(1000 / (safeFreq * 2)); // on/off cycle
flashOverlay.style.animationDuration = `${halfCycleMs}ms`;
flashOverlay.style.background = flashColor;
flashOverlay.classList.add('flashing');
}
function stopFlash() {
flashOverlay.classList.remove('flashing');
}
// Visualizer
function drawVisualizer() {
if (!analyser) return;
animationId = requestAnimationFrame(drawVisualizer);
const bufferLength = analyser.fftSize;
const dataArray = new Uint8Array(bufferLength);
analyser.getByteTimeDomainData(dataArray);
ctx.fillStyle = '
#0a0a1a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 4;
ctx.strokeStyle = '
#e94560';
ctx.shadowBlur = 20;
ctx.shadowColor = '
#e94560';
ctx.beginPath();
const sliceWidth = canvas.width / bufferLength;
let x = 0;
for (let i = 0; i < bufferLength; i ) {
const v = dataArray[i] / 128.0;
const y = v * canvas.height / 2;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
x = sliceWidth;
}
ctx.stroke();
}
resizeCanvas();
</script>
</body>
</html>