sonicgrid-synthface / index.html
RyderMusic's picture
Improve look and feel and responsiveness and make it have a even more unique novel feel
819d6db verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NOLAN RYDER MUSIC | Sonic Fabric</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://unpkg.com/three@0.132.2/build/three.min.js"></script>
<script src="https://unpkg.com/vanta@latest/dist/vanta.globe.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;600;700&display=swap');
:root {
--primary: #00f0ff;
--secondary: #ff00e4;
--accent: #00ff9d;
--bg-dark: #050505;
--bg-light: #0c0c0c;
--text-primary: #f0f0f0;
--text-secondary: #b0b0b0;
}
body {
font-family: 'Space Grotesk', 'JetBrains Mono', monospace;
background: radial-gradient(circle at 50% 50%, var(--bg-light) 0%, var(--bg-dark) 100%);
color: var(--text-primary);
overflow-x: hidden;
min-height: 100vh;
scroll-behavior: smooth;
}
.canvas-container {
background:
repeating-linear-gradient(
45deg,
rgba(0, 240, 255, 0.02) 0px,
rgba(0, 240, 255, 0.02) 1px,
transparent 1px,
transparent 3px
),
repeating-linear-gradient(
-45deg,
rgba(255, 0, 228, 0.02) 0px,
rgba(255, 0, 228, 0.02) 1px,
transparent 1px,
transparent 3px
),
radial-gradient(circle at center, #1c1c1c, #0c0c0c 70%);
filter: brightness(1.1) contrast(1.2);
}
.light-streak::before {
content: "";
position: absolute;
top: 0;
left: -30%;
width: 60%;
height: 100%;
background: linear-gradient(
120deg,
transparent,
rgba(0, 240, 255, 0.1),
rgba(255, 0, 228, 0.1),
transparent
);
transform: skewX(-20deg);
animation: lightSweep 9s infinite linear;
mix-blend-mode: screen;
}
@keyframes lightSweep {
0% { left: -30%; opacity: 0; }
30% { left: 110%; opacity: 0.3; }
100% { left: 110%; opacity: 0; }
}
@keyframes lightStreak {
0% { transform: translateX(-10%) translateY(-10%); opacity: 0.03; }
100% { transform: translateX(110%) translateY(110%); opacity: 0.1; }
}
.light-streak {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
pointer-events: none;
}
.canvas-container::after {
content: "";
position: absolute;
inset: 0;
background-image: radial-gradient(circle, rgba(255,255,255,0.05) 1px, transparent 1.5px);
background-size: 24px 24px;
mix-blend-mode: overlay;
opacity: 0.3;
animation: pulseMatrix 3s infinite ease-in-out alternate;
}
@keyframes pulseMatrix {
0%, 100% { opacity: 0.25; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.01); }
}
.dot {
width: 4px;
height: 4px;
border-radius: 50%;
position: absolute;
background-color: var(--primary);
box-shadow:
0 0 8px currentColor,
0 0 16px rgba(0, 240, 255, 0.3);
transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
will-change: transform, opacity;
mix-blend-mode: screen;
}
.dot::after {
content: '';
position: absolute;
top: -4px;
left: -4px;
right: -4px;
bottom: -4px;
border-radius: 50%;
border: 1px solid rgba(0, 240, 255, 0.5);
pointer-events: none;
animation: pulseGlow 2s infinite alternate;
}
@keyframes pulseGlow {
0% { opacity: 0.3; transform: scale(1); }
100% { opacity: 0.8; transform: scale(1.2); }
}
/* ---- Layers ---- */
#bg-root {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
}
#shader-canvas {
position: absolute;
inset: 0;
z-index: 3; /* Higher priority for visual feedback */
display: block;
mix-blend-mode: screen;
}
#dot-matrix {
position: absolute;
inset: 0;
z-index: 2;
pointer-events: none;
mix-blend-mode: overlay;
--dot-size: 2px;
--cell: 18px;
background-image: radial-gradient(circle, rgba(255,255,255,0.5) var(--dot-size), transparent calc(var(--dot-size) + 0.1px));
background-size: var(--cell) var(--cell);
opacity: 0.5;
will-change: transform, opacity;
filter: contrast(1.5);
}
/* Subtle breathing */
@keyframes dotBreath {
0% { opacity: 0.28; transform: scale(1); }
100% { opacity: 0.42; transform: scale(1.005); }
}
/* Your metallic base stays on body */
body {
background: radial-gradient(75% 60% at 50% 40%, #0c0c0c 0%, #050505 100%);
}
.canvas-container {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.audio-controls, .bg-gray-900\/50 {
background: linear-gradient(
145deg,
rgba(5, 5, 5, 0.8),
rgba(12, 12, 12, 0.9)
);
border: 1px solid rgba(0, 240, 255, 0.1);
box-shadow:
inset 0 0 12px rgba(0, 240, 255, 0.05),
0 4px 16px rgba(0, 0, 0, 0.8);
backdrop-filter: blur(8px);
transition: all 0.3s ease;
}
.audio-controls:hover {
box-shadow:
inset 0 0 16px rgba(0, 240, 255, 0.1),
0 6px 20px rgba(0, 0, 0, 0.9);
}
.pulse {
animation: pulse 2s infinite cubic-bezier(0.4, 0, 0.6, 1);
}
@keyframes pulse {
0%, 100% {
opacity: 0.3;
transform: scale(0.98);
}
50% {
opacity: 1;
transform: scale(1.02);
}
}
/* Responsive improvements */
@media (max-width: 768px) {
#ui-root {
padding: 1rem;
}
.hero-section {
flex-direction: column;
text-align: center;
}
.audio-controls {
padding: 1rem;
}
.grid {
grid-template-columns: 1fr;
}
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-dark);
}
::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent);
}
</style>
</head>
<body class="min-h-screen">
<!-- Background stack -->
<div id="bg-root" aria-hidden="true">
<div id="shader-canvas" class="canvas-container"></div>
<div id="dot-matrix"></div>
</div>
<!-- Light streaks overlay -->
<div class="light-streak"></div>
<!-- Main Content -->
<main id="ui-root" class="container mx-auto px-4 py-12 relative z-[3] max-w-7xl">
<!-- Hero Section -->
<section class="flex flex-col md:flex-row items-center justify-between mb-16">
<div class="md:w-1/2 mb-8 md:mb-0">
<h1 class="text-5xl md:text-6xl lg:text-7xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-primary via-accent to-secondary" style="letter-spacing: 0.02em;">
NOLAN RYDER
</h1>
<p class="text-xl text-gray-300 mb-6">
<span class="text-primary">Sonic Architect</span><span class="text-secondary">Electronic Alchemist</span>
</p>
<div class="flex flex-wrap gap-4">
<a href="#music" class="px-6 py-3 bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90 text-black font-medium rounded-full transition-all transform hover:scale-105 shadow-lg shadow-primary/20">
Explore Music
</a>
<a href="#shows" class="px-6 py-3 border border-secondary text-secondary hover:bg-secondary/10 rounded-full transition-all transform hover:scale-105 shadow-lg shadow-secondary/10">
Upcoming Shows
</a>
</div>
</div>
<div class="md:w-1/2 flex justify-center">
<div class="relative w-64 h-64 md:w-80 md:h-80 rounded-full overflow-hidden border-4 border-primary/30 shadow-2xl shadow-primary/30 hover:shadow-primary/50 transition-all duration-300 transform hover:scale-105">
<img src="http://static.photos/music/640x360/123" alt="Nolan Ryder" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-gradient-to-t from-black/80 to-transparent flex items-end p-6">
<p class="text-primary text-sm">
Currently touring: <span class="font-medium">Sonic Fabric Tour 2024</span>
</p>
</div>
<div class="absolute inset-0 rounded-full border-2 border-primary/20 pointer-events-none animate-pulse"></div>
</div>
</div>
</section>
<!-- Music Section -->
<section id="music" class="mb-20">
<h2 class="text-3xl font-bold mb-8 flex items-center">
<span class="w-12 h-0.5 bg-amber-400 mr-4"></span>
Latest Releases
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-12">
<div class="group relative rounded-xl overflow-hidden border border-gray-800 hover:border-primary transition-all duration-300 transform hover:-translate-y-1 hover:shadow-lg hover:shadow-primary/20">
<div class="aspect-square bg-gray-900/50 relative">
<img src="http://static.photos/music/640x360/1" alt="Quantum Echoes" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black/40 group-hover:opacity-0 transition-opacity"></div>
<button class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-all duration-300">
<div class="w-16 h-16 rounded-full bg-primary/90 flex items-center justify-center hover:scale-110 transition-transform shadow-lg shadow-primary/30">
<i data-feather="play" class="text-black w-6 h-6"></i>
</div>
</button>
</div>
<div class="p-4">
<h3 class="text-xl font-medium mb-1">Quantum Echoes</h3>
<p class="text-gray-400 text-sm mb-3">Single • 2024</p>
<div class="flex justify-between items-center">
<span class="text-primary">Play</span>
<span class="text-gray-500 text-sm">3:42</span>
</div>
</div>
</div>
<div class="group relative rounded-xl overflow-hidden border border-gray-800 hover:border-amber-400 transition-colors">
<div class="aspect-square bg-gray-900/50 relative">
<img src="http://static.photos/music/640x360/2" alt="Neon Circuits" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black/40 group-hover:opacity-0 transition-opacity"></div>
<button class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<div class="w-16 h-16 rounded-full bg-amber-400/90 flex items-center justify-center hover:scale-110 transition-transform">
<i data-feather="play" class="text-black w-6 h-6"></i>
</div>
</button>
</div>
<div class="p-4">
<h3 class="text-xl font-medium mb-1">Neon Circuits</h3>
<p class="text-gray-400 text-sm mb-3">EP • 2023</p>
<div class="flex justify-between items-center">
<span class="text-amber-400">Play</span>
<span class="text-gray-500 text-sm">5 tracks</span>
</div>
</div>
</div>
<div class="group relative rounded-xl overflow-hidden border border-gray-800 hover:border-amber-400 transition-colors">
<div class="aspect-square bg-gray-900/50 relative">
<img src="http://static.photos/music/640x360/3" alt="Fabric Mix 01" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black/40 group-hover:opacity-0 transition-opacity"></div>
<button class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<div class="w-16 h-16 rounded-full bg-amber-400/90 flex items-center justify-center hover:scale-110 transition-transform">
<i data-feather="play" class="text-black w-6 h-6"></i>
</div>
</button>
</div>
<div class="p-4">
<h3 class="text-xl font-medium mb-1">Fabric Mix 01</h3>
<p class="text-gray-400 text-sm mb-3">Mix • 2023</p>
<div class="flex justify-between items-center">
<span class="text-amber-400">Play</span>
<span class="text-gray-500 text-sm">58:22</span>
</div>
</div>
</div>
</div>
<!-- Audio Visualizer Section -->
<div class="audio-controls rounded-xl p-6 mb-12 border border-gray-800 shadow-lg">
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6">
<h2 class="text-2xl font-bold mb-4 md:mb-0">SONIC FABRIC VISUALIZER</h2>
<div class="flex space-x-4">
<button id="micToggle" class="flex items-center space-x-2 px-4 py-2 rounded-full bg-blue-500/10 hover:bg-blue-500/20 border border-blue-400/30 transition-colors">
<i data-feather="mic" class="text-blue-400"></i>
<span class="text-blue-100">Live Input</span>
</button>
<label for="audioUpload" class="flex items-center space-x-2 px-4 py-2 rounded-full bg-blue-500/10 hover:bg-blue-500/20 border border-blue-400/30 transition-colors cursor-pointer">
<i data-feather="upload" class="text-blue-400"></i>
<span class="text-blue-100">Upload Track</span>
<input id="audioUpload" type="file" accept="audio/*" class="hidden">
</label>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<button class="mode-btn px-4 py-3 rounded-lg border border-gray-800 hover:border-blue-400 transition-colors" data-mode="pulse">
<i data-feather="activity" class="text-blue-400 mb-2"></i>
<h3 class="font-medium">Pulse Grid</h3>
<p class="text-sm text-gray-400">Oscilloscope amplitude waves</p>
</button>
<button class="mode-btn px-4 py-3 rounded-lg border border-gray-800 hover:border-blue-400 transition-colors" data-mode="drift">
<i data-feather="wind" class="text-blue-400 mb-2"></i>
<h3 class="font-medium">Vector Drift</h3>
<p class="text-sm text-gray-400">Controlled orbital motion</p>
</button>
<button class="mode-btn px-4 py-3 rounded-lg border border-gray-800 hover:border-blue-400 transition-colors" data-mode="circuit">
<i data-feather="cpu" class="text-blue-400 mb-2"></i>
<h3 class="font-medium">Logic Flow</h3>
<p class="text-sm text-gray-400">Transient circuit connections</p>
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-gray-900/50 rounded-xl p-6 border border-gray-800">
<h3 class="text-lg font-semibold mb-4 flex items-center text-blue-400">
<i data-feather="sliders" class="mr-2"></i> Visual Parameters
</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-1">Density</label>
<input type="range" id="densityControl" min="50" max="500" value="200" class="w-full accent-blue-400">
</div>
<div>
<label class="block text-sm font-medium mb-1">Sensitivity</label>
<input type="range" id="sensitivityControl" min="1" max="10" value="5" class="w-full accent-amber-400">
</div>
<div>
<label class="block text-sm font-medium mb-1">Color Theme</label>
<select id="colorControl" class="w-full bg-gray-800 rounded-lg px-3 py-2 border border-gray-700 text-white">
<option value="blue">Blue</option>
<option value="cyan">Cyan</option>
<option value="dual">Dual Tone</option>
</select>
</div>
</div>
</div>
<div class="bg-gray-900/50 rounded-xl p-6 border border-gray-800">
<h3 class="text-lg font-semibold mb-4 flex items-center text-amber-400">
<i data-feather="info" class="mr-2"></i> About the Visualizer
</h3>
<p class="text-gray-300 mb-4 leading-relaxed">
This reactive dot matrix transforms audio input into organic motion patterns. Each dot represents an LED cell in a digital fabric that responds to frequency and amplitude.
</p>
<ul class="space-y-2 text-sm text-gray-400">
<li class="flex items-start">
<i data-feather="chevron-right" class="w-4 h-4 mr-2 mt-0.5 text-blue-400"></i>
<span>Bass frequencies create large-scale pulses</span>
</li>
<li class="flex items-start">
<i data-feather="chevron-right" class="w-4 h-4 mr-2 mt-0.5 text-amber-400"></i>
<span>Treble creates micro-flickers and detail</span>
</li>
<li class="flex items-start">
<i data-feather="chevron-right" class="w-4 h-4 mr-2 mt-0.5 text-amber-400"></i>
<span>Cursor proximity bends local dots magnetically</span>
</li>
</ul>
</div>
</div>
</div>
</section>
<!-- Shows Section -->
<section id="shows" class="mb-20">
<h2 class="text-3xl font-bold mb-8 flex items-center">
<span class="w-12 h-0.5 bg-amber-400 mr-4"></span>
Upcoming Shows
</h2>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="border-b border-gray-800 text-left text-gray-400">
<th class="pb-3 pr-6">Date</th>
<th class="pb-3 pr-6">Venue</th>
<th class="pb-3 pr-6">Location</th>
<th class="pb-3 pr-6">Tickets</th>
</tr>
</thead>
<tbody>
<tr class="border-b border-gray-800 hover:bg-gray-900/50 transition-colors">
<td class="py-4 pr-6">
<p class="font-medium">June 15, 2024</p>
<p class="text-sm text-gray-400">8:00 PM</p>
</td>
<td class="py-4 pr-6">
<p class="font-medium">Neon Gardens</p>
<p class="text-sm text-gray-400">With special guests</p>
</td>
<td class="py-4 pr-6">
<p>Berlin, DE</p>
</td>
<td class="py-4">
<a href="#" class="text-blue-400 hover:text-blue-300 font-medium">Get Tickets</a>
</td>
</tr>
<tr class="border-b border-gray-800 hover:bg-gray-900/50 transition-colors">
<td class="py-4 pr-6">
<p class="font-medium">June 22, 2024</p>
<p class="text-sm text-gray-400">10:00 PM</p>
</td>
<td class="py-4 pr-6">
<p class="font-medium">Circuit Club</p>
<p class="text-sm text-gray-400">Sonic Fabric showcase</p>
</td>
<td class="py-4 pr-6">
<p>London, UK</p>
</td>
<td class="py-4">
<a href="#" class="text-amber-400 hover:text-amber-300 font-medium">Get Tickets</a>
</td>
</tr>
<tr class="border-b border-gray-800 hover:bg-gray-900/50 transition-colors">
<td class="py-4 pr-6">
<p class="font-medium">July 5, 2024</p>
<p class="text-sm text-gray-400">9:30 PM</p>
</td>
<td class="py-4 pr-6">
<p class="font-medium">Quantum</p>
<p class="text-sm text-gray-400">Album launch party</p>
</td>
<td class="py-4 pr-6">
<p>Amsterdam, NL</p>
</td>
<td class="py-4">
<a href="#" class="text-amber-400 hover:text-amber-300 font-medium">Get Tickets</a>
</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- Newsletter -->
<section class="bg-gradient-to-r from-gray-900/80 to-gray-900/40 rounded-xl p-8 border border-primary/20 mb-20 hover:border-primary/40 transition-all duration-300">
<div class="max-w-2xl mx-auto text-center">
<h2 class="text-2xl font-bold mb-2">Join the Sonic Fabric</h2>
<p class="text-gray-300 mb-6">Get exclusive updates, unreleased tracks, and presale access</p>
<form class="flex flex-col sm:flex-row gap-4">
<input type="email" placeholder="Your email" class="flex-1 bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-white focus:border-amber-400 focus:ring-amber-400">
<button type="submit" class="px-6 py-3 bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90 text-black font-medium rounded-lg transition-all transform hover:scale-105 shadow-lg shadow-primary/20">
Subscribe
</button>
</form>
</div>
</section>
</main>
<!-- Footer -->
<footer class="bg-black/50 border-t border-primary/10 py-12 backdrop-blur-lg">
<div class="container mx-auto px-4">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="mb-6 md:mb-0">
<h2 class="text-xl font-bold mb-2">NOLAN RYDER</h2>
<p class="text-gray-400">Sonic Architect • Electronic Alchemist</p>
</div>
<div class="flex space-x-6 mb-6 md:mb-0">
<a href="#" class="text-gray-400 hover:text-primary transition-all transform hover:scale-110">
<i data-feather="instagram"></i>
</a>
<a href="#" class="text-gray-400 hover:text-amber-400 transition-colors">
<i data-feather="facebook"></i>
</a>
<a href="#" class="text-gray-400 hover:text-amber-400 transition-colors">
<i data-feather="twitter"></i>
</a>
<a href="#" class="text-gray-400 hover:text-amber-400 transition-colors">
<i data-feather="youtube"></i>
</a>
<a href="#" class="text-gray-400 hover:text-amber-400 transition-colors">
<i data-feather="spotify"></i>
</a>
</div>
<div class="text-sm text-gray-500">
&copy; 2024 Nolan Ryder. All rights reserved.
</div>
</div>
</div>
</footer>
<!-- Audio Visualizer Script -->
<script>
document.addEventListener('DOMContentLoaded', () => {
feather.replace();
// Update feather icons after upload button is clicked
document.getElementById('audioUpload').addEventListener('click', () => {
setTimeout(() => feather.replace(), 100);
});
// Audio Context Setup
let audioContext;
let analyser;
let dataArray;
let microphone;
let audioSource;
let isMicActive = false;
let isAudioActive = false;
// Visualizer Parameters
let currentMode = 'pulse';
let dotDensity = 200;
let sensitivity = 5;
let colorTheme = 'amber';
let dots = [];
let lightStreak = document.querySelector('.light-streak');
// Canvas Setup
const container = document.getElementById('shader-canvas');
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
container.appendChild(canvas);
function resizeCanvas() {
canvas.width = container.offsetWidth;
canvas.height = container.offsetHeight;
initDots();
}
function initDots() {
dots = [];
const dotCount = Math.floor((canvas.width * canvas.height) / (10000 - dotDensity));
for (let i = 0; i < dotCount; i++) {
dots.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
size: Math.random() * 2 + 1,
originalX: Math.random() * canvas.width,
originalY: Math.random() * canvas.height,
speed: Math.random() * 0.3 + 0.1,
angle: Math.random() * Math.PI * 2,
opacity: Math.random() * 0.7 + 0.3,
freqResponse: Math.random() * 0.8 + 0.2 // Individual frequency response
});
}
}
function connectDots() {
const maxDistance = 100;
for (let i = 0; i < dots.length; i++) {
for (let j = i + 1; j < dots.length; j++) {
const dx = dots[i].x - dots[j].x;
const dy = dots[i].y - dots[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < maxDistance) {
ctx.strokeStyle = `rgba(234, 234, 234, ${1 - distance/maxDistance})`;
ctx.lineWidth = 0.3;
ctx.beginPath();
ctx.moveTo(dots[i].x, dots[i].y);
ctx.lineTo(dots[j].x, dots[j].y);
ctx.stroke();
}
}
}
}
function animate() {
requestAnimationFrame(animate);
// Clear with dynamic fade based on audio intensity
let fadeAmount = 0.1;
if ((isMicActive || isAudioActive) && analyser) {
const timeData = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteTimeDomainData(timeData);
const rms = Math.sqrt(timeData.reduce((sum, x) => sum + (x-128)**2, 0) / timeData.length);
fadeAmount = 0.2 - (rms / 128) * 0.15;
}
ctx.fillStyle = `rgba(0,0,0,${Math.max(0.05, fadeAmount)})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Get frequency data if audio is active
let frequencyData;
if ((isMicActive || isAudioActive) && analyser) {
frequencyData = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(frequencyData);
}
// Update and draw dots based on current mode
dots.forEach((dot, index) => {
// Base movement patterns
if (currentMode === 'pulse') {
if (frequencyData) {
// Frequency bands analysis
const lowFreq = frequencyData[Math.floor(index/dots.length * 10)] / 255;
const midFreq = frequencyData[Math.floor(index/dots.length * 50)] / 255;
const highFreq = frequencyData[Math.floor(index/dots.length * 150)] / 255;
// Combined response with different weights
const intensity = (lowFreq * 0.5 + midFreq * 0.3 + highFreq * 0.2) * sensitivity * dot.freqResponse;
// Dynamic response
dot.size = intensity * 6 + 1;
dot.opacity = Math.min(intensity * 4, 0.9);
dot.y = dot.originalY + (Math.sin(Date.now()*0.001 + index) * intensity * 40);
// Color modulation for highs
if (highFreq > 0.6) {
dot.colorBoost = Math.min(1, dot.colorBoost ? dot.colorBoost * 1.2 : 1.2);
} else {
dot.colorBoost = dot.colorBoost ? dot.colorBoost * 0.95 : 1;
}
}
}
else if (currentMode === 'drift') {
dot.x = dot.originalX + Math.sin(Date.now() * dot.speed / 1000) * 20;
dot.y = dot.originalY + Math.cos(Date.now() * dot.speed / 1000) * 20;
if (frequencyData) {
const frequencyIndex = Math.floor(index / dots.length * frequencyData.length);
const intensity = frequencyData[frequencyIndex] / 255 * sensitivity;
dot.x += (Math.random() - 0.5) * intensity * 10;
dot.y += (Math.random() - 0.5) * intensity * 10;
}
}
else if (currentMode === 'circuit') {
if (frequencyData) {
const frequencyIndex = Math.floor(index / dots.length * frequencyData.length);
const intensity = frequencyData[frequencyIndex] / 255 * sensitivity;
dot.size = intensity * 2 + 0.5;
dot.opacity = Math.min(intensity * 1.5, 0.7);
}
}
// Draw dot with enhanced audio reactivity
let dotColor;
const colorBoost = dot.colorBoost || 1;
if (colorTheme === 'amber') {
dotColor = `rgba(0, ${Math.min(191, 150 * colorBoost)}, ${Math.min(255, 255 * colorBoost)}, ${dot.opacity})`;
} else if (colorTheme === 'cyan') {
dotColor = `rgba(0, ${Math.min(255, 150 * colorBoost)}, ${Math.min(255, 255 * colorBoost)}, ${dot.opacity})`;
} else {
const hue = index % 2 === 0 ? 38 : 186;
dotColor = `hsla(${hue}, ${90 * colorBoost}%, ${Math.min(80, 60 * colorBoost)}%, ${dot.opacity})`;
}
// Add glow effect for loud sounds
if (frequencyData && dot.opacity > 0.7) {
ctx.shadowBlur = dot.size * 2;
ctx.shadowColor = dotColor;
} else {
ctx.shadowBlur = 0;
}
ctx.fillStyle = dotColor;
ctx.beginPath();
ctx.arc(dot.x, dot.y, dot.size, 0, Math.PI * 2);
ctx.fill();
});
// Enhanced connections in circuit mode
if (currentMode === 'circuit' && frequencyData) {
ctx.lineWidth = 0.5 + (frequencyData[10]/255) * 2;
connectDots();
}
// Visual feedback for active audio
if ((isMicActive || isAudioActive) && frequencyData) {
const peakFreq = frequencyData.reduce((max, val, idx) => val > max.val ? {val, idx} : max, {val: 0, idx: 0});
if (peakFreq.val > 200) {
const x = (peakFreq.idx / frequencyData.length) * canvas.width;
const y = canvas.height/2;
const gradient = ctx.createRadialGradient(x, y, 0, x, y, 100);
gradient.addColorStop(0, `rgba(255,255,255,${peakFreq.val/400})`);
gradient.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = gradient;
ctx.fillRect(x-100, y-100, 200, 200);
}
}
// Animate light streak based on audio
if ((isMicActive || isAudioActive) && frequencyData) {
const avgIntensity = frequencyData.reduce((a, b) => a + b, 0) / frequencyData.length / 255;
lightStreak.style.opacity = 0.03 + (avgIntensity * 0.1);
lightStreak.style.animationDuration = `${12 - (avgIntensity * 8)}s`;
const glowIntensity = Math.min(1, (avgIntensity + (frequencyData[0] / 255)) / 1.5);
document.getElementById('shader-canvas').style.filter =
`brightness(${1 + glowIntensity * 0.6}) contrast(${1.1 + glowIntensity * 0.3})`;
// Update dot matrix
const matrix = document.getElementById('dot-matrix');
const base = 0.18;
const extra = 0.35 * avgIntensity;
matrix.style.opacity = (base + extra).toFixed(3);
const cell = Math.round(18 + 10 * (1 - avgIntensity));
matrix.style.setProperty('--cell', `${cell}px`);
}
}
// Initialize audio context
function initAudio() {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser();
analyser.fftSize = 512; // Increased frequency resolution
analyser.smoothingTimeConstant = 0.8; // Smoother transitions
const bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
// Create a gain node for better control
const gainNode = audioContext.createGain();
gainNode.gain.value = 1.5; // Boost input slightly
gainNode.connect(analyser);
return gainNode;
}
// Toggle microphone input
document.getElementById('micToggle').addEventListener('click', async () => {
if (!audioContext) initAudio();
try {
if (!isMicActive) {
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false
}
});
microphone = audioContext.createMediaStreamSource(stream);
const gainNode = initAudio();
microphone.connect(gainNode);
isAudioActive = false;
isMicActive = true;
document.getElementById('micToggle').classList.add('bg-blue-500/20', 'border-blue-400');
} else {
if (microphone) {
microphone.disconnect();
isMicActive = false;
document.getElementById('micToggle').classList.remove('bg-blue-500/20', 'border-blue-400');
}
}
} catch (err) {
console.error('Error accessing microphone:', err);
alert('Could not access microphone. Please check permissions.');
}
});
// Handle audio file upload
document.getElementById('audioUpload').addEventListener('change', function(e) {
if (!audioContext) initAudio();
const file = e.target.files[0];
if (!file) return;
if (isMicActive) {
microphone.disconnect();
isMicActive = false;
document.getElementById('micToggle').classList.remove('bg-cyan-900/50', 'border-cyan-400');
}
if (audioSource) {
audioSource.disconnect();
isAudioActive = false;
}
const fileReader = new FileReader();
fileReader.onload = function() {
audioContext.decodeAudioData(fileReader.result, function(buffer) {
const source = audioContext.createBufferSource();
source.buffer = buffer;
const gainNode = audioContext.createGain();
gainNode.gain.value = 1.2;
source.connect(gainNode);
gainNode.connect(analyser);
source.start(0);
audioSource = source;
isAudioActive = true;
// Handle when audio ends
source.onended = function() {
isAudioActive = false;
document.querySelector('label[for="audioUpload"]').classList.remove('bg-blue-900/50', 'border-blue-400');
};
document.querySelector('label[for="audioUpload"]').classList.add('bg-blue-900/50', 'border-blue-400');
});
};
fileReader.readAsArrayBuffer(file);
});
// Mode buttons
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.addEventListener('click', () => {
currentMode = btn.dataset.mode;
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('border-blue-400', 'border-blue-400'));
btn.classList.add('border-blue-400');
});
});
// Parameter controls
document.getElementById('densityControl').addEventListener('input', (e) => {
dotDensity = e.target.value;
initDots();
});
document.getElementById('sensitivityControl').addEventListener('input', (e) => {
sensitivity = e.target.value;
});
document.getElementById('colorControl').addEventListener('change', (e) => {
colorTheme = e.target.value;
});
// Mouse interaction
canvas.addEventListener('mousemove', (e) => {
if (currentMode === 'drift') {
const mouseX = e.clientX;
const mouseY = e.clientY;
dots.forEach(dot => {
const dx = mouseX - dot.x;
const dy = mouseY - dot.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
dot.x += dx * 0.01;
dot.y += dy * 0.01;
}
});
}
});
// Initialize
window.addEventListener('resize', resizeCanvas);
window.addEventListener('scroll', () => {
const scrollRatio = window.scrollY / (document.body.scrollHeight - window.innerHeight);
lightStreak.style.transform = `rotate(${5 + scrollRatio * 10}deg)`;
});
resizeCanvas();
animate();
// Ambient breathing effect when no audio
// Enhanced idle animation
setInterval(() => {
if (!isMicActive && !isAudioActive) {
dots.forEach((dot, i) => {
const time = Date.now() * 0.0005;
dot.opacity = 0.5 + Math.sin(time + i*0.1) * 0.3;
dot.x = dot.originalX + Math.sin(time*0.3 + i) * 15;
dot.y = dot.originalY + Math.cos(time*0.35 + i) * 15;
});
}
}, 16); // 60fps refresh
});
</script>
</body>
</html>