ThatAIguyA world of ideas

THE CONSCIOUSNESS ATLAS
Read. Experiment. Follow the evidence.

ConsciousnessLearn · Explore · Joseph Anady

Janady13/Jennys-Drumste / drum-song-mode.html · Lines 1501–2622

Source identity, revision, and recovered contents.

GitHub sourceSource text recovered

A continuation of Janady13/Jennys-Drumste / drum-song-mode.html. The complete source identity, every recorded occurrence, and linked equations are on its first page.

Source transcript · continued

Download complete source text ↓2,622 lines · 94.3 KB
while (time < duration) { pattern.push([time, 'kick']); pattern.push([time, 'ride']); pattern.push([time + beatMs * 2, 'snare']); pattern.push([time + beatMs * 2, 'ride']); pattern.push([time + beatMs * 4, 'kick']); pattern.push([time + beatMs * 4, 'ride']); pattern.push([time + beatMs * 6, 'snare']); pattern.push([time + beatMs * 6, 'ride']); time += beatMs * 8; } return pattern; } } ]; // ═══════════════════════════════════════════════════════════════════════════ // GAME STATE // ═══════════════════════════════════════════════════════════════════════════ let gameState = { isPlaying: false, isPracticeMode: false, currentSong: null, notes: [], score: 0, combo: 0, maxStreak: 0, multiplier: 1, notesHit: 0, notesMissed: 0, totalNotes: 0, starPower: 0, starPowerActive: false, crowdMeter: 50, lastNoteTime: 0, gameStartTime: 0, playbackSpeed: 1 }; // YouTube Player let player = null; let isPlayerReady = false; // Canvas contexts let highwayCanvas, highwayCtx; let effectsCanvas, effectsCtx; // MIDI let midiAccess = null; // Tap tempo let tapTimes = []; let tapTimeout = null; // Active effects let activeEffects = []; // Animation frame let animationId = null; // ═══════════════════════════════════════════════════════════════════════════ // INITIALIZATION // ═══════════════════════════════════════════════════════════════════════════ function init() { // Setup canvases highwayCanvas = document.getElementById('noteHighway'); effectsCanvas = document.getElementById('effectsCanvas'); resizeCanvases(); window.addEventListener('resize', resizeCanvases); highwayCtx = highwayCanvas.getContext('2d'); effectsCtx = effectsCanvas.getContext('2d'); // Load song library loadSongLibrary(); // Setup SEFERIM initSeferim(); // Setup keyboard controls setupKeyboardControls(); // Setup drum pad clicks setupDrumPads(); // Setup speed slider setupSpeedControl(); // Try to connect MIDI connectMIDI(); // Start render loop requestAnimationFrame(gameLoop); } function resizeCanvases() { const container = document.querySelector('.highway-container'); highwayCanvas.width = container.clientWidth; highwayCanvas.height = container.clientHeight; effectsCanvas.width = window.innerWidth; effectsCanvas.height = window.innerHeight; } // ═══════════════════════════════════════════════════════════════════════════ // YOUTUBE PLAYER // ═══════════════════════════════════════════════════════════════════════════ function onYouTubeIframeAPIReady() { player = new YT.Player('youtube-player', { height: '100%', width: '100%', videoId: '', playerVars: { 'playsinline': 1, 'controls': 0, 'disablekb': 1, 'modestbranding': 1, 'rel': 0 }, events: { 'onReady': onPlayerReady, 'onStateChange': onPlayerStateChange, 'onError': onPlayerError } }); } function onPlayerReady(event) { isPlayerReady = true; console.log('YouTube player ready'); document.getElementById('loadingIndicator').classList.remove('active'); } function onPlayerStateChange(event) { if (event.data === YT.PlayerState.PLAYING) { if (!gameState.isPlaying && gameState.notes.length > 0) { // Sync game start with video gameState.gameStartTime = performance.now() - (player.getCurrentTime() * 1000); } } else if (event.data === YT.PlayerState.ENDED) { endGame(); } } function onPlayerError(event) { console.error('YouTube player error:', event.data); alert('Error loading video. Please check the URL and try again.'); document.getElementById('loadingIndicator').classList.remove('active'); } function loadYouTubeVideo() { const url = document.getElementById('youtubeUrl').value.trim(); const videoId = extractYouTubeId(url); if (!videoId) { alert('Please enter a valid YouTube URL'); return; } if (!isPlayerReady) { alert('YouTube player is still loading. Please wait...'); return; } document.getElementById('loadingIndicator').classList.add('active'); // Load the video player.loadVideoById(videoId); player.pauseVideo(); // Wait for video to load and get duration setTimeout(() => { const duration = player.getDuration() * 1000; const bpm = parseInt(document.getElementById('bpmInput').value) || 120; // Generate pattern for the video generatePatternForVideo(videoId, bpm, duration); document.getElementById('nowPlayingTitle').textContent = 'Custom Song'; document.getElementById('nowPlayingArtist').textContent = 'YouTube Video'; document.getElementById('loadingIndicator').classList.remove('active'); // Set current song gameState.currentSong = { title: 'Custom Song', artist: 'YouTube', youtubeId: videoId, bpm: bpm }; }, 2000); } function extractYouTubeId(url) { const patterns = [ /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/, /^([a-zA-Z0-9_-]{11})$/ ]; for (const pattern of patterns) { const match = url.match(pattern); if (match) return match[1]; } return null; } function generatePatternForVideo(videoId, bpm, duration) { // Check if we have a predefined pattern const predefinedSong = SONG_DATABASE.find(s => s.youtubeId === videoId); if (predefinedSong) { gameState.notes = predefinedSong.generatePattern(duration).map(([time, drum]) => ({ time: time + (predefinedSong.offset || 0), drum: drum, hit: false, missed: false, isStarNote: Math.random() < 0.1 })); } else { // Generate a basic pattern based on BPM gameState.notes = generateBasicPattern(bpm, duration); } gameState.totalNotes = gameState.notes.length; updateNotesDisplay(); } function generateBasicPattern(bpm, duration) { const pattern = []; const beatMs = 60000 / bpm; let time = beatMs * 4; // Start after 4 beats while (time < duration - 5000) { // Random pattern variety const patternType = Math.floor(time / (beatMs * 16)) % 4; if (patternType === 0) { // Basic rock beat pattern.push({ time, drum: 'kick', hit: false, missed: false, isStarNote: Math.random() < 0.08 }); pattern.push({ time, drum: 'hihat', hit: false, missed: false, isStarNote: false }); pattern.push({ time: time + beatMs, drum: 'hihat', hit: false, missed: false, isStarNote: false }); pattern.push({ time: time + beatMs * 2, drum: 'snare', hit: false, missed: false, isStarNote: Math.random() < 0.08 }); pattern.push({ time: time + beatMs * 2, drum: 'hihat', hit: false, missed: false, isStarNote: false }); pattern.push({ time: time + beatMs * 3, drum: 'hihat', hit: false, missed: false, isStarNote: false }); } else if (patternType === 1) { // Crash on downbeat pattern.push({ time, drum: 'crash', hit: false, missed: false, isStarNote: Math.random() < 0.1 }); pattern.push({ time, drum: 'kick', hit: false, missed: false, isStarNote: false }); pattern.push({ time: time + beatMs * 2, drum: 'snare', hit: false, missed: false, isStarNote: false }); pattern.push({ time: time + beatMs * 2, drum: 'kick', hit: false, missed: false, isStarNote: false }); for (let i = 0; i < 4; i++) { pattern.push({ time: time + beatMs * i, drum: 'hihat', hit: false, missed: false, isStarNote: false }); } } else if (patternType === 2) { // Tom fills pattern.push({ time, drum: 'kick', hit: false, missed: false, isStarNote: false }); pattern.push({ time: time + beatMs, drum: 'tom1', hit: false, missed: false, isStarNote: Math.random() < 0.1 }); pattern.push({ time: time + beatMs * 2, drum: 'snare', hit: false, missed: false, isStarNote: false }); pattern.push({ time: time + beatMs * 3, drum: 'tom2', hit: false, missed: false, isStarNote: Math.random() < 0.1 }); } else { // Ride pattern pattern.push({ time, drum: 'kick', hit: false, missed: false, isStarNote: false }); pattern.push({ time: time + beatMs * 2, drum: 'snare', hit: false, missed: false, isStarNote: false }); for (let i = 0; i < 4; i++) { pattern.push({ time: time + beatMs * i, drum: 'ride', hit: false, missed: false, isStarNote: false }); } } time += beatMs * 4; } // Sort by time pattern.sort((a, b) => a.time - b.time); return pattern; } // ═══════════════════════════════════════════════════════════════════════════ // SONG LIBRARY // ═══════════════════════════════════════════════════════════════════════════ function loadSongLibrary() { const container = document.getElementById('songLibrary'); container.innerHTML = ''; SONG_DATABASE.forEach(song => { const card = document.createElement('div'); card.className = 'song-card'; card.onclick = () => selectSong(song); card.innerHTML = ` <div class="song-title">${song.title}</div> <div class="song-artist">${song.artist}</div> <div class="song-meta"> <span class="song-bpm">${song.bpm} BPM</span> <span class="song-difficulty diff-${song.difficulty}">${song.difficulty}</span> </div> `; container.appendChild(card); }); } function selectSong(song) { // Update UI document.querySelectorAll('.song-card').forEach(c => c.classList.remove('active')); event.currentTarget.classList.add('active'); // Load video if (isPlayerReady) { document.getElementById('loadingIndicator').classList.add('active'); player.loadVideoById(song.youtubeId); player.pauseVideo(); document.getElementById('bpmInput').value = song.bpm; setTimeout(() => { const duration = player.getDuration() * 1000 || 180000; gameState.notes = song.generatePattern(duration).map(([time, drum]) => ({ time: time + (song.offset || 0), drum: drum, hit: false, missed: false, isStarNote: Math.random() < 0.1 })); gameState.totalNotes = gameState.notes.length; gameState.currentSong = song; document.getElementById('nowPlayingTitle').textContent = song.title; document.getElementById('nowPlayingArtist').textContent = song.artist; document.getElementById('loadingIndicator').classList.remove('active'); updateNotesDisplay(); }, 1500); } } // ═══════════════════════════════════════════════════════════════════════════ // GAME CONTROLS // ═══════════════════════════════════════════════════════════════════════════ function startGame() { if (!gameState.currentSong && gameState.notes.length === 0) { alert('Please select a song or load a YouTube video first!'); return; } // Reset game state resetGameState(); // Show countdown showCountdown(() => { gameState.isPlaying = true; gameState.gameStartTime = performance.now(); // Start video playback with speed player.setPlaybackRate(gameState.playbackSpeed); player.seekTo(0); player.playVideo(); document.getElementById('startBtn').textContent = 'PLAYING...'; document.getElementById('startBtn').disabled = true; }); } function resetGameState() { gameState.score = 0; gameState.combo = 0; gameState.maxStreak = 0; gameState.multiplier = 1; gameState.notesHit = 0; gameState.notesMissed = 0; gameState.starPower = 0; gameState.starPowerActive = false; gameState.crowdMeter = 50; // Reset notes gameState.notes.forEach(note => { note.hit = false; note.missed = false; }); updateAllDisplays(); } function showCountdown(callback) { const overlay = document.getElementById('countdownOverlay'); const numberEl = document.getElementById('countdownNumber'); const textEl = document.getElementById('countdownText'); overlay.classList.add('active'); let count = 3; textEl.textContent = gameState.currentSong ? gameState.currentSong.title : 'Get Ready!'; const interval = setInterval(() => { if (count > 0) { numberEl.textContent = count; numberEl.style.animation = 'none'; numberEl.offsetHeight; // Trigger reflow numberEl.style.animation = 'countdownPulse 1s ease-in-out'; count--; } else { numberEl.textContent = 'GO!'; numberEl.style.color = '#00ff88'; setTimeout(() => { overlay.classList.remove('active'); numberEl.style.color = ''; callback(); }, 500); clearInterval(interval); } }, 1000); } function endGame() { gameState.isPlaying = false; player.pauseVideo(); document.getElementById('startBtn').textContent = 'START'; document.getElementById('startBtn').disabled = false; // Process with SEFERIM if (window.SEFERIM && gameState.currentSong) { const accuracy = gameState.totalNotes > 0 ? (gameState.notesHit / gameState.totalNotes) * 100 : 0; window.SEFERIM.processSongLearning( gameState.currentSong, { overall: accuracy }, accuracy ); } // Show results showResults(); } function showResults() { const accuracy = gameState.totalNotes > 0 ? Math.round((gameState.notesHit / gameState.totalNotes) * 100) : 0; // Calculate stars let stars = 0; if (accuracy >= 95) stars = 5; else if (accuracy >= 85) stars = 4; else if (accuracy >= 70) stars = 3; else if (accuracy >= 50) stars = 2; else if (accuracy >= 30) stars = 1; document.getElementById('resultsSong').textContent = gameState.currentSong ? gameState.currentSong.title : 'Custom Song'; document.getElementById('resultsStars').textContent = '★'.repeat(stars) + '☆'.repeat(5 - stars); document.getElementById('resultScore').textContent = gameState.score.toLocaleString(); document.getElementById('resultAccuracy').textContent = accuracy + '%'; document.getElementById('resultStreak').textContent = gameState.maxStreak; document.getElementById('resultNotes').textContent = `${gameState.notesHit}/${gameState.totalNotes}`; document.getElementById('resultsOverlay').classList.add('active'); } function restartSong() { document.getElementById('resultsOverlay').classList.remove('active'); startGame(); } function backToMenu() { document.getElementById('resultsOverlay').classList.remove('active'); } function togglePractice() { const toggle = document.getElementById('practiceToggle'); toggle.classList.toggle('active'); gameState.isPracticeMode = toggle.classList.contains('active'); } // ═══════════════════════════════════════════════════════════════════════════ // INPUT HANDLING // ═══════════════════════════════════════════════════════════════════════════ function setupKeyboardControls() { document.addEventListener('keydown', (e) => { const drum = KEY_MAP[e.key.toLowerCase()]; if (drum) { e.preventDefault(); handleDrumHit(drum); } // Star power activation if (e.key === 'Enter' && gameState.starPower >= 50 && !gameState.starPowerActive) { activateStarPower(); } }); } function setupDrumPads() { document.querySelectorAll('.drum-lane').forEach(pad => { pad.addEventListener('mousedown', () => { const drum = pad.dataset.drum; handleDrumHit(drum); }); }); } async function connectMIDI() { try { midiAccess = await navigator.requestMIDIAccess(); const inputs = midiAccess.inputs.values(); for (const input of inputs) { input.onmidimessage = handleMIDIMessage; } console.log('MIDI connected'); } catch (err) { console.log('MIDI not available:', err); } } function handleMIDIMessage(message) { const [status, note, velocity] = message.data; if (status >= 144 && status <= 159 && velocity > 0) { const drum = DRUM_MIDI_MAP[note]; if (drum) { handleDrumHit(drum); } } } function handleDrumHit(drum) { // Visual feedback const pad = document.querySelector(`.drum-lane[data-drum="${drum}"]`); if (pad) { pad.classList.add('hit'); setTimeout(() => pad.classList.remove('hit'), 100); } // Create hit effect createHitEffect(drum); if (!gameState.isPlaying) return; // Check for note hit const currentTime = performance.now() - gameState.gameStartTime; const adjustedTime = currentTime * gameState.playbackSpeed; checkNoteHit(drum, adjustedTime); // Process with SEFERIM if (window.SEFERIM) { window.SEFERIM.processDrumHit(drum, 100, adjustedTime, adjustedTime, gameState.currentSong ? gameState.currentSong.title : 'freeplay'); } } function checkNoteHit(drum, currentTime) { // Find closest unhit note for this drum let closestNote = null; let closestDiff = Infinity; for (const note of gameState.notes) { if (note.drum !== drum || note.hit || note.missed) continue; const diff = Math.abs(note.time - currentTime); if (diff < closestDiff && diff < TIMING.miss) { closestNote = note; closestDiff = diff; } } if (closestNote) { closestNote.hit = true; // Determine judgment let judgment, points; if (closestDiff <= TIMING.perfect) { judgment = 'PERFECT'; points = 100; } else if (closestDiff <= TIMING.great) { judgment = 'GREAT'; points = 75; } else if (closestDiff <= TIMING.good) { judgment = 'GOOD'; points = 50; } else { judgment = 'OK'; points = 25; } // Update stats gameState.notesHit++; gameState.combo++; gameState.maxStreak = Math.max(gameState.maxStreak, gameState.combo); // Calculate multiplier if (gameState.combo >= 30) gameState.multiplier = 4; else if (gameState.combo >= 20) gameState.multiplier = 3; else if (gameState.combo >= 10) gameState.multiplier = 2; else gameState.multiplier = 1; // Star power bonus const starBonus = gameState.starPowerActive ? 2 : 1; gameState.score += points * gameState.multiplier * starBonus; // Star power charge if (closestNote.isStarNote) { gameState.starPower = Math.min(100, gameState.starPower + 10); } else { gameState.starPower = Math.min(100, gameState.starPower + 2); } // Crowd meter gameState.crowdMeter = Math.min(100, gameState.crowdMeter + 2); // Show judgment showJudgment(judgment); // Create explosion createExplosion(drum, judgment); } else { // Missed hit (wrong timing or wrong drum) // Don't break combo for phantom hits } updateAllDisplays(); } function missNote(note) { note.missed = true; gameState.notesMissed++; gameState.combo = 0; gameState.multiplier = 1; if (!gameState.isPracticeMode) { gameState.crowdMeter = Math.max(0, gameState.crowdMeter - 5); if (gameState.crowdMeter <= 0) { endGame(); } } showJudgment('MISS'); updateAllDisplays(); } // ═══════════════════════════════════════════════════════════════════════════ // STAR POWER // ═══════════════════════════════════════════════════════════════════════════ function activateStarPower() { if (gameState.starPower < 50 || gameState.starPowerActive) return; gameState.starPowerActive = true; document.getElementById('starPowerFill').classList.add('active'); document.getElementById('starPowerStatus').textContent = 'ACTIVE!'; // Drain star power over time const drainInterval = setInterval(() => { gameState.starPower -= 2; updateStarPowerDisplay(); if (gameState.starPower <= 0) { gameState.starPowerActive = false; document.getElementById('starPowerFill').classList.remove('active'); document.getElementById('starPowerStatus').textContent = 'CHARGING'; clearInterval(drainInterval); } }, 100); } // ═══════════════════════════════════════════════════════════════════════════ // VISUAL EFFECTS // ═══════════════════════════════════════════════════════════════════════════ function showJudgment(text) { const display = document.getElementById('judgmentDisplay'); display.textContent = text; display.className = 'judgment-display show judgment-' + text.toLowerCase(); setTimeout(() => { display.classList.remove('show'); }, 300); } function createHitEffect(drum) { const pad = document.querySelector(`.drum-lane[data-drum="${drum}"]`); if (!pad) return; const rect = pad.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; // Add ring effect for (let i = 0; i < 8; i++) { activeEffects.push({ type: 'ring', x: x, y: y, radius: 20, maxRadius: 60 + Math.random() * 30, color: DRUM_COLORS[drum], alpha: 1 }); } } function createExplosion(drum, judgment) { const pad = document.querySelector(`.drum-lane[data-drum="${drum}"]`); if (!pad) return; const rect = pad.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; const particleCount = judgment === 'PERFECT' ? 20 : 10; for (let i = 0; i < particleCount; i++) { const angle = (Math.PI * 2 / particleCount) * i; const speed = 3 + Math.random() * 5; activeEffects.push({ type: 'particle', x: x, y: y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, radius: 3 + Math.random() * 4, color: DRUM_COLORS[drum], alpha: 1, decay: 0.02 + Math.random() * 0.02 }); } } function updateEffects() { effectsCtx.clearRect(0, 0, effectsCanvas.width, effectsCanvas.height); for (let i = activeEffects.length - 1; i >= 0; i--) { const effect = activeEffects[i]; if (effect.type === 'ring') { effect.radius += 3; effect.alpha -= 0.05; effectsCtx.beginPath(); effectsCtx.arc(effect.x, effect.y, effect.radius, 0, Math.PI * 2); effectsCtx.strokeStyle = effect.color + Math.floor(effect.alpha * 255).toString(16).padStart(2, '0'); effectsCtx.lineWidth = 3; effectsCtx.stroke(); if (effect.alpha <= 0) { activeEffects.splice(i, 1); } } else if (effect.type === 'particle') { effect.x += effect.vx; effect.y += effect.vy; effect.vy += 0.2; // Gravity effect.alpha -= effect.decay; effectsCtx.beginPath(); effectsCtx.arc(effect.x, effect.y, effect.radius, 0, Math.PI * 2); effectsCtx.fillStyle = effect.color + Math.floor(Math.max(0, effect.alpha) * 255).toString(16).padStart(2, '0'); effectsCtx.fill(); if (effect.alpha <= 0) { activeEffects.splice(i, 1); } } } } // ═══════════════════════════════════════════════════════════════════════════ // NOTE HIGHWAY RENDERING // ═══════════════════════════════════════════════════════════════════════════ function renderHighway() { const width = highwayCanvas.width; const height = highwayCanvas.height; // Clear highwayCtx.fillStyle = 'rgba(10, 0, 20, 0.3)'; highwayCtx.fillRect(0, 0, width, height); // Draw lane lines const laneCount = DRUMS.length; const laneWidth = width / laneCount; highwayCtx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; highwayCtx.lineWidth = 1; for (let i = 1; i < laneCount; i++) { highwayCtx.beginPath(); highwayCtx.moveTo(i * laneWidth, 0); highwayCtx.lineTo(i * laneWidth, height); highwayCtx.stroke(); } // Draw beat lines if (gameState.currentSong) { const bpm = parseInt(document.getElementById('bpmInput').value) || 120; const beatMs = 60000 / bpm; const currentTime = gameState.isPlaying ? (performance.now() - gameState.gameStartTime) * gameState.playbackSpeed : 0; const lookAhead = 2000; // 2 seconds of notes visible for (let time = currentTime; time < currentTime + lookAhead; time += beatMs) { const y = height - ((time - currentTime) / lookAhead * (height - 100)); highwayCtx.strokeStyle = 'rgba(255, 255, 255, 0.05)'; highwayCtx.beginPath(); highwayCtx.moveTo(0, y); highwayCtx.lineTo(width, y); highwayCtx.stroke(); } } // Draw notes if (gameState.isPlaying || gameState.notes.length > 0) { const currentTime = gameState.isPlaying ? (performance.now() - gameState.gameStartTime) * gameState.playbackSpeed : 0; const lookAhead = 2000; const hitZoneY = height - 80; for (const note of gameState.notes) { if (note.hit || note.missed) continue; const timeDiff = note.time - currentTime; // Check for missed notes if (timeDiff < -TIMING.miss && gameState.isPlaying) { missNote(note); continue; } // Only render notes within view if (timeDiff > lookAhead || timeDiff < -200) continue; const y = hitZoneY - (timeDiff / lookAhead * (hitZoneY - 50)); const laneIndex = DRUMS.indexOf(note.drum); const x = laneIndex * laneWidth + laneWidth / 2; // Note glow const gradient = highwayCtx.createRadialGradient(x, y, 0, x, y, 25); gradient.addColorStop(0, DRUM_COLORS[note.drum] + '80'); gradient.addColorStop(1, 'transparent'); highwayCtx.fillStyle = gradient; highwayCtx.fillRect(x - 30, y - 30, 60, 60); // Note body highwayCtx.beginPath(); highwayCtx.arc(x, y, 18, 0, Math.PI * 2); if (note.isStarNote) { // Star note - special appearance highwayCtx.fillStyle = '#00d4ff'; highwayCtx.fill(); highwayCtx.strokeStyle = '#ffffff'; highwayCtx.lineWidth = 3; highwayCtx.stroke(); // Star symbol highwayCtx.fillStyle = '#ffffff'; highwayCtx.font = '12px Arial'; highwayCtx.textAlign = 'center'; highwayCtx.textBaseline = 'middle'; highwayCtx.fillText('★', x, y); } else { highwayCtx.fillStyle = DRUM_COLORS[note.drum]; highwayCtx.fill(); highwayCtx.strokeStyle = '#ffffff'; highwayCtx.lineWidth = 2; highwayCtx.stroke(); } } } // Draw hit zone line highwayCtx.strokeStyle = '#ffffff'; highwayCtx.lineWidth = 2; highwayCtx.beginPath(); highwayCtx.moveTo(0, height - 80); highwayCtx.lineTo(width, height - 80); highwayCtx.stroke(); } // ═══════════════════════════════════════════════════════════════════════════ // DISPLAY UPDATES // ═══════════════════════════════════════════════════════════════════════════ function updateAllDisplays() { updateScoreDisplay(); updateComboDisplay(); updateStarPowerDisplay(); updateCrowdDisplay(); updateNotesDisplay(); updateSeferimDisplay(); } function updateScoreDisplay() { document.getElementById('scoreDisplay').textContent = gameState.score.toLocaleString(); document.getElementById('headerScore').textContent = gameState.score.toLocaleString(); document.getElementById('multiplierDisplay').textContent = gameState.multiplier + 'x'; } function updateComboDisplay() { document.getElementById('comboDisplay').textContent = gameState.combo; document.getElementById('headerStreak').textContent = gameState.combo; document.getElementById('maxStreakDisplay').textContent = gameState.maxStreak; const accuracy = gameState.notesHit + gameState.notesMissed > 0 ? Math.round((gameState.notesHit / (gameState.notesHit + gameState.notesMissed)) * 100) : 100; document.getElementById('accuracyDisplay').textContent = accuracy + '%'; } function updateStarPowerDisplay() { const fill = document.getElementById('starPowerFill'); fill.style.width = gameState.starPower + '%'; if (gameState.starPower >= 50 && !gameState.starPowerActive) { fill.classList.add('ready'); document.getElementById('starPowerStatus').textContent = 'READY!'; } else if (!gameState.starPowerActive) { fill.classList.remove('ready'); document.getElementById('starPowerStatus').textContent = 'CHARGING'; } } function updateCrowdDisplay() { const fill = document.getElementById('crowdFill'); fill.style.width = gameState.crowdMeter + '%'; const reaction = document.getElementById('crowdReaction'); if (gameState.crowdMeter >= 80) { fill.style.background = 'linear-gradient(90deg, #00ff88, #00ffcc)'; reaction.textContent = 'AMAZING!'; } else if (gameState.crowdMeter >= 60) { fill.style.background = 'linear-gradient(90deg, #f7c948, #00ff88)'; reaction.textContent = 'Great!'; } else if (gameState.crowdMeter >= 40) { fill.style.background = 'linear-gradient(90deg, #ff6b35, #f7c948)'; reaction.textContent = 'Good'; } else if (gameState.crowdMeter >= 20) { fill.style.background = 'linear-gradient(90deg, #ff006e, #ff6b35)'; reaction.textContent = 'Meh...'; } else { fill.style.background = 'linear-gradient(90deg, #ff0000, #ff006e)'; reaction.textContent = 'BOO!'; } } function updateNotesDisplay() { document.getElementById('notesHitDisplay').textContent = `${gameState.notesHit}/${gameState.totalNotes}`; } function updateVideoTime() { if (!player || !isPlayerReady) return; try { const current = player.getCurrentTime() || 0; const duration = player.getDuration() || 0; const formatTime = (s) => { const mins = Math.floor(s / 60); const secs = Math.floor(s % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; }; document.getElementById('videoTime').textContent = `${formatTime(current)} / ${formatTime(duration)}`; } catch (e) { // Player not ready } } // ═══════════════════════════════════════════════════════════════════════════ // SPEED CONTROL // ═══════════════════════════════════════════════════════════════════════════ function setupSpeedControl() { const slider = document.getElementById('speedSlider'); const display = document.getElementById('speedValue'); slider.addEventListener('input', () => { const speed = slider.value / 100; gameState.playbackSpeed = speed; display.textContent = slider.value + '%'; if (player && isPlayerReady && gameState.isPlaying) { player.setPlaybackRate(speed); } }); } // ═══════════════════════════════════════════════════════════════════════════ // TAP TEMPO // ═══════════════════════════════════════════════════════════════════════════ function startTapTempo() { const now = performance.now(); // Clear old taps if (tapTimeout) clearTimeout(tapTimeout); tapTimes.push(now); // Keep only recent taps if (tapTimes.length > 8) { tapTimes.shift(); } // Calculate BPM if we have enough taps if (tapTimes.length >= 2) { const intervals = []; for (let i = 1; i < tapTimes.length; i++) { intervals.push(tapTimes[i] - tapTimes[i-1]); } const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length; const bpm = Math.round(60000 / avgInterval); if (bpm >= 60 && bpm <= 300) { document.getElementById('bpmInput').value = bpm; } } // Reset after 2 seconds of no taps tapTimeout = setTimeout(() => { tapTimes = []; }, 2000); } // ═══════════════════════════════════════════════════════════════════════════ // SEFERIM INTEGRATION // ═══════════════════════════════════════════════════════════════════════════ function initSeferim() { const container = document.getElementById('seferimFamilies'); container.innerHTML = ''; for (let i = 0; i < 16; i++) { const bar = document.createElement('div'); bar.className = 'family-bar'; bar.id = `family-bar-${i}`; bar.innerHTML = '<div class="family-bar-fill" style="height: 0%"></div>'; container.appendChild(bar); } } function updateSeferimDisplay() { if (!window.SEFERIM) return; try { const status = window.SEFERIM.getStatus(); document.getElementById('seferimPhi').textContent = status.PhiGlobal.toFixed(2); document.getElementById('seferimK').textContent = status.K.toLocaleString(); document.getElementById('seferimLevel').textContent = status.difficulty.level; status.families.forEach((family, index) => { const bar = document.getElementById(`family-bar-${index}`); if (bar) { const fill = bar.querySelector('.family-bar-fill'); if (fill) { fill.style.height = `${family.coherence * 100}%`; } if (family.pressure > 0.3) { bar.classList.add('active'); } else { bar.classList.remove('active'); } } }); } catch (e) { console.log('SEFERIM not ready'); } } // ═══════════════════════════════════════════════════════════════════════════ // GAME LOOP // ═══════════════════════════════════════════════════════════════════════════ function gameLoop() { renderHighway(); updateEffects(); updateVideoTime(); animationId = requestAnimationFrame(gameLoop); } // ═══════════════════════════════════════════════════════════════════════════ // INITIALIZATION // ═══════════════════════════════════════════════════════════════════════════ document.addEventListener('DOMContentLoaded', init); // Wait for SEFERIM const checkSeferim = setInterval(() => { if (window.SEFERIM) { clearInterval(checkSeferim); updateSeferimDisplay(); setInterval(updateSeferimDisplay, 2000); console.log('[SEFERIM] Song Mode Integration Active'); } }, 100); </script> </body> </html>

Search equations, builds, manuscript text, and recovered sources.