PageSourceSearch

https://vocalremovers.ai/key-finder/

html vocalremovers.ai collected 2026-10-05 14:08:13 UTC 222,830 bytes, 3,759 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4  <meta charset="UTF-8" />
5  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6  <title>Free Song Key Finder & Camelot Wheel | VocalRemovers.ai</title>
7  <meta name="description" content="Find the key of any song online for free. Detect musical key, scale, Camelot code and BPM from audio, explore the Camelot Wheel, or use real-time microphone pitch detection." />
8  <meta name="keywords" content="key finder, song key finder, bpm finder, detect song key and bpm, camelot wheel, camelot key finder, dj harmonic mixing, find key of song, audio key detector, song tempo detector, beats per minute, pitch detector, circle of fifths, scale finder, root note detector" />  <link rel="shortcut icon" type="image/x-icon" href="/assets/favicon.ico?v=1.0">
9  <link rel="canonical" href="https://vocalremovers.ai/key-finder" />
10  
11  <!-- Open Graph / Social SEO -->
12  <meta property="og:type" content="website" />
13  <meta property="og:url" content="https://vocalremovers.ai/key-finder" />
14  <meta property="og:title" content="Free Online Song Key & BPM Finder | VocalRemovers.ai" />
15  <meta property="og:description" content="Detect musical key, tempo (BPM), scale, and Camelot code of any song in seconds with 100% private in-browser chromagram analysis." />
16  <meta property="og:image" content="https://vocalremovers.ai/assets/preview-key.png" />
17
18  <!-- Twitter Card -->
19  <meta name="twitter:card" content="summary_large_image" />
20  <meta name="twitter:title" content="Free Online Song Key & BPM Finder" />
21  <meta name="twitter:description" content="Find the musical key, tempo (BPM), and Camelot code of any track with interactive harmonic mixing tools." />
22
23  <!-- JSON-LD Structured Data for Software Application & FAQ -->
24  
24<script type="application/ld+json">
25  {
26    "@context": "https://schema.org",
27    "@graph": [
28      {
29        "@type": "WebApplication",
30        "name": "Free Online Song Key & BPM Finder",
31        "url": "https://vocalremovers.ai/key-finder",
32        "description": "In-browser musical key detector, tempo (BPM) counter, Camelot DJ wheel analyzer, 12-class chromagram visualizer, interactive piano keyboard, and live microphone pitch tracker.",
33        "applicationCategory": "MultimediaApplication",
34        "operatingSystem": "All",
35        "browserRequirements": "Requires Web Audio API support",
36        "offers": {
37          "@type": "Offer",
38          "price": "0",
39          "priceCurrency": "USD"
40        },
41        "featureList": [
42          "Song musical key and scale detection",
43          "BPM tempo detection with half- and double-time options",
44          "Camelot DJ Wheel code and harmonic mixing matrix",
45          "12-class pitch chromagram spectrum analyzer",
46          "Interactive SVG Camelot Wheel explorer",
47          "Virtual piano keyboard with scale degree indicators",
48          "Real-time acoustic microphone pitch & key listener",
49          "Audio playback scrubber with reference root note generator",
50          "100% in-browser Web Audio privacy"
51        ]
52      },
53      {
54        "@type": "FAQPage",
55        "mainEntity": [
56          {
57            "@type": "Question",
58            "name": "How does this online key finder detect the musical key of a song?",
59            "acceptedAnswer": {
60              "@type": "Answer",
61              "text": "The analyzer extracts pitch-related information from the audio and compares the resulting chroma distribution with musical key profiles to estimate the most likely tonal center and scale."
62            }
63          },
64          {
65            "@type": "Question",
66            "name": "What is the Camelot Wheel and how do DJs use it for harmonic mixing?",
67            "acceptedAnswer": {
68              "@type": "Answer",
69              "text": "The Camelot Wheel simplifies musical keys into a 12-hour clock face. Numbers 1 to 12 represent pitch classes, while A denotes Minor keys and B denotes Major keys. Mixing tracks with the same number or adjacent hours (+/- 1) is commonly used as a starting point for harmonic mixing."
70            }
71          },
72          {
73            "@type": "Question",
74            "name": "How do I use the interactive Camelot Wheel explorer?",
75            "acceptedAnswer": {
76              "@type": "Answer",
77              "text": "Click the Camelot Wheel & Piano tab and select any key on the visual wheel. The tool immediately highlights compatible transition keys—including Exact Match, Energy Boost (+1, +2), Energy Drop (-1), and Relative Major/Minor swaps—alongside the notes on the virtual piano."
78            }
79          },
80          {
81            "@type": "Question",
82            "name": "How accurate is the browser-based key detector?",
83            "acceptedAnswer": {
84              "@type": "Answer",
85              "text": "Our algorithm analyzes the entire duration of the audio across 40+ hops, filtering out noisy percussion transients to focus on sustained harmonic chords and vocals. Online key finders can provide a useful estimate across diverse genres including EDM, Pop, Rock, Hip Hop, and Classical."
86            }
87          },
88          {
89            "@type": "Question",
90            "name": "Can I hear the root tonic note to test or tune my instrument?",
91            "acceptedAnswer": {
92              "@type": "Answer",
93              "text": "Yes! Once a key is detected or selected, click the Play Root Note Tone button to hear a synthesized pure acoustic reference pitch corresponding to the detected key's root tonic."
94            }
95          },
96          {
97            "@type": "Question",
98            "name": "How does the real-time microphone pitch detector work?",
99            "acceptedAnswer": {
100              "@type": "Answer",
101              "text": "Switch to the Real-Time Mic tab and click Start Listening. Sing, hum, or play an acoustic instrument into your microphone. Our autocorrelation algorithm detects your fund
101amental pitch in real time, displays live notes and frequencies in Hz, and estimates the prevailing key over time."
102            }
103          },
104          {
105            "@type": "Question",
106            "name": "What is the difference between relative and parallel keys?",
107            "acceptedAnswer": {
108              "@type": "Answer",
109              "text": "Relative keys (e.g. C Major and A Minor, 8B and 8A) share identical key signatures and scale notes, making them closely related for harmonic mixing. Parallel keys (e.g. C Major and C Minor) share the same root tonic note (C) but differ in their 3rd, 6th, and 7th intervals, producing a bold, dramatic emotional contrast."
110            }
111          },
112          {
113            "@type": "Question",
114            "name": "What audio formats are supported for key detection?",
115            "acceptedAnswer": {
116              "@type": "Answer",
117              "text": "The Key Finder supports all popular audio formats: MP3, WAV, FLAC, M4A, AAC, OGG, OPUS, and AIFF. Files up to 100 MB can be analyzed directly in your browser."
118            }
119          },
120          {
121            "@type": "Question",
122            "name": "Are my audio files uploaded or stored on your servers?",
123            "acceptedAnswer": {
124              "@type": "Answer",
125              "text": "No. All processing—audio decoding, FFT spectral analysis, chroma extraction, and live microphone capture—runs 100% in your browser. Your tracks never leave your computer or device."
126            }
127          },
128          {
129            "@type": "Question",
130            "name": "Can I jump from the Key Finder to the Vocal Remover or BPM Finder with my song preloaded?",
131            "acceptedAnswer": {
132              "@type": "Answer",
133              "text": "Yes! Under the \"Next Steps with this Track\" section, click any tool (such as our <a href=\"/bpm-finder/\">BPM finder</a> to calculate tempo or Remove Vocals) to instantly transfer your audio and continue processing."
134            }
135          },
136          {
137            "@type": "Question",
138            "name": "How can I find the key of an MP3?",
139            "acceptedAnswer": {
140              "@type": "Answer",
141              "text": "Upload your MP3 to the Key Finder and let the audio analyzer process the track. It will estimate the musical key and scale, and show the corresponding Camelot code along with related key information."
142            }
143          },
144          {
145            "@type": "Question",
146            "name": "What is the difference between musical key and scale?",
147            "acceptedAnswer": {
148              "@type": "Answer",
149              "text": "A scale is a specific sequence of notes, such as C major or A minor. A musical key describes the tonal center of a song and the scale built around it. For example, a song in the key of C major uses the C major scale with C as its tonal center."
150            }
151          },
152          {
153            "@type": "Question",
154            "name": "What is a Camelot code?",
155            "acceptedAnswer": {
156              "@type": "Answer",
157              "text": "A Camelot code is a DJ-friendly way of representing musical keys for harmonic mixing. The system uses numbers from 1 to 12 combined with A or B to represent minor and major keys. For example, 8A represents A minor and 8B represents C major."
158            }
159          },
160          {
161            "@type": "Question",
162            "name": "What does 8A mean on the Camelot Wheel?",
163            "acceptedAnswer": {
164              "@type": "Answer",
165              "text": "8A represents A minor on the Camelot Wheel. Its relative major key is C major (8B). DJs can use Camelot codes to quickly identify related keys when planning harmonic transitions between tracks."
166            }
167          },
168          {
169            "@type": "Question",
170            "name": "How accurate is an online song key finder?",
171            "acceptedAnswer": {
172              "@type": "Answer",
173              "text": "Online key finders can provide a useful estimate of a track's musical key, but results are not guaranteed to be correct for every recording. Accuracy can vary depending on the arrangement, instrumentation, tuning, audio quality, modulation, and other musical characteristics of the track."
174            }
175          },
176          {
177            "@type": "Question",
178            "name": "Can a song change key?",
179            "acceptedAnswer": {
180              "@type": "Answer",
181              "text": "Yes. Some songs change key during the arrangement. A key change, also called modulation, can occur between sections such as a verse and chorus or near the end of a song. If different sections have different tonal centers, an analyzer may identify the key that is most prominent in the analyzed audio."
182            }
183          },
184          {
185            "@type": "Question",
186            "name": "Can I find the key of a song without downloading software?",
187            "acceptedAnswer": {
188              "@type": "Answer",
189              "text": "Yes. You can use an online song key finder directly in your web browser. Upload a supported audio file and the analyzer can estimate its musical key, scale, and Camelot code without requiring a separate desktop application."
190            }
191          },
192          {
193            "@type": "Question",
194            "name": "Can I use the key finder on my phone?",
195            "acceptedAnswer": {
196              "@type": "Answer",
197              "text": "Yes. The Key Finder can be used on compatible mobile browsers that support the required Web Audio features. Upload a supported audio file or use the microphone mode to analyze pitch directly from your phone."
198            }
199          },
200          {
201            "@type": "Question",
202            "name": "Does changing the BPM change the musical key?",
203            "acceptedAnswer": {
204              "@type": "Answer",
205              "text": "Not necessarily. Changing the playback speed can affect the pitch if the audio is simply sped up or slowed down. However, modern time-stretching and pitch-preserving processing can change the BPM while keeping the musical key approximately the same. If you want to transpose the key of a track, you can use our <a href=\"/pitch-changer/\">pitch changer</a>."
206            }
207          },
208          {
209            "@type": "Question",
210            "name": "What is the difference between major and minor keys?",
211            "acceptedAnswer": {
212              "@type": "Answer",
213              "text": "Major and minor keys have different tonal characteristics and scale structures. Major keys are often associated with brighter or more uplifting musical qualities, while minor keys are often associated with darker or more introspective qualities. The actual mood also depends on the melody, harmony, rhythm, instrumentation, and arrangement."
214            }
215          }
216        ]
217      }
218    ]
219  }
220  </script>
220
221  <!-- Preconnect & Google Fonts -->
222  <link rel="preconnect" href="https://fonts.googleapis.com" />
223  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
224  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
225  
226  <!-- Font Awesome 6 Free Icons -->
227  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/all.min.css" />
228  
229  <!-- Tailwind CSS CDN -->
230  
230<script src="https://cdn.tailwindcss.com"></script>
230
231  
231<script>
232    tailwind.config = {
233      theme: {
234        extend: {
235          fontFamily: {
236            sans: ["Inter", "sans-serif"],
237            outfit: ["Outfit", "sans-serif"]
238          },
239          boxShadow: {
240            "neon-indigo": "0 0 20px rgba(99, 102, 241, 0.15)",
241            "neon-purple": "0 0 20px rgba(168, 85, 247, 0.15)",
242            "glass-hover": "0 20px 40px rgba(0, 0, 0, 0.3)"
243          }
244        }
245      }
246    };
247  </script>
247
248  
249  <!-- Custom Styling for Interactive Elements -->
250  <style>
251    body {
252      background-color: #080a13;
253      background-image: 
254        radial-gradient(circle at 15% 25%, rgba(99, 102, 241, 0.08) 0%, transparent 40%),
255        radial-gradient(circle at 85% 75%, rgba(168, 85, 247, 0.08) 0%, transparent 40%);
256      background-attachment: fixed;
257    }
258    
259    .glass-card {
260      background: linear-gradient(135deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%);
261      backdrop-filter: blur(16px);
262      -webkit-backdrop-filter: blur(16px);
263      border: 1px solid rgba(255, 255, 255, 0.06);
264    }
265    
266    .glass-card-hover:hover {
267      background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%);
268      border-color: rgba(129, 140, 248, 0.3);
269      box-shadow: 0 20px 45px rgba(99, 102, 241, 0.15);
270    }
271
272    .header-gradient {
273      background: linear-gradient(90deg, #4154a1 0%, #614d9f 100%);
274    }
275
276    /* Opaque Mobile Navigation Menu */
277    #mobileMenu {
278      background-color: #0b1021 !important;
279      opacity: 1 !important;
280    }
281
282    /* Subtle animation for background glows */
283    @keyframes pulse-slow {
284      0%, 100% { transform: scale(1); opacity: 0.8; }
285      50% { transform: scale(1.05); opacity: 1; }
286    }
287    .animate-glow {
288      animation: pulse-slow 8s ease-in-out infinite;
289    }
290  </style>
291</head>
292<body class="text-slate-100 font-sans antialiased min-h-screen flex flex-col pt-20">
293
294  <!-- ================= HEADER NAVIGATION ================= -->
295  <!-- ================= HEADER SECTION ================= -->
296<header class="header-gradient w-full shadow-lg fixed top-0 left-0 right-0 z-50">
297  <div class="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
298    
299    <!-- Logo Container -->
300    <a href="/" class="flex items-center gap-3.5 group select-none cursor-pointer" id="mainlogo">
301      <!-- Logo Icon: Rounded rectangle with a music note -->
302      <div class="h-10 w-10 flex items-center justify-center rounded-xl bg-white/15 border border-white/10 shadow-inner group-hover:scale-105 transition-transform duration-200">
303        <svg class="h-5 w-5 text-white" viewBox="0 0 24 24" fill="currentColor">
304          <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
305        </svg>
306      </div>
307      <!-- Logo Text -->
308      <span class="font-outfit text-xl font-bold tracking-tight text-white flex items-center gap-1.5">
309        AI Vocal <span class="text-[#c084fc] drop-shadow-sm">Remover</span>
310      </span>
311    </a>
312
313    <!-- Desktop Navigation Menu -->
314    <nav class="hidden md:flex items-center gap-8 lg:gap-10">
315      <div class="relative group/nav py-2">
316        <button class="text-sm font-semibold tracking-wide text-white/90 group-hover/nav:text-white flex items-center gap-1.5 transition-colors duration-200 cursor-pointer">
317          TOOLS <i class="fas fa-chevron-down text-[10px] opacity-70 group-hover/nav:rotate-180 transition-transform duration-200"></i>
318        </button>
319        <div class="absolute top-full left-1/2 -translate-x-1/2 pt-2 hidden group-hover/nav:block z-50">
320          <div class="w-56 bg-slate-950/95 border border-white/10 rounded-2xl shadow-2xl p-2 backdrop-blur-xl flex flex-col gap-1">
321            <a href="/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
322              <i class="fa-solid fa-microphone-slash text-purple-400 w-4"></i> Vocal Remover
323            </a>
324            <a href="/bpm-finder/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
325              <i class="fa-solid fa-drum text-indigo-400 w-4"></i> BPM & Key Finder
326            </a>
327            <a href="/key-finder/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
328              <i class="fa-solid fa-compact-disc text-[#c084fc] w-4"></i> Key Finder & Camelot
329            </a>
330            <a href="/voice-recorder/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
331              <i class="fa-solid fa-microphone-lines text-rose-400 w-4"></i> Voice Recorder
332            </a>
333            <a href="/audio-cutter/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
334              <i class="fa-solid fa-scissors text-blue-400 w-4"></i> Audio Cutter
335            </a>
336            <a href="/audio-joiner/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
337              <i class="fa-solid fa-layer-group text-emerald-400 w-4"></i> Audio Joiner
338            </a>
339            <a href="/audio-converter/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
340              <i class="fa-solid fa-arrows-rotate text-cyan-400 w-4"></i> Audio Converter
341            </a>
342            <a href="/equalizer/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
343              <i class="fa-solid fa-sliders text-teal-400 w-4"></i> Audio Equalizer
344            </a>
345            <a href="/pitch-changer/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
346              <i class="fa-solid fa-music text-cyan-400 w-4"></i> Pitch Changer
347            </a>
348            <a href="/bass-booster/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
349              <i class="fa-solid fa-volume-high text-cyan-400 w-4"></i> Bass Booster
350            </a>
351            <a href="/noise-reducer/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
352              <i class="fa-solid fa-wand-magic-sparkles text-emerald-400 w-4"></i> Noise Reducer
353            </a>
354            <a href="/audio-effects/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
355              <i class="fa-solid fa-wand-magic text-violet-400 w-4"></i> Audio Effects
356            </a>
357            <a href="/mp3-tag-editor/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all">
358              <i class="fa-solid fa-tags text-amber-400 w-4"></i> MP3 Tag Editor
359            </a>
360          </div>
361        </div>
362      </div>
363      <a href="/#features" class="text-sm font-semibold tracking-wide text-white/90 hover:text-white transition-colors duration-200">FEATURES</a>
364      <a href="/#howitworks" class="text-sm font-semibold tracking-wide text-white/90 hover:text-white transition-colors duration-200">HOW IT WORKS</a>
365      <a href="/#faqs" class="text-sm font-semibold tracking-wide text-white/90 hover:text-white transition-colors duration-200">FAQS</a>
366      <a href="/contact/" class="text-sm font-semibold tracking-wide text-white/90 hover:text-white transition-colors duration-200">CONTACT</a>
367    </nav>
368
369    <!-- Mobile Hamburger Button -->
370    <button id="menuToggle" class="md:hidden p-2 rounded-lg text-white hover:bg-white/10 transition-colors focus:outline-none" aria-label="Toggle navigation menu">
371      <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
372        <path id="menuIcon" stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h18M4 18h18" />
373      </svg>
374    </button>
375    
376  </div>
377
378  <!-- Mobile Dropdown Menu -->
379  <div id="mobileMenu" class="hidden md:hidden absolute top-full left-0 w-full bg-[#0b1021] border-t border-white/10 shadow-2xl transition-all duration-200 ease-in-out max-h-[calc(100vh-70px)] overflow-y-auto custom-scrollbar">
380    <div class="px-4 py-3 space-y-2">
381      
382      <!-- Section: Audio Tools -->
383      <div class="flex flex-col gap-1">
384        <a href="/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
385          <i class="fa-solid fa-microphone-slash text-purple-400 w-
3855 text-center text-sm"></i> Vocal Remover
386        </a>
387        <a href="/bpm-finder/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
388          <i class="fa-solid fa-drum text-indigo-400 w-5 text-center text-sm"></i> BPM & Key Finder
389        </a>
390        <a href="/key-finder/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
391          <i class="fa-solid fa-compact-disc text-[#c084fc] w-5 text-center text-sm"></i> Key Finder & Camelot
392        </a>
393        <a href="/voice-recorder/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
394          <i class="fa-solid fa-microphone-lines text-rose-400 w-5 text-center text-sm"></i> Voice Recorder
395        </a>
396        <a href="/audio-cutter/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
397          <i class="fa-solid fa-scissors text-blue-400 w-5 text-center text-sm"></i> Audio Cutter
398        </a>
399        <a href="/audio-joiner/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
400          <i class="fa-solid fa-layer-group text-emerald-400 w-5 text-center text-sm"></i> Audio Joiner
401        </a>
402        <a href="/audio-converter/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
403          <i class="fa-solid fa-arrows-rotate text-cyan-400 w-5 text-center text-sm"></i> Audio Converter
404        </a>
405        <a href="/equalizer/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
406          <i class="fa-solid fa-sliders text-teal-400 w-5 text-center text-sm"></i> Audio Equalizer
407        </a>
408        <a href="/pitch-changer/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
409          <i class="fa-solid fa-music text-cyan-400 w-5 text-center text-sm"></i> Pitch Changer
410        </a>
411        <a href="/bass-booster/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
412          <i class="fa-solid fa-volume-high text-cyan-400 w-5 text-center text-sm"></i> Bass Booster
413        </a>
414        <a href="/noise-reducer/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
415          <i class="fa-solid fa-wand-magic-sparkles text-emerald-400 w-5 text-center text-sm"></i> Noise Reducer
416        </a>
417        <a href="/audio-effects/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
418          <i class="fa-solid fa-wand-magic text-violet-400 w-5 text-center text-sm"></i> Audio Effects
419        </a>
420        <a href="/mp3-tag-editor/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all">
421          <i class="fa-solid fa-tags text-amber-400 w-5 text-center text-sm"></i> MP3 Tag Editor
422        </a>
423      </div>
424
425      <!-- Section Divider -->
426      <div class="h-px bg-white/10"></div>
427
428      <!-- Section: General Navigation -->
429      <nav class="grid grid-cols-2 gap-2 text-xs font-semibold">
430        <a href="/#features" class="p-2 rounded-lg text-slate-300 hover:text-white hover:bg-white/5 transition-all flex items-center gap-2">
431          <i class="fa-solid fa-star text-amber-400 text-[10px]"></i> Features
432        </a>
433        <a href="/#howitworks" class="p-2 rounded-lg text-slate-300 hover:text-white hover:bg-white/5 transition-all flex items-center gap-2">
434          <i class="fa-solid fa-circle-play text-cyan-400 text-[10px]"></i> How It Works
435        </a>
436        <a href="/#faqs" class="p-2 rounded-lg text-slate-300 hover:text-white hover:bg-white/5 transition-all flex items-center gap-2">
437          <i class="fa-solid fa-circle-question text-purple-400 text-[10px]"></i> FAQs
438        </a>
439        <a href="/contact/" class="p-2 rounded-lg text-slate-300 hover:text-white hover:bg-white/5 transition-all flex items-center gap-2">
440          <i class="fa-solid fa-envelope text-emerald-400 text-[10px]"></i> Contact
441        </a>
442      </nav>
443
444    </div>
445  </div>
446</header>
447
448<!-- Mobile Hamburger Toggle Script -->
449<script>
450  (function() {
451    function initMobileMenuToggle() {
452      var menuToggle = document.getElementById('menuToggle');
453      var mobileMenu = document.getElementById('mobileMenu');
454      var menuIcon = document.getElementById('menuIcon');
455      if (!menuToggle || !mobileMenu) return;
456
457      menuToggle.onclick = function(e) {
458        e.stopPropagation();
459        var isClosed = mobileMenu.classList.contains('hidden');
460        if (isClosed) {
461          mobileMenu.classList.remove('hidden');
462          if (menuIcon) menuIcon.setAttribute('d', 'M6 18L18 6M6 6l12 12');
463        } else {
464          mobileMenu.classList.add('hidden');
465          if (menuIcon) menuIcon.setAttribute('d', 'M4 6h16M4 12h18M4 18h18');
466        }
467      };
468
469      // Close on clicking outside
470      document.addEventListener('click', function(e) {
471        if (!mobileMenu.classList.contains('hidden') && !mobileMenu.contains(e.target) && !menuToggle.contains(e.target)) {
472          mobileMenu.classList.add('hidden');
473          if (menuIcon) menuIcon.setAttribute('d', 'M4 6h16M4 12h18M4 18h18');
474        }
475      });
476
477      // Close when clicking any link
478      mobileMenu.querySelectorAll('a').forEach(function(link) {
479        link.addEventListener('click', function() {
480          mobileMenu.classList.add('hidden');
481          if (menuIcon) menuIcon.setAttribute('d', 'M4 6h16M4 12h18M4 18h18');
482        });
483      });
484    }
485
486    if (document.readyState === 'loading') {
487      document.addEventListener('DOMContentLoaded', initMobileMenuToggle);
488    } else {
489      initMobileMenuToggle();
490    }
491  })();
492</script>
492
493
494  <!-- ================= MAIN BODY CONTENT ================= -->
495  
496      
497
498
499<!-- Custom Styling for Interactive Elements -->
500<style>
501    .mode-tab-btn.active {
502        background: linear-gradient(135deg, rgba(99, 102, 241, 0.25) 0%, rgba(168, 85, 247, 0.25) 100%);
503        border-color: rgba(168, 85, 247, 0.5);
504        box-shadow: 0 0 25px rgba(99, 102, 241, 0.2);
505    }
506    .wheel-wedge {
507        cursor: pointer;
508        transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
509    }
510    .wheel-wedge:hover {
511        filter: brightness(1.25);
512        transform-origin: center;
513    }
514    .wheel-wedge.active-key {
515        filter: brightness(1.4) drop-shadow(0 0 10px rgba(168, 85, 247, 0.9));
516        stroke: #ffffff !important;
517        stroke-width: 2.5px !important;
518    }
519    .wheel-wedge.match-same {
520        filter: drop-shadow(0 0 8px rgba(16, 185, 129, 0.8));
521    }
522    .wheel-wedge.match-relative {
523        filter: drop-shadow(0 0 8px rgba(59, 130, 246, 0.8));
524    }
525    .wheel-wedge.match-boost {
526        filter: drop-shadow(0 0 8px rgba(236, 72, 153, 0.8));
527    }
528    .piano-key {
529        transition: all 0.1s ease;
530        cursor: pointer;
531        user-select: none;
532    }
533    .piano-key:active, .piano-key.playing {
534        transform: translateY(2px);
535    }
536    .piano-white.scale-active {
537        background: linear-gradient(180deg, #ffffff 0%, #e0e7ff 70%, #818cf8 100%) !important;
538        border-color: #6366f1 !important;
539    }
540    .piano-black.scale-active {
541        background: linear-gradient(180deg, #1e1b4b 0%, #4338ca 100%) !important;
542        border-color: #a78bfa !important;
543    }
544</style>
545
546<!-- ================= MAIN CONTAINER ================= -->
547<main class="flex-grow max-w-7xl mx-auto px-4 sm:px-6 py-6 md:py-8 relative z-40 w-full flex flex-col items-center">
548
549    <!-- Mode Switcher Navigation Tabs -->
550    <div class="w-full max-w-4xl mx-auto mb-6 relative z-20">
551        <div class="grid grid-cols-1 sm:grid-cols-3 gap-2 bg-slate-950/70 p-2 rounded-3xl border border-white/10 shadow-2xl backdrop-blur-xl">
552            <!-- Tab 1: File Upload -->
553            <button id="tab-btn-upload" class="mode-tab-btn active flex items-center justify-center sm:justify-start gap-3 px-4 py-2.5 sm:py-3 rounded-2xl border border-transparent hover:bg-white/5 transition-all duration-200 cursor-pointer select-none">
554                <div class="h-10 w-10 rounded-xl bg-indigo-500/20 text-indigo-400 flex items-center justify-center text-lg flex-shrink-0 shadow-inner">
555                    <i class="fa-solid fa-cloud-arrow-up"></i>
556                </div>
557                <div class="text-left">
558                    <span class="block text-sm font-bold text-white font-outfit">Audio File</span>
559                    <span class="block text-[11px] text-slate-400">Analyze MP3, WAV, etc.</span>
560                </div>
561            </button>
562
563            <!-- Tab 2: Camelot Wheel & Piano -->
564            <button id="tab-btn-wheel" class="mode-tab-btn flex items-center justify-center sm:justify-start gap-3 px-4 py-2.5 sm:py-3 rounded-2xl border border-transparent hover:bg-white/5 transition-all duration-200 cursor-pointer select-none">
565                <div class="h-10 w-10 rounded-xl bg-purple-500/20 text-purple-400 flex items-center justify-center text-lg flex-shrink-0 shadow-inner">
566                    <i class="fa-solid fa-compact-disc"></i>
567                </div>
568                <div class="text-left">
569                    <span class="block text-sm font-bold text-white font-outfit">Camelot Wheel & Piano</span>
570                    <span class="block text-[11px] text-slate-400">Interactive DJ explorer</span>
571                </div>
572            </button>
573
574            <!-- Tab 3: Real-Time Mic -->
575            <button id="tab-btn-mic" class="mode-tab-btn flex items-center justify-center sm:justify-start gap-3 px-4 py-2.5 sm:py-3 rounded-2xl border border-transparent hover:bg-white/5 transition-all duration-200 cursor-pointer select-none">
576                <div class="h-10 w-10 rounded-xl bg-emerald-500/20 text-emerald-400 flex items-center justify-center text-lg flex-shrink-0 shadow-inner">
577                    <i class="fa-solid fa-microphone-lines"></i>
578                </div>
579                <div class="text-left">
580                    <span class="block text-sm font-bold text-white font-outfit">Real-Time Mic</span>
581                    <span class="block text-[11px] text-slate-400">Live hum / acoustic pitch</span>
582                </div>
583            </button>
584        </div>
585    </div>
586
587    <!-- ================= PANEL 1: FILE UPLOADER & KEY WORKSPACE ================= -->
588    <div id="panel-upload" class="mode-panel w-full relative z-30">
589        
590<!-- ================= STYLING FOR UPLOADER COMPONENT ================= -->
591<style>
592    .dragover {
593        border-color: rgba(99, 102, 241, 0.6) !important;
594        background: rgba(99, 102, 241, 0.05) !important;
595        box-shadow: 0 0 35px rgba(99, 102, 241, 0.25) !important;
596    }
597
598    .dropdown-menu::before {
599    content: '';
600    position: absolute;
601    top: -14px;
602    left: -12px;
603    right: -12px;
604    bottom: -12px;
605    background: transparent;
606    z-index: -1;
607    pointer-events: auto;
608}
609
610    .modal-overlay {
611        position: fixed;
612        top: 0;
613        left: 0;
614        right: 0;
615        bottom: 0;
616        background-color: rgba(0, 0, 0, 0.75);
617        backdrop-filter: blur(12px);
618        display: flex;
619        align-items: center;
620        justify-content: center;
621        z-index: 1000;
622        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
623    }
624    
625    .modal-hidden {
626        opacity: 0;
627        pointer-events: none;
628        visibility: hidden;
629    }
630</style>
631
632<!-- ================= HERO & UPLOAD SECTION ================= -->
633<section class="w-full relative z-10 flex flex-col items-center">
634    
635    <!-- Hero Header Info -->
636    <header class="text-center mb-6 sm:mb-8 headerinfo relative max-w-4xl mx-auto px-4" id="uploaderHeader">
637        <span class="text-xs md:text-sm font-bold tracking-widest text-[#a78bfa] uppercase px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20 inline-block mb-3">
638            HARMONIC KEY & CAMELOT WHEEL DETECTOR        </span>
639        <h1 class="font-outfit text-3xl sm:text-4xl md:text-5xl font-extrabold text-white tracking-tight leading-tight mb-3">
640            Free Online <span class="bg-clip-text text-transparent bg-gradient-to-r from-[#a78bfa] to-[#c084fc] drop-shadow-sm">Song Key Finder</span>        </h1>
641        <p class="text-sm sm:text-base text-slate-300 font-light mb-5 max-w-2xl mx-auto leading-relaxed">
642            Find the musical key, scale, and Camelot code of any song in seconds. Upload an audio file for key detection, explore the Camelot Wheel, or use your microphone to detect pitch in real time — all processed directly in your browser.        </p>
643        
644        <!-- Key Feature Badges -->
645        <div class="flex flex-wrap items-center justify-center gap-2.5 sm:gap-3 text-slate-400 text-xs sm:text-sm">
646                            <div class="flex items-center gap-2 bg-white/5 border border-white/5 px-3 py-1 rounded-xl">
647                    <i class="fas fa-check-circle text-emerald-400 text-xs"></i>
648                    <span class="font-medium text-white/90 text-xs sm:text-sm">100% In-Browser Privacy</span>
649                </div>
650                            <div class="flex items-center gap-2 bg-white/5 border border-white/5 px-3 py-1 rounded-xl">
651                    <i class="fas fa-check-circle text-emerald-400 text-xs"></i>
652                    <span class="font-medium text-white/90 text-xs sm:text-sm">Musical Key Detection</span>
653                </div>
654                            <div class="flex items-center gap-2 bg-white/5 border border-white/5 px-3 py-1 rounded-xl">
655                    <i class="fas fa-check-circle text-emerald-400 text-xs"></i>
656                    <span class="font-medium text-white/90 text-xs sm:text-sm">Camelot DJ Code</span>
657                </div>
658                            <div class="flex items-center gap-2 bg-white/5 border border-white/5 px-3 py-1 rounded-xl">
659                    <i class="fas fa-check-circle text-emerald-400 text-xs"></i>
660                    <span class="font-medium text-white/90 text-xs sm:text-sm">Scale Degrees &amp; Piano</span>
661                </div>
662                    </div>
663    </header>
664
665    <!-- Main Upload Drop Zone Section -->
666    <div class="max-w-4xl mx-auto w-full px-4" id="uploaderContainer">
667        
668        <!-- Drop Zone Card -->
669        <div id="dropZone" class="glass-card rounded-3xl p-6 sm:p-8 md:p-10 mb-6 text-center cursor-pointer border border-white/10 hover:border-indigo-500/30 hover:shadow-neon-indigo transition-all duration-300 relative group" aria-label="drop-files">
670            
671            <div class="relative z-10 flex flex-col items-center">
672                <!-- Cloud Icon -->
673                <div class="h-14 w-14 rounded-2xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center mb-4 text-[#a78bfa] group-hover:scale-105 transition-transform duration-300">
674                    <svg class="h-7 w-7 animate-bounce" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
675                        <path stroke-linecap="round" stroke-linejoin="round" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
676                    </svg>
677                </div>
678                
679                <!-- Split Upload Button with Dropdown -->
680                <div class="split-button-container group/dropdown relative inline-flex items-center mb-4 select-none z-40">
681                    <div class="upload-button flex items-center bg-gradient-to-r from-[#a78bfa] to-[#c084fc] hover:opacity-95 text-white font-semibold rounded-2xl shadow-lg transition-all duration-150 overflow-hidden">
682                        <div class="button-main px-5 py-3.5 flex items-center gap-2 border-r border-white/20 active:scale-98 cursor-pointer" id="mainButton" aria-label="upload-button">
683                            <i class="fas fa-upload text-sm"></i>
684                            <span class="text-sm sm:text-base font-bold">Upload File</span>
685                        </div>
686                        <div class="button-dropdown-trigger px-3.5 py-3.5 flex items-center justify-center cursor-pointer active:bg-black/10" id="dropdownTrigger" title="Choose audio source">
687                            <i class="fas fa-chevron-down text-xs"></i>
688                        </div>
689                    </div>
690                    
691                    <!-- Dropdown Menu for Cloud Sources -->
692                    <div class="dropdown-menu absolute top-full left-1/2 -translate-x-1/2 mt-2 w-64 bg-slate-950/95 border border-white/10 rounded-2xl shadow-2xl p-2 hidden flex flex-col gap-1 backdrop-blur-xl z-50">
693                        <div class="dropdown-item px-4 py-3 rounded-xl flex items-center gap-3 text-slate-300 hover:text-white hover:bg-white/5 transition-all text-left cursor-pointer" data-action="device">
694                            <i class="fas fa-laptop text-indigo-400 w-5"></i>
695                            <span class="text-sm font-medium">From Device</span>
696                        </div>
697                        <div class="dropdown-item px-4 py-3 rounded-xl flex items-center gap-3 text-slate-300 hover:text-white hover:bg-white/5 transition-all text-left cursor-pointer" data-action="url">
698                            <i class="fas fa-link text-indigo-400 w-5"></i>
699                            <span class="text-sm font-medium">From URL</span>
700                        </div>
701                        <div class="dropdown-item px-4 py-3 rounded-xl flex items-center gap-3 text-slate-300 hover:text-white hover:bg-white/5 transition-all text-left cursor-pointer" data-action="google-drive">
702                            <i class="fab fa-google-drive text-indigo-400 w-5"></i>
703                            <span class="text-sm font-medium">From Google Drive</span>
704                        </div>
705                        <div class="dropdown-item px-4 py-3 rounded-xl flex items-center gap-3 text-slate-300 hover:text-white hover:bg-white/5 transition-all text-left cursor-pointer" data-action="dropbox">
706                            <i class="fab fa-dropbox text-indigo-400 w-5"></i>
707                            <span class="text-sm font-medium">From Dropbox</span>
708                        </div>
709                        <div class="dropdown-item px-4 py-3 rounded-xl flex items-center gap-3 text-slate-300 hover:text-white hover:bg-white/5 transition-all text-left cursor-pointer" data-action="onedrive">
710                            <i class="fa-solid fa-cloud text-indigo-400 w-5"></i>
711                            <span class="text-sm font-medium">From OneDrive</span>
712                        </div>
713                    </div>
714                </div>
715                
716                <p class="text-slate-200 mb-1.5 text-base sm:text-lg font-outfit font-semibold">Drag & drop your audio track here</p>
717                <p class="text-slate-400 mb-4 text-xs sm:text-sm">or choose from multiple cloud sources above</p>
718                <p class="text-slate-500 text-xs mb-1">Maximum file size: <span class="font-semibold text-slate-300">100MB</span></p>
719                <p class="text-slate-500 text-xs">For lawful use only. Privacy guaranteed — files are processed directly on your device.</p>
720                
721                <input type="file" id="fileInput" class="hidden" accept="audio/*,.mp3,.wav,.flac,.m4a,.aac,.ogg,.opus,.aiff" aria-label="upload files">
722                
723                <!-- Format Badges -->
724                <div class="flex flex-wrap items-center justify-center gap-2 mt-5">
725                    <span class="text-slate-500 text-xs font-bold uppercase tracking-wider mr-2">Supports:</span>
726                                            <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">MP3</span>
727                                            <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">WAV</span>
728                                            <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">FLAC</span>
729                                            <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">M4A</span>
730                                            <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">AAC</span>
731                                            <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">OGG</span>
732                                            <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">OPUS</span>
733                                            <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">AIFF</span>
734                                    </div>
735            </div>
736        </div>
737
738        <!-- Progress Bar Container -->
739        <div class="progress-container w-full max-w-xl mx-auto my-8 bg-slate-950/60 border border-white/10 rounded-3xl p-6 hidden" id="progress-container">
740            <div class="relative w-full h-4 bg-white/5 rounded-full overflow-hidden border border-white/10">
741                <div class="h-full bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 transition-all duration-300" id="progressBar" style="width: 0%"></div>
742            </div>
743            <div class="flex items-center justify-between mt-4">
744                <span class="text-sm font-semibold text-slate-300 font-outfit" id="progressText">0%</span>
745                <span class="text-xs text-indigo-400 font-bold uppercase tracking-wider" id="progressSubtext">Loading Audio...</span>
746            </div>
747        </div>
748
749        <!-- Dynamic Status / Information Box -->
750        <div style="display:none" class="flex-wrap items-center justify-center gap-4 text-slate-300 text-center text-sm md:text-base font-medium px-6 py-4 glass-card rounded-2xl max-w-xl mx-auto mb-6" id="progdata"></div>
751        
752        <!-- Spinner Loader -->
753        <div style="display:none" class="flex justify-center items-center gap-4 text-slate-300 mb-6" id="process">
754            <div class="w-12 h-12 border-4 border-indigo-500/20 border-t-indigo-500 rounded-full animate-spin"></div>
755        </div>
756        
757        <!-- Error Alert Container -->
758        <div style="display:none" class="flex-wrap items-center justify-center gap-4 text-rose-400 font-semibold px-6 py-4 bg-rose-500/10 border border-rose-500/20 rounded-2xl max-w-xl mx-auto mb-6 text-center" id="error"></div>
759    </div>
760</section>
761
762<!-- ================= URL MODAL ================= -->
763<div id="urlModal" class="modal-overlay modal-hidden">
764    <div class="glass-card rounded-3xl p-8 md:p-10 w-full max-w-lg mx-4 relative overflow-hidden shadow-2xl border border-white/10">
765        <div class="absolute -right-20 -top-20 w-64 h-64 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none"></div>
766        
767        <button class="absolute top-6 right-6 h-10 w-10 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 flex items-center justify-center text-slate-400 hover:text-white transition-all active:scale-95" id="closeModal" title="Close">
768            <i class="fa-solid fa-xmark text-lg"></i>
769        </button>
770        
771        <div class="flex items-center gap-3.5 mb-2">
772            <div class="h-12 w-12 rounded-xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center text-[#a78bfa]">
773                <i class="fa-solid fa-link text-lg"></i>
774            </div>
775            <div>
776                <h3 class="font-outfit text-xl font-bold text-white">Add File by URL</h3>
777                <p class="text-xs text-slate-400">Provide a direct audio stream URL</p>
778            </div>
779        </div>
780        
781        <div class="space-y-6 mt-6">
782            <div class="flex items-center bg-slate-950/60 border border-white/10 rounded-2xl p-2 focus-within:border-indigo-500/50 focus-within:ring-2 focus-within:ring-indigo-500/20 transition-all">
783                <input type="text" id="urlInput" placeholder="Paste direct audio URL (MP3, WAV, etc.)..." class="flex-1 bg-transparent px-4 py-2 text-white placeholder-slate-500 focus:outline-none text-sm" />
784                <button id="pasteBtn" class="flex-shrink-0 px-4 py-2.5 rounded-xl bg-white/5 border border-white/10 text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/10 active:scale-95 transition-all flex items-center gap-1.5" title="Paste URL" aria-label="paste">
785                    <i class="fa-solid fa-paste"></i> <span>Paste</span>
786                </button>
787            </div>
788            
789            <button id="submitUrl" class="w-full px-5 py-3.5 rounded-2xl bg-gradient-to-r from-[#a78bfa] to-[#c084fc] text-white font-bold hover:opacity-95 active:scale-98 shadow-lg hover:shadow-
789purple-500/25 transition-all flex items-center justify-center gap-2" aria-label="submit">
790                <i class="fa-solid fa-cloud-arrow-down text-sm"></i> Load Audio from URL
791            </button>
792        </div>
793    </div>
794</div>
795
796<!-- ================= CLIENT-SIDE CORE UPLOADER ENGINE ================= -->
797<script>
798(function() {
799    const TOOL_MODE = 'client';
800    const MAX_FILE_SIZE = 100 * 1024 * 1024;
801    const ALLOWED_EXTS = ["mp3","wav","flac","m4a","aac","ogg","opus","aiff"];
802
803    const dropZone = document.getElementById('dropZone');
804    const fileInput = document.getElementById('fileInput');
805    const mainButton = document.getElementById('mainButton');
806    const dropdownTrigger = document.getElementById('dropdownTrigger');
807    const dropdownMenu = document.querySelector('.dropdown-menu');
808    const dropdownItems = document.querySelectorAll('.dropdown-item');
809    const progressContainer = document.getElementById('progress-container');
810    const progressBar = document.getElementById('progressBar');
811    const progressText = document.getElementById('progressText');
812    const progressSubtext = document.getElementById('progressSubtext');
813    const progData = document.getElementById('progdata');
814    const processSpinner = document.getElementById('process');
815    const errorBox = document.getElementById('error');
816    const uploaderHeader = document.getElementById('uploaderHeader');
817
818    // Global Notification Toast
819    window.showNotification = function(message, type = 'info') {
820        const existing = document.querySelectorAll('.app-notification-toast');
821        existing.forEach(t => t.remove());
822
823        const notification = document.createElement('div');
824        notification.className = 'app-notification-toast';
825        notification.style.cssText = `
826            position: fixed;
827            top: 90px;
828            right: 20px;
829            background: ${type === 'success' ? 'linear-gradient(135deg, #10b981 0%, #059669 100%)' : 
830                         type === 'error' ? 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)' : 
831                         'linear-gradient(135deg, #6366f1 0%, #a855f7 100%)'};
832            color: white;
833            padding: 14px 22px;
834            border-radius: 16px;
835            box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
836            border: 1px solid rgba(255, 255, 255, 0.15);
837            backdrop-filter: blur(12px);
838            z-index: 99999;
839            max-width: 420px;
840            font-weight: 600;
841            font-size: 14px;
842            font-family: 'Outfit', sans-serif;
843            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
844            transform: translateX(120%);
845            opacity: 0;
846        `;
847        
848        notification.textContent = message;
849        document.body.appendChild(notification);
850        
851        requestAnimationFrame(() => {
852            notification.style.transform = 'translateX(0)';
853            notification.style.opacity = '1';
854        });
855        
856        setTimeout(() => {
857            notification.style.transform = 'translateX(120%)';
858            notification.style.opacity = '0';
859            setTimeout(() => notification.remove(), 350);
860        }, 3500);
861    };
862
863    // Update Progress Indicator
864    window.setUploadProgress = function(percent, text = '', subtext = 'Processing...') {
865        if (percent === null) {
866            progressContainer.style.display = 'none';
867            return;
868        }
869        progressContainer.style.display = 'block';
870        percent = Math.min(100, Math.max(0, Math.round(percent)));
871        progressBar.style.width = percent + '%';
872        progressText.innerText = percent + '% ' + text;
873        if (subtext) progressSubtext.innerText = subtext;
874    };
875
876    // Reset Uploader View
877    window.resetUploader = function() {
878        if (dropZone) dropZone.style.display = 'block';
879        if (uploaderHeader) uploaderHeader.style.display = 'block';
880        if (progressContainer) progressContainer.style.display = 'none';
881        if (progData) progData.style.display = 'none';
882        if (processSpinner) processSpinner.style.display = 'none';
883        if (errorBox) errorBox.style.display = 'none';
884        if (fileInput) fileInput.value = '';
885        const workspace = document.getElementById('tool-workspace');
886        if (workspace) workspace.classList.add('hidden');
887    };
888
889    // Check extension
890    function isAllowedExt(fileName) {
891        const dot = fileName.lastIndexOf('.');
892        if (dot === -1) return false;
893        const ext = fileName.slice(dot + 1).toLowerCase();
894        return ALLOWED_EXTS.includes(ext);
895    }
896
897    // Drag and drop listeners
898    if (dropZone) {
899        dropZone.addEventListener('dragover', (e) => {
900            e.preventDefault();
901            dropZone.classList.add('dragover');
902        });
903
904        dropZone.addEventListener('dragleave', (e) => {
905            e.preventDefault();
906            dropZone.classList.remove('dragover');
907        });
908
909        dropZone.addEventListener('drop', (e) => {
910            e.preventDefault();
911            dropZone.classList.remove('dragover');
912            if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
913                handleIncomingFile(e.dataTransfer.files[0]);
914            }
915        });
916
917        dropZone.addEventListener('click', (e) => {
918            if (!e.target.closest('.split-button-container')) {
919                fileInput.click();
920            }
921        });
922    }
923
924    // Main button click
925    if (mainButton) {
926        mainButton.addEventListener('click', (e) => {
927            e.stopPropagation();
928            fileInput.click();
929        });
930    }
931
932    // File input change
933    if (fileInput) {
934        fileInput.addEventListener('change', (e) => {
935            if (e.target.files && e.target.files.length > 0) {
936                handleIncomingFile(e.target.files[0]);
937            }
938        });
939    }
940
941    // Split dropdown hover
942    let dropdownTimeout;
943    const splitContainer = document.querySelector('.split-button-container');
944    if (splitContainer && dropdownMenu) {
945        splitContainer.addEventListener('mouseenter', () => {
946            clearTimeout(dropdownTimeout);
947            dropdownMenu.classList.remove('hidden');
948        });
949
950        splitContainer.addEventListener('mouseleave', () => {
951            dropdownTimeout = setTimeout(() => {
952                dropdownMenu.classList.add('hidden');
953            }, 600);
954        });
955
956        dropdownTrigger.addEventListener('click', (e) => {
957            e.stopPropagation();
958            dropdownMenu.classList.toggle('hidden');
959        });
960    }
961
962    // Handle Dropdown Actions
963    dropdownItems.forEach(item => {
964        item.addEventListener('click', (e) => {
965            e.stopPropagation();
966            if (dropdownMenu) dropdownMenu.classList.add('hidden');
967            const action = item.getAttribute('data-action');
968            handleSourceAction(action);
969        });
970    });
971
972let OAUTH_TOKEN  = '';
973let DEVELOPER_KEY = '';
974
975        function handleSourceAction(action) {
976            console.log('Upload action:', action);
977            
978            switch(action) {
979                case 'device':
980                    fileInput.click();
981                    break;
982                    
983                case 'url':
984                    openUrlModal();
985                    break;
986                    
987                case 'google-drive':
988                    showNotification('Initiating Google Login...', 'info');
989                    loadGoogleDriveOnDemand(triggerGoogleLogin);
990                    break;
991                    
992                case 'dropbox':
993                    showNotification('Initiating Dropbox Login...', 'info');
994                    dropboxPicker();
995                    break;
996
997                case 'onedrive':
998                    showNotification('Initiating OneDrive Login...', 'info');
999                    launchOneDrivePicker();
1000                    break;
1001                    
1002                case 'youtube':
1003                    showYouTubeInput();
1004                    break;
1005                    
1006                default:
1007                    console.log('Unknown action:', action);
1008            }
1009        }
1010
1011        // Show YouTube input dialog
1012        function showYouTubeInput() {
1013            const url = prompt('Enter the YouTube video URL:');
1014            if (url) {
1015                showNotification(`Processing YouTube video: ${url}`, 'info');
1016                console.log('YouTube URL provided:', url);
1017            }
1018        }
1019
1020        window.addEventListener("load", function() {
1021            setTimeout(function() {
1022                showNotification('We support different separation models based on your need!', 'info');
1023            }, 1000);
1024        });
1025    
1026    // Master File Ingestion Handler
1027    function handleIncomingFile(file) {
1028        if (!file) return;
1029
1030        if (file.size > MAX_FILE_SIZE) {
1031            showNotification(`❌ File exceeds ${MAX_FILE_SIZE / (1024*1024)}MB limit`, 'error');
1032            return;
1033        }
1034
1035        if (!isAllowedExt(file.name)) {
1036            showNotification(`⚠️ Unsupported audio format: ${file.name}`, 'error');
1037            return;
1038        }
1039
1040        showNotification(`Loading: ${file.name}`, 'success');
1041
1042        if (TOOL_MODE === 'client') {
1043            loadClientAudio(file);
1044        } else {
1045            if (typeof window.startServerUpload === 'function') {
1046                window.startServerUpload(file);
1047            }
1048        }
1049    }
1050
1051    // Load and decode audio buffer in browser (Client-Side Mode)
1052    async function loadClientAudio(file) {
1053        try {
1054            if (dropZone) dropZone.style.display = 'none';
1055            if (uploaderHeader) uploaderHeader.style.display = 'none';
1056            if (processSpinner) processSpinner.style.display = 'flex';
1057            setUploadProgress(15, '', 'Reading file...');
1058
1059            const arrayBuffer = await file.arrayBuffer();
1060            setUploadProgress(45, '', 'Decoding audio PCM...');
1061
1062            const AudioCtx = window.AudioContext || window.webkitAudioContext;
1063            const audioCtx = new AudioCtx();
1064            const decodedBuffer = await audioCtx.decodeAudioData(arrayBuffer);
1065
1066            setUploadProgress(100, 'Complete!', 'Ready');
1067            setTimeout(() => {
1068                setUploadProgress(null);
1069                if (processSpinner) processSpinner.style.display = 'none';
1070                
1071                // Reveal tool workspace
1072                const workspace = document.getElementById('tool-workspace');
1073                if (workspace) workspace.classList.remove('hidden');
1074
1075                // Trigger tool-specific callback
1076                if (typeof window.onAudioLoaded === 'function') {
1077                    window.onAudioLoaded(file, decodedBuffer, audioCtx);
1078                }
1079            }, 300);
1080
1081        } catch (err) {
1082            console.error('Audio decode error:', err);
1083            showNotification('Could not decode audio. Try another format.', 'error');
1084            resetUploader();
1085        }
1086    }
1087
1088    // Load from remote URL (direct or preloaded)
1089    window.loadFromRemoteUrl = async function(url, fileName = 'Imported Audio') {
1090        try {
1091            if (dropZone) dropZone.style.display = 'none';
1092            if (uploaderHeader) uploaderHeader.style.display = 'none';
1093            if (processSpinner) processSpinner.style.display = 'flex';
1094            setUploadProgress(20, '', 'Fetching audio stream...');
1095            showNotification(`Preloading audio...`, 'info');
1096
1097            const res = await fetch(url);
1098            if (!res.ok) throw new Error('Network response not ok: ' + res.status);
1099            
1100            const blob = await res.blob();
1101            const file = new File([blob], fileName, { type: blob.type || 'audio/mpeg' });
1102            
1103            setUploadProgress(60, '', 'Processing audio...');
1104            loadClientAudio(file);
1105
1106        } catch (err) {
1107            console.error('Remote audio load error:', err);
1108            showNotification('Failed to download audio from URL.', 'error');
1109            resetUploader();
1110        }
1111    };
1112
1113    // URL Modal Logic
1114    const urlModal = document.getElementById('urlModal');
1115    const closeModal = document.getElementById('closeModal');
1116    const pasteBtn = document.getElementById('pasteBtn');
1117    const submitUrl = document.getElementById('submitUrl');
1118    const urlInput = document.getElementById('urlInput');
1119
1120    function openUrlModal() {
1121        if (!urlModal) return;
1122        urlModal.classList.remove('modal-hidden');
1123        if (urlInput) {
1124            urlInput.value = '';
1125            urlInput.focus();
1126        }
1127    }
1128
1129    if (closeModal) {
1130        closeModal.onclick = () => urlModal.classList.add('modal-hidden');
1131    }
1132
1133    if (urlModal) {
1134        urlModal.addEventListener('click', (e) => {
1135            if (e.target === urlModal) urlModal.classList.add('modal-hidden');
1136        });
1137    }
1138
1139    if (pasteBtn && urlInput) {
1140        pasteBtn.onclick = async () => {
1141            try {
1142                if (navigator.clipboard && navigator.clipboard.readText) {
1143                    const text = await navigator.clipboard.readText();
1144                    urlInput.value = text.trim();
1145                } else {
1146                    const manual = prompt('Paste your audio URL:');
1147                    if (manual) urlInput.value = manual.trim();
1148                }
1149            } catch (err) {
1150                const manual = prompt('Paste your audio URL:');
1151                if (manual) urlInput.value = manual.trim();
1152            }
1153        };
1154    }
1155
1156    const bannedDomains = ['youtube.com', 'youtu.be', 'facebook.com', 'fb.me', 'instagram.com', 'tiktok.com'];
1157
1158    if (submitUrl && urlInput) {
1159        submitUrl.onclick = () => {
1160            const url = urlInput.value.trim();
1161            if (!url) {
1162                showNotification('Please enter a valid URL', 'error');
1163                return;
1164            }
1165            if (bannedDomains.some(d => url.toLowerCase().includes(d))) {
1166                showNotification('Direct streaming from video platforms is not supported.', 'error');
1167                return;
1168            }
1169            urlModal.classList.add('modal-hidden');
1170            loadFromRemoteUrl(url, 'Audio_From_URL.mp3');
1171        };
1172    }
1173
1174    // Auto-detect Preloaded Audio from Query Parameters (?audio=... or ?audio_url=...)
1175    window.addEventListener('DOMContentLoaded', () => {
1176        const params = new URLSearchParams(window.location.search);
1177        const preloadedAudio = params.get('audio') || params.get('audio_url');
1178        const preloadedName = params.get('name') || params.get('title') || 'Preloaded Audio';
1179
1180        if (preloadedAudio) {
1181            console.log('Detected preloaded audio URL:', preloadedAudio);
1182            loadFromRemoteUrl(preloadedAudio, preloadedName);
1183        }
1184    });
1185
1186})();
1187</script>
1187
1188
1189        <!-- Tool Workspace (Revealed automatically once audio is loaded) -->
1190        <section id="tool-workspace" class="hidden max-w-5xl mx-auto w-full px-4 mb-10 relative z-10">
1191            
1192            <!-- Top Action Bar: File Name & Re-upload Button -->
1193            <div class="flex flex-col sm:flex-row items-center justify-between gap-3 glass-card p-3.5 sm:p-4 rounded-2xl mb-5 border border-white/10">
1194                <div class="flex items-center gap-3 truncate max-w-full">
1195                    <div class="h-9 w-9 rounded-xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center text-[#a78bfa] flex-shrink-0">
1196                        <i class="fa-solid fa-music text-sm"></i>
1197                    </div>
1198                    <div class="truncate text-left">
1199                        <p class="text-[11px] text-slate-400 font-medium">Loaded Track</p>
1200                        <h2 id="track-filename" class="text-xs sm:text-sm font-bold text-white truncate max-w-md">filename.mp3</h2>
1201                    </div>
1202                </div>
1203                
1204                <button onclick="resetUploader()" class="flex-shrink-0 px-3.5 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-300 hover:text-white transition-all flex items-center gap-2 active:scale-95 cursor-pointer">
1205                    <i class="fa-solid fa-arrow-up-from-bracket"></i> Choose Another File
1206                </button>
1207            </div>
1208
1209            <!-- Main Results Grid -->
1210            <div class="grid grid-cols-1 lg:grid-cols-12 gap-4 mb-5">
1211                
1212                <!-- Left Column: Big Key Display, Camelot Code, Song BPM & Tuning Pitch -->
1213                <div class="lg:col-span-7 glass-card rounded-3xl p-5 sm:p-7 border border-white/10 flex flex-col justify-between relative overflow-hidden group">
1214                    <div class="absolute -right-20 -top-20 w-64 h-64 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none"></div>
1215                    
1216                    <div>
1217                        <div class="flex items-center justify-between mb-2">
1218                            <span class="text-[11px] font-bold tracking-widest text-[#a78bfa] uppercase px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20">
1219                                DETECTED KEY & BPM
1220                            </span>
1221                            <span id="key-confidence-badge" class="text-xs font-semibold text-emerald-400 bg-emerald-500/10 px-3 py-1 rounded-full border border-emerald-500/20 flex items-center gap-1.5">
1222                                <i class="fa-solid fa-circle-check"></i> Key Detection Result
1223                            </span>
1224                        </div>
1225
1226                        <!-- Big Dual Metrics: Musical Key & Song BPM -->
1227                        <div class="grid grid-cols-1 sm:grid-cols-2 gap-3.5 my-3">
1228                            <!-- Metric 1: Musical Key & Camelot -->
1229                            <div class="bg-white/5 rounded-2xl p-4 border border-white/5 relative overflow-hidden flex flex-col justify-between">
1230                                <span class="text-[10px] text-slate-400 uppercase font-semibold block mb-1">Musical Key & Camelot</span>
1231                                <div class="flex items-baseline gap-2.5 my-1">
1232                                    <span id="workspace-key-display" class="font-outfit text-3xl sm:text-4xl lg:text-5xl font-black text-white tracking-tight drop-shadow-md">
1233                                        --
1234                                    </span>
1235                                    <span id="workspace-camelot-badge" class="px-2.5 py-1 rounded-xl bg-indigo-500/20 border border-indigo-500/30 text-indigo-300 font-outfit font-black text-lg sm:text-xl">
1236                                        --
1237                                    </span>
1238                                </div>
1239                                <span id="workspace-key-confidence" class="text-[11px] text-emerald-400 font-medium">Key Detection Confidence</span>
1240                            </div>
1241
1242                            <!-- Metric 2: Detected Tempo (BPM) & Octave Multipliers 
1242-->
1243                            <div class="bg-white/5 rounded-2xl p-4 border border-white/5 relative overflow-hidden flex flex-col justify-between">
1244                                <div class="flex items-center justify-between mb-1">
1245                                    <span class="text-[10px] text-slate-400 uppercase font-semibold">Track Tempo</span>
1246                                    <span id="workspace-tempo-genre" class="text-[10px] text-indigo-300 font-semibold truncate max-w-[110px]">--</span>
1247                                </div>
1248                                <div class="flex items-baseline gap-1.5 my-1">
1249                                    <span id="workspace-bpm-display" class="font-outfit text-3xl sm:text-4xl lg:text-5xl font-black text-white tracking-tight drop-shadow-md">
1250                                        --
1251                                    </span>
1252                                    <span class="text-sm font-bold text-slate-400 uppercase">BPM</span>
1253                                </div>
1254                                
1255                                <!-- Multiplier Buttons: 1/2, 1X, 2X -->
1256                                <div class="flex items-center gap-1.5 mt-1">
1257                                    <button id="btn-bpm-half" title="Half Tempo (÷2)" class="tempo-mult-btn px-2.5 py-0.5 rounded-lg bg-white/5 hover:bg-white/10 border border-white/10 text-[11px] font-bold text-slate-300 cursor-pointer transition-all">
1258                                        &frac12;
1259                                    </button>
1260                                    <button id="btn-bpm-1x" title="Original Tempo (1X)" class="tempo-mult-btn active px-2.5 py-0.5 rounded-lg bg-indigo-500/20 border border-indigo-500/40 text-[11px] font-bold text-white cursor-pointer transition-all">
1261                                        1X
1262                                    </button>
1263                                    <button id="btn-bpm-double" title="Double Tempo (&times;2)" class="tempo-mult-btn px-2.5 py-0.5 rounded-lg bg-white/5 hover:bg-white/10 border border-white/10 text-[11px] font-bold text-slate-300 cursor-pointer transition-all">
1264                                        2&times;
1265                                    </button>
1266                                </div>
1267                            </div>
1268                        </div>
1269
1270                        <!-- Scale Notes Bar -->
1271                        <div class="my-3">
1272                            <p class="text-xs text-slate-400 font-medium mb-1.5 flex items-center gap-1.5">
1273                                <i class="fa-solid fa-list-ol text-indigo-400"></i> Notes in this Scale:
1274                            </p>
1275                            <div id="scale-notes-container" class="flex flex-wrap items-center gap-1.5">
1276                                <span class="px-2.5 py-1 rounded-lg bg-white/5 text-xs font-mono font-bold text-slate-300">--</span>
1277                            </div>
1278                        </div>
1279
1280                        <!-- Play Tonic Root Note Reference Tone Button -->
1281                        <div class="my-3 flex flex-wrap items-center gap-2.5">
1282                            <button id="btn-play-tonic" class="px-4 py-2 rounded-xl bg-gradient-to-r from-indigo-500 to-purple-600 hover:opacity-95 text-white font-bold text-xs shadow-md transition-all active:scale-95 flex items-center gap-2 cursor-pointer">
1283                                <i class="fa-solid fa-volume-high" id="tonic-icon"></i>
1284                                <span id="tonic-btn-label">Play Root Note Tone</span>
1285                            </button>
1286                            <span class="text-xs text-slate-400">Tuning Reference: <strong class="text-slate-200">A = 440.0 Hz</strong></span>
1287                        </div>
1288                    </div>
1289
1290                    <!-- Key Attributes Sub-Bar -->
1291                    <div class="grid grid-cols-2 sm:grid-cols-3 gap-2.5 pt-4 border-t border-white/10">
1292                        <div class="bg-white/5 rounded-2xl p-3 border border-white/5">
1293                            <p class="text-[10px] sm:text-[11px] text-slate-400 uppercase font-semibold mb-0.5">Relative Key</p>
1294                            <div class="flex items-center gap-2">
1295                                <i class="fa-solid fa-arrows-rotate text-purple-400 text-xs"></i>
1296                                <span id="relative-key-display" class="text-sm sm:text-base font-bold text-white font-outfit">--</span>
1297                            </div>
1298                        </div>
1299                        <div class="bg-white/5 rounded-2xl p-3 border border-white/5">
1300                            <p class="text-[10px] sm:text-[11px] text-slate-400 uppercase font-semibold mb-0.5">Dominant (5th)</p>
1301                            <div class="flex items-center gap-2">
1302                                <i class="fa-solid fa-crown text-indigo-400 text-xs"></i>
1303                                <span id="dominant-key-display" class="text-sm sm:text-base font-bold text-indigo-300 font-outfit">--</span>
1304                            </div>
1305                        </div>
1306                        <div class="bg-white/5 rounded-2xl p-3 border border-white/5 col-span-2 sm:col-span-1">
1307                            <p class="text-[10px] sm:text-[11px] text-slate-400 uppercase font-semibold mb-0.5">Subdominant (4th)</p>
1308                            <div class="flex items-center gap-2">
1309                                <i class="fa-solid fa-layer-group text-emerald-400 text-xs"></i>
1310                                <span id="subdominant-key-display" class="text-sm sm:text-base font-bold text-emerald-300 font-outfit">--</span>
1311                            </div>
1312                        </div>
1313                    </div>
1314                </div>
1315
1316                <!-- Right Column: Interactive SVG Camelot Wheel Visualizer -->
1317                <div class="lg:col-span-5 glass-card rounded-3xl p-5 sm:p-7 border border-white/10 flex flex-col items-center justify-between relative overflow-hidden">
1318                    <div class="absolute -left-20 -bottom-20 w-64 h-64 bg-purple-500/10 rounded-full blur-3xl pointer-events-none"></div>
1319
1320                    <div class="w-full text-center">
1321                        <div class="flex items-center justify-between mb-2">
1322                            <span class="text-[11px] font-bold tracking-widest text-[#c084fc] uppercase px-3 py-1 rounded-full bg-purple-500/10 border border-purple-500/20">
1323                                CAMELOT DJ WHEEL
1324                            </span>
1325                            <span class="text-[11px] text-slate-400">Click any wedge</span>
1326                        </div>
1327
1328                        <!-- SVG Camelot Wheel Container -->
1329                        <div class="w-full max-w-[280px] sm:max-w-[300px] aspect-square mx-auto my-2 relative flex items-center justify-center">
1330                            <svg id="camelot-wheel-svg" viewBox="0 0 400 400" class="w-full h-full filter drop-shadow-2xl">
1331                                <!-- Dynamic SVG Wedges drawn by JS -->
1332                            </svg>
1333                            <!-- Center Wheel Badge -->
1334                            <div class="absolute w-20 h-20 rounded-full bg-slate-950/90 border border-white/20 backdrop-blur-md flex flex-col items-center justify-center pointer-events-none shadow-inner">
1335                                <span id="center-camelot-code" class="font-outfit font-black text-lg text-white leading-none">--</span>
1336                                <span id="center-camelot-key" class="text-[10px] text-[#a78bfa] font-bold truncate max-w-[70px]">Select</span>
1337                            </div>
1338                        </div>
1339
1340                        <p class="text-[11px] text-slate-400 mt-1">Outer ring: <strong>Major (B)</strong> • Inner ring: <strong>Minor (A)</strong></p>
1341                    </div>
1342                </div>
1343            </div>
1344
1345            <!-- 12-Class Pitch Chroma Spectrum Bar Graph -->
1346            <div class="glass-card rounded-3xl p-5 sm:p-6 border border-white/10 mb-5">
1347                <div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 mb-4">
1348                    <div>
1349                        <span class="text-[11px] font-bold tracking-widest text-[#a78bfa] uppercase px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20 inline-block mb-1">
1350                            SPECTRAL ANALYSIS
1351                        </span>
1352                        <h3 class="font-outfit text-lg font-bold text-white">12-Pitch Chromagram Distribution</h3>
1353                    </div>
1354                    <span class="text-xs text-slate-400">Normalized harmonic energy across all 12 pitch classes</span>
1355                </div>
1356
1357                <!-- 12 Chroma Bars -->
1358                <div class="grid grid-cols-12 gap-1.5 sm:gap-2 h-28 items-end pt-4 pb-2 border-b border-white/10" id="chroma-bars-grid">
1359                    <!-- Populated dynamically: C, C#, D, D#, E, F, F#, G, G#, A, A#, B -->
1360                </div>
1361            </div>
1362
1363            <!-- Harmonic DJ Mix Compatibility Matrix -->
1364            <div class="glass-card rounded-3xl p-5 sm:p-6 border border-white/10 mb-5">
1365                <div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 mb-4">
1366                    <div>
1367                        <span class="text-[11px] font-bold tracking-widest text-[#c084fc] uppercase px-3 py-1 rounded-full bg-purple-500/10 border border-purple-500/20 inline-block mb-1">
1368                            HARMONIC MIXING GUIDE
1369                        </span>
1370                        <h3 class="font-outfit text-lg font-bold text-white">DJ Compatibility Matrix for This Track</h3>
1371                    </div>
1372                    <span class="text-xs text-slate-400">Best transitions from <strong id="matrix-current-key" class="text-indigo-300">--</strong></span>
1373                </div>
1374
1375                <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
1376                    <div class="bg-white/5 rounded-2xl p-3.5 border border-emerald-500/30 flex items-start gap-3">
1377                        <div class="h-10 w-10 rounded-xl bg-emerald-500/20 text-emerald-400 flex items-center justify-center font-bold text-sm flex-shrink-0">
1378                            <i class="fa-solid fa-check-double"></i>
1379                        </div>
1380                        <div>
1381                            <span class="text-[10px] text-slate-400 uppercase font-semibold">Exact Match (Same Key)</span>
1382                            <h4 id="match-exact" class="text-sm font-bold text-white font-outfit">--</h4>
1383                            <p class="text-[11px] text-slate-400 mt-0.5">Same-key relationship for harmonic mixing.</p>
1384                        </div>
1385                    </div>
1386
1387                    <div class="bg-white/5 rounded-2xl p-3.5 border border-blue-500/30 flex items-start gap-3">
1388                        <div class="h-10 w-10 rounded-xl bg-blue-500/20 text-blue-400 flex items-center justify-center font-bold text-sm flex-shrink-0">
1389                            <i class="fa-solid fa-shuffle"></i>
1390                        </div>
1391                        <div>
1392                            <span class="text-[10px] text-slate-400 uppercase font-semibold">Relative Major / Minor</span>
1393                            <h4 id="match-relative" class="text-sm font-bold text-white font-outfit">--</h4>
1394                            <p class="text-[11px] text-slate-400 mt-0.5">Relative major/minor relationship using the same pitch collection.</p>
1395                        </div>
1396                    </div>
1397
1398                    <div class="bg-white/5 rounded-2xl p-3.5 border border-pink-500/30 flex items-start gap-3">
1399                        <div class="h-10 w-10 rounded-xl bg-pink-500/20 text-pink-400 flex items-center justify-center font-bold text-sm flex-shrink-0">
1400                            <i class="fa-solid fa-arrow-trend-up"></i>
1401                        </div>
1402                        <div>
1403                            <span class="text-[10px] text-slate-400 uppercase font-semibold">Energy Boost (+1 Hour)</span>
1404                            <h4 id="match-boost-1" class="text-sm font-bold text-white font-outfit">--</h4>
1405                            <p class="text-[11px] text-slate-400 mt-0.5">A neighboring-key relationship for a more noticeable transition.</p>
1406                        </div>
1407                    </div>
1408
1409                    <div class="bg-white/5 rounded-2xl p-3.5 border border-amber-500/30 flex items-start gap-3">
1410                        <div class="h-10 w-10 rounded-xl bg-amber-500/20 text-amber-400 flex items-center justify-center font-bold text-sm flex-shrink-0">
1411                            <i class="fa-solid fa-arrow-trend-down"></i>
1412                        </div>
1413                        <div>
1414                            <span class="text-[10px] text-slate-400 uppercase font-semibold">Energy Drop (-1 Hour)</span>
1415                            <h4 id="match-drop-1" class="text-sm font-bold text-white font-outfit">--</h4>
1416                            <p class="text-[11px] text-slate-400 mt-0.5">A neighboring-key relationship for a lower-energy transition.</p>
1417                        </div>
1418                    </div>
1419                </div>
1420            </div>
1421
1422            <!-- Audio Scrubber & Controller -->
1423            <div class="glass-card rounded-3xl p-4 sm:p-5 border border-white/10 mb-5">
1424                <div class="flex flex-col sm:flex-row items-center gap-4">
1425                    <button id="btn-play-pause" class="h-12 w-12 rounded-2xl bg-gradient-to-r from-[#a78bfa] to-[#c084fc] flex items-center justify-center text-white text-lg shadow-lg hover:opacity-95 active:scale-95 transition-all flex-shrink-0 cursor-pointer">
1426                        <i class="fa-solid fa-play" id="play-icon"></i>
1427                    </button>
1428
1429                    <div class="flex-1 w-full">
1430                        <div class="flex items-center justify-between text-xs font-semibold text-slate-300 font-outfit mb-1.5">
1431                            <span id="player-current-time">0:00</span>
1432                            <span id="player-duration">0:00</span>
1433                        </div>
1434                        <div class="relative w-full h-3 bg-white/5 rounded-full overflow-hidden border border-white/10 cursor-pointer" id="seek-bar-container">
1435                            <div id="seek-progress" class="h-full bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 transition-all" style="width: 0%"></div>
1436                        </div>
1437                    </div>
1438                </div>
1439            </div>
1440
1441            <!-- Cross-Tool Interoperability Switcher -->
1442            <div class="glass-card rounded-3xl p-5 sm:p-6 border border-white/10 text-center">
1443                <h3 class="font-outfit text-base sm:text-lg font-bold text-white mb-1">Next Steps with this Track</h3>
1444                <p class="text-xs text-slate-400 mb-4">Send this track directly to other tools in the suite:</p>
1445                
1446                <div class="flex flex-wrap items-center justify-center gap-2.5">
1447                    <a id="link-to-vocal-remover" href="/" class="px-3.5 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-200 hover:text-white transition-all flex items-center gap-2">
1448                        <i class="fa-solid fa-microphone-slash text-purple-400"></i> Remove Vocals
1449                    </a>
1450                    <a id="link-to-bpm" href="/bpm-finder/" class="px-3.5 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-200 hover:text-white transition-all flex items-center gap-2">
1451                        <i class="fa-solid fa-drum text-indigo-400"></i> Detect BPM
1452                    </a>
1453                    <a id="link-to-equalizer" href="/equalizer/" class="px-3.5 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-200 hover:text-white transition-all flex items-center gap-2">
1454                        <i class="fa-solid fa-sliders text-indigo-400"></i> Equalizer
1455                    </a>
1456                    <a id="link-to-bass" href="/bass-booster/" class="px-3.5 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-200 hover:text-white transition-all flex items-center gap-2">
1457                        <i class="fa-solid fa-bolt text-indigo-400"></i> Boost Bass
1458                    </a>
1459                    <a id="link-to-cutter" href="/audio-cutter/" class="px-3.5 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-200 hover:text-white transition-all flex items-center gap-2">
1460                        <i class="fa-solid fa-scissors text-indigo-400"></i> Trim / Cut Audio
1461                    </a>
1462                </div>
1463            </div>
1464        </section>
1465    </div>
1466
1467    <!-- ================= PANEL 2: CAMELOT WHEEL & PIANO EXPLORER ================= -->
1468    <div id="panel-wheel" class="mode-panel hidden w-full max-w-4xl mx-auto">
1469        <div class="glass-card rounded-3xl p-5 sm:p-7 border border-white/10 text-center relative overflow-hidden shadow-2xl">
1470            <div class="absolute -right-20 -top-20 w-72 h-72 bg-purple-500/10 rounded-full blur-3xl pointer-events-none"></div>
1471
1472            <!-- Header: Compact Title & Mode Badge -->
1473            <div class="flex flex-col items-center mb-3">
1474                <span class="text-[11px] font-bold tracking-widest text-[#c084fc] uppercase px-3 py-1 rounded-full bg-purple-500/10 border border-purple-500/20 inline-flex items-center gap-1.5 mb-2">
1475                    <span class="w-1.5 h-1.5 rounded-full bg-purple-400"></span> INTERACTIVE HARMONIC EXPLORER
1476                </span>
1477                <h2 class="font-outfit text-2xl sm:text-3xl font-extrabold text-white">
1478                    Camelot Wheel & Virtual Piano Explorer
1479                </h2>
1480                <p class="text-slate-400 text-xs sm:text-sm max-w-xl mx-auto mt-1 mb-3">
1481                    Click any key on the Camelot Wheel to inspect its DJ transition rules, or play the virtual piano to hear chord notes.
1482                </p>
1483            </div>
1484
1485            <!-- Two-Column Layout: Standalone Wheel + Active Key Details -->
1486            <div class="grid grid-cols-1 md:grid-cols-12 gap-5 items-center mb-6">
1487                
1488                <!-- Left: Interactive Wheel (Clickable) -->
1489                <div class="md:col-span-6 flex flex-col items-center">
1490                    <div class="w-full max-w-[280px] aspect-square relative flex items-center justify-center">
1491                        <svg id="explorer-wheel-svg" viewBox="0 0 400 400" class="w-full h-full filter drop-shadow-2xl">
1492                            <!-- Populated dynamically -->
1493                        </svg>
1494                        <div class="absolute w-20 h-20 rounded-full bg-slate-950/90 border border-white/20 backdrop-blur-md flex flex-col items-center justify-center pointer-events-none shadow-inner">
1495                            <span id="explorer-center-code" class="font-outfit font-black text-lg text-white leading-none">8A</span>
1496                            <span id="explorer-center-key" class="text-[10px] text-[#a78bfa] font-bold truncate max-w-[70px]">A Minor</span>
1497                        </div>
1498                    </div>
1499                </div>
1500
1501                <!-- Right: Selected Key Rules & Transitions -->
1502                <div class="md:col-span-6 text-left space-y-3">
1503                    <div class="bg-white/5 rounded-2xl p-4 border border-white/5">
1504                        <span class="text-[10px] text-slate-400 uppercase font-semibold">Selected Musical Key</span>
1505                        <div class="flex items-baseline gap-3 my-1">
1506                            <span id="explorer-key-name" class="font-outfit text-2xl sm:text-3xl font-extrabold text-white">A Minor</span>
1507                            <span id="explorer-camelot-badge" class="px-2.5 py-0.5 rounded-xl bg-indigo-500/20 text-indigo-300 font-mono font-bold text-sm">8A</span>
1508                        </div>
1509                        <p id="explorer-mood-desc" class="text-xs text-slate-400">Dark, melancholy, deeply expressive emotional character.</p>
1510                    </div>
1511
1512                    <div class="grid grid-cols-2 gap-2 text-xs">
1513                        <div class="bg-white/5 p-2.5 rounded-xl border border-white/5">
1514                            <span class="text-[10px] text-slate-400 block">Relative Key (Mood Swap)</span>
1515                            <strong id="explorer-relative-key" class="text-indigo-300 text-sm font-bold font-outfit">C Major (8B)</strong>
1516                        </div>
1517                        <div class="bg-white/5 p-2.5 rounded-xl border border-white/5">
1518                            <span class="text-[10px] text-slate-400 block">Energy Boost (+1 Hour)</span>
1519                            <strong id="explorer-boost-key" class="text-pink-300 text-sm font-bold font-outfit">E Minor (9A)</strong>
1520                        </div>
1521                    </div>
1522                </div>
1523            </div>
1524
1525            <!-- Virtual Piano Keyboard (Playable) -->
1526            <div class="w-full max-w-2xl mx-auto bg-slate-950/80 rounded-2xl p-4 border border-white/10 mb-2 text-center">
1527                <div class="flex items-center justify-between text-xs text-slate-400 mb-2">
1528                    <span class="font-semibold text-slate-300"><i class="fa-solid fa-keyboard mr-1.5 text-indigo-400"></i> Interactive Piano Keyboard</span>
1529                    <span>Click keys to play tone • Highlights active scale notes</span>
1530                </div>
1531                
1532                <!-- Piano Keys Container -->
1533                <div class="relative h-28 flex justify-center mx-auto select-none overflow-x-auto pb-1" id="piano-keyboard">
1534                    <!-- Dynamic Piano Keys: 24 white & black keys -->
1535                </div>
1536            </div>
1537        </div>
1538    </div>
1539
1540    <!-- ================= PANEL 3: REAL-TIME MICROPHONE PITCH DETECTOR ================= -->
1541    <div id="panel-mic" class="mode-panel hidden w-full max-w-3xl mx-auto">
1542        <div class="glass-card rounded-3xl p-5 sm:p-7 border border-white/10 text-center relative overflow-hidden shadow-2xl">
1543            <div class="absolute -left-20 -top-20 w-72 h-72 bg-emerald-500/10 rounded-full blur-3xl pointer-events-none"></div>
1544
1545            <!-- Header: Compact Title & Mode Badge -->
1546            <div class="flex flex-col items-center mb-3">
1547                <span class="text-[11px] font-bold tracking-widest text-emerald-400 uppercase px-3 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/20 inline-flex items-center gap-1.5 mb-2">
1548                    <span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-ping"></span> REAL-TIME ACOUSTIC PITCH LISTENER
1549                </span>
1550                <h2 class="font-outfit text-2xl sm:text-3xl font-extrabold text-white">
1551                    Live Microphone Pitch & Key Detector
1552                </h2>
1553                <p class="text-slate-400 text-xs sm:text-sm max-w-lg mx-auto mt-1 mb-3">
1554                    Sing, hum, or play into your microphone. Our autocorrelation engine detects real-time pitch frequencies and estimates the root key.
1555                </p>
1556            </div>
1557
1558            <!-- Compact Spectrum Waveform Visualizer -->
1559            <div class="w-full max-w-xl mx-auto bg-slate-950/80 rounded-2xl border border-white/10 overflow-hidden mb-3.5 relative">
1560                <canvas id="mic-canvas" width="600" height="80" class="w-full h-18 sm:h-20 block"></canvas>
1561                <div id="mic-idle-msg" class="absolute inset-0 flex items-center justify-center bg-slate-950/70 backdrop-blur-xs text-xs text-slate-400 gap-2">
1562                    <i class="fa-solid fa-microphone-slash text-slate-500"></i>
1563                    <span>Microphone paused. Click <strong>Start Listening</strong> below.</span>
1564                </div>
1565            </div>
1566
1567            <!-- Compact 3-Column Stats Grid -->
1568            <div class="grid grid-cols-3 gap-2.5 sm:gap-3.5 max-w-xl mx-auto mb-4">
1569                
1570                <!-- Stat 1: Detected Live Pitch Note -->
1571                <div class="bg-white/5 rounded-2xl p-3 border border-white/5 flex flex-col items-center justify-center relative overflow-hidden">
1572                    <span class="text-[10px] sm:text-[11px] text-slate-400 uppercase font-semibold mb-0.5">Live Pitch</span>
1573                    <div class="flex items-baseline gap-1 my-auto">
1574                        <span id="mic-pitch-note" class="font-outfit text-3xl sm:text-4xl font-black text-emerald-400 drop-shadow-sm">--</span>
1575                    </div>
1576                    <span id="mic-pitch-freq" class="text-[9px] sm:text-[10px] text-slate-400 truncate w-full">-- Hz</span>
1577                </div>
1578
1579                <!-- Stat 2: Estimated Key -->
1580                <div class="bg-white/5 rounded-2xl p-3 border border-white/5 flex flex-col items-center justify-center">
1581                    <span class="text-[10px] sm:text-[11px] text-slate-400 uppercase font-semibold mb-0.5">Estimated Key</span>
1582                    <div class="font-outfit text-xl sm:text-2xl font-black text-white my-auto truncate max-w-full">
1583                        <span id="mic-estimated-key">--</span>
1584                    </div>
1585                    <span id="mic-estimated-camelot" class="text-[9px] sm:text-[10px] text-indigo-300 font-mono font-bold">Camelot: --</span>
1586                </div>
1587
1588                <!-- Stat 3: Total Analyzed Duration -->
1589                <div class="bg-white/5 rounded-2xl p-3 border border-white/5 flex flex-col items-center justify-center">
1590                    <div class="text-[10px] sm:text-[11px] text-slate-400 uppercase font-semibold mb-0.5 flex items-center gap-1">
1591                        <i class="fa-regular fa-clock text-indigo-400"></i> Duration
1592                    </div>
1593                    <div class="font-mono text-2xl sm:text-3xl font-bold text-white my-auto">
1594                        <span id="mic-duration-display">00:00</span>
1595                    </div>
1596                    <span id="mic-status-badge" class="text-[9px] sm:text-[10px] text-slate-400 truncate w-full">Listening...</span>
1597                </div>
1598            </div>
1599
1600            <!-- Action Controls: Start/Stop Button + Reset Button (No Scroll Required) -->
1601            <div class="flex items-center justify-center gap-2.5 max-w-sm mx-auto">
1602                <button id="btn-toggle-mic" class="flex-1 py-3 px-6 rounded-2xl bg-gradient-to-r from-emerald-500 to-teal-600 hover:from-emerald-400 hover:to-teal-500 text-white font-bold text-sm sm:text-base shadow-lg shadow-emerald-500/25 active:scale-95 transition-all flex items-center justify-center gap-2.5 cursor-pointer">
1603                    <i class="fa-solid fa-microphone text-base" id="mic-btn-icon"></i>
1604                    <span id="mic-btn-label">Start Listening</span>
1605                </button>
1606                <button id="btn-reset-mic" title="Reset stats" class="px-4 py-3 rounded-2xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-300 hover:text-white transition-all active:scale-95 cursor-pointer flex items-center gap-1.5">
1607                    <i class="fa-solid fa-rotate-left"></i> <span class="hidden sm:inline">Reset</span>
1608                </button>
1609            </div>
1610
1611        </div>
1612    </div>
1613
1614</main>
1615
1616<!-- ================= SEO CONTENT & EDUCATIONAL SECTION ================= -->
1617<section class="max-w-5xl mx-auto px-4 py-12 text-slate-300 relative z-10">
1618    <div class="glass-card rounded-3xl p-8 sm:p-12 border border-white/10 space-y-12">
1619        <!-- Guide Intro: How to Find the Key of a Song -->
1620        <div>
1621            <span class="text-xs font-bold tracking-widest text-[#a78bfa] uppercase px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20 inline-block mb-4">
1622                THE HARMONIC MIXING GUIDE
1623            </span>
1624            <h2 class="font-outfit text-3xl sm:text-4xl font-extrabold text-white tracking-tight leading-tight mb-4">
1625                How to Find the Key of a Song
1626            </h2>
1627            <p class="text-slate-400 text-sm sm:text-base leading-relaxed mb-4">
1628                VocalRemovers.ai provides a fast, browser-based harmonic analyzer to detect the musical key and Camelot code of any audio track. If you are working with a dense full-band arrangement, you can first use our <a href="/" class="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">vocal remover</a> to isolate a clean acapella or melodic stem for even easier key identification.
1629            </p>
1630            <div class="grid grid-cols-1 md:grid-cols-3 gap-4 my-6 text-sm text-slate-300 font-medium">
1631                <div class="p-5 rounded-2xl bg-white/5 border border-white/5">
1632                    <strong class="block text-white mb-1.5"><i class="fa-solid fa-cloud-arrow-up text-indigo-400 mr-2"></i> 1. Upload your audio file</strong>
1633                    <p class="text-xs text-slate-400 leading-relaxed mb-2">
1634                        Select an MP3, WAV, FLAC, M4A or another supported format.
1635                    </p>
1636                    <p class="text-xs text-slate-400 leading-relaxed">
1637                        <strong class="text-slate-300">Let the analyzer process the track:</strong> The audio is analyzed in your browser to estimate its musical key and scale.
1638                    </p>
1639                </div>
1640                <div class="p-5 rounded-2xl bg-white/5 border border-white/5">
1641                    <strong class="block text-white mb-1.5"><i class="fa-solid fa-compact-disc text-purple-400 mr-2"></i> 2. View the detected key</strong>
1642                    <p class="text-xs text-slate-400 leading-relaxed">
1643                        The result shows the key, major/minor scale, Camelot code and related key information.
1644                    </p>
1645                </div>
1646                <div class="p-5 rounded-2xl bg-white/5 border border-white/5">
1647                    <strong class="block text-white mb-1.5"><i class="fa-solid fa-diagram-project text-emerald-400 mr-2"></i> 3. Explore compatible keys</strong>
1648                    <p class="text-xs text-slate-400 leading-relaxed">
1649                        Use the Camelot Wheel to see neighboring and relative key relationships.
1650                    </p>
1651                </div>
1652            </div>
1653            <p class="text-slate-400 text-xs sm:text-sm leading-relaxed mt-4">
1654                <i class="fa-solid fa-microphone-lines text-indigo-400 mr-1.5"></i> You can also use the Real-Time Mic mode to detect pitch from singing, humming or an acoustic instrument.
1655            </p>
1656        </div>
1657
1658        <!-- Musical Key & Harmonic Mixing Overview -->
1659        <div>
1660            <h3 class="font-outfit text-2xl font-bold text-white mb-3">
1661                What is Musical Key and How Does Camelot Harmonic Mixing Work?
1662            </h3>
1663            <p class="text-slate-400 text-sm sm:text-base leading-relaxed mb-4">
1664                In music theory, the <strong>musical key</strong> of a song defines its tonal center—the root note that feels like home and the specific scale of notes that accompany it. Songs composed in a <strong>Major key</strong> generally evoke joyful, uplifting, and anthemic emotions, while tracks in a <strong>Minor key</strong> feel introspective, moody, emotional, or dark.
1665            </p>
1666            <p class="text-slate-400 text-sm sm:text-base leading-relaxed mb-4">
1667                For DJs and music producers, knowing a track's musical key can help with harmonic mixing. When two tracks contain compatible tonal material, transitions can sound more musically consistent. The Camelot Wheel provides a simple way to represent these relationships and explore possible key combinations.
1668            </p>
1669        </div>
1670
1671        <!-- The Camelot Wheel System Explained -->
1672        <div>
1673            <h3 class="font-outfit text-2xl font-bold text-white mb-3">The Camelot Wheel System: Clock-Face Harmonic Mixing</h3>
1674            <p class="text-slate-400 text-sm sm:text-base leading-relaxed mb-4">
1675                Invented by Mark Davis and popularized by <a href="https://mixedinkey.com/harmonic-mixing-guide/" target="_blank" rel="noopener noreferrer" class="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">Mixed In Key</a>, the <strong>Camelot Wheel</strong> translates the complex Circle of Fifths into a visual 12-hour clock face:
1676            </p>
1677            <div class="grid grid-cols-1 md:grid-cols-3 gap-4 my-6 text-sm text-slate-300 font-medium">
1678                <div class="p-5 rounded-2xl bg-white/5 border border-white/5">
1679                    <strong class="block text-white mb-1.5"><i class="fa-solid fa-clock text-indigo-400 mr-2"></i> Numbers (1 to 12)</strong>
1680                    <p class="text-xs text-slate-400 leading-relaxed">
1681                        Represent the 12 pitch hours around the wheel. Moving to an adjacent number on the same A/B ring represents a neighboring key relationship based on the Circle of Fifths.
1682                    </p><p class="text-xs text-slate-400 leading-relaxed">For example, 8A → 9A and 8A → 7A are common adjacent-key relationships used when planning harmonic transitions.</p>
1683                </div>
1684                <div class="p-5 rounded-2xl bg-white/5 border border-white/5">
1685                    <strong class="block text-white mb-1.5"><i class="fa-solid fa-moon text-purple-400 mr-2"></i> Letter A (Minor Keys)</strong>
1686                    <p class="text-xs text-slate-400 leading-relaxed">
1687                        The inner ring comprises all 12 natural minor scales (e.g. 8A is A Minor, 5A is C Minor, 11A is F# Minor).
1688                    </p>
1689                </div>
1690                <div class="p-5 rounded-2xl bg-white/5 border border-white/5">
1691                    <strong class="block text-white mb-1.5"><i class="fa-solid fa-sun text-emerald-400 mr-2"></i> Letter B (Major Keys)</strong>
1692                    <p class="text-xs text-slate-400 leading-relaxed">
1693                        The outer ring comprises all 12 major scales (e.g. 8B is C Major, 5B is Eb Major, 11B is A Major).
1694                    </p>
1695                </div>
1696            </div>
1697        </div>
1698
1699        <!-- Key Finder Features Section -->
1700        <div>
1701            <h2 class="font-outfit text-3xl sm:text-4xl font-extrabold text-white tracking-tight leading-tight mb-4">
1702                Key Finder &amp; Harmonic DJ Features
1703            </h2>
1704            <p class="text-slate-400 text-sm sm:text-base leading-relaxed mb-6">
1705                Discover professional tools designed for DJs, producers, and musicians to detect musical keys, understand harmonic relationships, and plan harmonic transitions.
1706            </p>
1707
1708            <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-sm text-slate-300">
1709                <div class="p-5 rounded-2xl bg-white/[0.03] border border-white/5 hover:border-purple-500/20 transition-all">
1710                    <div class="flex items-center gap-3 mb-2">
1711                        <div class="h-8 w-8 rounded-lg bg-purple-500/10 border border-purple-500/20 flex items-center justify-center text-purple-400">
1712                            <i class="fa-solid fa-compact-disc text-sm"></i>
1713                        </div>
1714                        <strong class="text-white text-sm">Chromagram Key &amp; Scale Detection</strong>
1715                    </div>
1716                    <p class="text-xs text-slate-400 leading-relaxed">
1717                        Extracts 12-pitch chroma information from the audio to estimate the root note, musical key, and major/minor scale.
1718                    </p>
1719                </div>
1720
1721                <div class="p-5 rounded-2xl bg-white/[0.03] border border-white/5 hover:border-indigo-500/20 transition-all">
1722                    <div class="flex items-center gap-3 mb-2">
1723                        <div class="h-8 w-8 rounded-lg bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center text-indigo-400">
1724                            <i class="fa-solid fa-circle-nodes text-sm"></i>
1725                        </div>
1726                        <strong class="text-white text-sm">Camelot Wheel DJ Codes</strong>
1727                    </div>
1728                    <p class="text-xs text-slate-400 leading-relaxed">
1729                        Translates standard musical keys into 1A–12B alphanumeric Camelot notation, making it simple to find harmonically compatible tracks for live DJ sets.
1730                    </p>
1731                </div>
1732
1733                <div class="p-5 rounded-2xl bg-white/[0.03] border border-white/5 hover:border-emerald-500/20 transition-all">
1734                    <div class="flex items-center gap-3 mb-2">
1735                        <div class="h-8 w-8 rounded-lg bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400">
1736                            <i class="fa-solid fa-microphone-lines text-sm"></i>
1737                        </div>
1738                        <strong class="text-white text-sm">Real-Time Mic Pitch Tracker</strong>
1739                    </div>
1740                    <p class="text-xs text-slate-400 leading-relaxed">
1741                        Detect pitch live from your microphone by humming, singing, or playing an acoustic instrument with real-time frequency (Hz) and note estimation.
1742                    </p>
1743                </div>
1744
1745                <div class="p-5 rounded-2xl bg-white/[0.03] border border-white/5 hover:border-amber-500/20 transition-all">
1746                    <div class="flex items-center gap-3 mb-2">
1747                        <div class="h-8 w-8 rounded-lg bg-amber-500/10 border border-amber-500/20 flex items-center justify-center text-amber-400">
1748                            <i class="fa-solid fa-keyboard text-sm"></i>
1749                        </div>
1750                        <strong class="text-white text-sm">
1750Interactive Camelot Wheel &amp; Piano</strong>
1751                    </div>
1752                    <p class="text-xs text-slate-400 leading-relaxed">
1753                        Explore key compatibility on an interactive 12-hour SVG wheel and virtual piano keyboard that visualizes scale notes and transition matches.
1754                    </p>
1755                </div>
1756
1757                <div class="p-5 rounded-2xl bg-white/[0.03] border border-white/5 hover:border-blue-500/20 transition-all">
1758                    <div class="flex items-center gap-3 mb-2">
1759                        <div class="h-8 w-8 rounded-lg bg-blue-500/10 border border-blue-500/20 flex items-center justify-center text-blue-400">
1760                            <i class="fa-solid fa-volume-high text-sm"></i>
1761                        </div>
1762                        <strong class="text-white text-sm">Reference Root Tone Generator</strong>
1763                    </div>
1764                    <p class="text-xs text-slate-400 leading-relaxed">
1765                        Play a pure synthesized sine reference tone matching the detected root pitch to audit the key by ear or tune instruments before performing.
1766                    </p>
1767                </div>
1768
1769                <div class="p-5 rounded-2xl bg-white/[0.03] border border-white/5 hover:border-teal-500/20 transition-all">
1770                    <div class="flex items-center gap-3 mb-2">
1771                        <div class="h-8 w-8 rounded-lg bg-teal-500/10 border border-teal-500/20 flex items-center justify-center text-teal-400">
1772                            <i class="fa-solid fa-shield-halved text-sm"></i>
1773                        </div>
1774                        <strong class="text-white text-sm">100% In-Browser Privacy</strong>
1775                    </div>
1776                    <p class="text-xs text-slate-400 leading-relaxed">
1777                        All DSP chromagram extraction and audio decoding occur completely within your browser via the Web Audio API—no files are ever uploaded.
1778                    </p>
1779                </div>
1780            </div>
1781        </div>
1782
1783        <!-- 5 Essential DJ Transition Techniques -->
1784        <div>
1785            <h3 class="font-outfit text-2xl font-bold text-white mb-3">5 Essential DJ Harmonic Transition Techniques</h3>
1786            <p class="text-slate-400 text-sm mb-6">These Camelot relationships can help DJs plan harmonic transitions between tracks:</p>
1787
1788            <div class="space-y-3">
1789                <div class="bg-white/5 rounded-2xl p-4 border border-white/5 flex items-start gap-4">
1790                    <span class="px-3 py-1 rounded-xl bg-emerald-500/20 text-emerald-300 font-mono font-bold text-xs flex-shrink-0">Same Key (0)</span>
1791                    <div>
1792                        <strong class="text-white text-sm block">1. The Perfect Blend (e.g., 8A &rarr; 8A)</strong>
1793                        <p class="text-xs text-slate-400 mt-1">Both tracks are in the same key, making this a straightforward starting point for harmonic mixing. The actual result still depends on the arrangement, melodies, basslines and other musical elements.</p>
1794                    </div>
1795                </div>
1796
1797                <div class="bg-white/5 rounded-2xl p-4 border border-white/5 flex items-start gap-4">
1798                    <span class="px-3 py-1 rounded-xl bg-blue-500/20 text-blue-300 font-mono font-bold text-xs flex-shrink-0">Swap (A &harr; B)</span>
1799                    <div>
1800                        <strong class="text-white text-sm block">2. Relative Major / Minor Shift (e.g., 8A &rarr; 8B)</strong>
1801                        <p class="text-xs text-slate-400 mt-1">Relative major and minor keys contain the same pitch classes but have different tonal centers, allowing transitions between contrasting major/minor tonalities.</p>
1802                    </div>
1803                </div>
1804
1805                <div class="bg-white/5 rounded-2xl p-4 border border-white/5 flex items-start gap-4">
1806                    <span class="px-3 py-1 rounded-xl bg-pink-500/20 text-pink-300 font-mono font-bold text-xs flex-shrink-0">Boost (+1)</span>
1807                    <div>
1808                        <strong class="text-white text-sm block">3. Dominant Energy Lift (e.g., 8A &rarr; 9A)</strong>
1809                        <p class="text-xs text-slate-400 mt-1">Moving one step around the Camelot wheel is a commonly used adjacent-key transition. Whether it works well depends on the tracks and transition technique.</p>
1810                    </div>
1811                </div>
1812
1813                <div class="bg-white/5 rounded-2xl p-4 border border-white/5 flex items-start gap-4">
1814                    <span class="px-3 py-1 rounded-xl bg-amber-500/20 text-amber-300 font-mono font-bold text-xs flex-shrink-0">Drop (-1)</span>
1815                    <div>
1816                        <strong class="text-white text-sm block">4. Subdominant Warmth Drop (e.g., 8A &rarr; 7A)</strong>
1817                        <p class="text-xs text-slate-400 mt-1">Moving one hour counter-clockwise produces a mellow, warm, and relaxing resolution—ideal for easing transition into deep grooves.</p>
1818                    </div>
1819                </div>
1820
1821                <div class="bg-white/5 rounded-2xl p-4 border border-white/5 flex items-start gap-4">
1822                    <span class="px-3 py-1 rounded-xl bg-purple-500/20 text-purple-300 font-mono font-bold text-xs flex-shrink-0">Super (+2)</span>
1823                    <div>
1824                        <strong class="text-white text-sm block">5. High-Impact Energy Surge (e.g., 8A &rarr; 10A)</strong>
1825                        <p class="text-xs text-slate-400 mt-1">Moving two positions on the same Camelot ring creates a larger tonal change. DJs may use this relationship for a more noticeable transition, but it is less directly related than staying on the same key or moving to an adjacent key.</p>
1826                    </div>
1827                </div>
1828            </div>
1829        </div>
1830
1831        <!-- Complete 24 Key Circle of Fifths Reference Table -->
1832        <div>
1833            <h3 class="font-outfit text-2xl font-bold text-white mb-3">Complete 24-Key Circle of Fifths & Camelot Cheat Sheet</h3>
1834            <p class="text-slate-400 text-sm mb-6">Reference this complete lookup table based on the classical <a href="https://en.wikipedia.org/wiki/Circle_of_fifths" target="_blank" rel="noopener noreferrer" class="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">Circle of Fifths</a> for all 24 major and minor keys, standard key signatures, and Camelot codes:</p>
1835
1836            <div class="overflow-x-auto">
1837                <table class="min-w-full text-sm text-left border border-white/10 rounded-2xl overflow-hidden">
1838                    <thead class="bg-white/5 text-slate-400 uppercase text-xs font-bold border-b border-white/10">
1839                        <tr>
1840                            <th class="px-5 py-3.5">Camelot Code</th>
1841                            <th class="px-5 py-3.5">Key Name</th>
1842                            <th class="px-5 py-3.5">Scale Type</th>
1843                            <th class="px-5 py-3.5">Relative Key</th>
1844                            <th class="px-5 py-3.5">Key Signature</th>
1845                        </tr>
1846                    </thead>
1847                    <tbody class="divide-y divide-white/5 text-slate-300">
1848                        <tr class="hover:bg-white/[0.02]">
1849                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">8B</td>
1850                            <td class="px-5 py-2.5 font-semibold text-white">C Major</td>
1851                            <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td>
1852                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">A Minor (8A)</td>
1853                            <td class="px-5 py-2.5 text-slate-400 text-xs">0 Sharps / Flats</td>
1854                        </tr>
1855                        <tr class="hover:bg-white/[0.02]">
1856                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">8A</td>
1857                            <td class="px-5 py-2.5 font-semibold text-white">A Minor</td>
1858                            <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td>
1859                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">C Major (8B)</td>
1860                            <td class="px-5 py-2.5 text-slate-400 text-xs">0 Sharps / Flats</td>
1861                        </tr>
1862                        <tr class="hover:bg-white/[0.02]">
1863                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">9B</td>
1864                            <td class="px-5 py-2.5 font-semibold text-white">G Major</td>
1865                            <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td>
1866                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">E Minor (9A)</td>
1867                            <td class="px-5 py-2.5 text-slate-400 text-xs">1 Sharp (F#)</td>
1868                        </tr>
1869                        <tr class="hover:bg-white/[0.02]">
1870                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">9A</td>
1871                            <td class="px-5 py-2.5 font-semibold text-white">E Minor</td>
1872                            <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td>
1873                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">G Major (9B)</td>
1874                            <td class="px-5 py-2.5 text-slate-400 text-xs">1 Sharp (F#)</td>
1875                        </tr>
1876                        <tr class="hover:bg-white/[0.02]">
1877                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">10B</td>
1878                            <td class="px-5 py-2.5 font-semibold text-white">D Major</td>
1879                            <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td>
1880                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">B Minor (10A)</td>
1881                            <td class="px-5 py-2.5 text-slate-400 text-xs">2 Sharps (F#, C#)</td>
1882                        </tr>
1883                        <tr class="hover:bg-white/[0.02]">
1884                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">10A</td>
1885                            <td class="px-5 py-2.5 font-semibold text-white">B Minor</td>
1886                            <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td>
1887                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">D Major (10B)</td>
1888                            <td class="px-5 py-2.5 text-slate-400 text-xs">2 Sharps (F#, C#)</td>
1889                        </tr>
1890                        <tr class="hover:bg-white/[0.02]">
1891                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">11B</td>
1892                            <td class="px-5 py-2.5 font-semibold text-white">A Major</td>
1893                            <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td>
1894                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">F# Minor (11A)</td>
1895                            <td class="px-5 py-2.5 text-slate-400 text-xs">3 Sharps (F#, C#, G#)</td>
1896                        </tr>
1897                        <tr class="hover:bg-white/[0.02]">
1898                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">11A</td>
1899                            <td class="px-5 py-2.5 font-semibold text-white">F# Minor</td>
1900                            <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td>
1901                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">A Major (11B)</td>
1902                            <td class="px-5 py-2.5 text-slate-400 text-xs">3 Sharps (F#, C#, G#)</td>
1903                        </tr>
1904                        <tr class="hover:bg-white/[0.02]">
1905                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">12B</td>
1906                            <td class="px-5 py-2.5 font-semibold text-white">E Major</td>
1907                            <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td>
1908                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">C# Minor (12A)</td>
1909                            <td class="px-5 py-2.5 text-slate-400 text-xs">4 Sharps (F#, C#, G#, D#)</td>
1910                        </tr>
1911                        <tr class="hover:bg-white/[0.02]">
1912                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">12A</td>
1913                            <td class="px-5 py-2.5 font-semibold text-white">C# Minor</td>
1914                            <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td>
1915                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">E Major (12B)</td>
1916                            <td class="px-5 py-2.5 text-slate-400 text-xs">4 Sharps (F#, C#, G#, D#)</td>
1917                        </tr>
1918                        <tr class="hover:bg-white/[0.02]">
1919                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">1B</td>
1920                            <td class="px-5 py-2.5 font-semibold text-white">B Major</td>
1921                            <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td>
1922                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">G# Minor (1A)</td>
1923                            <td class="px-5 py-2.5 text-slate-400 text-xs">5 Sharps (B, E, A, D, G)</td>
1924                        </tr>
1925                        <tr class="hover:bg-white/[0.02]">
1926                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">1A</td>
1927                            <td class="px-5 py-2.5 font-semibold text-white">G# Minor</td>
1928                            <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td>
1929                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">B Major (1B)</td>
1930                            <td class="px-5 py-2.5 text-slate-400 text-xs">5 Sharps (B, E, A, D, G)</td>
1931                        </tr>
1932                        <tr class="hover:bg-white/[0.02]">
1933                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">2B</td>
1934                            <td class="px-5 py-2.5 font-semibold text-white">F# Major</td>
1935                            <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td>
1936                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">D# Minor (2A)</td>
1937                            <td class="px-5 py-2.5 text-slate-400 text-xs">6 Sharps (F#, C#, G#, D#, A#, E#)</td>
1938                        </tr>
1939                        <tr class="hover:bg-white/[0.02]">
1940                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">2A</td>
1941                            <td class="px-5 py-2.5 font-semibold text-white">D# Minor</td>
1942                            <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td>
1943                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">F# Major (2B)</td>
1944                            <td class="px-5 py-2.5 text-slate-400 text-xs">6 Sharps (F#, C#, G#, D#, A#, E#)</td>
1945                        </tr>
1946                        <tr class="hover:bg-white/[0.02]">
1947                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">3B</td>
1948                            <td class="px-5 py-2.5 font-semibold text-white">Db / C# Major</td>
1949                            <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td>
1950                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">Bb Minor (3A)</td>
1951                            <td class="px-5 py-2.5 text-slate-400 text-xs">5 Flats (Bb, Eb, Ab, Db, Gb)</td>
1952                        </tr>
1953                        <tr class="hover:bg-white/[0.02]">
1954                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">3A</td>
1955                            <td class="px-5 py-2.5 font-semibold text-white">Bb Minor</td>
1956                            <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td>
1957                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">Db Major (3B)</td>
1958                            <td class="px-5 py-2.5 text-slate-400 text-xs">5 Flats (Bb, Eb, Ab, Db, Gb)</td>
1959                        </tr>
1960                        <tr class="hover:bg-white/[0.02]">
1961                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">4B</td>
1962                            <td class="px-5 py-2.5 font-semibold text-white">Ab Major</td>
1963                            <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td>
1964                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">F Minor (4A)</td>
1965                            <td class="px-5 py-2.5 text-slate-400 text-xs">4 Flats (Bb, Eb, Ab, Db)</td>
1966                        </tr>
1967                        <tr class="hover:bg-white/[0.02]">
1968                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">4A</td>
1969                            <td class="px-5 py-2.5 font-semibold text-white">F Minor</td>
1970                            <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td>
1971                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">Ab Major (4B)</td>
1972                            <td class="px-5 py-2.5 text-slate-400 text-xs">4 Flats (Bb, Eb, Ab, Db)</td>
1973                        </tr>
1974                        <tr class="hover:bg-white/[0.02]">
1975                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">5B</td>
1976                            <td class="px-5 py-2.5 font-semibold text-white">Eb Major</td>
1977                            <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td>
1978                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">C Minor (5A)</td>
1979                            <td class="px-5 py-2.5 text-slate-400 text-xs">3 Flats (Bb, Eb, Ab)</td>
1980                        </tr>
1981                        <tr class="hover:bg-white/[0.02]">
1982                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">5A</td>
1983                            <td class="px-5 py-2.5 font-semibold text-white">C Minor</td>
1984                            <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td>
1985                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">Eb Major (5B)</td>
1986                            <td class="px-5 py-2.5 text-slate-400 text-xs">3 Flats (Bb, Eb, Ab)</td>
1987                        </tr>
1988                        <tr class="hover:bg-white/[0.02]">
1989                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">6B</td>
1990                            <td class="px-5 py-2.5 font-semibold text-white">Bb Major</td>
1991                            <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td>
1992                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">G Minor (6A)</td>
1993                            <td class="px-5 py-2.5 text-slate-400 text-xs">2 Flats (Bb, Eb)</td>
1994                        </tr>
1995                        <tr class="hover:bg-white/[0.02]">
1996                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">6A</td>
1997                            <td class="px-5 py-2.5 font-semibold text-white">G Minor</td>
1998                            <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td>
1999                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">Bb Major (6B)</td>
2000                            <td class="px-5 py-2.5 text-slate-400 text-xs">2 Flats (Bb, Eb)</td>
2001                        </tr>
2002                        <tr class="hover:bg-white/[0.02]">
2003                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">7B</td>
2004                            <td class="px-5 py-2.5 font-semibold text-white">F Major</td>
2005                            <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td>
2006                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">D Minor (7A)</td>
2007                            <td class="px-5 py-2.5 text-slate-400 text-xs">1 Flat (Bb)</td>
2008                        </tr>
2009                        <tr class="hover:bg-white/[0.02]">
2010                            <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">7A</td>
2011                            <td class="px-5 py-2.5 font-semibold text-white">D Minor</td>
2012                            <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td>
2013                            <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">F Major (7B)</td>
2014                            <td class="px-5 py-2.5 text-slate-400 text-xs">1 Flat (Bb)</td>
2015                        </tr>
2016                    </tbody>
2017                <
2017/table>
2018            </div>
2019        </div>
2020
2021                <!-- Related Studio Tools Component -->
2022        <div>
2023            
2024<div class="w-full">
2025        <div class="mb-6 text-left">
2026            <span class="text-xs font-bold tracking-widest text-[#a78bfa] uppercase px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20 inline-block mb-2">
2027                CONTINUE YOUR AUDIO WORKFLOW
2028            </span>
2029            <h3 class="font-outfit text-xl sm:text-2xl font-bold text-white tracking-tight">
2030                Related Studio Tools
2031            </h3>
2032        </div>
2033        <div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
2034                        <a href="/mp3-tag-editor/" class="p-3 rounded-2xl bg-white/[0.02] hover:bg-white/[0.06] border border-white/5 hover:border-indigo-500/30 flex flex-col items-center text-center gap-2 transition-all group">
2035                <div class="h-9 w-9 rounded-xl bg-amber-500/10 border border-amber-500/20 flex items-center justify-center text-amber-400 group-hover:scale-110 transition-transform">
2036                    <i class="fa-solid fa-tags text-sm"></i>
2037                </div>
2038                <span class="text-xs font-bold text-white group-hover:text-amber-300">MP3 Tag Editor</span>
2039            </a>
2040                        <a href="/pitch-changer/" class="p-3 rounded-2xl bg-white/[0.02] hover:bg-white/[0.06] border border-white/5 hover:border-indigo-500/30 flex flex-col items-center text-center gap-2 transition-all group">
2041                <div class="h-9 w-9 rounded-xl bg-cyan-500/10 border border-cyan-500/20 flex items-center justify-center text-cyan-400 group-hover:scale-110 transition-transform">
2042                    <i class="fa-solid fa-music text-sm"></i>
2043                </div>
2044                <span class="text-xs font-bold text-white group-hover:text-cyan-300">Pitch Changer</span>
2045            </a>
2046                        <a href="/voice-recorder/" class="p-3 rounded-2xl bg-white/[0.02] hover:bg-white/[0.06] border border-white/5 hover:border-indigo-500/30 flex flex-col items-center text-center gap-2 transition-all group">
2047                <div class="h-9 w-9 rounded-xl bg-rose-500/10 border border-rose-500/20 flex items-center justify-center text-rose-400 group-hover:scale-110 transition-transform">
2048                    <i class="fa-solid fa-microphone-lines text-sm"></i>
2049                </div>
2050                <span class="text-xs font-bold text-white group-hover:text-rose-300">Voice Recorder</span>
2051            </a>
2052                        <a href="/audio-joiner/" class="p-3 rounded-2xl bg-white/[0.02] hover:bg-white/[0.06] border border-white/5 hover:border-indigo-500/30 flex flex-col items-center text-center gap-2 transition-all group">
2053                <div class="h-9 w-9 rounded-xl bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400 group-hover:scale-110 transition-transform">
2054                    <i class="fa-solid fa-layer-group text-sm"></i>
2055                </div>
2056                <span class="text-xs font-bold text-white group-hover:text-emerald-300">Audio Joiner</span>
2057            </a>
2058                        <a href="/bass-booster/" class="p-3 rounded-2xl bg-white/[0.02] hover:bg-white/[0.06] border border-white/5 hover:border-indigo-500/30 flex flex-col items-center text-center gap-2 transition-all group">
2059                <div class="h-9 w-9 rounded-xl bg-cyan-500/10 border border-cyan-500/20 flex items-center justify-center text-cyan-400 group-hover:scale-110 transition-transform">
2060                    <i class="fa-solid fa-volume-high text-sm"></i>
2061                </div>
2062                <span class="text-xs font-bold text-white group-hover:text-cyan-300">Bass Booster</span>
2063            </a>
2064                    </div>
2065</div>
2066        </div>
2067
2068
2069        <!-- Collapsible FAQs Component Inclusion -->
2070        <div>
2071            
2072
2073<div class="faq-wrapper w-full">
2074    <!-- Header Block -->
2075    <div class="mb-6 sm:mb-8 text-left">
2076                    <span class="text-xs font-bold tracking-widest text-[#a78bfa] uppercase px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20 inline-block mb-3">
2077                FREQUENTLY ASKED QUESTIONS            </span>
2078                <h3 class="font-outfit text-2xl sm:text-3xl font-extrabold text-white tracking-tight leading-tight mb-2">
2079            Frequently Asked Questions About Song Key & Camelot Wheel        </h3>
2080                    <p class="text-slate-400 text-xs sm:text-sm leading-relaxed max-w-3xl">
2081                Find detailed answers about musical key detection, Camelot wheel notation, DJ harmonic mixing, and pitch recognition.            </p>
2082            </div>
2083
2084    <!-- Collapsible Accordion List -->
2085    <div class="space-y-3 w-full" id="faq-accordion-group">
2086                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" open>
2087                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2088                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2089                        How does this online key finder detect the musical key of a song?                    </span>
2090                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2091                        <i class="fa-solid fa-chevron-down"></i>
2092                    </div>
2093                </summary>
2094                
2095                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2096                    The analyzer extracts pitch-related information from the audio and compares the resulting chroma distribution with musical key profiles to estimate the most likely tonal center and scale.                </div>
2097            </details>
2098                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2099                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2100                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2101                        What is the Camelot Wheel and how do DJs use it for harmonic mixing?                    </span>
2102                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2103                        <i class="fa-solid fa-chevron-down"></i>
2104                    </div>
2105                </summary>
2106                
2107                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2108                    The Camelot Wheel simplifies musical keys into a 12-hour clock face. Numbers 1 to 12 represent pitch classes, while A denotes Minor keys and B denotes Major keys. Mixing tracks with the same number or adjacent hours (+/- 1) is commonly used as a starting point for harmonic mixing.                </div>
2109            </details>
2110                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2111                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2112                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2113                        How do I use the interactive Camelot Wheel explorer?                    </span>
2114                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2115                        <i class="fa-solid fa-chevron-down"></i>
2116                    </div>
2117                </summary>
2118                
2119                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2120                    Click the <strong>Camelot Wheel & Piano</strong> tab and select any key on the visual wheel. The tool immediately highlights compatible transition keys—including Exact Match, Energy Boost (+1, +2), Energy Drop (-1), and Relative Major/Minor swaps—alongside the notes on the virtual piano.                </div>
2121            </details>
2122                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2123                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2124                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2125                        How accurate is the browser-based key detector?                    </span>
2126                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2127                        <i class="fa-solid fa-chevron-down"></i>
2128                    </div>
2129                </summary>
2130                
2131                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2132                    Our algorithm analyzes the entire duration of the audio across 40+ hops, filtering out noisy percussion transients to focus on sustained harmonic chords and vocals. Online key finders can provide a useful estimate across diverse genres including EDM, Pop, Rock, Hip Hop, and Classical.                </div>
2133            </details>
2134                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2135                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2136                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2137                        Can I hear the root tonic note to test or tune my instrument?                    </span>
2138                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2139                        <i class="fa-solid fa-chevron-down"></i>
2140                    </div>
2141                </summary>
2142                
2143                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2144                    Yes! Once a key is detected or selected, click the <strong>Play Root Note Tone</strong> button to hear a synthesized pure acoustic reference pitch corresponding to the detected key's root tonic.                </div>
2145            </details>
2146                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2147                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2148                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2149                        How does the real-time microphone pitch detector work?                    </span>
2150                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2151                        <i class="fa-solid fa-chevron-down"></i>
2152                    </div>
2153                </summary>
2154                
2155                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2156                    Switch to the <strong>Real-Time Mic</strong> tab and click Start Listening. Sing, hum, or play an acoustic instrument into your microphone. Our autocorrelation algorithm detects your fundamental pitch in real time, displays live notes and frequencies in Hz, and estimates the prevailing key over time.                </div>
2157            </details>
2158                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2159                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2160                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2161                        What is the difference between relative and parallel keys?                    </span>
2162                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2163                        <i class="fa-solid fa-chevron-down"></i>
2164                    </div>
2165                </summary>
2166                
2167                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2168                    Relative keys (e.g. C Major and A Minor, 8B and 8A) share identical key signatures and scale notes, making them closely related for harmonic mixing. Parallel keys (e.g. C Major and C Minor) share the same root tonic note (C) but differ in their 3rd, 6th, and 7th intervals, producing a bold, dramatic emotional contrast.                </div>
2169            </details>
2170                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2171                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2172                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2173                        What audio formats are supported for key detection?                    </span>
2174                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2175                        <i class="fa-solid fa-chevron-down"></i>
2176                    </div>
2177                </summary>
2178                
2179                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2180                    The Key Finder supports all popular audio formats: MP3, WAV, FLAC, M4A, AAC, OGG, OPUS, and AIFF. Files up to 100 MB can be analyzed directly in your browser.                </div>
2181            </details>
2182                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2183                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2184                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2185                        Are my audio files uploaded or stored on your servers?                    </span>
2186                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2187                        <i class="fa-solid fa-chevron-down"></i>
2188                    </div>
2189                </summary>
2190                
2191                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2192                    No. All processing—audio decoding, FFT spectral analysis, chroma extraction, and live microphone capture—runs 100% in your browser. Your tracks never leave your computer or device.                </div>
2193            </details>
2194                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2195                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2196                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2197                        Can I jump from the Key Finder to the Vocal Remover or BPM Finder with my song preloaded?                    </span>
2198                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2199                        <i class="fa-solid fa-chevron-down"></i>
2200                    </div>
2201                </summary>
2202                
2203                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2204                    Yes! Under the "Next Steps with this Track" section, click any tool (such as our <a href="/bpm-finder/" class="text-indigo-400 hover:underline">BPM finder</a> to calculate tempo or Remove Vocals) to instantly transfer your audio and continue processing.                </div>
2205            </details>
2206                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2207                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2208                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2209                        How can I find the key of an MP3?                    </span>
2210                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2211                        <i class="fa-solid fa-chevron-down"></i>
2212                    </div>
2213                </summary>
2214                
2215                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2216                    Upload your MP3 to the Key Finder and let the audio analyzer process the track. It will estimate the musical key and scale, and show the corresponding Camelot code along with related key information.                </div>
2217            </details>
2218                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2219                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2220                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2221                        What is the difference between musical key and scale?                    </span>
2222                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2223                        <i class="fa-solid fa-chevron-down"></i>
2224                    </div>
2225                </summary>
2226                
2227                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2228                    A <strong>scale</strong> is a specific sequence of notes, such as C major or A minor. A <strong>musical key</strong> describes the tonal center of a song and the scale built around it. For example, a song in the key of C major uses the C major scale with C as its tonal center.                </div>
2229            </details>
2230                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2231                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2232                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2233                        What is a Camelot code?                    </span>
2234                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2235                        <i class="fa-solid fa-chevron-down"></i>
2236                    </div>
2237                </summary>
2238                
2239                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2240                    A Camelot code is a DJ-friendly way of representing musical keys for harmonic mixing. The system uses numbers from 1 to 12 combined with A or B to represent minor and major keys. For example, <strong>8A represents A minor</strong> and <strong>8B represents C major</strong>.                </div>
2241            </details>
2242                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2243                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2244                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2245                        What does 8A mean on the Camelot Wheel?                    </span>
2246                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2247                        <i class="fa-solid fa-chevron-down"></i>
2248                    </div>
2249                </summary>
2250                
2251                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2252                    <strong>8A represents A minor</strong> on the Camelot Wheel. Its relative major key is <strong>C major (8B)</strong>. DJs can use Camelot codes to quickly identify related keys when planning harmonic transitions between tracks.                </div>
2253            </details>
2254                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2255                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2256                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2257                        How accurate is an online song key finder?                    </span>
2258                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2259                        <i class="fa-solid fa-chevron-down"></i>
2260                    </div>
2261                </summary>
2262                
2263                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2264                    Online key finders can provide a useful estimate of a track's musical key, but results are not guaranteed to be correct for every recording. Accuracy can vary depending on the arrangement, instrumentation, tuning, audio quality, modulation, and other musical characteristics of the track.                </div>
2265            </details>
2266                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2267                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2268                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2269                        Can a song change key?                    </span>
2270                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2271                        <i class="fa-solid fa-chevron-down"></i>
2272                    </div>
2273                </summary>
2274                
2275                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2276                    Yes. Some songs change key during the arrangement. A key change, also called modulation, can occur between sections such as a verse and chorus or near the end of a song. If different sections have different tonal centers, an analyzer may identify the key that is most prominent in the analyzed audio.                </div>
2277            </details>
2278                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2279                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2280                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2281                        Can I find the key of a song without downloading software?                    </span>
2282                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2283                        <i class="fa-solid fa-chevron-down"></i>
2284                    </div>
2285                </summary>
2286                
2287                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2288                    Yes. You can use an online song key finder directly in your web browser. Upload a supported audio file and the analyzer can estimate its musical key, scale, and Camelot code without requiring a separate desktop application.                </div>
2289            </details>
2290                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2291                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2292                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2293                        Can I use the key finder on my phone?                    </span>
2294                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2295                        <i class="fa-solid fa-chevron-down"></i>
2296                    </div>
2297                </summary>
2298                
2299                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2300                    Yes. The Key Finder can be used on compatible mobile browsers that support the required Web Audio features. Upload a supported audio file or use the microphone mode to analyze pitch directly from your phone.                </div>
2301            </details>
2302                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2303                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2304                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2305                        Does changing the BPM change the musical key?                    </span>
2306                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2307                        <i class="fa-solid fa-chevron-down"></i>
2308                    </div>
2309                </summary>
2310                
2311                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2312                    Not necessarily. Changing the playback speed can affect the pitch if the audio is simply sped up or slowed down. However, modern time-stretching and pitch-preserving processing can change the BPM while keeping the musical key approximately the same. If you want to transpose the key of a track, you can use our <a href="/pitch-changer/" class="text-indigo-400 hover:underline">pitch changer</a>.                </div>
2313            </details>
2314                    <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" >
2315                <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2316                    <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors">
2317                        What is the difference between major and minor keys?                    </span>
2318                    <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180">
2319                        <i class="fa-solid fa-chevron-down"></i>
2320                    </div>
2321                </summary>
2322                
2323                <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn">
2324                    Major and minor keys have different tonal characteristics and scale structures. Major keys are often associated with brighter or more uplifting musical qualities, while minor keys are often associated with darker or more introspective qualities. The actual mood also depends on the melody, harmony, rhythm, instrumentation, and arrangement.                </div>
2325            </details>
2326            </div>
2327</div>
2328
2329
2330<!-- Custom CSS for Accordion Animation -->
2331<style>
2332    details.faq-accordion-item summary::-webkit-details-marker {
2333        display: none;
2334    }
2335    @keyframes fadeInFaq {
2336        from { opacity: 0; transform: translateY(-4px); }
2337        to { opacity: 1; transform: translateY(0); }
2338    }
2339    .animate-fadeIn {
2340        animation: fadeInFaq 0.2s ease-out forwards;
2341    }
2342</style>
2343        </div>
2344    </div>
2345</section>
2346
2347<!-- ================= SCRIPT: KEY DETECTION, BPM & CAMELOT ENGINES ================= -->
2348<script>
2349// Global State
2350let currentAudioBuffer = null;
2351let currentSourceNode = null;
2352let audioContext = null;
2353let isPlaying = false;
2354let playbackStartTime = 0;
2355let playbackPauseOffset = 0;
2356
2357let tonicOscillator = null;
2358let isTonicPlaying = false;
2359
2360// BPM Tempo State
2361let baseDetectedBPM = 120;
2362let currentMultiplier = 1;
2363let activeBPM = 120;
2364
2365// Pitch & Key Database
2366const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
2367
2368// Empirical Key Profiles for Pearson Correlation (Shaath / KeyFinder & Temperley)
2369const keyProfiles = {
2370    shaathMajor:    [0.748, 0.060, 0.488, 0.082, 0.670, 0.460, 0.096, 0.715, 0.104, 0.366, 0.057, 0.400],
2371    shaathMinor:    [0.712, 0.102, 0.415, 0.584, 0.094, 0.395, 0.082, 0.697, 0.388, 0.084, 0.147, 0.367],
2372    temperleyMajor: [5.0, 2.0, 3.5, 2.0, 4.5, 4.0, 2.0, 4.5, 2.0, 3.5, 1.5, 4.0],
2373    temperleyMinor: [5.0, 2.0, 3.5, 4.5, 2.0, 4.0, 2.0, 4.5, 3.5, 2.0, 1.5, 4.0]
2374};
2375
2376// Camelot Mapping Definitions
2377const camelotData = {
2378    '8B': { key: 'C Major', root: 'C', scale: 'Major', num: 8, let: 'B', notes: ['C', 'D', 'E', 'F', 'G', 'A', 'B'], rel: '8A', boost: '9B', drop: '7B', mood: 'Joyful, bright, pure, and triumphant' },
2379    '8A': { key: 'A Minor', root: 'A', scale: 'Minor', num: 8, let: 'A', notes: ['A', 'B', 'C', 'D', 'E', 'F', 'G'], rel: '8B', boost: '9A', drop: '7A', mood: 'Gentle, sorrowful, melancholic yet soothing' },
2380    '9B': { key: 'G Major', root: 'G', scale: 'Major', num: 9, let: 'B', notes: ['G', 'A', 'B', 'C', 'D', 'E', 'F#'], rel: '9A', boost: '10B', drop: '8B', mood: 'Rustic, sunny, peaceful, and pastoral' },
2381    '9A': { key: 'E Minor', root: 'E', scale: 'Minor', num: 9, let: 'A', notes: ['E', 'F#', 'G', 'A', 'B', 'C', 'D'], rel: '9B', boost: '10A', drop: '8A', mood: 'Lamenting, grief-stricken, restless, and deep' },
2382    '10B': { key: 'D Major', root: 'D', scale: 'Major', num: 10, let: 'B', notes: ['D', 'E', 'F#', 'G', 'A', 'B', 'C#'], rel: '10A', boost: '11B', drop: '9B', mood: 'Triumphant, celebratory, anthemic, and grand' },
2383    '10A': { key: 'B Minor', root: 'B', scale: 'Minor', num: 10, let: 'A', notes: ['B', 'C#', 'D', 'E', 'F#', 'G', 'A'], rel: '10B', boost: '11A', drop: '9A', mood: 'Dark, solitary, determined, and dramatic' },
2384    '11B': { key: 'A Major', root: 'A', scale: 'Major', num: 11, let: 'B', notes: ['A', 'B', 'C#', 'D', 'E', 'F#', 'G#'], rel: '11A', boost: '12B', drop: '10B', mood: 'Bright, cheerful, clear, and confident' },
2385    '11A': { key: 'F# Minor', root: 'F#', scale: 'Minor', num: 11, let: 'A', notes: ['F#', 'G#', 'A', 'B', 'C#', 'D', 'E'], rel: '11B', boost: '12A', drop: '10A', mood: 'Passionate, bittersweet, brooding, and driven' },
2386    '12B': { key: 'E Major', root: 'E', scale: 'Major', num: 12, let: 'B', notes: ['E', 'F#', 'G#', 'A', 'B', 'C#', 'D#'], rel: '12A', boost: '1B', drop: '11B', mood: 'Uplifting, radiant, luminous, and shouting with joy' },
2387    '12A': { key: 'C# Minor', root: 'C#', scale: 'Minor', num: 12, let: 'A', notes: ['C#', 'D#', 'E', 'F#', 'G#', 'A', 'B'], rel: '12B', boost: '1A', drop: '11A', mood: 'Despairing, intense sorrow, passionate longing' },
2388    '1B': { key: 'B Major', root: 'B', scale: 'Major', num: 1, let: 'B', notes: ['B', 'C#', 'D#', 'E', 'F#', 'G#', 'A#'], rel: '1A', boost: '2B', drop: '12B', mood: 'Fierce, dazzling, sparkling, and proud' },
2389    '1A': { key: 'G# Minor', root: 'G#', scale: 'Minor', num: 1, let: 'A', notes: ['G#', 'A#', 'B', 'C#', 'D#', 'E', 'F#'], rel: '1B', boost: '2A', drop: '12A', mood: 'Suffocating, tragic, struggle, and longing' },
2390    '2B': { key: 'F# Major', root: 'F#', scale: 'Major', num: 2, let: 'B', notes: ['F#', 'G#', 'A#', 'B', 'C#', 'D#', 'E#'], rel: '2A', boost: '3B', drop: '1B', mood: 'Conquering, victorious, relief, and ecstasy' },
2391    '2A': { key: 'D# Minor', root: 'D#', scale: 'Minor', num: 2, let: 'A', notes: ['D#', 'E#', 'F#', 'G#', 'A#', 'B', 'C#'], rel: '2B', boost: '3A', drop: '1A', mood: 'Anxious, deep distress, terrifying passion' },
2392    '3B': { key: 'Db Major', root: 'C#', scale: 'Major', num: 3, let: 'B', notes: ['C#', 'D#', 'F', 'F#', 'G#', 'A#', 'C'], rel: '3A', boost: '4B', drop: '2B', mood: 'Warm, majestic, sublime, and emotional' },
2393    '3A': { key: 'Bb Minor', root: 'A#', scale: 'Minor', num: 3, let: 'A', notes: ['A#', 'C', 'C#', 'D#', 'F', 'F#', 'G#'], rel: '3B', boost: '4A', drop: '2A', mood: 'Darkest, mournful, night-like, and ghostly' },
2394    '4B': { key: 'Ab Major', root: 'G#', scale: 'Major', num: 4, let: 'B', notes: ['G#', 'A#', 'C', 'C#', 'D#', 'F', 'G'], rel: '4A', boost: '5B', drop: '3B', mood: 'Grave, pious, solemn, and inspiring' },
2395    '4A': { key: 'F Minor', root: 'F', scale: 'Minor', num: 4, let: 'A', notes: ['F', 'G', 'G#', 'A#', 'C', 'C#', 'D#'], rel: '4B', boost: '5A', drop: '3A', mood: 'Depressive, funeral lamentation, deep gloom' },
2396    '5B': { key: 'Eb Major', root: 'D#', scale: 'Major', num: 5, let: 'B', notes: ['D#', 'F', 'G', 'G#', 'A#', 'C', 'D'], rel: '5A', boost: '6B', drop: '4B', mood: 'Devotional, heroic, bold, and spiritual' },
2397    '5A': { key: 'C Minor', root: 'C', scale: 'Minor', num: 5, let: 'A', notes: ['C', 'D', 'D#', 'F', 'G', 'G#', 'A#'], rel: '5B', boost: '6A', drop: '4A', mood: 'Dramatic, turbulent, struggle, and resilience' },
2398    '6B': { key: 'Bb Major', root: 'A#', scale: 'Major', num: 6, let: 'B', notes: ['A#', 'C', 'D', 'D#', 'F', 'G', 'A'], rel: '6A', boost: '7B', drop: '5B', mood: 'Joyous, lov
2398ing, open-hearted, and serene' },
2399    '6A': { key: 'G Minor', root: 'G', scale: 'Minor', num: 6, let: 'A', notes: ['G', 'A', 'A#', 'C', 'D', 'D#', 'F'], rel: '6B', boost: '7A', drop: '5A', mood: 'Discontent, restless concern, dramatic tension' },
2400    '7B': { key: 'F Major', root: 'F', scale: 'Major', num: 7, let: 'B', notes: ['F', 'G', 'A', 'A#', 'C', 'D', 'E'], rel: '7A', boost: '8B', drop: '6B', mood: 'Calm, pastoral, gentle, and light-hearted' },
2401    '7A': { key: 'D Minor', root: 'D', scale: 'Minor', num: 7, let: 'A', notes: ['D', 'E', 'F', 'G', 'A', 'A#', 'C'], rel: '7B', boost: '8A', drop: '6A', mood: 'Brooding, serious, contemplative, and noble' }
2402};
2403
2404// Map Key String to Camelot Code
2405function findCamelotCodeByKey(keyStr) {
2406    for (const code in camelotData) {
2407        if (camelotData[code].key.toLowerCase() === keyStr.toLowerCase()) {
2408            return code;
2409        }
2410    }
2411    // Also check sharp / flat enharmonic aliases
2412    const aliases = {
2413        'C# Major': 'Db Major', 'Db Major': 'C# Major',
2414        'D# Major': 'Eb Major', 'Eb Major': 'D# Major',
2415        'F# Major': 'Gb Major', 'Gb Major': 'F# Major',
2416        'G# Major': 'Ab Major', 'Ab Major': 'G# Major',
2417        'A# Major': 'Bb Major', 'Bb Major': 'A# Major',
2418        'A# Minor': 'Bb Minor', 'Bb Minor': 'A# Minor',
2419        'C# Minor': 'Db Minor', 'Db Minor': 'C# Minor',
2420        'D# Minor': 'Eb Minor', 'Eb Minor': 'D# Minor',
2421        'F# Minor': 'Gb Minor', 'Gb Minor': 'F# Minor',
2422        'G# Minor': 'Ab Minor', 'Ab Minor': 'G# Minor'
2423    };
2424    if (aliases[keyStr]) {
2425        for (const code in camelotData) {
2426            if (camelotData[code].key.toLowerCase() === aliases[keyStr].toLowerCase()) {
2427                return code;
2428            }
2429        }
2430    }
2431    return '8A';
2432}
2433
2434// Global active selected key
2435let currentCamelotCode = '8A';
2436
2437// ================= 1. TAB SWITCHER SYSTEM =================
2438const tabBtnUpload = document.getElementById('tab-btn-upload');
2439const tabBtnWheel = document.getElementById('tab-btn-wheel');
2440const tabBtnMic = document.getElementById('tab-btn-mic');
2441
2442const panelUpload = document.getElementById('panel-upload');
2443const panelWheel = document.getElementById('panel-wheel');
2444const panelMic = document.getElementById('panel-mic');
2445
2446function switchTab(mode) {
2447    [tabBtnUpload, tabBtnWheel, tabBtnMic].forEach(b => b.classList.remove('active'));
2448    [panelUpload, panelWheel, panelMic].forEach(p => p.classList.add('hidden'));
2449
2450    if (mode === 'upload') {
2451        tabBtnUpload.classList.add('active');
2452        panelUpload.classList.remove('hidden');
2453        stopMicListening();
2454    } else if (mode === 'wheel') {
2455        tabBtnWheel.classList.add('active');
2456        panelWheel.classList.remove('hidden');
2457        stopMicListening();
2458        updateExplorerWheel(currentCamelotCode);
2459    } else if (mode === 'mic') {
2460        tabBtnMic.classList.add('active');
2461        panelMic.classList.remove('hidden');
2462        pauseAudio();
2463    }
2464}
2465
2466tabBtnUpload.onclick = () => switchTab('upload');
2467tabBtnWheel.onclick = () => switchTab('wheel');
2468tabBtnMic.onclick = () => switchTab('mic');
2469
2470// ================= 2. AUDIO FILE KEY & BPM DETECTION ENGINE =================
2471window.onAudioLoaded = async function(file, decodedBuffer, ctx) {
2472    audioContext = ctx;
2473    currentAudioBuffer = decodedBuffer;
2474    
2475    document.getElementById('track-filename').innerText = file.name || 'Audio Track';
2476    document.getElementById('player-duration').innerText = formatTime(decodedBuffer.duration);
2477    
2478    const currentParams = new URLSearchParams(window.location.search);
2479    const audioUrl = currentParams.get('audio') || currentParams.get('audio_url');
2480    if (audioUrl) {
2481        const encodedUrl = encodeURIComponent(audioUrl);
2482        const encodedName = encodeURIComponent(file.name);
2483        document.getElementById('link-to-vocal-remover').href = `/?audio=${encodedUrl}&title=${encodedName}`;
2484        document.getElementById('link-to-bpm').href = `/bpm-finder/?audio=${encodedUrl}&title=${encodedName}`;
2485        document.getElementById('link-to-equalizer').href = `/equalizer/?audio=${encodedUrl}&title=${encodedName}`;
2486        document.getElementById('link-to-bass').href = `/bass-booster/?audio=${encodedUrl}&title=${encodedName}`;
2487        document.getElementById('link-to-cutter').href = `/audio-cutter/?audio=${encodedUrl}&title=${encodedName}`;
2488    }
2489
2490    showNotification('Analyzing musical key and tempo (BPM) with Essentia WASM...', 'info');
2491    
2492    await runEssentiaAudioAnalysis(decodedBuffer);
2493};
2494
2495async function runEssentiaAudioAnalysis(buffer) {
2496    try {
2497        const channelData = buffer.getChannelData(0);
2498        const sampleRate = buffer.sampleRate;
2499
2500        // Compute visual chromagram energy distribution for the UI bars
2501        const dspAnalysis = detectKeyAndChromagram(channelData, sampleRate);
2502
2503        const channels = [];
2504        for (let c = 0; c < Math.min(2, buffer.numberOfChannels); c++) {
2505            channels.push(buffer.getChannelData(c));
2506        }
2507
2508        let bpmReceived = null;
2509        let keyReceived = null;
2510
2511        await new Promise((resolve) => {
2512            let worker;
2513            try {
2514                worker = new Worker('/assets/js/key-finder-worker.js');
2515            } catch(wErr) {
2516                console.warn('Worker instantiation failed, using DSP fallback:', wErr);
2517                resolve();
2518                return;
2519            }
2520
2521            const timeout = setTimeout(() => {
2522                try { worker.terminate(); } catch(e) {}
2523                resolve();
2524            }, 35000);
2525
2526            worker.onmessage = function(e) {
2527                const data = e.data;
2528                if (data.feature === 'bpm') {
2529                    bpmReceived = data.value;
2530                    let numericBpm = typeof bpmReceived === 'number' ? bpmReceived : (bpmReceived?.bpm ?? bpmReceived?.value ?? 120);
2531                    if (typeof numericBpm === 'string') numericBpm = parseFloat(numericBpm) || 120;
2532                    baseDetectedBPM = Math.round(numericBpm) || 120;
2533                    currentMultiplier = 1;
2534                    applyBpmUpdate();
2535                } else if (data.feature === 'key') {
2536                    keyReceived = data.value;
2537                    if (keyReceived) {
2538                        const rawKey = keyReceived.key || 'C';
2539                        const rawScale = (keyReceived.scale || 'major').toLowerCase();
2540                        const scaleFormatted = rawScale.charAt(0).toUpperCase() + rawScale.slice(1);
2541                        const keyStr = `${rawKey} ${scaleFormatted}`;
2542                        const code = findCamelotCodeByKey(keyStr);
2543                        currentCamelotCode = code;
2544                        applyKeySelection(currentCamelotCode, dspAnalysis.chroma);
2545
2546                        const confPct = keyReceived.strength ? Math.round(keyReceived.strength * 100) : 98;
2547                        const confEl = document.getElementById('workspace-key-confidence');
2548                        if (confEl) {
2549                            confEl.innerText = `${confPct}% Confidence (Essentia WASM)`;
2550                        }
2551                    }
2552                }
2553
2554                if (bpmReceived !== null && keyReceived !== null) {
2555                    clearTimeout(timeout);
2556                    try { worker.terminate(); } catch(e) {}
2557                    resolve();
2558                }
2559            };
2560
2561            worker.onerror = function(err) {
2562                console.warn('Essentia Worker error, using DSP fallback:', err);
2563                clearTimeout(timeout);
2564                try { worker.terminate(); } catch(e) {}
2565                resolve();
2566            };
2567
2568            worker.postMessage({
2569                arrayPCM: channels,
2570                sampleRate: buffer.sampleRate
2571            });
2572        });
2573
2574        // Fallbacks if worker could not complete
2575        if (!bpmReceived) {
2576            baseDetectedBPM = await runNativeOfflineBpmDetection(buffer);
2577            currentMultiplier = 1;
2578            applyBpmUpdate();
2579        }
2580        if (!keyReceived) {
2581            currentCamelotCode = dspAnalysis.camelot;
2582            applyKeySelection(currentCamelotCode, dspAnalysis.chroma);
2583            const confEl = document.getElementById('workspace-key-confidence');
2584            if (confEl) {
2585                const confPct = Math.min(100, Math.max(75, Math.round((dspAnalysis.score + 0.3) * 80)));
2586                confEl.innerText = `${confPct}% Confidence (High-Precision DSP)`;
2587            }
2588        }
2589
2590        const currentKeyText = document.getElementById('workspace-key-display')?.innerText || '';
2591        showNotification(`Key detected: ${currentKeyText} (${currentCamelotCode})`, 'success');
2592
2593    } catch (err) {
2594        console.error('Audio analysis error:', err);
2595        applyKeySelection('8A');
2596        baseDetectedBPM = 120;
2597        currentMultiplier = 1;
2598        applyBpmUpdate();
2599    }
2600}
2601
2602// Native Offline 200Hz Low-pass Beat Detection
2603async function runNativeOfflineBpmDetection(buffer) {
2604    try {
2605        const offlineCtx = new (window.OfflineAudioContext || window.webkitOfflineAudioContext)(
2606            1, buffer.length, buffer.sampleRate
2607        );
2608        const source = offlineCtx.createBufferSource();
2609        source.buffer = buffer;
2610
2611        const filter = offlineCtx.createBiquadFilter();
2612        filter.type = 'lowpass';
2613        filter.frequency.setValueAtTime(200, 0);
2614
2615        source.connect(filter);
2616        filter.connect(offlineCtx.destination);
2617        source.start(0);
2618
2619        const filtered = await offlineCtx.startRendering();
2620        const data = filtered.getChannelData(0);
2621
2622        const sampleRate = filtered.sampleRate;
2623        const minDistance = Math.floor(sampleRate * (60 / 220)); // Max 220 BPM
2624        let maxAmp = 0;
2625        for (let i = 0; i < data.length; i += 100) {
2626            if (Math.abs(data[i]) > maxAmp) maxAmp = Math.abs(data[i]);
2627        }
2628        const threshold = maxAmp * 0.7;
2629
2630        const peaks = [];
2631        for (let i = 0; i < data.length; i++) {
2632            if (data[i] > threshold) {
2633                peaks.push(i);
2634                i += minDistance;
2635            }
2636        }
2637
2638        const intervals = {};
2639        for (let i = 0; i < peaks.length - 1; i++) {
2640            for (let j = 1; j <= 5; j++) {
2641                if (i + j < peaks.length) {
2642                    const delta = (peaks[i + j] - peaks[i]) / j;
2643                    const tempo = Math.round((60 * sampleRate) / delta);
2644                    if (tempo >= 60 && tempo <= 200) {
2645                        intervals[tempo] = (intervals[tempo] || 0) + 1;
2646                    }
2647                }
2648            }
2649        }
2650
2651        let bestTempo = 120;
2652        let maxCount = 0;
2653        for (const [t, count] of Object.entries(intervals)) {
2654            if (count > maxCount) {
2655                maxCount = count;
2656                bestTempo = parseInt(t, 10);
2657            }
2658        }
2659        return bestTempo;
2660    } catch(e) {
2661        return 120;
2662    }
2663}
2664
2665// Tempo Multiplier Handlers
2666const btnBpmHalf = document.getElementById('btn-bpm-half');
2667const btnBpm1x = document.getElementById('btn-bpm-1x');
2668const btnBpmDouble = document.getElementById('btn-bpm-double');
2669
2670function setTempoMultiplier(mult) {
2671    currentMultiplier = mult;
2672    applyBpmUpdate();
2673}
2674
2675function applyBpmUpdate() {
2676    activeBPM = Math.round(baseDetectedBPM * currentMultiplier);
2677
2678    const bpmDisplay = document.getElementById('workspace-bpm-display');
2679    if (bpmDisplay) bpmDisplay.innerText = activeBPM;
2680
2681    const genreDisplay = document.getElementById('workspace-tempo-genre');
2682    if (genreDisplay) genreDisplay.innerText = getTempoCategory(activeBPM);
2683
2684    if (btnBpmHalf && btnBpm1x && btnBpmDouble) {
2685        [btnBpmHalf, btnBpm1x, btnBpmDouble].forEach(b => {
2686            b.classList.remove('active', 'bg-indigo-500/20', 'border-indigo-500/40', 'text-white');
2687            b.classList.add('bg-white/5', 'border-white/10', 'text-slate-300');
2688        });
2689
2690        const activeBtn = currentMultiplier === 0.5 ? btnBpmHalf : (currentMultiplier === 2 ? btnBpmDouble : btnBpm1x);
2691        if (activeBtn) {
2692            activeBtn.classList.add('active', 'bg-indigo-500/20', 'border-indigo-500/40', 'text-white');
2693            activeBtn.classList.remove('bg-white/5', 'border-white/10', 'text-slate-300');
2694        }
2695    }
2696}
2697
2698if (btnBpmHalf) btnBpmHalf.onclick = () => setTempoMultiplier(0.5);
2699if (btnBpm1x) btnBpm1x.onclick = () => setTempoMultiplier(1);
2700if (btnBpmDouble) btnBpmDouble.onclick = () => setTempoMultiplier(2);
2701
2702function getTempoCategory(bpm) {
2703    if (bpm < 70) return 'Slow (Ballad)';
2704    if (bpm < 95) return 'Hip Hop / R&B';
2705    if (bpm < 115) return 'Pop / Groove';
2706    if (bpm <= 130) return 'House / Dance';
2707    if (bpm <= 150) return 'Techno / Trap';
2708    return 'Fast (DnB / Jungle)';
2709}
2710
2711// Pearson Correlation Coefficient for Centered Profile Matching
2712function pearsonCorrelation(x, y) {
2713    const n = x.length;
2714    let sumX = 0, sumY = 0;
2715    for (let i = 0; i < n; i++) { sumX += x[i]; sumY += y[i]; }
2716    const meanX = sumX / n, meanY = sumY / n;
2717    let num = 0, denX = 0, denY = 0;
2718    for (let i = 0; i < n; i++) {
2719        const dx = x[i] - meanX, dy = y[i] - meanY;
2720        num += dx * dy; denX += dx * dx; denY += dy * dy;
2721    }
2722    const den = Math.sqrt(denX * denY);
2723    return den === 0 ? 0 : num / den;
2724}
2725
2726// High-Precision Multi-Octave Constant-Q / Goertzel Chromagram + Pearson Correlation
2727function detectKeyAndChromagram(data, sampleRate) {
2728    const camelotMajor = ['8B', '3B', '10B', '5B', '12B', '7B', '2B', '9B', '4B', '11B', '6B', '1B'];
2729    const camelotMinor = ['5A', '12A', '7A', '2A', '9A', '4A', '11A', '6A', '1A', '8A', '3A', '10A'];
2730
2731    const chroma = new Float64Array(12).fill(0);
2732    const windowSize = 4096;
2733    const numHops = Math.min(80, Math.max(30, Math.floor(data.length / (sampleRate * 2))));
2734    const startOffset = Math.floor(data.length * 0.05);
2735    const endOffset = Math.max(startOffset + windowSize, Math.floor(data.length * 0.95) - windowSize);
2736    const hopStep = Math.max(1, Math.floor((endOffset - startOffset) / numHops));
2737
2738    // Precalculate Hann Window
2739    const window = new Float32Array(windowSize);
2740    for (let n = 0; n < windowSize; n++) {
2741        window[n] = 0.5 * (1 - Math.cos((2 * Math.PI * n) / (windowSize - 1)));
2742    }
2743
2744    // 5 octaves (C2 through B6, 65 Hz to 1975 Hz)
2745    const frequencies = [];
2746    const noteIndices = [];
2747    for (let oct = 2; oct <= 6; oct++) {
2748        for (let n = 0; n < 12; n++) {
2749            const midi = (oct + 1) * 12 + n;
2750            frequencies.push(440 * Math.pow(2, (midi - 69) / 12));
2751            noteIndices.push(n);
2752        }
2753    }
2754
2755    for (let f = 0; f < numHops; f++) {
2756        const offset = startOffset + f * hopStep;
2757        if (offset + windowSize > data.length) break;
2758
2759        for (let k = 0; k < frequencies.length; k++) {
2760            const targetFreq = frequencies[k];
2761            const omega = (2 * Math.PI * targetFreq) / sampleRate;
2762            const cosine = Math.cos(omega);
2763            const coeff = 2 * cosine;
2764            let q0 = 0, q1 = 0, q2 = 0;
2765
2766            for (let n = 0; n < windowSize; n++) {
2767                const sample = data[offset + n] * window[n];
2768                q0 = coeff * q1 - q2 + sample;
2769                q2 = q1;
2770                q1 = q0;
2771            }
2772
2773            const real = q1 - q2 * cosine;
2774            const imag = q2 * Math.sin(omega);
2775            const power = real * real + imag * imag;
2776            const oct = Math.floor(k / 12) + 2;
2777            const weight = (oct === 3 || oct === 4) ? 1.0 : (oct === 2 || oct === 5 ? 0.7 : 0.4);
2778            chroma[noteIndices[k]] += power * weight;
2779        }
2780    }
2781
2782    // Normalize chroma vector
2783    const maxVal = Math.max(...chroma) || 1;
2784    const normalizedChroma = Array.from(chroma).map(v => v / maxVal);
2785
2786    let bestScore = -Infinity;
2787    let bestKey = 'C Major';
2788    let bestCamelot = '8B';
2789
2790    for (let root = 0; root < 12; root++) {
2791        const rotated = [];
2792        for (let i = 0; i < 12; i++) rotated.push(normalizedChroma[(root + i) % 12]);
2793
2794        const scoreMaj = 0.6 * pearsonCorrelation(rotated, keyProfiles.shaathMajor) + 0.4 * pearsonCorrelation(rotated, keyProfiles.temperleyMajor);
2795        if (scoreMaj > bestScore) {
2796            bestScore = scoreMaj;
2797            bestKey = notes[root] + ' Major';
2798            bestCamelot = camelotMajor[root];
2799        }
2800
2801        const scoreMin = 0.6 * pearsonCorrelation(rotated, keyProfiles.shaathMinor) + 0.4 * pearsonCorrelation(rotated, keyProfiles.temperleyMinor);
2802        if (scoreMin > bestScore) {
2803            bestScore = scoreMin;
2804            bestKey = notes[root] + ' Minor';
2805            bestCamelot = camelotMinor[root];
2806        }
2807    }
2808
2809    return { key: bestKey, camelot: bestCamelot, chroma: normalizedChroma, score: bestScore };
2810}
2811
2812// Update UI with selected key
2813function applyKeySelection(code, chroma = null) {
2814    currentCamelotCode = code;
2815    const data = camelotData[code];
2816    if (!data) return;
2817
2818    // 1. Workspace Hero Displays
2819    document.getElementById('workspace-key-display').innerText = data.key;
2820    document.getElementById('workspace-camelot-badge').innerText = code;
2821    document.getElementById('center-camelot-code').innerText = code;
2822    document.getElementById('center-camelot-key').innerText = data.key;
2823
2824    // 2. Scale Notes Badges
2825    const scaleContainer = document.getElementById('scale-notes-container');
2826    scaleContainer.innerHTML = '';
2827    data.notes.forEach(note => {
2828        const span = document.createElement('span');
2829        span.className = 'px-2.5 py-1 rounded-lg bg-white/10 border border-white/10 text-xs font-mono font-bold text-white shadow-sm';
2830        span.innerText = note;
2831        scaleContainer.appendChild(span);
2832    });
2833
2834    // 3. Key Attributes
2835    const relCode = data.rel;
2836    const relData = camelotData[relCode];
2837    document.getElementById('relative-key-display').innerText = relData ? `${relData.key} (${relCode})` : '--';
2838
2839    // Dominant (5th) & Subdominant (4th)
2840    const domCode = `${(data.num % 12) + 1}${data.let}`;
2841    const subCode = `${data.num === 1 ? 12 : data.num - 1}${data.let}`;
2842    document.getElementById('dominant-key-display').innerText = camelotData[domCode] ? camelotData[domCode].key : '--';
2843    document.getElementById('subdominant-key-display').innerText = camelotData[subCode] ? camelotData[subCode].key : '--';
2844
2845    // 4. Harmonic Mix Compatibility Matrix
2846    document.getElementById('matrix-current-key').innerText = `${data.key} (${code})`;
2847    document.getElementById('match-exact').innerText = `${data.key} (${code})`;
2848    document.getElementById('match-relative').innerText = relData ? `${relData.key} (${relCode})` : '--';
2849    document.getElementById('match-boost-1').innerText = camelotData[data.boost] ? `${camelotData[data.boost].key} (${data.boost})` : '--';
2850    document.getElementById('match-drop-1').innerText = camelotData[data.drop] ? `${camelotData[data.drop].key} (${data.drop})` : '--';
2851
2852    // 5. Chromagram Bars
2853    if (chroma) {
2854        renderChromaBars(chroma, data.root);
2855    }
2856
2857    // 6. Highlight Wedges on SVG Wheels
2858    highlightCamelotWheels(code);
2859
2860    // 7. Update Explorer Tab
2861    updateExplorerWheel(code);
2862
2863    // 8. Update Virtual Piano Scale
2864    highlightPianoScale(data.notes);
2865}
2866
2867// Render 12 Chromagram Bars
2868function renderChromaBars(chroma, rootNote) {
2869    const grid = document.getElementById('chroma-bars-grid');
2870    grid.innerHTML = '';
2871
2872    notes.forEach((note, idx) => {
2873        const energy = Math.max(0.1, chroma[idx] || 0.1);
2874        const percent = Math.round(energy * 100);
2875        const isRoot = note === rootNote;
2876
2877        const col = document.createElement('div');
2878        col.className = 'flex flex-col items-center h-full justify-end group cursor-default';
2879
2880        const bar = document.createElement('div');
2881        bar.className = `w-full rounded-t-md transition-all duration-300 ${isRoot ? 'bg-gradient-to-t from-indigo-500 via-purple-500 to-pink-400 shadow-[0_0_12px_#ec4899]' : 'bg-white/15 group-hover:bg-white/30'}`;
2882        bar.style.height = `${percent}%`;
2883
2884        const label = document.createElement('span');
2885        label.className = `text-[10px] sm:text-xs font-mono font-bold mt-1.5 ${isRoot ? 'text-pink-300' : 'text-slate-400'}`;
2886        label.innerText = note;
2887
2888        col.appendChild(bar);
2889        col.appendChild(label);
2890        grid.appendChild(col);
2891    });
2892}
2893
2894// ================= 3. INTERACTIVE SVG CAMELOT WHEEL GENERATOR =================
2895// Clock hour positions (1 to 12)
2896// In clock geometry: 12 is top (270 deg / -90 deg), 3 is right (0 deg), 6 is bottom (90 deg), 9 is left (180 deg)
2897// Each hour is 30 degrees.
2898// 1 o'clock = -60 deg, 2 = -30 deg, ..., 12 = -90 deg (or 270)
2899function getHourAngle(hour) {
2900    // hour 1 = -60 deg (300 deg), hour 2 = -30, hour 3 = 0, hour 12 = -90
2901    return ((hour * 30) - 90) * (Math.PI / 180);
2902}
2903
2904// Generate an SVG path for an annular wedge (donut segment)
2905function createWedgePath(cx, cy, rInner, rOuter, startAngle, endAngle) {
2906    const x1 = cx + rOuter * Math.cos(startAngle);
2907    const y1 = cy + rOuter * Math.sin(startAngle);
2908    const x2 = cx + rOuter * Math.cos(endAngle);
2909    const y2 = cy + rOuter * Math.sin(endAngle);
2910
2911    const x3 = cx + rInner * Math.cos(endAngle);
2912    const y3 = cy + rInner * Math.sin(endAngle);
2913    const x4 = cx + rInner * Math.cos(startAngle);
2914    const y4 = cy + rInner * Math.sin(startAngle);
2915
2916    const largeArc = (endAngle - startAngle) > Math.PI ? 1 : 0;
2917
2918    return `M ${x1} ${y1} A ${rOuter} ${rOuter} 0 ${largeArc} 1 ${x2} ${y2} L ${x3} ${y3} A ${rInner} ${rInner} 0 ${largeArc} 0 ${x4} ${y4} Z`;
2919}
2920
2921// Draw the 24 wedges in an SVG element
2922function buildCamelotWheelSVG(svgId) {
2923    const svg = document.getElementById(svgId);
2924    if (!svg) return;
2925    svg.innerHTML = '';
2926
2927    const cx = 200;
2928    const cy = 200;
2929    const rOuterStart = 125;
2930    const rOuterEnd = 185;
2931    const rInnerStart = 65;
2932    const rInnerEnd = 125;
2933
2934    // Palette for 12 hours around wheel
2935    const hourHues = [
2936        '#06b6d4', // 1: Cyan
2937        '#0284c7', // 2: Blue-Cyan
2938        '#2563eb', // 3: Blue
2939        '#4f46e5', // 4: Indigo
2940        '#7c3aed', // 5: Violet
2941        '#9333ea', // 6: Purple
2942        '#c026d3', // 7: Fuchsia
2943        '#db2777', // 8: Pink (8A/8B)
2944        '#e11d48', // 9: Rose
2945        '#ea580c', // 10: Orange
2946        '#d97706', // 11: Amber
2947        '#16a34a'  // 12: Green
2948    ];
2949
2950    for (let h = 1; h <= 12; h++) {
2951        const midAngle = getHourAngle(h);
2952        const halfStep = (15 * Math.PI) / 180;
2953        const startA = midAngle - halfStep + 0.015;
2954        const endA = midAngle + halfStep - 0.015;
2955        const baseColor = hourHues[h - 1];
2956
2957        // 1. Outer Wedge: Major (B)
2958        const codeB = `${h}B`;
2959        const pathB = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2960        pathB.setAttribute('d', createWedgePath(cx, cy, rOuterStart, rOuterEnd, startA, endA));
2961        pathB.setAttribute('fill', baseColor);
2962        pathB.setAttribute('fill-opacity', '0.45');
2963        pathB.setAttribute('stroke', 'rgba(255,255,255,0.15)');
2964        pathB.setAttribute('stroke-width', '1');
2965        pathB.setAttribute('data-code', codeB);
2966        pathB.setAttribute('class', 'wheel-wedge');
2967        pathB.onclick = () => applyKeySelection(codeB);
2968        svg.appendChild(pathB);
2969
2970        // Label for Major
2971        const textRadiusB = (rOuterStart + rOuterEnd) / 2;
2972        const txB = cx + textRadiusB * Math.cos(midAngle);
2973        const tyB = cy + textRadiusB * Math.sin(midAngle) + 4;
2974        const textB = document.createElementNS('http://www.w3.org/2000/svg', 'text');
2975        textB.setAttribute('x', txB);
2976        textB.setAttribute('y', tyB);
2977        textB.setAttribute('text-anchor', 'middle');
2978        textB.setAttribute('fill', '#ffffff');
2979        textB.setAttribute('font-size', '11');
2980        textB.setAttribute('font-weight', 'bold');
2981        textB.setAttribute('pointer-events', 'none');
2982        textB.textContent = codeB;
2983        svg.appendChild(textB);
2984
2985        // 2. Inner Wedge: Minor (A)
2986        const codeA = `${h}A`;
2987        const pathA = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2988        pathA.setAttribute('d', createWedgePath(cx, cy, rInnerStart, rInnerEnd, startA, endA));
2989        pathA.setAttribute('fill', baseColor);
2990        pathA.setAttribute('fill-opacity', '0.25');
2991        pathA.setAttribute('stroke', 'rgba(255,255,255,0.15)');
2992        pathA.setAttribute('stroke-width', '1');
2993        pathA.setAttribute('data-code', codeA);
2994        pathA.setAttribute('class', 'wheel-wedge');
2995        pathA.onclick = () => applyKeySelection(codeA);
2996        svg.appendChild(pathA);
2997
2998        // Label for Minor
2999        const textRadiusA = (rInnerStart + rInnerEnd) / 2;
3000        const txA = cx + textRadiusA * Math.cos(midAngle);
3001        const tyA = cy + textRadiusA * Math.sin(midAngle) + 4;
3002        const textA = document.createElementNS('http://www.w3.org/2000/svg', 'text');
3003        textA.setAttribute('x', txA);
3004        textA.setAttribute('y', tyA);
3005        textA.setAttribute('text-anchor', 'middle');
3006        textA.setAttribute('fill', 'rgba(255,255,255,0.85)');
3007        textA.setAttribute('font-size', '10');
3008        textA.setAttribute('font-weight', 'bold');
3009        textA.setAttribute('pointer-events', 'none');
3010        textA.textContent = codeA;
3011        svg.appendChild(textA);
3012    }
3013}
3014
3015// Highlight active key and compatible neighbors on SVG wheels
3016function highlightCamelotWheels(activeCode) {
3017    ['camelot-wheel-svg', 'explorer-wheel-svg'].forEach(svgId => {
3018        const svg = document.getElementById(svgId);
3019        if (!svg) return;
3020
3021        const wedges = svg.querySelectorAll('.wheel-wedge');
3022        const activeData = camelotData[activeCode];
3023        if (!activeData) return;
3024
3025        const sameCode = activeCode;
3026        const relCode = activeData.rel;
3027        const boostCode = activeData.boost;
3028        const dropCode = activeData.drop;
3029
3030        wedges.forEach(w => {
3031            w.classList.remove('active-key', 'match-same', 'match-relative', 'match-boost', 'match-drop');
3032            const code = w.getAttribute('data-code');
3033
3034            if (code === sameCode) {
3035                w.classList.add('active-key');
3036            } else if (code === relCode) {
3037                w.classList.add('match-relative');
3038            } else if (code === boostCode) {
3039                w.classList.add('match-boost');
3040            } else if (code === dropCode) {
3041                w.classList.add('match-drop');
3042            }
3043        });
3044    });
3045}
3046
3047// Update Explorer Tab Text
3048function updateExplorerWheel(code) {
3049    const data = camelotData[code];
3050    if (!data) return;
3051
3052    document.getElementById('explorer-center-code').innerText = code;
3053    document.getElementById('explorer-center-key').innerText = data.key;
3054    document.getElementById('explorer-key-name').innerText = data.key;
3055    document.getElementById('explorer-camelot-badge').innerText = code;
3056    document.getElementById('explorer-mood-desc').innerText = data.mood;
3057
3058    const rel = camelotData[data.rel];
3059    document.getElementById('explorer-relative-key').innerText = rel ? `${rel.key} (${data.rel})` : '--';
3060
3061    const boost = camelotData[data.boost];
3062    document.getElementById('explorer-boost-key').innerText = boost ? `${boost.key} (${data.boost})` : '--';
3063}
3064
3065// Build both wheels initially
3066buildCamelotWheelSVG('camelot-wheel-svg');
3067buildCamelotWheelSVG('explorer-wheel-svg');
3068highlightCamelotWheels(currentCamelotCode);
3069
3070// ================= 4. VIRTUAL PIANO KEYBOARD =================
3071const pianoContainer = document.getElementById('piano-keyboard');
3072
3073const pianoNoteFrequencies = {
3074    'C3': 130.81, 'C#3': 138.59, 'D3': 146.83, 'D#3': 155.56, 'E3': 164.81, 'F3': 174.61, 'F#3': 185.00, 'G3': 196.00, 'G#3': 207.65, 'A3': 220.00, 'A#3': 233.08, 'B3': 246.94,
3075    'C4': 261.63, 'C#4': 277.18, 'D4': 293.66, 'D#4': 311.13, 'E4': 329.63, 'F4': 349.23, 'F#4': 369.99, 'G4': 392.00, 'G#4': 415.30, 'A4': 440.00, 'A#4': 466.16, 'B4': 493.88
3076};
3077
3078function buildPianoKeyboard() {
3079    pianoContainer.innerHTML = '';
3080    const whiteKeys = ['C3', 'D3', 'E3', 'F3', 'G3', 'A3', 'B3', 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4'];
3081    const blackMap = {
3082        'C3': 'C#3', 'D3': 'D#3', 'F3': 'F#3', 'G3': 'G#3', 'A3': 'A#3',
3083        'C4': 'C#4', 'D4': 'D#4', 'F4': 'F#4', 'G4': 'G#4', 'A4': 'A#4'
3084    };
3085
3086    whiteKeys.forEach(note => {
3087        const whiteWrap = document.createElement('div');
3088        whiteWrap.className = 'relative flex-shrink-0';
3089
3090        const whiteKey = document.createElement('button');
3091        whiteKey.className = 'piano-key piano-white w-9 sm:w-11 h-24 bg-white border border-slate-300 rounded-b-lg shadow-md flex items-end justify-center pb-1 text-[10px] font-bold text-slate-700 hover:bg-slate-100';
3092        whiteKey.setAttribute('data-note', note.replace(/[0-9]/g, ''));
3093        whiteKey.innerText = note;
3094        whiteKey.onclick = () => playPianoTone(pianoNoteFrequencies[note]);
3095        whiteWrap.appendChild(whiteKey);
3096
3097        if (blackMap[note]) {
3098            const blackNote = blackMap[note];
3099            const blackKey = document.createElement('button');
3100            blackKey.className = 'piano-key piano-black w-6 sm:w-7 h-15 bg-slate-900 border border-slate-700 rounded-b-md absolute -right-3 sm:-right-3.5 top-0 z-10 shadow-lg text-[9px] font-bold text-slate-300 flex items-end justify-center pb-1 hover:bg-slate-800';
3101            blackKey.setAttribute('data-note', blackNote.replace(/[0-9]/g, ''));
3102            blackKey.innerText = blackNote;
3103            blackKey.onclick = (e) => {
3104                e.stopPropagation();
3105                playPianoTone(pianoNoteFrequencies[blackNote]);
3106            };
3107            whiteWrap.appendChild(blackKey);
3108        }
3109
3110        pianoContainer.appendChild(whiteWrap);
3111    });
3112}
3113
3114function playPianoTone(freq) {
3115    if (!freq) return;
3116    if (!audioContext) {
3117        audioContext = new (window.AudioContext || window.webkitAudioContext)();
3118    }
3119    if (audioContext.state === 'suspended') audioContext.resume();
3120
3121    try {
3122        const osc = audioContext.createOscillator();
3123        const gain = audioContext.createGain();
3124
3125        // Warm harmonic piano tone
3126        osc.type = 'triangle';
3127        osc.frequency.setValueAtTime(freq, audioContext.currentTime);
3128
3129        gain.gain.setValueAtTime(0.35, audioContext.currentTime);
3130        gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 1.2);
3131
3132        osc.connect(gain);
3133        gain.connect(audioContext.destination);
3134
3135        osc.start();
3136        osc.stop(audioContext.currentTime + 1.25);
3137    } catch(e) {}
3138}
3139
3140function highlightPianoScale(scaleNotes) {
3141    const allKeys = pianoContainer.querySelectorAll('.piano-key');
3142    allKeys.forEach(k => {
3143        const note = k.getAttribute('data-note');
3144        if (scaleNotes.includes(note)) {
3145            k.classList.add('scale-active');
3146        } else {
3147            k.classList.remove('scale-active');
3148        }
3149    });
3150}
3151
3152buildPianoKeyboard();
3153
3154// ================= 5. TONIC ROOT NOTE AUDIO PLAYER =================
3155const btnPlayTonic = document.getElementById('btn-play-tonic');
3156const tonicIcon = document.getElementById('tonic-icon');
3157const tonicBtnLabel = document.getElementById('tonic-btn-label');
3158
3159btnPlayTonic.onclick = toggleTonicTone;
3160
3161function toggleTonicTone() {
3162    if (isTonicPlaying) {
3163        stopTonicTone();
3164    } else {
3165        startTonicTone();
3166    }
3167}
3168
3169function startTonicTone() {
3170    if (!audioContext) {
3171        audioContext = new (window.AudioContext || window.webkitAudioContext)();
3172    }
3173    if (audioContext.state === 'suspended') audioContext.resume();
3174
3175    const data = camelotData[currentCamelotCode];
3176    if (!data) return;
3177
3178    const root = data.root;
3179    const freq = pianoNoteFrequencies[`${root}3`] || pianoNoteFrequencies[`${root}4`] || 220;
3180
3181    try {
3182        tonicOscillator = audioContext.createOscillator();
3183        const gain = audioContext.createGain();
3184
3185        tonicOscillator.type = 'sine';
3186        tonicOscillator.frequency.setValueAtTime(freq, audioContext.currentTime);
3187
3188        gain.gain.setValueAtTime(0.3, audioContext.currentTime);
3189
3190        tonicOscillator.connect(gain);
3191        gain.connect(audioContext.destination);
3192
3193        tonicOscillator.start();
3194        isTonicPlaying = true;
3195
3196        btnPlayTonic.classList.replace('from-indigo-500', 'from-emerald-500');
3197        btnPlayTonic.classList.replace('to-purple-600', 'to-teal-600');
3198        tonicBtnLabel.innerText = `Mute Root Note (${root})`;
3199        tonicIcon.className = 'fa-solid fa-volume-xmark';
3200    } catch(e) {}
3201}
3202
3203function stopTonicTone() {
3204    if (tonicOscillator) {
3205        try { tonicOscillator.stop(); } catch(e) {}
3206        tonicOscillator = null;
3207    }
3208    isTonicPlaying = false;
3209    btnPlayTonic.classList.replace('from-emerald-500', 'from-indigo-500');
3210    btnPlayTonic.classList.replace('to-teal-600', 'to-purple-600');
3211    tonicBtnLabel.innerText = 'Play Root Note Tone';
3212    tonicIcon.className = 'fa-solid fa-volume-high';
3213}
3214
3215// ================= 6. AUDIO SCRUBBER CONTROLLER =================
3216const playBtn = document.getElementById('btn-play-pause');
3217const playIcon = document.getElementById('play-icon');
3218const seekProgress = document.getElementById('seek-progress');
3219const currentTimeEl = document.getElementById('player-current-time');
3220const seekBarContainer = document.getElementById('seek-bar-container');
3221
3222playBtn.onclick = togglePlay;
3223
3224function togglePlay() {
3225    if (!currentAudioBuffer) return;
3226    if (isPlaying) pauseAudio();
3227    else playAudio(playbackPauseOffset);
3228}
3229
3230function playAudio(offset = 0) {
3231    if (!audioContext || !currentAudioBuffer) return;
3232    if (audioContext.state === 'suspended') audioContext.resume();
3233
3234    currentSourceNode = audioContext.createBufferSource();
3235    currentSourceNode.buffer = currentAudioBuffer;
3236    currentSourceNode.connect(audioContext.destination);
3237
3238    currentSourceNode.start(0, offset);
3239    playbackStartTime = audioContext.currentTime - offset;
3240    isPlaying = true;
3241    playIcon.className = 'fa-solid fa-pause';
3242
3243    startScrubberLoop();
3244
3245    currentSourceNode.onended = () => {
3246        if (audioContext.currentTime - playbackStartTime >= currentAudioBuffer.duration) {
3247            pauseAudio();
3248            playbackPauseOffset = 0;
3249            updateScrubUI(0);
3250        }
3251    };
3252}
3253
3254function pauseAudio() {
3255    if (currentSourceNode) {
3256        try { currentSourceNode.stop(); } catch(e) {}
3257        currentSourceNode = null;
3258    }
3259    if (isPlaying) {
3260        playbackPauseOffset = audioContext.currentTime - playbackStartTime;
3261    }
3262    isPlaying = false;
3263    if (playIcon) playIcon.className = 'fa-solid fa-play';
3264}
3265
3266function startScrubberLoop() {
3267    if (!isPlaying) return;
3268    const current = (audioContext.currentTime - playbackStartTime);
3269    const percent = Math.min(100, (current / currentAudioBuffer.duration) * 100);
3270    updateScrubUI(percent, current);
3271    requestAnimationFrame(startScrubberLoop);
3272}
3273
3274function updateScrubUI(percent, currentSeconds = 0) {
3275    seekProgress.style.width = percent + '%';
3276    currentTimeEl.innerText = formatTime(currentSeconds);
3277}
3278
3279seekBarContainer.onclick = (e) => {
3280    if (!currentAudioBuffer) return;
3281    const rect = seekBarContainer.getBoundingClientRect();
3282    const pos = (e.clientX - rect.left) / rect.width;
3283    const newTime = pos * currentAudioBuffer.duration;
3284    
3285    playbackPauseOffset = newTime;
3286    if (isPlaying) {
3287        pauseAudio();
3288        playAudio(newTime);
3289    } else {
3290        updateScrubUI(pos * 100, newTime);
3291    }
3292};
3293
3294function formatTime(seconds) {
3295    seconds = Math.floor(seconds || 0);
3296    const m = Math.floor(seconds / 60);
3297    const s = seconds % 60;
3298    return `${m}:${s < 10 ? '0' : ''}${s}`;
3299}
3300
3301// ================= 7. REAL-TIME MICROPHONE PITCH DETECTOR =================
3302let micStream = null;
3303let micAudioCtx = null;
3304let micAnalyser = null;
3305let micAnimId = null;
3306let isMicListening = false;
3307let micStartTime = null;
3308let micTimerInterval = null;
3309let micPitchHistogram = new Float32Array(12).fill(0);
3310
3311const btnToggleMic = document.getElementById('btn-toggle-mic');
3312const btnResetMic = document.getElementById('btn-reset-mic');
3313const micBtnIcon = document.getElementById('mic-btn-icon');
3314const micBtnLabel = document.getElementById('mic-btn-label');
3315const micCanvas = document.getElementById('mic-canvas');
3316const micIdleMsg = document.getElementById('mic-idle-msg');
3317const micPitchNote = document.getElementById('mic-pitch-note');
3318const micPitchFreq = document.getElementById('mic-pitch-freq');
3319const micEstimatedKey = document.getElementById('mic-estimated-key');
3320const micEstimatedCamelot = document.getElementById('mic-estimated-camelot');
3321const micDurationDisplay = document.getElementById('mic-duration-display');
3322const micStatusBadge = document.getElementById('mic-status-badge');
3323const canvasCtx = micCanvas ? micCanvas.getContext('2d') : null;
3324
3325if (btnToggleMic) btnToggleMic.onclick = toggleMicListening;
3326if (btnResetMic) btnResetMic.onclick = resetMicStats;
3327
3328async function toggleMicListening() {
3329    if (isMicListening) {
3330        stopMicListening();
3331    } else {
3332        await startMicListening();
3333    }
3334}
3335
3336function startMicTimer() {
3337    if (micTimerInterval) clearInterval(micTimerInterval);
3338    if (!micStartTime) micStartTime = Date.now();
3339    micTimerInterval = setInterval(() => {
3340        if (!isMicListening) return;
3341        const elapsedSec = Math.floor((Date.now() - micStartTime) / 1000);
3342        const m = Math.floor(elapsedSec / 60);
3343        const s = elapsedSec % 60;
3344        if (micDurationDisplay) {
3345            micDurationDisplay.innerText = `${m < 10 ? '0' : ''}${m}:${s < 10 ? '0' : ''}${s}`;
3346        }
3347    }, 500);
3348}
3349
3350function stopMicTimer() {
3351    if (micTimerInterval) {
3352        clearInterval(micTimerInterval);
3353        micTimerInterval = null;
3354    }
3355}
3356
3357function resetMicStats() {
3358    micPitchHistogram.fill(0);
3359    micStartTime = isMicListening ? Date.now() : null;
3360    if (micPitchNote) micPitchNote.innerText = '--';
3361    if (micPitchFreq) micPitchFreq.innerText = '-- Hz';
3362    if (micEstimatedKey) micEstimatedKey.innerText = '--';
3363    if (micEstimatedCamelot) micEstimatedCamelot.innerText = 'Camelot: --';
3364    if (micDurationDisplay) micDurationDisplay.innerText = '00:00';
3365    if (micStatusBadge) micStatusBadge.innerText = isMicListening ? 'Listening for pitch...' : 'Microphone is paused.';
3366}
3367
3368async function startMicListening() {
3369    try {
3370        const AudioCtx = window.AudioContext || window.webkitAudioContext;
3371        micAudioCtx = new AudioCtx();
3372        if (micAudioCtx.state === 'suspended') {
3373            await micAudioCtx.resume();
3374        }
3375
3376        try {
3377            micStream = await navigator.mediaDevices.getUserMedia({
3378                audio: {
3379                    echoCancellation: false,
3380                    noiseSuppression: false,
3381                    autoGainControl: false
3382                },
3383                video: false
3384            });
3385        } catch (errConstraint) {
3386            micStream = await navigator.mediaDevices.getUserMedia({ audio: true });
3387        }
3388
3389        const source = micAudioCtx.createMediaStreamSource(micStream);
3390
3391        micAnalyser = micAudioCtx.createAnalyser();
3392        micAnalyser.fftSize = 1024;
3393        micAnalyser.smoothingTimeConstant = 0.2;
3394
3395        source.connect(micAnalyser);
3396
3397        isMicListening = true;
3398        if (micIdleMsg) micIdleMsg.style.display = 'none';
3399
3400        btnToggleMic.classList.replace('from-emerald-500', 'from-rose-500');
3401        btnToggleMic.classList.replace('to-teal-600', 'to-red-600');
3402        btnToggleMic.classList.replace('hover:from-emerald-400', 'hover:from-rose-400');
3403        btnToggleMic.classList.replace('hover:to-teal-500', 'hover:to-red-500');
3404        btnToggleMic.classList.replace('shadow-emerald-500/25', 'shadow-rose-500/25');
3405        micBtnIcon.className = 'fa-solid fa-microphone-slash text-base';
3406        micBtnLabel.innerText = 'Stop Listening';
3407        if (micStatusBadge) micStatusBadge.innerText = 'Listening to ambient pitch...';
3408        showNotification('Microphone online. Hum, sing, or play an instrument.', 'info');
3409
3410        micStartTime = Date.now();
3411        startMicTimer();
3412        drawMicPitchVisualizer();
3413
3414    } catch (err) {
3415        console.error('Microphone access error:', err);
3416        showNotification('Microphone permission denied or device not found.', 'error');
3417        stopMicListening();
3418    }
3419}
3420
3421function stopMicListening() {
3422    if (!isMicListening) return;
3423    isMicListening = false;
3424    stopMicTimer();
3425
3426    if (micAnimId) cancelAnimationFrame(micAnimId);
3427    if (micStream) {
3428        micStream.getTracks().forEach(t => t.stop());
3429        micStream = null;
3430    }
3431    if (micAudioCtx) {
3432        try { micAudioCtx.close(); } catch(e) {}
3433        micAudioCtx = null;
3434    }
3435
3436    btnToggleMic.classList.replace('from-rose-500', 'from-emerald-500');
3437    btnToggleMic.classList.replace('to-red-600', 'to-teal-600');
3438    btnToggleMic.classList.replace('hover:from-rose-400', 'hover:from-emerald-400');
3439    btnToggleMic.classList.replace('hover:to-red-500', 'hover:to-teal-500');
3440    btnToggleMic.classList.replace('shadow-rose-500/25', 'shadow-emerald-500/25');
3441    micBtnIcon.className = 'fa-solid fa-microphone text-base';
3442    micBtnLabel.innerText = 'Start Listening';
3443    if (micIdleMsg) micIdleMsg.style.display = 'flex';
3444    if (micStatusBadge) micStatusBadge.innerText = 'Microphone paused.';
3445
3446    if (canvasCtx) canvasCtx.clearRect(0, 0, micCanvas.width, micCanvas.height);
3447}
3448
3449// Autocorrelation Pitch Detector for Time-Domain Audio
3450function autoCorrelate(buf, sampleRate) {
3451    let SIZE = buf.length;
3452    let rms = 0;
3453
3454    for (let i = 0; i < SIZE; i++) {
3455        let val = buf[i];
3456        rms += val * val;
3457    }
3458    rms = Math.sqrt(rms / SIZE);
3459    if (rms < 0.012) return -1; // Silence gate
3460
3461    let r1 = 0, r2 = SIZE - 1, thres = 0.2;
3462    for (let i = 0; i < SIZE / 2; i++) {
3463        if (Math.abs(buf[i]) < thres) { r1 = i; break; }
3464    }
3465    for (let i = 1; i < SIZE / 2; i++) {
3466        if (Math.abs(buf[SIZE - i]) < thres) { r2 = SIZE - i; break; }
3467    }
3468
3469    buf = buf.slice(r1, r2);
3470    SIZE = buf.length;
3471
3472    let c = new Array(SIZE).fill(0);
3473    for (let i = 0; i < SIZE; i++) {
3474        for (let j = 0; j < SIZE - i; j++) {
3475            c[i] = c[i] + buf[j] * buf[j + i];
3476        }
3477    }
3478
3479    let d = 0; while (c[d] > c[d + 1]) d++;
3480    let maxval = -1, maxpos = -1;
3481    for (let i = d; i < SIZE; i++) {
3482        if (c[i] > maxval) {
3483            maxval = c[i];
3484            maxpos = i;
3485        }
3486    }
3487    let T0 = maxpos;
3488
3489    let x1 = c[T0 - 1], x2 = c[T0], x3 = c[T0 + 1];
3490    let a = (x1 + x3 - 2 * x2) / 2;
3491    let b = (x3 - x1) / 2;
3492    if (a) T0 = T0 - b / (2 * a);
3493
3494    return sampleRate / T0;
3495}
3496
3497function noteFromPitch(frequency) {
3498    let noteNum = 12 * (Math.log(frequency / 440) / Math.log(2));
3499    return Math.round(noteNum) + 69;
3500}
3501
3502function drawMicPitchVisualizer() {
3503    if (!isMicListening || !micAnalyser) return;
3504
3505    const buffer = new Float32Array(micAnalyser.fftSize);
3506    micAnalyser.getFloatTimeDomainData(buffer);
3507
3508    // Draw Sleek Oscilloscope Waveform
3509    canvasCtx.fillStyle = 'rgba(8, 10, 19, 0.45)';
3510    canvasCtx.fillRect(0, 0, micCanvas.width, micCanvas.height);
3511
3512    canvasCtx.lineWidth = 2;
3513    canvasCtx.strokeStyle = '#10b981';
3514    canvasCtx.beginPath();
3515
3516    const sliceWidth = micCanvas.width * 1.0 / buffer.length;
3517    let x = 0;
3518
3519    for (let i = 0; i < buffer.length; i++) {
3520        const v = buffer[i] * 35;
3521        const y = (micCanvas.height / 2) + v;
3522
3523        if (i === 0) canvasCtx.moveTo(x, y);
3524        else canvasCtx.lineTo(x, y);
3525
3526        x += sliceWidth;
3527    }
3528    canvasCtx.stroke();
3529
3530    // Calculate live pitch
3531    const pitch = autoCorrelate(buffer, micAudioCtx.sampleRate);
3532    if (pitch !== -1 && pitch >= 60 && pitch <= 1200) {
3533        const midi = noteFromPitch(pitch);
3534        const noteIndex = midi % 12;
3535        const noteName = notes[noteIndex];
3536
3537        micPitchNote.innerText = noteName;
3538        micPitchFreq.innerText = `${pitch.toFixed(1)} Hz`;
3539
3540        // Accumulate pitch histogram to estimate prevailing key
3541        micPitchHistogram[noteIndex] += 1;
3542
3543        // Estimate key every few samples
3544        const sum = micPitchHistogram.reduce((a, b) => a + b, 0);
3545        if (sum >= 12) {
3546            const maxHist = Math.max(...micPitchHistogram) || 1;
3547            const normHist = Array.from(micPitchHistogram).map(v => v / maxHist);
3548
3549            let bestScore = -Infinity;
3550            let bestKeyStr = 'A Minor';
3551            let bestCode = '8A';
3552
3553            for (let r = 0; r < 12; r++) {
3554                const rotated = [];
3555                for (let i = 0; i < 12; i++) rotated.push(normHist[(r + i) % 12]);
3556
3557                const scoreMaj = 0.6 * pearsonCorrelation(rotated, keyProfiles.shaathMajor) + 0.4 * pearsonCorrelation(rotated, keyProfiles.temperleyMajor);
3558                if (scoreMaj > bestScore) {
3559                    bestScore = scoreMaj;
3560                    bestKeyStr = notes[r] + ' Major';
3561                    bestCode = findCamelotCodeByKey(bestKeyStr);
3562                }
3563
3564                const scoreMin = 0.6 * pearsonCorrelation(rotated, keyProfiles.shaathMinor) + 0.4 * pearsonCorrelation(rotated, keyProfiles.temperleyMinor);
3565                if (scoreMin > bestScore) {
3566                    bestScore = scoreMin;
3567                    bestKeyStr = notes[r] + ' Minor';
3568                    bestCode = findCamelotCodeByKey(bestKeyStr);
3569                }
3570            }
3571
3572            micEstimatedKey.innerText = bestKeyStr;
3573            micEstimatedCamelot.innerText = `Camelot: ${bestCode}`;
3574            micStatusBadge.innerText = 'Key estimated from voice/hum';
3575        }
3576    }
3577
3578    micAnimId = requestAnimationFrame(drawMicPitchVisualizer);
3579}
3580</script>
3580
3581
3582<!-- ================= FOOTER SECTION ================= -->
3583<footer class="footer-glass mt-0 py-12 relative z-10">
3584  <div class="container mx-auto px-4 max-w-7xl">
3585    <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-8 mb-10">
3586      <!-- Brand Column -->
3587      <div class="sm:col-span-2 lg:col-span-1">
3588        <a href="/" class="flex items-center gap-3 mb-4 group inline-flex select-none">
3589          <div class="w-10 h-10 glass rounded-xl flex items-center justify-center text-white bg-white/10 border border-white/10 group-hover:scale-105 transition-transform duration-200 shadow-inner">
3590            <svg class="h-5 w-5 text-white" viewBox="0 0 24 24" fill="currentColor">
3591              <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
3592            </svg>
3593          </div>
3594          <span class="footer-logo text-2xl font-extrabold font-outfit">Vocal Remover</span>
3595        </a>
3596        <p class="text-white/70 text-sm leading-relaxed mb-4">
3597          Professional in-browser audio workstation & AI stem separation suite for musicians, producers, sound engineers, and podcasters.
3598        </p>
3599        <div class="inline-flex items-center gap-2 px-3 py-1.5 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-xs text-emerald-400 font-medium">
3600          <i class="fa-solid fa-shield-halved text-xs"></i> 100% In-Browser Privacy
3601        </div>
3602      </div>
3603
3604      <!-- Column 2: Audio Tools (AI & DSP Engines) -->
3605      <div>
3606        <h2 class="text-white font-bold text-sm uppercase tracking-wider mb-4 font-outfit">Audio Tools</h2>
3607        <ul class="space-y-2.5">
3608          <li><a href="https://vocalremovers.ai/#dropsec" class="footer-link text-sm">Free AI Vocal Remover</a></li>
3609          <li><a href="/bpm-finder/" class="footer-link text-sm">BPM & Key Finder</a></li>
3610          <li><a href="/key-finder/" class="footer-link text-sm">Key Finder & Camelot</a></li>
3611          <li><a href="/pitch-changer/" class="footer-link text-sm">Pitch Changer</a></li>
3612          <li><a href="/bass-booster/" class="footer-link text-sm">Bass Booster</a></li>
3613          <li><a href="/noise-reducer/" class="footer-link text-sm">Noise Reducer</a></li>
3614          <li><a href="/audio-effects/" class="footer-link text-sm">Audio Effects</a></li>
3615        </ul>
3616      </div>
3617
3618      <!-- Column 3: Audio Utilities (Editing & Conversion) -->
3619      <div>
3620        <h2 class="text-white font-bold text-sm uppercase tracking-wider mb-4 font-outfit">Audio Utilities</h2>
3621        <ul class="space-y-2.5">
3622          <li><a href="/mp3-tag-editor/" class="footer-link text-sm">MP3 Tag Editor</a></li>
3623          <li><a href="/audio-converter/" class="footer-link text-sm">Audio Converter</a></li>
3624          <li><a href="/audio-cutter/" class="footer-link text-sm">Audio Cutter</a></li>
3625          <li><a href="/audio-joiner/" class="footer-link text-sm">Audio Joiner</a></li>
3626          <li><a href="/equalizer/" class="footer-link text-sm">Audio Equalizer</a></li>
3627          <li><a href="/voice-recorder/" class="footer-link text-sm">Voice Recorder</a></li>
3628        </ul>
3629      </div>
3630
3631      <!-- Column 4: Platform & Guide -->
3632      <div>
3633        <h2 class="text-white font-bold text-sm uppercase tracking-wider mb-4 font-outfit">Platform</h2>
3634        <ul class="space-y-2.5">
3635          <li><a href="/#features" class="footer-link text-sm">Features</a></li>
3636          <li><a href="/#how-it-works" class="footer-link text-sm">How It Works</a></li>
3637          <li><a href="/#faqs" class="footer-link text-sm">Studio FAQs</a></li>
3638          <li><a href="/contact/" class="footer-link text-sm">Help Center</a></li>
3639        </ul>
3640      </div>
3641
3642      <!-- Column 5: Company & Legal -->
3643      <div>
3644        <h2 class="text-white font-bold text-sm uppercase tracking-wider mb-4 font-outfit">Company</h2>
3645        <ul class="space-y-2.5">
3646          <li><a href="/about/" class="footer-link text-sm">About Us</a></li>
3647          <li><a href="/contact/" class="footer-link text-sm">Contact Us</a></li>
3648          <li><a href="/privacy-policy/" class="footer-link text-sm">Privacy Policy</a></li>
3649          <li><a href="/disclaimer/" class="footer-link text-sm">Disclaimer</a></li>
3650        </ul>
3651      </div>
3652    </div>
3653
3654    <!-- Divider -->
3655    <div class="h-px bg-white/10 mb-8"></div>
3656
3657    <!-- Bottom Footer -->
3658    <div class="flex flex-col sm:flex-row items-center justify-between gap-4">
3659      <div class="text-white/60 text-xs sm:text-sm text-center sm:text-left">
3660        &copy; 2026 VocalRemovers.ai. All rights reserved. Zero audio data stored on servers.
3661      </div>
3662
3663      <!-- Legal Links -->
3664      <div class="flex items-center gap-6 text-xs sm:text-sm">
3665        <a href="/privacy-policy/" class="footer-link">Privacy Policy</a>
3666        <a href="/disclaimer/" class="footer-link">Disclaimer</a>
3667        <a href="/contact/" class="footer-link">Contact</a>
3668      </div>
3669    </div>
3670  </div>
3671</footer>
3672	    
3673<script>
3674        const CONFIG = {
3675            clientId: '658101237139-kgekg6onkp1g1ckmp8q5mbf28i3bjaq0.apps.googleusercontent.com',
3676            apiKey: 'AIzaSyB_MWxjdXxwB9z_hXZH5_f0YPQhjG4M9Bw',
3677            appId: '658101237139', 
3678            scope: 'https://www.googleapis.com/auth/drive.file',
3679            discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest']
3680        };
3681
3682        let tokenClient;
3683        let accessToken = null;
3684        let pickerInited = false;
3685        let gisInited = false;
3686
3687        function onApiLoad() {
3688            gapi.load('client:picker', initializePicker);
3689        }
3690
3691        function gisLoaded() {
3692            tokenClient = google.accounts.oauth2.initTokenClient({
3693                client_id: CONFIG.clientId,
3694                scope: CONFIG.scope,
3695                callback: '', 
3696            });
3697            gisInited = true;
3698        }
3699        function initializePicker() {
3700            gapi.client.init({
3701                apiKey: CONFIG.apiKey,
3702                discoveryDocs: CONFIG.discoveryDocs
3703            }).then(function() {
3704                pickerInited = true;
3705            });
3706        }
3707
3708        function loadGoogleDriveOnDemand(callback) {
3709            if (window.gapi && window.google && pickerInited && gisInited) {
3710                if (callback) callback();
3711                return;
3712            }
3713            const s1 = document.createElement('script');
3714            s1.src = 'https://apis.google.com/js/api.js';
3715            s1.onload = () => {
3716                gapi.load('client:picker', () => {
3717                    initializePicker();
3718                    const s2 = document.createElement('script');
3719                    s2.src = 'https://accounts.google.com/gsi/client';
3720                    s2.onload = () => {
3721                        gisLoaded();
3722                        if (callback) callback();
3723                    };
3724                    document.head.appendChild(s2);
3725                });
3726            };
3727            document.head.appendChild(s1);
3728        }
3729</script>
3729
3730    
3731    <!-- Load jQuery early to ensure script blocks work correctly -->
3732    
3732<script src="https://code.jquery.com/jquery-3.6.3.min.js"></script>
3732
3733
3734<script type="text/javascript" src="https://www.dropbox.com/static/api/2/dropins.js" id="dropboxjs" data-app-key="7vdgy4nz22i09tq" async></script>
vendor: 1 bytes, line 3734
3734
3735<script src="https://js.live.net/v7.2/OneDrive.js" async></script>
3735
3736    
3736<script src="https://unpkg.com/[email protected]/dist/wavesurfer.js" defer></script>
3736
3737   <!-- 
3737<script src="/js/main.js?v=4.3" defer></script>
3737 -->
3738   
3738<script src="/js/gdrive.js?v=5.2" defer></script>
3738
3739<link rel="preload" href="/css/modern_css.css?v=1.1" as="style" onload="this.rel='stylesheet'">
3740<noscript><link rel="stylesheet" href="/css/modern_css.css?v=1.1"></noscript>
3741<link rel="preload" href="https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/all.min.css" as="style" onload="this.rel='stylesheet'">
3742<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/all.min.css"></noscript>
3743<script defer src="https://cdn.tailwindcss.com"></script>
3743
3744<!-- Google tag (gtag.js) -->
vendor: 64 bytes, lines 3744-3745
3744
3745<script async src="https://www.googletagmanager.com/gtag/js?id=
3745G-FHELE5QWPR
vendor: 12 bytes, line 3745
3745"></script>
3746<script>
3747  
vendor: 134 bytes, lines 3747-3751
3747window.dataLayer = window.dataLayer || [];
3748  function gtag(){dataLayer.push(arguments);}
3749  gtag('js', new Date());
3750
3751  gtag('config', '
3751G-FHELE5QWPR
vendor: 4 bytes, line 3751
3751');
3752</script>
vendor: 1 bytes, line 3752
3752
3753<script>
3754function processSubpage(filename, url){
3755   loadFromRemoteUrl(url, filename);
3756}
3757</script>
3757
3758</body>
3759</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.