Skip to content

Instantly share code, notes, and snippets.

@CrazybutSolid
Created March 17, 2026 12:42
Show Gist options
  • Select an option

  • Save CrazybutSolid/c15d7bd6a6877e32c41925a7538b8a64 to your computer and use it in GitHub Desktop.

Select an option

Save CrazybutSolid/c15d7bd6a6877e32c41925a7538b8a64 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>White Noise</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { width: 100%; height: 100%; }
body {
font-family: 'DM Sans', sans-serif;
background: #0d1117;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
min-height: 500px;
overflow: hidden;
user-select: none;
}
.ambient {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
}
.orb {
position: absolute;
border-radius: 50%;
filter: blur(80px);
opacity: 0;
transition: opacity 2s ease;
}
.orb1 { width: 400px; height: 400px; background: #1a3a5c; top: -100px; left: -100px; }
.orb2 { width: 350px; height: 350px; background: #0d2d1f; bottom: -80px; right: -80px; }
.orb3 { width: 300px; height: 300px; background: #1f1040; top: 50%; left: 50%; transform: translate(-50%,-50%); }
body.playing .orb { opacity: 1; }
.ripple-container {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.ripple {
position: absolute;
width: 200px;
height: 200px;
border-radius: 50%;
border: 1px solid rgba(100, 180, 255, 0.15);
opacity: 0;
}
body.playing .ripple {
animation: rippleOut 3s ease-out infinite;
}
.ripple:nth-child(2) { animation-delay: 1s !important; }
.ripple:nth-child(3) { animation-delay: 2s !important; }
@keyframes rippleOut {
0% { transform: scale(1); opacity: 0.5; }
100% { transform: scale(3.5); opacity: 0; }
}
.content {
position: relative;
z-index: 10;
display: flex;
flex-direction: column;
align-items: center;
gap: 40px;
}
.label-top {
font-size: 11px;
letter-spacing: 0.25em;
text-transform: uppercase;
color: rgba(255,255,255,0.25);
transition: color 1s ease;
}
body.playing .label-top { color: rgba(255,255,255,0.45); }
.btn-wrap {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.btn {
width: 160px;
height: 160px;
border-radius: 50%;
border: 1px solid rgba(255,255,255,0.1);
background: rgba(255,255,255,0.04);
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
background: radial-gradient(circle at 40% 35%, rgba(255,255,255,0.07), transparent 65%);
}
.btn:active { transform: scale(0.95); }
body.playing .btn {
border-color: rgba(100, 180, 255, 0.3);
background: rgba(100, 180, 255, 0.06);
box-shadow: 0 0 40px rgba(100, 180, 255, 0.08), inset 0 0 30px rgba(100, 180, 255, 0.04);
}
.icon {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.play-icon { display: flex; }
.stop-icon { display: none; }
body.playing .play-icon { display: none; }
body.playing .stop-icon { display: flex; }
.btn-label {
font-size: 11px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: rgba(255,255,255,0.4);
font-weight: 300;
transition: color 0.3s;
}
body.playing .btn-label { color: rgba(100, 180, 255, 0.7); }
.volume-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
width: 200px;
}
.vol-label {
font-size: 10px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: rgba(255,255,255,0.2);
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
height: 2px;
background: rgba(255,255,255,0.1);
border-radius: 2px;
outline: none;
cursor: pointer;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: rgba(255,255,255,0.5);
cursor: pointer;
transition: background 0.2s;
}
body.playing input[type=range]::-webkit-slider-thumb {
background: rgba(100, 180, 255, 0.8);
}
input[type=range]::-webkit-slider-runnable-track {
height: 2px;
border-radius: 2px;
}
</style>
</head>
<body>
<div class="ambient">
<div class="orb orb1"></div>
<div class="orb orb2"></div>
<div class="orb orb3"></div>
</div>
<div class="ripple-container">
<div class="ripple"></div>
<div class="ripple"></div>
<div class="ripple"></div>
</div>
<div class="content">
<div class="label-top">white noise</div>
<div class="btn-wrap">
<button class="btn" id="btn" onclick="toggle()">
<div class="icon play-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<polygon points="7,4 20,12 7,20" fill="rgba(255,255,255,0.6)"/>
</svg>
</div>
<div class="icon stop-icon">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none">
<rect x="3" y="3" width="6" height="16" rx="1.5" fill="rgba(100,180,255,0.8)"/>
<rect x="13" y="3" width="6" height="16" rx="1.5" fill="rgba(100,180,255,0.8)"/>
</svg>
</div>
<span class="btn-label" id="btn-label">play</span>
</button>
</div>
<div class="volume-section">
<span class="vol-label">volume</span>
<input type="range" id="vol" min="0" max="100" value="70" oninput="setVolume(this.value)">
</div>
</div>
<script>
let ctx, source, gainNode, playing = false;
function toggle() {
if (!playing) {
ctx = new (window.AudioContext || window.webkitAudioContext)();
const bufSize = ctx.sampleRate * 4;
const buf = ctx.createBuffer(2, bufSize, ctx.sampleRate);
for (let ch = 0; ch < 2; ch++) {
const d = buf.getChannelData(ch);
for (let i = 0; i < bufSize; i++) d[i] = Math.random() * 2 - 1;
}
source = ctx.createBufferSource();
source.buffer = buf;
source.loop = true;
gainNode = ctx.createGain();
gainNode.gain.value = document.getElementById('vol').value / 100;
source.connect(gainNode);
gainNode.connect(ctx.destination);
source.start();
playing = true;
document.body.classList.add('playing');
} else {
gainNode.gain.setTargetAtTime(0, ctx.currentTime, 0.3);
setTimeout(() => { source.stop(); ctx.close(); }, 400);
playing = false;
document.body.classList.remove('playing');
}
}
function setVolume(v) {
if (gainNode) gainNode.gain.setTargetAtTime(v / 100, ctx.currentTime, 0.05);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment