Spaces:
Running
Running
Improve look and feel and responsiveness and make it have a even more unique novel feel
819d6db verified | <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"> | |
| © 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> | |