Press "tab" or "]" for Menu 1 , press "[" or "`" for Menu 2 + Auto Next Song + Custom datetime suggestion
// ==UserScript==
// @name music chill
// @namespace http://tampermonkey.net/
// @version 6.81
// @description Press "tab" or "]" for Menu 1 , press "[" or "`" for Menu 2 + Auto Next Song + Custom datetime suggestion
// @author XUAN & taochsgamedekiemgai2207 + Mood Audio + Effects + Custom EQ
// @match *://*/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const AudioConfig = {
defaultVolume: 85,
currentMood: 'studio',
reverb: 0.15,
stereoWiden: 1.3,
compressor: true,
limiter: true,
noiseGate: true
};
const MoodPresets = {
studio: {
name: '🎚️ Studio',
bass: 8,
mid: 3,
treble: 5,
reverb: 0.1,
stereo: 1.2,
desc: '✨ Âm thanh phòng thu chuyên nghiệp',
effect: 'Cân bằng hoàn hảo giữa các dải tần, âm thanh trung thực như trong phòng thu'
},
deep: {
name: '🌊 Deep',
bass: 12,
mid: 2,
treble: 3,
reverb: 0.2,
stereo: 1.3,
desc: '🎵 Âm trầm sâu lắng',
effect: 'Tăng cường bass, âm thanh ấm áp và sâu lắng, phù hợp nhạc ballad, chill'
},
bass: {
name: '🔊 Bass Boost',
bass: 15,
mid: 1,
treble: 4,
reverb: 0.15,
stereo: 1.4,
desc: '💥 Bass mạnh mẽ',
effect: 'Bass được đẩy lên tối đa, âm thanh sống động như trong club, phù hợp EDM, rap'
},
bright: {
name: '✨ Bright',
bass: 4,
mid: 4,
treble: 10,
reverb: 0.1,
stereo: 1.2,
desc: '🌟 Âm thanh sáng',
effect: 'Tăng treble, âm thanh trong trẻo và chi tiết, phù hợp nhạc acoustic, classical'
},
vocal: {
name: '🎤 Vocal',
bass: 5,
mid: 8,
treble: 6,
reverb: 0.12,
stereo: 1.1,
desc: '🎙️ Giọng hát rõ',
effect: 'Tập trung vào dải mid, giọng hát nổi bật và ấm áp, phù hợp bolero, tình ca'
},
chill: {
name: '😌 Chill',
bass: 6,
mid: 4,
treble: 4,
reverb: 0.25,
stereo: 1.4,
desc: '🍃 Thư giãn nhẹ nhàng',
effect: 'Âm thanh nhẹ nhàng, thoải mái, không gian rộng, phù hợp lofi, ambient'
},
cinematic: {
name: '🎬 Cinematic',
bass: 10,
mid: 5,
treble: 6,
reverb: 0.3,
stereo: 1.5,
desc: '🎥 Âm thanh điện ảnh',
effect: 'Không gian rộng lớn như rạp phim, âm thanh hoành tráng, phù hợp soundtrack'
},
night: {
name: '🌙 Night',
bass: 7,
mid: 3,
treble: 3,
reverb: 0.2,
stereo: 1.3,
desc: '🌃 Về đêm',
effect: 'Ấm áp và nhẹ nhàng, tạo cảm giác thư thái cho buổi tối'
},
morning: {
name: '☀️ Morning',
bass: 5,
mid: 5,
treble: 7,
reverb: 0.15,
stereo: 1.2,
desc: '🌅 Buổi sáng',
effect: 'Tươi sáng, trong trẻo, đánh thức mọi giác quan'
},
energy: {
name: '⚡ Energy',
bass: 9,
mid: 6,
treble: 8,
reverb: 0.1,
stereo: 1.3,
desc: '🔥 Sôi động',
effect: 'Năng lượng tràn đầy, âm thanh mạnh mẽ, phù hợp gym, thể thao'
},
jazz: {
name: '🎷 Jazz',
bass: 6,
mid: 6,
treble: 5,
reverb: 0.2,
stereo: 1.3,
desc: '🎺 Jazz club',
effect: 'Ấm áp như trong quán jazz, tiếng kèn và bass mềm mại'
},
acoustic: {
name: '🎸 Acoustic',
bass: 4,
mid: 7,
treble: 6,
reverb: 0.18,
stereo: 1.2,
desc: '🎵 Âm thanh mộc',
effect: 'Tự nhiên như đang nghe nhạc sống, phù hợp guitar, piano'
},
bluetooth: {
name: '📱 Bluetooth',
bass: 10,
mid: 4,
treble: 6,
reverb: 0.12,
stereo: 1.1,
desc: '🎧 Tối ưu loa Bluetooth',
effect: 'Tối ưu cho loa Bluetooth, bass mạnh mà không bị rè'
},
headphone: {
name: '🎧 Headphone',
bass: 7,
mid: 5,
treble: 6,
reverb: 0.1,
stereo: 1.3,
desc: '🎧 Tai nghe',
effect: 'Tối ưu cho tai nghe, âm trường rộng và chi tiết'
},
classical: {
name: '🎻 Classical',
bass: 5,
mid: 6,
treble: 7,
reverb: 0.25,
stereo: 1.4,
desc: '🎼 Nhạc cổ điển',
effect: 'Không gian rộng, dàn nhạc giao hưởng sống động'
},
rock: {
name: '🤘 Rock',
bass: 11,
mid: 5,
treble: 7,
reverb: 0.15,
stereo: 1.3,
desc: '🎸 Rock mạnh mẽ',
effect: 'Guitar điện và trống mạnh mẽ, phù hợp rock, metal'
},
smallSpeaker: {
name: '📢 Small Speaker',
bass: 8,
mid: 6,
treble: 8,
reverb: 0.08,
stereo: 1.1,
desc: '📱 Loa nhỏ',
effect: 'Tối ưu cho loa điện thoại, laptop, âm thanh rõ ràng'
},
clear: {
name: '💎 Clear',
bass: 5,
mid: 5,
treble: 5,
reverb: 0.05,
stereo: 1.1,
desc: '✨ Trong trẻo',
effect: 'Âm thanh tự nhiên nhất, không màu mè, nghe như tác giả muốn'
},
threeD: {
name: '🌀 3D Surround',
bass: 8,
mid: 4,
treble: 6,
reverb: 0.3,
stereo: 1.8,
desc: '🎯 Âm thanh vòm',
effect: 'Âm thanh vòm 3D, không gian bao quanh, sống động'
}
};
// ==================== AUDIO ENGINE ====================
class StudioAudioEngine {
constructor() {
this.context = null;
this.nodes = {};
this.isActive = false;
this.videoElement = null;
this.currentMood = 'studio';
}
async init() {
try {
this.context = new (window.AudioContext || window.webkitAudioContext)({
latencyHint: 'interactive',
sampleRate: 48000
});
this.createAudioNodes();
if (this.context.state === 'suspended') {
await this.context.resume();
}
this.isActive = true;
console.log('🎧 Studio Audio Engine ready');
} catch (e) {
console.log('Audio engine not available');
}
}
createAudioNodes() {
this.nodes.compressor = this.context.createDynamicsCompressor();
this.nodes.compressor.threshold.value = -24;
this.nodes.compressor.knee.value = 30;
this.nodes.compressor.ratio.value = 12;
this.nodes.compressor.attack.value = 0.003;
this.nodes.compressor.release.value = 0.25;
this.nodes.bassEQ = this.context.createBiquadFilter();
this.nodes.bassEQ.type = 'lowshelf';
this.nodes.bassEQ.frequency.value = 150;
this.nodes.midEQ = this.context.createBiquadFilter();
this.nodes.midEQ.type = 'peaking';
this.nodes.midEQ.frequency.value = 1000;
this.nodes.midEQ.Q.value = 1;
this.nodes.trebleEQ = this.context.createBiquadFilter();
this.nodes.trebleEQ.type = 'highshelf';
this.nodes.trebleEQ.frequency.value = 4000;
this.nodes.reverb = this.context.createConvolver();
this.createReverb();
this.nodes.merger = this.context.createChannelMerger(2);
this.nodes.splitter = this.context.createChannelSplitter(2);
this.nodes.gainL = this.context.createGain();
this.nodes.gainR = this.context.createGain();
this.nodes.limiter = this.context.createDynamicsCompressor();
this.nodes.limiter.threshold.value = -6;
this.nodes.limiter.knee.value = 0;
this.nodes.limiter.ratio.value = 20;
this.nodes.limiter.attack.value = 0.001;
this.nodes.limiter.release.value = 0.01;
this.nodes.masterGain = this.context.createGain();
this.nodes.masterGain.gain.value = AudioConfig.defaultVolume / 100 * 1.2;
this.applyMood(AudioConfig.currentMood);
}
createReverb() {
try {
const duration = 1.5;
const decay = 2;
const sampleRate = this.context.sampleRate;
const length = sampleRate * duration;
const impulse = this.context.createBuffer(2, length, sampleRate);
for (let channel = 0; channel < 2; channel++) {
const channelData = impulse.getChannelData(channel);
for (let i = 0; i < length; i++) {
channelData[i] = (Math.random() * 2 - 1) *
Math.pow(1 - i / length, decay);
}
}
this.nodes.reverb.buffer = impulse;
} catch (e) {
console.log('Reverb creation failed');
}
}
showMoodEffect(moodId) {
const mood = MoodPresets[moodId];
if (!mood) return;
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px 30px;
border-radius: 15px;
z-index: 1000000;
font-family: Arial, sans-serif;
box-shadow: 0 10px 40px rgba(0,0,0,0.25);
animation: moodPopIn 0.5s ease-out;
text-align: center;
max-width: 300px;
border: 2px solid rgba(255,255,255,0.2);
`;
notification.innerHTML = `
<div style="font-size: 40px; margin-bottom: 10px;">${mood.name.split(' ')[0]}</div>
<div style="font-size: 18px; font-weight: bold; margin-bottom: 5px;">${mood.desc}</div>
<div style="font-size: 14px; opacity: 0.9; margin-bottom: 15px;">${mood.effect}</div>
<div style="display: flex; gap: 10px; justify-content: center;">
<div style="background: rgba(255,255,255,0.2); padding: 5px 10px; border-radius: 20px;">
Bass: ${mood.bass}dB
</div>
<div style="background: rgba(255,255,255,0.2); padding: 5px 10px; border-radius: 20px;">
Treble: ${mood.treble}dB
</div>
</div>
<div style="margin-top: 10px; font-size: 12px; opacity: 0.7;">⚡ Đã kích hoạt</div>
`;
const style = document.createElement('style');
style.textContent = `
@keyframes moodPopIn {
0% { transform: translate(-50%, -30%) scale(0.8); opacity: 0; }
100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
}
@keyframes moodPopOut {
0% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
100% { transform: translate(-50%, -70%) scale(0.8); opacity: 0; }
}
`;
document.head.appendChild(style);
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'moodPopOut 0.3s ease-in forwards';
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 300);
}, 3000);
this.showEQVisualizer(mood);
}
showEQVisualizer(mood) {
const visualizer = document.createElement('div');
visualizer.style.cssText = `
position: fixed;
bottom: 30px;
right: 30px;
background: rgba(0,0,0,0.25);
color: white;
padding: 15px;
border-radius: 10px;
z-index: 999999;
font-family: Arial, sans-serif;
backdrop-filter: blur(5px);
border: 1px solid #4CAF50;
animation: slideInRight 0.3s ease-out;
`;
const bars = ['Bass', 'Mid', 'Treble'];
const values = [mood.bass, mood.mid, mood.treble];
const maxValue = 15;
let barsHtml = '<div style="margin-bottom: 10px; color: #4CAF50;">🎚️ EQ Visualizer</div>';
bars.forEach((bar, index) => {
const percentage = (values[index] / maxValue) * 100;
barsHtml += `
<div style="margin-bottom: 8px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 3px;">
<span>${bar}</span>
<span>${values[index]}dB</span>
</div>
<div style="width: 200px; height: 20px; background: #333; border-radius: 10px; overflow: hidden;">
<div style="width: ${percentage}%; height: 100%; background: linear-gradient(90deg, #4CAF50, #8BC34A); border-radius: 10px;"></div>
</div>
</div>
`;
});
barsHtml += `
<div style="margin-top: 10px; display: flex; gap: 10px; color: #aaa; font-size: 11px;">
<span>🌊 Reverb: ${Math.round(mood.reverb * 100)}%</span>
<span>🎯 Stereo: ${mood.stereo}x</span>
</div>
`;
visualizer.innerHTML = barsHtml;
const style = document.createElement('style');
style.textContent = `
@keyframes slideInRight {
from { transform: translateX(100px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
`;
document.head.appendChild(style);
document.body.appendChild(visualizer);
setTimeout(() => {
if (visualizer.parentNode) {
visualizer.style.animation = 'slideInRight 0.3s ease-out reverse';
setTimeout(() => visualizer.remove(), 300);
}
}, 5000);
}
applyMood(moodId) {
if (!this.isActive || !this.nodes.bassEQ) return;
const mood = MoodPresets[moodId] || MoodPresets.studio;
this.currentMood = moodId;
this.nodes.bassEQ.gain.value = mood.bass;
this.nodes.midEQ.gain.value = mood.mid;
this.nodes.trebleEQ.gain.value = mood.treble;
AudioConfig.reverb = mood.reverb;
AudioConfig.stereoWiden = mood.stereo;
console.log(`🎵 Applied mood: ${mood.name}`);
const moodDisplay = document.getElementById('currentMood');
if (moodDisplay) {
moodDisplay.innerHTML = `${mood.name} <span style="color:#aaa; font-size:10px;">(${mood.desc})</span>`;
}
if (window.bassSlider) window.bassSlider.value = mood.bass;
if (window.midSlider) window.midSlider.value = mood.mid;
if (window.trebleSlider) window.trebleSlider.value = mood.treble;
if (window.reverbSlider) window.reverbSlider.value = Math.round(mood.reverb * 100);
if (window.stereoSlider) window.stereoSlider.value = mood.stereo;
this.showMoodEffect(moodId);
}
// ==================== THÊM: RECONNECT ĐỂ CẬP NHẬT REVERB + STEREO (CHO CUSTOM) ====================
reconnectToVideo() {
if (!this.videoElement || !this.isActive) {
console.log('⚠️ Không có video để reconnect');
return;
}
try {
this.connectToVideo(this.videoElement);
console.log('🎧 Audio chain đã reconnect với thiết lập mới (Reverb + Stereo)');
} catch (e) {
console.log('Reconnect failed:', e);
}
}
connectToVideo(videoElement) {
if (!this.isActive || !this.context || !videoElement) return false;
try {
this.videoElement = videoElement;
if (this.nodes.source) {
this.nodes.source.disconnect();
}
this.nodes.source = this.context.createMediaElementSource(videoElement);
let currentNode = this.nodes.source;
currentNode.connect(this.nodes.bassEQ);
currentNode = this.nodes.bassEQ;
currentNode.connect(this.nodes.midEQ);
currentNode = this.nodes.midEQ;
currentNode.connect(this.nodes.trebleEQ);
currentNode = this.nodes.trebleEQ;
currentNode.connect(this.nodes.compressor);
currentNode = this.nodes.compressor;
// SỬ DỤNG AudioConfig thay vì mood (để custom hoạt động)
const stereo = AudioConfig.stereoWiden;
const reverbLevel = AudioConfig.reverb;
if (stereo > 1) {
currentNode.connect(this.nodes.splitter);
this.nodes.splitter.connect(this.nodes.gainL, 0);
this.nodes.splitter.connect(this.nodes.gainR, 1);
this.nodes.gainL.gain.value = 1;
this.nodes.gainR.gain.value = 1;
this.nodes.gainL.connect(this.nodes.merger, 0, 0);
this.nodes.gainR.connect(this.nodes.merger, 0, 1);
currentNode = this.nodes.merger;
}
if (reverbLevel > 0.05) {
const dryGain = this.context.createGain();
const wetGain = this.context.createGain();
dryGain.gain.value = 1 - reverbLevel;
wetGain.gain.value = reverbLevel;
currentNode.connect(dryGain);
currentNode.connect(this.nodes.reverb);
this.nodes.reverb.connect(wetGain);
dryGain.connect(this.nodes.limiter);
wetGain.connect(this.nodes.limiter);
} else {
currentNode.connect(this.nodes.limiter);
}
this.nodes.limiter.connect(this.nodes.masterGain);
this.nodes.masterGain.connect(this.context.destination);
console.log(`🎵 Studio audio active - Mood: ${MoodPresets[this.currentMood].name} | Reverb: ${reverbLevel} | Stereo: ${stereo}`);
return true;
} catch (e) {
console.log('Could not connect audio processor:', e);
return false;
}
}
setVolume(vol) {
if (this.nodes.masterGain) {
this.nodes.masterGain.gain.value = (vol / 100) * 1.2;
}
}
}
const audioEngine = new StudioAudioEngine();
// ==================== MOOD LIGHTING (HIỆU ỨNG ÁNH SÁNG THEO NHẠC) ====================
class MoodLighting {
constructor() {
this.isActive = false;
this.analyser = null;
this.dataArray = null;
this.canvas = null;
this.ctx = null;
this.animationId = null;
this.intensity = 0;
this.smoothIntensity = 0;
this.colorHue = 0;
this.bassBoost = 1.5;
}
init(audioContext, sourceNode) {
if (!audioContext || !sourceNode) return false;
try {
this.analyser = audioContext.createAnalyser();
this.analyser.fftSize = 256;
this.analyser.smoothingTimeConstant = 0.3;
const bufferLength = this.analyser.frequencyBinCount;
this.dataArray = new Uint8Array(bufferLength);
sourceNode.connect(this.analyser);
this.createCanvas();
this.isActive = true;
this.startAnimation();
console.log('🎨 Mood Lighting initialized');
return true;
} catch(e) {
console.log('Mood Lighting init failed:', e);
return false;
}
}
createCanvas() {
this.canvas = document.createElement('canvas');
this.canvas.id = 'moodLightingCanvas';
this.canvas.style.position = 'fixed';
this.canvas.style.top = '0';
this.canvas.style.left = '0';
this.canvas.style.width = '100%';
this.canvas.style.height = '100%';
this.canvas.style.zIndex = '-1'; // Đặt dưới cùng để không che menu
this.canvas.style.pointerEvents = 'none';
this.canvas.style.transition = 'opacity 0.5s';
this.ctx = this.canvas.getContext('2d');
this.resizeCanvas();
window.addEventListener('resize', () => this.resizeCanvas());
document.body.insertBefore(this.canvas, document.body.firstChild);
}
resizeCanvas() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
startAnimation() {
if (!this.isActive) return;
const animate = () => {
if (!this.isActive || !this.analyser) {
if (this.animationId) cancelAnimationFrame(this.animationId);
return;
}
this.analyser.getByteFrequencyData(this.dataArray);
// Tính cường độ trung bình (tập trung vào dải bass: 0-10)
let sum = 0;
const bassRange = Math.min(10, this.dataArray.length);
for (let i = 0; i < bassRange; i++) {
sum += this.dataArray[i];
}
let avg = sum / bassRange; // 0-255
// Chuẩn hóa intensity (0-1) và làm mượt
const targetIntensity = Math.min(1, avg / 128);
this.smoothIntensity = this.smoothIntensity * 0.7 + targetIntensity * 0.3;
// Thay đổi màu sắc dựa trên intensity và tần số
// Hue chạy từ 0->360 theo intensity và thời gian (tạo hiệu ứng nhấp nháy)
const now = Date.now() / 1000;
this.colorHue = (now * 30 + this.smoothIntensity * 180) % 360;
// Saturation và Lightness thay đổi theo intensity
const sat = 60 + this.smoothIntensity * 40; // 60-100%
const light = 15 + this.smoothIntensity * 25; // 15-40% (tối hơn để chữ dễ đọc)
// Vẽ gradient theo góc
this.drawGradient(this.colorHue, sat, light, this.smoothIntensity);
this.animationId = requestAnimationFrame(animate);
};
animate();
}
drawGradient(hue, sat, light, intensity) {
const w = this.canvas.width;
const h = this.canvas.height;
const grad = this.ctx.createLinearGradient(0, 0, w, h);
// Màu chính dựa trên hue
const mainColor = `hsl(${hue}, ${sat}%, ${light}%)`;
// Màu phụ xoay hue 30 độ
const secondColor = `hsl(${(hue + 30) % 360}, ${sat}%, ${light * 0.7}%)`;
// Màu thứ ba xoay 60 độ
const thirdColor = `hsl(${(hue + 60) % 360}, ${sat * 0.8}%, ${light * 0.5}%)`;
grad.addColorStop(0, mainColor);
grad.addColorStop(0.5, secondColor);
grad.addColorStop(1, thirdColor);
this.ctx.fillStyle = grad;
this.ctx.fillRect(0, 0, w, h);
// Thêm hiệu ứng ánh sáng nhấp nháy (overlay trắng trong suốt)
if (intensity > 0.3) {
const flashIntensity = Math.min(0.3, (intensity - 0.3) * 0.8);
this.ctx.fillStyle = `rgba(255, 255, 255, ${flashIntensity * 0.5})`;
this.ctx.fillRect(0, 0, w, h);
}
}
stop() {
this.isActive = false;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
if (this.canvas) {
this.canvas.remove();
this.canvas = null;
}
this.analyser = null;
console.log('🎨 Mood Lighting stopped');
}
}
const moodLighting = new MoodLighting();
let lightingEnabled = false;
// Thêm nút bật/tắt Mood Lighting vào menu (sau khi đã có musicMenuContainer)
// Tìm vị trí thích hợp: sau phần EQ custom hoặc trước volume control
// Chúng ta sẽ chèn sau customContainer nhưng trước volumeLabel
// Để đảm bảo, ta sẽ tìm nút reconnect và chèn sau nó
// Hàm này được gọi sau khi audioEngine đã kết nối với video (trong onPlayerReady)
function initMoodLightingWithSource() {
if (!audioEngine.isActive || !audioEngine.context || !audioEngine.nodes.masterGain) {
setTimeout(initMoodLightingWithSource, 1000);
return;
}
// Kết nối mood lighting từ masterGain (để lấy tín hiệu đã qua xử lý)
moodLighting.init(audioEngine.context, audioEngine.nodes.masterGain);
}
// Thêm nút trong menu (sau khi customContainer đã tồn tại)
setTimeout(() => {
const customContainer = document.querySelector('#musicMenuContainer > div:nth-child(4)'); // Tùy chỉnh EQ container
if (customContainer) {
const lightingBtn = document.createElement('button');
lightingBtn.textContent = '💡 BẬT HIỆU ỨNG ÁNH SÁNG';
lightingBtn.style.backgroundColor = 'rgba(156, 39, 176, 0.3)';
lightingBtn.style.border = '1px solid rgba(156, 39, 176, 0.5)';
lightingBtn.style.marginTop = '10px';
lightingBtn.addEventListener('click', () => {
if (!lightingEnabled) {
if (!moodLighting.isActive) {
initMoodLightingWithSource();
}
lightingEnabled = true;
lightingBtn.textContent = '💡 TẮT HIỆU ỨNG ÁNH SÁNG';
lightingBtn.style.backgroundColor = 'rgba(156, 39, 176, 0.6)';
} else {
moodLighting.stop();
lightingEnabled = false;
lightingBtn.textContent = '💡 BẬT HIỆU ỨNG ÁNH SÁNG';
lightingBtn.style.backgroundColor = 'rgba(156, 39, 176, 0.3)';
}
});
customContainer.appendChild(lightingBtn);
}
}, 1000);
// Ghi đè onPlayerReady để khởi tạo mood lighting sau khi player sẵn sàng
const originalOnPlayerReady = window.onPlayerReady;
window.onPlayerReady = function(event) {
if (originalOnPlayerReady) originalOnPlayerReady(event);
// Đợi audioEngine kết nối với video rồi mới init mood lighting
setTimeout(() => {
if (audioEngine.isActive && audioEngine.nodes.masterGain) {
moodLighting.init(audioEngine.context, audioEngine.nodes.masterGain);
// Mặc định tắt, đợi người dùng bật
moodLighting.stop();
moodLighting.isActive = false;
lightingEnabled = false;
const btn = document.querySelector('#musicMenuContainer button:last-of-type'); // tìm nút vừa thêm (có thể không chính xác)
if (btn && btn.textContent.includes('BẬT HIỆU ỨNG')) {
btn.style.backgroundColor = 'rgba(156, 39, 176, 0.3)';
}
}
}, 2000);
};
// Tiếp tục giữ nguyên toàn bộ code còn lại (các phần menu số 2, table gradient, fb downloader, style...)
// === BIẾN GLOBAL CHO CUSTOM SLIDERS (để sync khi đổi mood) ===
let bassSlider, midSlider, trebleSlider, reverbSlider, stereoSlider;
// Create the music menu container
const musicMenuContainer = document.createElement('div');
musicMenuContainer.id = 'musicMenuContainer';
musicMenuContainer.style.position = 'absolute';
musicMenuContainer.style.top = '0';
musicMenuContainer.style.left = '0';
musicMenuContainer.style.width = '280px';
musicMenuContainer.style.maxHeight = '100vh';
musicMenuContainer.style.overflowY = 'auto';
musicMenuContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.1)';
musicMenuContainer.style.color = '#fff';
musicMenuContainer.style.borderRadius = '10px';
musicMenuContainer.style.padding = '20px';
musicMenuContainer.style.zIndex = '9999';
musicMenuContainer.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.25)';
musicMenuContainer.style.display = 'none';
// Title
const musicTitle = document.createElement('h2');
musicTitle.textContent = ' bấm "tab" hoặc "]" để mở/tắt menu , bấm "[" hoặc "`" để mở menu số 2';
musicTitle.style.textAlign = 'center';
musicTitle.style.fontSize = '14px';
musicMenuContainer.appendChild(musicTitle);
// ==================== PHẦN ĐỀ NGHỊ BÀI HÁT THEO THỜI GIAN ====================
const suggestionContainer = document.createElement('div');
suggestionContainer.style.marginBottom = '10px';
suggestionContainer.style.padding = '10px';
suggestionContainer.style.backgroundColor = 'rgba(255, 193, 7, 0.15)';
suggestionContainer.style.borderRadius = '10px';
suggestionContainer.style.border = '1px solid rgba(255, 193, 7, 0.25)';
suggestionContainer.style.backdropFilter = 'blur(8px)';
suggestionContainer.style.textAlign = 'center';
const clockDiv = document.createElement('div');
clockDiv.style.fontSize = '28px';
clockDiv.style.fontWeight = 'bold';
clockDiv.style.fontFamily = 'monospace';
clockDiv.style.letterSpacing = '2px';
clockDiv.style.textShadow = '0 0 10px rgba(255,193,7,0.25)';
clockDiv.style.marginBottom = '10px';
const suggestedSongDiv = document.createElement('div');
suggestedSongDiv.style.fontSize = '14px';
suggestedSongDiv.style.marginBottom = '10px';
suggestedSongDiv.style.padding = '8px';
suggestedSongDiv.style.backgroundColor = 'rgba(0,0,0,0.25)';
suggestedSongDiv.style.borderRadius = '20px';
suggestedSongDiv.style.wordBreak = 'break-word';
const playSuggestBtn = document.createElement('button');
playSuggestBtn.textContent = '🎵 Phát ngay';
playSuggestBtn.style.backgroundColor = 'rgba(255, 193, 7, 0.25)';
playSuggestBtn.style.border = '1px solid rgba(255, 193, 7, 0.25)';
playSuggestBtn.style.marginTop = '5px';
playSuggestBtn.style.padding = '8px';
playSuggestBtn.style.fontSize = '12px';
playSuggestBtn.style.borderRadius = '30px';
playSuggestBtn.style.cursor = 'pointer';
playSuggestBtn.style.transition = 'all 0.2s';
playSuggestBtn.addEventListener('mouseover', () => {
playSuggestBtn.style.backgroundColor = 'rgba(255, 193, 7, 0.25)';
playSuggestBtn.style.transform = 'scale(1.02)';
});
playSuggestBtn.addEventListener('mouseout', () => {
playSuggestBtn.style.backgroundColor = 'rgba(255, 193, 7, 0.25)';
playSuggestBtn.style.transform = 'scale(1)';
});
suggestionContainer.appendChild(clockDiv);
suggestionContainer.appendChild(suggestedSongDiv);
suggestionContainer.appendChild(playSuggestBtn);
musicMenuContainer.appendChild(suggestionContainer);
// ==================== THÊM: ĐỀ XUẤT THEO NGÀY/GIỜ TÙY CHỈNH ====================
const customTimeContainer = document.createElement('div');
customTimeContainer.style.marginBottom = '10px';
customTimeContainer.style.padding = '20px';
customTimeContainer.style.backgroundColor = 'rgba(33, 150, 243, 0.15)';
customTimeContainer.style.borderRadius = '10px';
customTimeContainer.style.border = '1px solid rgba(33, 150, 243, 0.1)';
customTimeContainer.style.backdropFilter = 'blur(8px)';
customTimeContainer.style.textAlign = 'center';
const customTimeTitle = document.createElement('div');
customTimeTitle.textContent = '📅 Đề xuất theo thời gian tùy chỉnh';
customTimeTitle.style.fontWeight = 'bold';
customTimeTitle.style.marginBottom = '10px';
customTimeTitle.style.color = '#90caf9';
customTimeContainer.appendChild(customTimeTitle);
// Input date
const dateInput = document.createElement('input');
dateInput.type = 'date';
dateInput.style.width = '100%';
dateInput.style.padding = '8px';
dateInput.style.marginBottom = '8px';
dateInput.style.backgroundColor = 'rgba(255,255,255,0.1)';
dateInput.style.border = '1px solid rgba(255,255,255,0.2)';
dateInput.style.borderRadius = '8px';
dateInput.style.color = '#fff';
dateInput.style.fontSize = '12px';
// Input time
const timeInput = document.createElement('input');
timeInput.type = 'time';
timeInput.step = '1';
timeInput.style.width = '100%';
timeInput.style.padding = '8px';
timeInput.style.marginBottom = '8px';
timeInput.style.backgroundColor = 'rgba(255,255,255,0.1)';
timeInput.style.border = '1px solid rgba(255,255,255,0.2)';
timeInput.style.borderRadius = '8px';
timeInput.style.color = '#fff';
timeInput.style.fontSize = '12px';
// Input seconds (optional)
const secondsInput = document.createElement('input');
secondsInput.type = 'number';
secondsInput.min = '0';
secondsInput.max = '59';
secondsInput.value = '0';
secondsInput.placeholder = 'Giây (0-59)';
secondsInput.style.width = '100%';
secondsInput.style.padding = '8px';
secondsInput.style.marginBottom = '8px';
secondsInput.style.backgroundColor = 'rgba(255,255,255,0.1)';
secondsInput.style.border = '1px solid rgba(255,255,255,0.2)';
secondsInput.style.borderRadius = '8px';
secondsInput.style.color = '#fff';
secondsInput.style.fontSize = '12px';
// Suggest button
const suggestCustomBtn = document.createElement('button');
suggestCustomBtn.textContent = '🎲 Gợi ý theo thời gian này';
suggestCustomBtn.style.backgroundColor = 'rgba(33, 150, 243, 0.1)';
suggestCustomBtn.style.border = '1px solid rgba(33, 150, 243, 0.1)';
suggestCustomBtn.style.padding = '8px';
suggestCustomBtn.style.borderRadius = '30px';
suggestCustomBtn.style.cursor = 'pointer';
suggestCustomBtn.style.fontSize = '12px';
suggestCustomBtn.style.marginBottom = '8px';
suggestCustomBtn.style.width = '100%';
// Display custom suggestion result
const customSuggestedDiv = document.createElement('div');
customSuggestedDiv.style.fontSize = '13px';
customSuggestedDiv.style.padding = '8px';
customSuggestedDiv.style.backgroundColor = 'rgba(0,0,0,0.25)';
customSuggestedDiv.style.borderRadius = '20px';
customSuggestedDiv.style.wordBreak = 'break-word';
customSuggestedDiv.style.marginTop = '8px';
// Play button for custom suggestion
const playCustomBtn = document.createElement('button');
playCustomBtn.textContent = '🎵 Phát bài này';
playCustomBtn.style.backgroundColor = 'rgba(33, 150, 243, 0.3)';
playCustomBtn.style.border = '1px solid rgba(33, 150, 243, 0.5)';
playCustomBtn.style.padding = '8px';
playCustomBtn.style.borderRadius = '30px';
playCustomBtn.style.cursor = 'pointer';
playCustomBtn.style.fontSize = '12px';
playCustomBtn.style.marginTop = '8px';
playCustomBtn.style.display = 'none';
customTimeContainer.appendChild(dateInput);
customTimeContainer.appendChild(timeInput);
customTimeContainer.appendChild(secondsInput);
customTimeContainer.appendChild(suggestCustomBtn);
customTimeContainer.appendChild(customSuggestedDiv);
customTimeContainer.appendChild(playCustomBtn);
musicMenuContainer.appendChild(customTimeContainer);
// Helper: get song based on timestamp (seconds)
function getSongByTimestamp(totalSeconds) {
const validSongs = musicList.filter(song => song.name && song.url);
if (validSongs.length === 0) return null;
const index = Math.floor(totalSeconds) % validSongs.length;
return validSongs[index];
}
// Helper: get song from Date object
function getSongFromDate(date) {
const totalSeconds = Math.floor(date.getTime() / 1000);
return getSongByTimestamp(totalSeconds);
}
// Custom suggest logic
function updateCustomSuggestion() {
let dateStr = dateInput.value;
let timeStr = timeInput.value;
let seconds = parseInt(secondsInput.value) || 0;
if (!dateStr || !timeStr) {
customSuggestedDiv.innerHTML = '⚠️ Vui lòng chọn đầy đủ ngày và giờ.';
playCustomBtn.style.display = 'none';
return;
}
try {
const dateTimeStr = `${dateStr}T${timeStr}:${seconds.toString().padStart(2,'0')}`;
const selectedDate = new Date(dateTimeStr);
if (isNaN(selectedDate.getTime())) throw new Error('Invalid date');
const song = getSongFromDate(selectedDate);
if (song) {
customSuggestedDiv.innerHTML = `🎧 <strong>Đề xuất:</strong> ${song.name}`;
playCustomBtn.style.display = 'block';
// Store current custom song for play button
playCustomBtn.onclick = () => {
const originalButtons = musicItemContainer.querySelectorAll('button');
let targetButton = null;
for (let i = 0; i < originalButtons.length; i++) {
if (originalButtons[i].textContent.includes(song.name)) {
targetButton = originalButtons[i];
break;
}
}
playMusic(song, targetButton || playCustomBtn);
};
} else {
customSuggestedDiv.innerHTML = '❌ Không tìm thấy bài hát nào.';
playCustomBtn.style.display = 'none';
}
} catch(e) {
customSuggestedDiv.innerHTML = '❌ Ngày giờ không hợp lệ.';
playCustomBtn.style.display = 'none';
}
}
suggestCustomBtn.addEventListener('click', updateCustomSuggestion);
// Set default datetime to now
const now = new Date();
const defaultDate = now.toISOString().split('T')[0];
const defaultTime = now.toTimeString().slice(0,5);
dateInput.value = defaultDate;
timeInput.value = defaultTime;
secondsInput.value = now.getSeconds();
// ==================== MOOD SELECTOR ====================
const moodContainer = document.createElement('div');
moodContainer.style.marginBottom = '15px';
moodContainer.style.padding = '10px';
moodContainer.style.backgroundColor = 'rgba(76, 175, 80, 0.2)';
moodContainer.style.borderRadius = '8px';
moodContainer.style.border = '1px solid #4CAF50';
const moodTitle = document.createElement('div');
moodTitle.textContent = '🎵 TÂM TRẠNG ÂM THANH';
moodTitle.style.fontWeight = 'bold';
moodTitle.style.marginBottom = '10px';
moodTitle.style.color = '#4CAF50';
moodTitle.style.textAlign = 'center';
moodContainer.appendChild(moodTitle);
const currentMoodDisplay = document.createElement('div');
currentMoodDisplay.id = 'currentMood';
currentMoodDisplay.style.marginBottom = '10px';
currentMoodDisplay.style.padding = '5px';
currentMoodDisplay.style.backgroundColor = 'rgba(255,255,255,0.1)';
currentMoodDisplay.style.borderRadius = '5px';
currentMoodDisplay.style.textAlign = 'center';
currentMoodDisplay.style.fontSize = '12px';
currentMoodDisplay.innerHTML = '🎚️ Studio <span style="color:#aaa; font-size:10px;">(✨ Âm thanh phòng thu chuyên nghiệp)</span>';
moodContainer.appendChild(currentMoodDisplay);
// Grid mood buttons
const moodGrid = document.createElement('div');
moodGrid.style.display = 'grid';
moodGrid.style.gridTemplateColumns = 'repeat(2, 1fr)';
moodGrid.style.gap = '5px';
const moodIds = ['studio', 'deep', 'bass', 'bright', 'vocal', 'chill', 'cinematic', 'night', 'morning', 'energy', 'jazz', 'acoustic', 'bluetooth', 'headphone', 'classical', 'rock', 'smallSpeaker', 'clear', 'threeD'];
moodIds.forEach(moodId => {
const mood = MoodPresets[moodId];
const btn = document.createElement('button');
btn.textContent = mood.name;
btn.title = mood.desc; // Hiện tooltip khi hover
btn.style.padding = '8px 4px';
btn.style.backgroundColor = moodId === 'studio' ? '#4CAF50' : 'rgba(33, 150, 243, 0.25)';
btn.style.color = 'white';
btn.style.border = 'none';
btn.style.borderRadius = '5px';
btn.style.cursor = 'pointer';
btn.style.fontSize = '11px';
btn.style.transition = 'all 0.2s';
btn.style.whiteSpace = 'nowrap';
btn.style.overflow = 'hidden';
btn.style.textOverflow = 'ellipsis';
btn.onmouseover = () => {
btn.style.backgroundColor = moodId === 'studio' ? '#45a049' : '#2196F3';
btn.style.transform = 'scale(1.02)';
};
btn.onmouseout = () => {
btn.style.backgroundColor = moodId === 'studio' ? '#4CAF50' : 'rgba(33, 150, 243, 0.25)';
btn.style.transform = 'scale(1)';
};
btn.onclick = () => {
audioEngine.applyMood(moodId);
moodGrid.querySelectorAll('button').forEach(b => {
b.style.backgroundColor = 'rgba(33, 150, 243, 0.25)';
});
btn.style.backgroundColor = '#4CAF50';
};
moodGrid.appendChild(btn);
});
moodContainer.appendChild(moodGrid);
musicMenuContainer.appendChild(moodContainer);
// ==================== THÊM: PHẦN TÙY CHỈNH EQ (ĐỘ TRẦM - CAO VÚT - NHẸ NHÀNG) ====================
const customContainer = document.createElement('div');
customContainer.style.marginTop = '15px';
customContainer.style.padding = '12px';
customContainer.style.backgroundColor = 'rgba(33, 150, 243, 0.25)';
customContainer.style.borderRadius = '8px';
customContainer.style.border = '1px solid #2196F3';
const customTitle = document.createElement('div');
customTitle.textContent = '🎛️ TÙY CHỈNH EQ (theo ý bạn)';
customTitle.style.fontWeight = 'bold';
customTitle.style.marginBottom = '12px';
customTitle.style.color = '#2196F3';
customTitle.style.textAlign = 'center';
customContainer.appendChild(customTitle);
// Bass (Độ trầm)
const bassLabel = document.createElement('label');
bassLabel.textContent = 'Độ trầm (Bass dB):';
bassLabel.style.display = 'block';
bassLabel.style.marginBottom = '4px';
bassLabel.style.fontSize = '13px';
customContainer.appendChild(bassLabel);
bassSlider = createSlider(-15, 15, 8);
bassSlider.style.marginBottom = '8px';
bassSlider.addEventListener('input', () => {
if (audioEngine.isActive && audioEngine.nodes.bassEQ) {
audioEngine.nodes.bassEQ.gain.value = parseFloat(bassSlider.value);
}
});
customContainer.appendChild(bassSlider);
// Mid
const midLabel = document.createElement('label');
midLabel.textContent = 'Độ trung (Mid dB):';
midLabel.style.display = 'block';
midLabel.style.marginBottom = '4px';
midLabel.style.fontSize = '13px';
customContainer.appendChild(midLabel);
midSlider = createSlider(-12, 12, 3);
midSlider.style.marginBottom = '8px';
midSlider.addEventListener('input', () => {
if (audioEngine.isActive && audioEngine.nodes.midEQ) {
audioEngine.nodes.midEQ.gain.value = parseFloat(midSlider.value);
}
});
customContainer.appendChild(midSlider);
// Treble (Độ cao vút)
const trebleLabel = document.createElement('label');
trebleLabel.textContent = 'Độ cao vút (Treble dB):';
trebleLabel.style.display = 'block';
trebleLabel.style.marginBottom = '4px';
trebleLabel.style.fontSize = '13px';
customContainer.appendChild(trebleLabel);
trebleSlider = createSlider(-12, 15, 5);
trebleSlider.style.marginBottom = '8px';
trebleSlider.addEventListener('input', () => {
if (audioEngine.isActive && audioEngine.nodes.trebleEQ) {
audioEngine.nodes.trebleEQ.gain.value = parseFloat(trebleSlider.value);
}
});
customContainer.appendChild(trebleSlider);
// Reverb (Nhẹ nhàng - Độ vang)
const reverbLabel = document.createElement('label');
reverbLabel.textContent = 'Độ vang / Nhẹ nhàng (Reverb %):';
reverbLabel.style.display = 'block';
reverbLabel.style.marginBottom = '4px';
reverbLabel.style.fontSize = '13px';
customContainer.appendChild(reverbLabel);
reverbSlider = createSlider(0, 50, 15);
reverbSlider.style.marginBottom = '8px';
reverbSlider.addEventListener('input', () => {
AudioConfig.reverb = parseFloat(reverbSlider.value) / 100;
});
customContainer.appendChild(reverbSlider);
// Stereo
const stereoLabel = document.createElement('label');
stereoLabel.textContent = 'Độ rộng âm trường (Stereo x):';
stereoLabel.style.display = 'block';
stereoLabel.style.marginBottom = '4px';
stereoLabel.style.fontSize = '13px';
customContainer.appendChild(stereoLabel);
stereoSlider = createSlider(1, 2, 1.3);
stereoSlider.step = '0.1';
stereoSlider.style.marginBottom = '10px';
stereoSlider.addEventListener('input', () => {
AudioConfig.stereoWiden = parseFloat(stereoSlider.value);
});
customContainer.appendChild(stereoSlider);
// Nút reconnect (vì Reverb + Stereo cần rebuild chain)
const reconnectBtn = createButton('🔄 Cập nhật Reverb & Stereo', 'rgba(255, 140, 0, 0.25)');
reconnectBtn.style.marginTop = '5px';
reconnectBtn.addEventListener('click', () => {
audioEngine.reconnectToVideo();
});
customContainer.appendChild(reconnectBtn);
// Thêm custom vào menu
musicMenuContainer.appendChild(customContainer);
// Volume control
const volumeLabel = document.createElement('label');
volumeLabel.textContent = 'âm thanh:';
musicMenuContainer.appendChild(volumeLabel);
const volumeSlider = createSlider(0, 100, 85);
volumeSlider.addEventListener('input', () => {
const volume = volumeSlider.value;
if (player) {
player.setVolume(volume);
}
audioEngine.setVolume(volume);
});
musicMenuContainer.appendChild(volumeSlider);
// Mute/Unmute button
const muteButton = createButton('tắt tiếng', 'rgba(255, 165, 0, 0.25)');
let isMuted = false;
muteButton.addEventListener('click', () => {
isMuted = !isMuted;
if (player) {
player.setVolume(isMuted ? 0 : volumeSlider.value);
}
audioEngine.setVolume(isMuted ? 0 : volumeSlider.value);
muteButton.textContent = isMuted ? 'mở tiếng' : 'tắt tiếng';
});
musicMenuContainer.appendChild(muteButton);
// Playback speed control
const speedLabel = document.createElement('label');
speedLabel.textContent = 'tốc độ nhạc:';
musicMenuContainer.appendChild(speedLabel);
const speedSelect = createSelect([0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65,0.7,0.75,0.8,0.85,0.9,0.95,1,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9, 2,3,4,5,6,7,8,9,10,100,1000,1000000,1000000], 'x');
speedSelect.addEventListener('change', () => {
player.setPlaybackRate(parseFloat(speedSelect.value));
});
musicMenuContainer.appendChild(speedSelect);
// Track progress bar
const progressContainer = document.createElement('div');
const progressBar = document.createElement('input');
progressBar.type = 'range';
progressBar.min = '0';
progressBar.max = '100';
progressBar.value = '0';
progressBar.style.width = '100%';
progressBar.style.marginBottom = '10px';
progressBar.disabled = true;
progressBar.addEventListener('input', () => {
const time = (progressBar.value / 100) * player.getDuration();
player.seekTo(time);
});
progressContainer.appendChild(progressBar);
musicMenuContainer.appendChild(progressContainer);
// Song duration display
const durationDisplay = document.createElement('div');
durationDisplay.textContent = 'thời gian: 0:00 / 0:00';
durationDisplay.style.textAlign = 'center';
musicMenuContainer.appendChild(durationDisplay);
// ==================== THÊM BIẾN CHO LOOP VÀ AUTO NEXT ====================
let loopMode = 'none';
let sequentialMode = false; // Chế độ tự động chuyển bài tiếp theo
let currentMusic = null;
let currentMusicIndex = -1; // Vị trí bài hiện tại trong musicList
let musicButtons = []; // Mảng lưu các button tương ứng với từng bài
// ==================== THÊM NÚT LOOP ====================
const loopButton = createButton('➡️ Không lặp', 'rgba(128, 128, 128,0.25)');
loopButton.addEventListener('click', () => {
if (loopMode === 'none') {
loopMode = 'single';
loopButton.textContent = '🔂 Lặp bài này';
loopButton.style.backgroundColor = 'rgba(76, 175, 80,0.25)';
} else {
loopMode = 'none';
loopButton.textContent = '➡️ Không lặp';
loopButton.style.backgroundColor = 'rgba(128, 128, 128,0.25)';
}
});
musicMenuContainer.appendChild(loopButton);
// ==================== THÊM NÚT AUTO NEXT (CHUYỂN BÀI TIẾP THEO) ====================
const sequentialButton = createButton('⏭️ Auto next: OFF', 'rgba(0, 150, 255,0.25)');
sequentialButton.addEventListener('click', () => {
sequentialMode = !sequentialMode;
sequentialButton.textContent = sequentialMode ? '⏭️ Auto next: ON' : '⏭️ Auto next: OFF';
sequentialButton.style.backgroundColor = sequentialMode ? 'rgba(0, 200, 100,0.35)' : 'rgba(0, 150, 255,0.25)';
});
musicMenuContainer.appendChild(sequentialButton);
// ==================== CÁC NÚT ĐIỀU KHIỂN (TẠM DỪNG, NGẪU NHIÊN, DỪNG) ====================
// Create Pause/Play Button
const pauseButton = createButton('TẠM DỪNG', 'rgba(0, 255, 0,0.25)');
pauseButton.addEventListener('click', () => {
if (currentPlayingID) {
const state = player.getPlayerState();
if (state === YT.PlayerState.PLAYING) {
player.pauseVideo();
pauseButton.textContent = 'BẮT ĐẦU ';
} else {
player.playVideo();
pauseButton.textContent = 'TẠM DỪNG';
}
}
});
musicMenuContainer.appendChild(pauseButton);
// Create Random Play Button
const randomPlayButton = createButton('ngẫu nhiên BÀI NHẠC', 'rgba(255, 0, 255, 0.25)');
randomPlayButton.addEventListener('click', () => {
const randomIndex = Math.floor(Math.random() * musicList.length);
const randomMusic = musicList[randomIndex];
const targetButton = musicButtons[randomIndex] || null;
playMusic(randomMusic, targetButton);
});
musicMenuContainer.appendChild(randomPlayButton);
// Create Stop Music button
const stopButton = createButton('Dừng nhạc', 'rgba(255, 0, 0, 0.25)');
stopButton.addEventListener('click', () => {
if (currentPlayingID) {
player.stopVideo();
currentPlayingID = null;
if (currentButton) {
currentButton.textContent = currentButton.textContent.replace(' (Chill nào)', ' (nghe thử xem)');
currentButton.style.color = '#fff';
}
}
});
musicMenuContainer.appendChild(stopButton);
// List of music genres and their YouTube links
const musicList = [
{ name: 'Địa Ngục Trần Gian (Thazh x Đông Remix)', url: 'https://www.youtube.com/watch?v=05S2uf37ufc' },
{ name: 'The Right Path', url: 'https://www.youtube.com/watch?v=o_Figo57l8s&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=114&ab_channel=KainRecordings' },
{ name: 'Glass Animals', url: 'https://www.youtube.com/watch?v=6APgzehjAQM&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=113' },
{ name: 'Lucky x Go Girl', url: 'https://www.youtube.com/watch?v=cnbUjIhuryc' },
{ name: 'chill 1', url: 'https://www.youtube.com/watch?v=AQbvCfRCFvg&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=111&pp=gAQBiAQB8AUB' },
{ name: 'Darkside', url: 'https://www.youtube.com/watch?v=AIikYWUbAB0&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=110' },
{ name: 'lovely', url: 'https://www.youtube.com/watch?v=0Ghcjygr66g&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=106' },
{ name: 'chill 2', url: 'https://www.youtube.com/watch?v=5yaTVGSXAMc&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=103' },
{ name: 'chill 3', url: 'https://www.youtube.com/watch?v=Y6AiKryrDyk&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=104&pp=gAQBiAQB8AUB' },
{ name: 'I Want You to Know', url: 'https://www.youtube.com/watch?v=iwPEPqNerPY&ab_channel=BeatPanda' },
{ name: 'Tháng Năm Không Quên', url: 'https://www.youtube.com/watch?v=g3s-OdZazbQ&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=102' },
{ name: 'Bài Hát Liên Quân', url: 'https://www.youtube.com/watch?v=CxXN5DmI9s4&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=101' },
{ name: 'DDTank - Tết Nhà Bà Hoan Parody ', url: 'https://www.youtube.com/watch?v=NRRs9HyfNMc&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=95' },
{ name: 'Legendary', url: 'https://www.youtube.com/watch?v=cTlshvPrIZo&list=RDQMBlegB1oalTw&index=19' },
{ name: 'ĐẾ VƯƠNG', url: 'https://www.youtube.com/watch?v=qkPgUgkQE4Y&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=93' },
{ name: 'Lost Sky', url: 'https://www.youtube.com/watch?v=L7kF4MXXCoA&list=RDQMBlegB1oalTw&index=21' },
{ name: 'Tướng Quân ', url: 'https://www.youtube.com/watch?v=U-hAhjg56HU&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=92' },
{ name: 'candyland', url: 'https://www.youtube.com/watch?v=IhchfhxvPKI&ab_channel=Tobu' },
{ name: 'Vicetone - Nevada', url: 'https://www.youtube.com/watch?v=AnMhdn0wJ4I&list=RDQMBlegB1oalTw&index=25' },
{ name: 'Believer ', url: 'https://www.youtube.com/watch?v=FXqp9WiFWzc&list=RDQMBlegB1oalTw&index=17' },
{ name: 'On & On', url: 'https://www.youtube.com/watch?v=XsZZQPKLChY&list=RDQMBlegB1oalTw&index=15' },
{ name: '💋💜Nevada💜💋', url: 'https://www.youtube.com/watch?v=bLrOTFDU2ZI&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=109&ab_channel=Detective.J' },
{ name: 'Legends Never Die', url: 'https://www.youtube.com/watch?v=r6zIGXun57U&list=RDQMBlegB1oalTw&index=3' },
{ name: 'nevada', url: 'https://www.youtube.com/watch?v=2jzxIOCYzEM&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=108&ab_channel=Jarctix' },
{ name: 'Tinh Vệ (精卫) ', url: 'https://www.youtube.com/watch?v=pWAQZ3bg1Qk' },
{ name: '花海DJ (周杰伦 - JayChou)', url: 'https://www.youtube.com/watch?v=8lxgMpheygs' },
{ name: 'Trăng ơi trăng à - 月光呀月光 slow 0.8x', url: 'https://www.youtube.com/watch?v=YIzMUdPA2s4' },
{ name: 'đáy biển', url: 'https://www.youtube.com/watch?v=8Byd6elEkbw' },
{ name: 'chill 4', url: 'https://www.youtube.com/watch?v=-GDI6oT6Jp8&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=2' },
{ name: 'xích linh', url: 'https://www.youtube.com/watch?v=Ba0A_7RKc-k' },
{ name: 'giỏ nổi lên rồi', url: 'https://www.youtube.com/watch?v=n9iKoJ9ZE-Q' },
{ name: 'chill 5', url: 'https://www.youtube.com/watch?v=xZhkxpwkYiA&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=84&pp=gAQBiAQB8AUB' },
{ name: 'nhất lộ sinh hoa', url: 'https://www.youtube.com/watch?v=S1ElLh_hf3k' },
{ name: 'chill 6', url: 'https://www.youtube.com/watch?v=w35LzCA0YLY&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=82&pp=gAQBiAQB8AUB' },
{ name: 'chill 7', url: 'https://www.youtube.com/watch?v=40EAqsXibhM&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=81&pp=gAQBiAQB8AUB' },
{ name: 'chill 8', url: 'https://www.youtube.com/watch?v=J4L-1FLXVLM&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=80&pp=gAQBiAQB8AUB' },
{ name: 'vây giữ', url: 'https://www.youtube.com/watch?v=G_fVwdpLKZ4' },
{ name: 'mười năm nhân gian', url: 'https://www.youtube.com/watch?v=9cSWgDSZ7X0' },
{ name: 'chill 9', url: 'https://www.youtube.com/watch?v=bb5ucM01Em0&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=76&pp=gAQBiAQB8AUB' },
{ name: 'nhân sinh quán', url: 'https://www.youtube.com/watch?v=NxEDFmqWFLo' },
{ name: 'quan sơn tửu', url: 'https://www.youtube.com/watch?v=M6KC0C4aWbA' },
{ name: 'chill 12', url: 'https://www.youtube.com/watch?v=ocfOBZSOwGc&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=72&pp=gAQBiAQB8AUB' },
{ name: 'chill 13', url: 'https://www.youtube.com/watch?v=caf7T4AubSE&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=71&pp=gAQBiAQB8AUB' },
{ name: 'chill 14', url: 'https://www.youtube.com/watch?v=ZhGWQuqE758&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=65&pp=gAQBiAQB8AUB' },
{ name: 'chill 15', url: 'https://www.youtube.com/watch?v=h3aWuBzimTk&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=66&pp=gAQBiAQB8AUB' },
{ name: 'chill 16', url: 'https://www.youtube.com/watch?v=RKF4Tn7P-MQ&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=63&pp=gAQBiAQB8AUB' },
{ name: '红色高跟鞋( giày cao gót màu đỏ )', url: 'https://www.youtube.com/watch?v=muvNBWqtrv4' },
{ name: 'Run Free', url: 'https://www.youtube.com/watch?v=o3KXwe-7A-I&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=60&ab_channel=ATLAST' },
{ name: 'Shadow Of The Sun', url: 'https://www.youtube.com/watch?v=HsM9VucuCtw&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=59&ab_channel=AZURA' },
{ name: 'Cưới Thôi', url: 'https://www.youtube.com/watch?v=JOWqEpONn9w&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=58' },
{ name: 'Ấn Nút Nhớ Thả Giấc Mơ', url: 'https://www.youtube.com/watch?v=sSljdfttEl8&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=58&ab_channel=N26Music%E2%99%AA' },
{ name: 'huan BACK HOME', url: 'https://www.youtube.com/watch?v=8Tx36l5MGxg&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=55&pp=gAQBiAQB8AUB' },
{ name: 'WAY BACK HOME', url: 'https://www.youtube.com/watch?v=1kehqCLudyg&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=54&pp=gAQBiAQB8AUB' },
{ name: 'THAT GIRL', url: 'https://www.youtube.com/watch?v=OUtbNopS4xU&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=53&pp=gAQBiAQB8AUB' },
{ name: 'PRETTY GIRL', url: 'https://www.youtube.com/watch?v=ptIXwyxf7XQ&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=52&pp=gAQBiAQB8AUB' },
{ name: 'DREAM-SAVE ME', url: 'https://www.youtube.com/watch?v=rREz6DYDXng&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=48&pp=gAQBiAQB8AUB' },
{ name: 'ALAN- PLAY', url: 'https://www.youtube.com/watch?v=YQRHrco73g4&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=44&pp=gAQBiAQB8AUB' },
{ name: 'NHAC REVIEW PHIM', url: 'https://www.youtube.com/watch?v=ialVTirpQ5Q&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=43&pp=gAQBiAQB8AUB' },
{ name: 'LOVE IS GONE', url: 'https://www.youtube.com/watch?v=c6SZy7miyaY&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=42&pp=gAQBiAQB8AUB' },
{ name: 'MONODY', url: 'https://www.youtube.com/watch?v=1MZR0BEniIY&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=36&pp=gAQBiAQB8AUB' },
{ name: 'STEORT HEAL', url: 'https://www.youtube.com/watch?v=Y1pq2oLXbTQ&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=33&pp=gAQBiAQB8AUB' },
{ name: 'CLOSE THE SUN', url: 'https://www.youtube.com/watch?v=VyXm3GTdNf0' },
{ name: 'PSYCHO', url: 'https://www.youtube.com/watch?v=YvLRu5vcr68' },
{ name: 'Yami', url: 'https://www.youtube.com/watch?v=eTgxYFXP1hc&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=14&ab_channel=C%C3%A0Chua' },
{ name: 'RETUNR', url: 'https://www.youtube.com/watch?v=m4Hg_JMtJqI&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=15&pp=gAQBiAQB8AUB' },
{ name: 'TOP EDM', url: 'https://www.youtube.com/watch?v=xlTZywrfO7E&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=17&pp=gAQBiAQB8AUB' },
{ name: 'HIS THEME', url: 'https://www.youtube.com/watch?v=qOpsp9bJFP4&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=12&pp=gAQBiAQB8AUB' },
{ name: 'CHILL 17', url: 'https://www.youtube.com/watch?v=ybgvaC4rfAE&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&ab_channel=%F0%9D%90%92%F0%9D%90%9A%F0%9D%90%9D%F0%9D%90%82%F0%9D%90%A1%F0%9D%90%A2%F0%9D%90%A5%F0%9D%90%A5-%F0%9D%90%94%F0%9D%90%92%F0%9D%90%94%F0%9D%90%8A' },
{ name: 'CHILL 18', url: 'https://www.youtube.com/watch?v=-GDI6oT6Jp8&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=2&pp=gAQBiAQB8AUB' },
{ name: 'CHILL 19', url: 'https://www.youtube.com/watch?v=6LW7tcryoXs&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=3&pp=gAQBiAQB8AUB' },
{ name: 'CHILL 20', url: 'https://www.youtube.com/watch?v=6LW7tcryoXs&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=3&pp=gAQBiAQB8AUB' },
{ name: 'CHILL 21', url: 'https://www.youtube.com/watch?v=6I5gYHn-QOk&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=4&pp=gAQBiAQB8AUB' },
{ name: 'CHILL 22', url: 'https://www.youtube.com/watch?v=5sG4k7rj6MY&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=5&pp=gAQBiAQB8AUB' },
{ name: 'CHILL 23', url: 'https://www.youtube.com/watch?v=0wsMSc5hDQg&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=6&pp=gAQBiAQB8AUB' },
{ name: 'CHILL 24', url: 'https://www.youtube.com/watch?v=5TZ33vy_v5w&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=7&pp=gAQBiAQB8AUB' },
{ name: 'CHILL 25', url: 'https://www.youtube.com/watch?v=5TZ33vy_v5w&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=7&pp=gAQBiAQB8AUB' },
{ name: 'Người yêu bỏ lỡ', url: 'https://www.youtube.com/watch?v=DK23U3sJrYg' },
{ name: 'CHILL 26', url: 'https://www.youtube.com/watch?v=gfsUC3F8pYA&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=8&pp=gAQBiAQB8AUB' },
{ name: 'CHILL 27', url: 'https://www.youtube.com/watch?v=zFx0o4epvPA&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=9&pp=gAQBiAQB8AUB' },
{ name: 'intro ( real love )', url: 'https://www.youtube.com/watch?v=66nH4dXp_-o' },
{ name: 'in the end', url: 'https://www.youtube.com/watch?v=WNeLUngb-Xg' },
{ name: '花火が瞬く夜に', url: 'https://www.youtube.com/watch?v=xZ5Rrrul7h8' },
{ name: 'Melancholy', url: 'https://www.youtube.com/watch?v=RxglYGHuqFc' },
{ name: 'Bá hổ thuyết', url: 'https://www.youtube.com/watch?v=6pomVpUk5-Y' },
{ name: 'windy hill', url: 'https://www.youtube.com/watch?v=qV97ux4NA28&pp=ygUKd2luZHkgaGlsbA%3D%3D' },
{ name: '海底 của 一支榴莲', url: 'https://www.youtube.com/watch?v=v5gK8np-OTA' },
{ name: '下辈子还要和你成个家', url: 'https://www.youtube.com/watch?v=6CMITr9dTws' },
{ name: 'past lives', url: 'https://www.youtube.com/watch?v=iNa1n6Gch7E' },
{ name: 'lovely', url: 'https://www.youtube.com/watch?v=8VLXHyHRXjc&pp=ygUGbG92ZWx5' },
{ name: '送给未来的你', url: 'https://www.youtube.com/watch?v=iQfIcgA8qHg' },
{ name: '刚好遇见你 sieu hay', url: 'https://www.youtube.com/watch?v=aEhq4WxBYqM' },
{ name: 'ALAN 2', url: 'https://www.youtube.com/watch?v=jlQ2hs0EANo' },
{ name: 'top chil tq', url: 'https://www.youtube.com/watch?v=2EmGToTikIY' },
{ name: 'My Sunset', url: 'https://www.youtube.com/watch?v=GpkHJlyV7TQ' },
{ name: 'Quan sơn tửu', url: 'https://www.youtube.com/watch?v=wvkGE4mtTB0' },
{ name: 'Lifeline', url: 'https://www.youtube.com/watch?v=rWTmSHXVfCM' },
{ name: 'dancing with your ghost', url: 'https://www.youtube.com/watch?v=emm0uGDGg2o' },
{ name: 'shape of you', url: 'https://www.youtube.com/watch?v=Ksin3zNXvzo' },
{ name: 'seasons', url: 'https://www.youtube.com/watch?v=Ymts4ldfPws' },
{ name: 'L.I.F.E', url: 'https://www.youtube.com/watch?v=_tSWg-KOslM' },
{ name: 'summersong', url: 'https://www.youtube.com/watch?v=HoCw_gaCHXE' },
{ name: '芒种" (Mang Chủng)', url: 'https://www.youtube.com/watch?v=vgbrIy08e2w' },
{ name: 'china-X', url: 'https://www.youtube.com/watch?v=qgVXS8l5smo' },
{ name: 'breathe', url: 'https://www.youtube.com/watch?v=KbT-qpE3Kl4' },
{ name: 'dynasty', url: 'https://www.youtube.com/watch?v=5OESzopq3dE' },
{ name: 'Hồng chiêu nguyện', url: 'https://www.youtube.com/watch?v=lbNPMskXNHo' },
{ name: '20 EDM tq', url: 'https://www.youtube.com/watch?v=IAMbG8OiExU' },
{ name: 'top nhac', url: 'https://www.youtube.com/watch?v=u5WZnV3AoA4' },
{ name: 'Closer', url: 'https://www.youtube.com/watch?v=PT2_F-1esPk&ab_channel=ChainsmokersVEVO' },
{ name: 'dusk till dawn', url: 'https://www.youtube.com/watch?v=p-eS-_olx9M&ab_channel=7clouds' },
{ name: 'Mood ', url: 'https://www.youtube.com/watch?v=f1J4dRTMy9A&ab_channel=7clouds' },
{ name: 'royalty', url: 'https://www.youtube.com/watch?v=oOi3oJmfz4o&ab_channel=7clouds' },
{ name: 'Nova', url: 'https://www.youtube.com/watch?v=Rq-0NxKUR-Y&ab_channel=SrMichi' },
{ name: 'Fight', url: 'https://www.youtube.com/watch?v=EVpm3pHYaV4&ab_channel=BeatBrothers' },
{ name: 'First Date', url: 'https://www.youtube.com/watch?v=AVK0BIVqLLc&ab_channel=frad' },
{ name: 'Vacation', url: 'https://www.youtube.com/watch?v=TidRG-baYi8&ab_channel=Nh%E1%BA%ADtH%E1%BA%A3oTr%E1%BA%A7n' },
{ name: 'anh thanh niên', url: 'https://www.youtube.com/watch?v=HPL74s4VPdk&pp=ygUPYW5oIHRoYW5oIG5pw6pu' },
{ name: 'kẹo bông ngòn ', url: 'https://www.youtube.com/watch?v=sHa5nQO3jwA&ab_channel=H2KMusic' },
{ name: 'Mây x Gió', url: 'https://www.youtube.com/watch?v=0A6hCfFZVj4&ab_channel=DuzmeMusic' },
{ name: 'spectre', url: 'https://www.youtube.com/watch?v=wJnBTPUQS5A' },
{ name: 'alone', url: 'https://www.youtube.com/watch?v=1-xGerv5FOk' },
{ name: 'faded', url: 'https://www.youtube.com/watch?v=60ItHLz5WEA' },
{ name: 'chill 28', url: 'https://www.youtube.com/watch?v=4vayrx6PFCQ&ab_channel=AGNES%F0%9F%94%A5' },
{ name: 'chill 29', url: 'https://www.youtube.com/watch?v=icxO53ZyK7A' },
{ name: 'chill 30', url: 'https://www.youtube.com/watch?v=AQbvCfRCFvg' },
{ name: 'On my way', url: 'https://www.youtube.com/watch?v=ETqXUBFZpkE&ab_channel=LOFI_LINES' },
{ name: 'Nothin On Me', url: 'https://www.youtube.com/watch?v=qmbB3uR92j8&ab_channel=H%E1%BB%A7Mu%E1%BB%91iM%E1%BA%B7n' },
{ name: 'PIXELS', url: 'https://www.youtube.com/watch?v=EUyQgyzpAbE&ab_channel=brianjcb' },
{ name: 'Nothing on you', url: 'https://www.youtube.com/watch?v=U573mlR4rYw&ab_channel=DuskMusicASIA' },
{ name: 'So Far Away ', url: 'https://www.youtube.com/watch?v=rA0jSPEoyk4&ab_channel=ITMMUSIC' },
{ name: 'sold out', url: 'https://www.youtube.com/watch?v=clKvFcl0zwo&ab_channel=LyricsMusic' },
{ name: 'the way still love you', url: 'https://www.youtube.com/watch?v=MsBEu1iWsF4&ab_channel=TopTikTok' },
{ name: 'Thăm cố tri', url: 'https://www.youtube.com/watch?v=409xjHOU63E' },
{ name: 'top tq sieu chill', url: 'https://www.youtube.com/watch?v=TmRvke5Ue-k&ab_channel=Tr.T.Kh%C3%A1nhHuy%E1%BB%81n' },
{ name: 'Reverse溯 (版钢琴', url: 'https://www.youtube.com/watch?v=v7xRVTXWkbU&ab_channel=HyQMusic%E3%83%84' },
{ name: 'DEATH BED', url: 'https://www.youtube.com/watch?v=jJPMnTXl63E&list=PLB8Tk-JabtWed_6pE4H7yqctxbntnjwEe&index=10&pp=gAQBiAQB8AUB' },
{ name: 'Cry For Me x Giày Cao Gót Màu Đỏ', url: 'https://www.youtube.com/watch?v=2OM6Y_kunAM' },
{ name: 'Cry For Me', url: 'https://www.youtube.com/watch?v=72hp9Bqfpz4&pp=ygULY3J5IGZvciBtZSA%3D' },
{ name: 'Kiếp sau chắc sẽ không còn gặp được anh', url: 'https://www.youtube.com/watch?v=uhlYZ5b91RA' },
{ name: 'china p', url: 'https://www.youtube.com/watch?v=-3vdk1Qf4mI' },
{ name: 'china rain', url: 'https://www.youtube.com/watch?v=GqC8jWzmiLA' },
{ name: 'Erik Lund - Summertime', url: 'https://www.youtube.com/watch?v=uuiA9Htp1xQ' },
{ name: 'ightning ocean deed', url: 'https://www.youtube.com/watch?v=tW7oWvb-76Q' },
{ name: 'are you lost - park bird', url: 'https://www.youtube.com/watch?v=PJgEu_Ecuf8' },
{ name: 'Back one day', url: 'https://www.youtube.com/watch?v=2QdPxdcMhFQ' },
{ name: 'Collapsing world', url: 'https://www.youtube.com/watch?v=sOSqUMZgfoE' },
{ name: '所念皆星河 ( Nỗi nhớ tựa thiên hà )', url: 'https://www.youtube.com/watch?v=Mkwn5MHsJa0' },
{ name: 'Komorebi', url: 'https://www.youtube.com/watch?v=v1up3zGBYZU' },
{ name: '城南花已开 ( Thành nam hoa nở )', url: 'https://www.youtube.com/watch?v=AhlsPpuILuk' },
{ name: '告白之夜 ( Đêm tỏ tình )', url: 'https://www.youtube.com/watch?v=vMK1kuJGgnQ' },
{ name: '幻昼', url: 'https://www.youtube.com/watch?v=9eWNB_aMi-s' },
{ name: 'My soul', url: 'https://www.youtube.com/watch?v=935RgNLNuno' },
{ name: '青空 ( Bầu trời xanh )', url: 'https://www.youtube.com/watch?v=TkAOz9m-HNk' },
{ name: 'origin', url: 'https://www.youtube.com/watch?v=gZIDNZh9U14' },
{ name: 'let me love you x we dont talk anymore', url: 'https://www.youtube.com/watch?v=Boyvu7-wO1I' },
{ name: 'Normal no more', url: 'https://www.youtube.com/watch?v=iGrN7Gb8DtI' },
{ name: 'flower day piano', url: 'https://www.youtube.com/watch?v=asUUKGlCvDM' },
{ name: 'golden hour', url: 'https://www.youtube.com/watch?v=PEM0Vs8jf1w' },
{ name: 'sky blue', url: 'https://www.youtube.com/watch?v=BiwAlE4Inok' },
{ name: 'before spring ends', url: 'https://www.youtube.com/watch?v=s75xe7wKxSM' },
{ name: 'ha chi chua toi piano', url: 'https://www.youtube.com/watch?v=XN4PhnVfZt4' },
{ name: 'forest mixtape', url: 'https://www.youtube.com/watch?v=kwdeazF4j2o' },
{ name: 'điều anh biết', url: 'https://www.youtube.com/watch?v=ocjUUfk9k1Y' },
{ name: 'chúng ta không thuộc về nhau', url: 'https://www.youtube.com/watch?v=qGRU3sRbaYw' },
{ name: 'Nắng Lung Linh ', url: 'https://www.youtube.com/watch?v=wO9r4kj-ErU' },
{ name: ' 100 years love', url: 'https://www.youtube.com/watch?v=47ahoiV9qGY' },
{ name: 'Chưa chắc remix', url: 'https://www.youtube.com/watch?v=dpYG7NQL8oM' },
{ name: 'Đường Tôi Chở Em Về', url: 'https://www.youtube.com/watch?v=OuNo8Tkb3lI' },
{ name: 'Nụ Cười Của Cô Ấy', url: 'https://www.youtube.com/watch?v=EVcwgI-dLBg' },
{ name: 'Phonecert mashup', url: 'https://www.youtube.com/watch?v=dcQwM3KPXWc' },
{ name: 'chill 31', url: 'https://www.youtube.com/watch?v=tVQ_uDRs_7U' },
{ name: 'chill 32', url: 'https://www.youtube.com/watch?v=b-FHfiM8ILo' },
{ name: 'chill 33', url: 'https://www.youtube.com/watch?v=UqOnN8Qnh4Y' },
{ name: 'chill 34', url: 'https://www.youtube.com/watch?v=i9rBIgU0Lps' },
{ name: 'chill 35', url: 'https://www.youtube.com/watch?v=ZX2mjf9dFH8' },
{ name: 'chill 36', url: 'https://www.youtube.com/watch?v=F4JgISE0zdc' },
{ name: 'chill 37', url: 'https://www.youtube.com/watch?v=CCgt6IwzZoU' },
{ name: 'chill 38', url: 'https://www.youtube.com/watch?v=1bMg6REj2HU' },
{ name: 'chill 39', url: 'https://www.youtube.com/watch?v=vcwszTnFkIY&list=RDGMEMCMFH2exzjBeE_zAHHJOdxg&start_radio=1&rv=1bMg6REj2HU' },
{ name: 'Không hẹn gặp lại', url: 'https://www.youtube.com/watch?v=MAd0QlVRYWc&list=RDGMEMCMFH2exzjBeE_zAHHJOdxg&index=11' },
{ name: 'Call of Silence', url: 'https://www.youtube.com/watch?v=SVBvYbobwEw&list=RDGMEMCMFH2exzjBeE_zAHHJOdxg&index=13' },
{ name: 'chill 40', url: 'https://www.youtube.com/watch?v=oEwrAwb_4jM' },
{ name: 'chill 41', url: 'https://www.youtube.com/watch?v=nN4PvclzdIc' },
{ name: '[小红书]《02》', url: 'https://www.youtube.com/watch?v=wqbk6ZJ8jhs' },
{ name: ' Gặp gỡ đã là quẻ Thượng Thượng ', url: 'https://www.youtube.com/watch?v=_3o-qe3IRYM' },
{ name: 'Beyond The Sea Is Freedom (完整版)', url: 'https://www.youtube.com/watch?v=dxDhOf727rg' },
{ name: 'chill 40', url: 'https://www.youtube.com/watch?v=yMuBSxSIS04&t' },
{ name: 'Night crusing', url: 'https://www.youtube.com/watch?v=TCtG_SJF6As' }
];
let currentPlayingID = null;
let currentButton = null;
// Initialize music list
const musicItemContainer = document.createElement('div');
musicList.forEach((music, idx) => addMusicButton(music, idx));
musicMenuContainer.appendChild(musicItemContainer);
document.body.appendChild(musicMenuContainer);
// ==================== PHẦN TÌM KIẾM BÀI HÁT ====================
// Tạo container cho tìm kiếm
const searchContainer = document.createElement('div');
searchContainer.style.marginBottom = '15px';
searchContainer.style.padding = '12px';
searchContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.25)';
searchContainer.style.borderRadius = '12px';
searchContainer.style.border = '1px solid rgba(255, 255, 255, 0.25)';
// Input tìm kiếm
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = '🔍 Tìm bài hát...';
searchInput.style.width = '100%';
searchInput.style.padding = '10px';
searchInput.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
searchInput.style.border = '1px solid rgba(255, 255, 255, 0.2)';
searchInput.style.borderRadius = '30px';
searchInput.style.color = '#fff';
searchInput.style.fontSize = '13px';
searchInput.style.outline = 'none';
searchInput.style.transition = 'all 0.3s';
searchInput.addEventListener('focus', () => {
searchInput.style.backgroundColor = 'rgba(255, 255, 255, 0.15)';
searchInput.style.borderColor = 'rgba(76, 175, 80, 0.5)';
});
searchInput.addEventListener('blur', () => {
searchInput.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
searchInput.style.borderColor = 'rgba(255, 255, 255, 0.2)';
});
// Container hiển thị kết quả tìm kiếm
const searchResultsContainer = document.createElement('div');
searchResultsContainer.style.marginTop = '10px';
searchResultsContainer.style.maxHeight = '300px';
searchResultsContainer.style.overflowY = 'auto';
searchResultsContainer.style.display = 'none';
// Thêm vào menu
searchContainer.appendChild(searchInput);
searchContainer.appendChild(searchResultsContainer);
// Chèn vào trước danh sách nhạc (musicItemContainer)
musicMenuContainer.insertBefore(searchContainer, musicItemContainer);
// Hàm lọc và hiển thị kết quả
function updateSearchResults() {
const keyword = searchInput.value.trim().toLowerCase();
if (keyword === '') {
searchResultsContainer.style.display = 'none';
musicItemContainer.style.display = 'block';
return;
}
// Lọc bài hát
const filtered = musicList.filter(music => music.name && music.name.toLowerCase().includes(keyword));
if (filtered.length === 0) {
searchResultsContainer.innerHTML = '<div style="text-align:center; padding:15px; color:rgba(255,255,255,0.25);">Không tìm thấy bài hát nào 😢</div>';
} else {
// Tạo danh sách kết quả
let html = '';
filtered.forEach(music => {
html += `
<button class="search-result-btn" data-name="${music.name.replace(/"/g, '"')}" data-url="${music.url}" style="
width: 100%;
padding: 10px;
margin-bottom: 8px;
background: rgba(0, 255, 255, 0.1);
border: 1px solid rgba(0, 255, 255, 0.2);
border-radius: 10px;
color: #fff;
font-size: 12px;
text-align: left;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
">🎵 ${music.name}</button>
`;
});
searchResultsContainer.innerHTML = html;
// Gắn sự kiện click cho từng nút kết quả
document.querySelectorAll('.search-result-btn').forEach(btn => {
btn.addEventListener('click', () => {
const name = btn.getAttribute('data-name');
const url = btn.getAttribute('data-url');
const music = { name, url };
// Tìm nút tương ứng trong danh sách gốc (nếu có) để cập nhật trạng thái
const originalButtons = musicItemContainer.querySelectorAll('button');
let targetButton = null;
for (let i = 0; i < originalButtons.length; i++) {
if (originalButtons[i].textContent.includes(name)) {
targetButton = originalButtons[i];
break;
}
}
playMusic(music, targetButton || btn);
// Ẩn kết quả tìm kiếm sau khi chọn
searchInput.value = '';
searchResultsContainer.style.display = 'none';
musicItemContainer.style.display = 'block';
});
});
}
searchResultsContainer.style.display = 'block';
musicItemContainer.style.display = 'none';
}
// Lắng nghe sự kiện input
searchInput.addEventListener('input', updateSearchResults);
// Thêm style cho scrollbar trong kết quả tìm kiếm
const searchStyle = document.createElement('style');
searchStyle.textContent = `
#musicMenuContainer .search-results-container::-webkit-scrollbar {
width: 5px;
}
#musicMenuContainer .search-results-container::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
}
#musicMenuContainer .search-results-container::-webkit-scrollbar-thumb {
background: rgba(0, 255, 255, 0.3);
border-radius: 10px;
}
.search-result-btn:hover {
background: rgba(0, 255, 255, 0.25) !important;
transform: translateX(5px);
}
`;
document.head.appendChild(searchStyle);
// ==================== KẾT THÚC PHẦN TÌM KIẾM ====================
function addMusicButton(music, index) {
const musicItem = document.createElement('div');
musicItem.style.display = 'flex';
musicItem.style.alignItems = 'center';
musicItem.style.marginBottom = '10px';
const musicButton = createButton(music.name + ' (nghe thử xem)', 'rgba(0, 255, 255, 0.25)');
musicButton.addEventListener('click', () => {
playMusic(music, musicButton);
});
musicItem.appendChild(musicButton);
musicItemContainer.appendChild(musicItem);
musicButtons.push(musicButton); // lưu button để dùng cho auto next
}
// Hàm phát bài tiếp theo (theo thứ tự)
function playNextSong() {
if (!sequentialMode) return;
if (musicList.length === 0) return;
let nextIndex = (currentMusicIndex + 1) % musicList.length;
// Nếu không tìm thấy index hiện tại (có thể do chưa có bài nào phát) thì chọn bài đầu
if (currentMusicIndex === -1) nextIndex = 0;
const nextMusic = musicList[nextIndex];
const nextButton = musicButtons[nextIndex] || null;
playMusic(nextMusic, nextButton);
}
// Play the selected music
function playMusic(music, musicButton) {
if (currentPlayingID && currentButton) {
player.stopVideo();
currentButton.textContent = currentButton.textContent.replace(' (Chill nào)', ' (nghe thử xem)');
currentButton.style.color = '#fff';
}
const videoID = extractVideoID(music.url);
if (videoID) {
currentMusic = music;
// Cập nhật chỉ số bài hiện tại
currentMusicIndex = musicList.findIndex(m => m.url === music.url);
if (currentMusicIndex === -1) currentMusicIndex = 0;
player.loadVideoById(videoID);
player.playVideo();
currentPlayingID = videoID;
currentButton = musicButton;
musicButton.textContent = music.name + ' (Chill nào)';
musicButton.style.color = 'lightgreen';
}
}
// Create buttons for volume, speed, etc.
function createButton(text, bgColor) {
const button = document.createElement('button');
button.textContent = text;
button.style.width = '100%';
button.style.padding = '10px';
button.style.backgroundColor = bgColor;
button.style.color = '#fff';
button.style.border = 'none';
button.style.borderRadius = '5px';
button.style.cursor = 'pointer';
button.style.transition = 'background-color 0.3s';
button.addEventListener('mouseover', () => {
button.style.backgroundColor = 'rgba(255, 255, 255, 0.25)';
});
button.addEventListener('mouseout', () => {
button.style.backgroundColor = bgColor;
});
return button;
}
// Create a slider
function createSlider(min, max, defaultValue) {
const slider = document.createElement('input');
slider.type = 'range';
slider.min = min;
slider.max = max;
slider.value = defaultValue;
slider.style.width = '100%';
slider.style.marginBottom = '10px';
return slider;
}
// Create a select dropdown
function createSelect(options, suffix) {
const select = document.createElement('select');
options.forEach(option => {
const opt = document.createElement('option');
opt.value = option;
opt.textContent = `${option}${suffix}`;
select.appendChild(opt);
});
select.style.width = '100%';
return select;
}
// Create an invisible player container
const playerContainer = document.createElement('div');
playerContainer.id = 'musicPlayer';
playerContainer.style.position = 'fixed';
playerContainer.style.bottom = '0';
playerContainer.style.right = '0';
playerContainer.style.width = '0';
playerContainer.style.height = '0';
document.body.appendChild(playerContainer);
// Load YouTube IFrame Player API
let tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
let firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// Create YouTube player
let player;
window.onYouTubeIframeAPIReady = function() {
player = new YT.Player('musicPlayer', {
height: '0',
width: '0',
videoId: '',
playerVars: {
'autoplay': 1,
'controls': 0,
'mute': 0,
'vq': 'hd1080',
'iv_load_policy': 3
},
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
};
function onPlayerReady(event) {
console.log('YouTube Player is ready');
player.setVolume(85);
audioEngine.init().then(() => {
setTimeout(() => {
try {
const iframe = player.getIframe();
const videoEl = iframe.contentDocument?.querySelector('video');
if (videoEl) {
audioEngine.connectToVideo(videoEl);
}
} catch (e) {}
}, 2000);
});
}
function onPlayerStateChange(event) {
if (event.data === YT.PlayerState.PLAYING) {
updateDurationDisplay();
if (window.progressInterval) {
clearInterval(window.progressInterval);
}
window.progressInterval = setInterval(updateProgressBar, 1000);
}
if (event.data === YT.PlayerState.ENDED) {
if (loopMode === 'single' && currentMusic) {
// Lặp lại bài hiện tại
const videoID = extractVideoID(currentMusic.url);
if (videoID) {
player.loadVideoById(videoID);
player.playVideo();
}
} else if (sequentialMode) {
// Tự động chuyển bài tiếp theo
playNextSong();
}
}
}
function updateDurationDisplay() {
const duration = player.getDuration();
durationDisplay.textContent = `thời gian: ${formatTime(player.getCurrentTime())} / ${formatTime(duration)}`;
progressBar.max = duration;
}
function updateProgressBar() {
if (player && currentPlayingID) {
progressBar.value = player.getCurrentTime();
updateDurationDisplay();
}
}
function formatTime(seconds) {
if (!seconds || isNaN(seconds)) return '0:00';
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${minutes}:${secs < 10 ? '0' + secs : secs}`;
}
function extractVideoID(url) {
const videoIDMatch = url.match(/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/);
return videoIDMatch ? videoIDMatch[1] : null;
}
document.addEventListener('keydown', (event) => {
if (event.key === 'Tab') {
musicMenuContainer.style.display = musicMenuContainer.style.display === 'block' ? 'none' : 'block';
}
if (event.key === ']') {
musicMenuContainer.style.display = musicMenuContainer.style.display === 'block' ? 'none' : 'block';
}
if (event.key === ' ') {
pauseButton.click();
}
if (event.key === 's' || event.key === 'S') {
stopButton.click();
}
});
musicMenuContainer.style.display = 'block';
// ==================== CẬP NHẬT ĐỒNG HỒ VÀ ĐỀ XUẤT BÀI HÁT ====================
function updateSuggestion() {
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
clockDiv.textContent = `${hours}:${minutes}:${seconds}`;
// Tính tổng số giây trong ngày
const totalSeconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
// Lấy danh sách bài hát hợp lệ (có tên và url)
const validSongs = musicList.filter(song => song.name && song.url);
if (validSongs.length > 0) {
const index = totalSeconds % validSongs.length;
const suggested = validSongs[index];
suggestedSongDiv.innerHTML = `🎧 <strong>Đề xuất:</strong> ${suggested.name}`;
// Lưu bài hát đề xuất để nút phát sử dụng
playSuggestBtn.onclick = () => {
// Tìm nút tương ứng trong danh sách (nếu có) để cập nhật trạng thái
const originalButtons = musicItemContainer.querySelectorAll('button');
let targetButton = null;
for (let i = 0; i < originalButtons.length; i++) {
if (originalButtons[i].textContent.includes(suggested.name)) {
targetButton = originalButtons[i];
break;
}
}
playMusic(suggested, targetButton || playSuggestBtn);
};
} else {
suggestedSongDiv.innerHTML = '🎧 Không có bài hát nào để đề xuất.';
playSuggestBtn.onclick = null;
}
}
updateSuggestion();
setInterval(updateSuggestion, 1000);
// ==================== GỢI Ý PLAYLIST THEO CẢM XÚC (NÂNG CẤP + REROLL) ====================
const emotionContainer = document.createElement('div');
emotionContainer.style.marginBottom = '20px';
emotionContainer.style.padding = '12px';
emotionContainer.style.backgroundColor = 'rgba(156, 39, 176, 0.2)';
emotionContainer.style.borderRadius = '16px';
emotionContainer.style.border = '1px solid rgba(156, 39, 176, 0.4)';
emotionContainer.style.backdropFilter = 'blur(8px)';
const emotionTitle = document.createElement('div');
emotionTitle.textContent = '🎭 CẢM XÚC HIỆN TẠI - GỢI Ý PLAYLIST';
emotionTitle.style.fontWeight = 'bold';
emotionTitle.style.textAlign = 'center';
emotionTitle.style.marginBottom = '12px';
emotionTitle.style.color = '#CE93D8';
emotionTitle.style.fontSize = '13px';
emotionContainer.appendChild(emotionTitle);
// ========== KHO TỪ KHÓA KHỔNG LỒ CHO MỖI CẢM XÚC ==========
const masterKeywords = {
happy: [
'happy', 'sun', 'upbeat', 'energy', 'legend', 'believer', 'fight', 'run', 'sold out', 'on my way',
'nothing on you', 'closer', 'dusk till dawn', 'shape of you', 'summer', 'vacation', 'smile', 'joy',
'dance', 'party', 'celebration', 'vui', 'hạnh phúc', 'năng lượng', 'tích cực', 'cười', 'nhảy múa',
'vui vẻ', 'sunshine', 'wonderful', 'good time', 'alright', 'feel good', 'bright', 'sunny', 'happy now',
'legendary', 'royalty', 'on & on', 'first date', 'mood', 'pretty girl', 'that girl', 'way back home',
'i want you to know', 'candyland', 'nevada', 'victone', 'glass animals', 'lucky', 'go girl', 'run free',
'summertime', 'back one day', 'normal no more', 'pixels', 'nothing on you', 'so far away', 'the way still love you',
'closer', 'dynasty', 'top edm', 'his theme', 'alan play', 'dream save me', 'legendary', 'believer', 'fight',
'cưới thôi', 'nắng lung linh', '100 years love', 'phonecert mashup', 'mây x gió', 'kẹo bông ngòn', 'anh thanh niên',
'top nhac', 'nova', 'first date', 'mood', 'psycho', 'yami', 'return', 'đế vương'
],
sad: [
'sad', 'cry', 'lonely', 'hurt', 'gone', 'melancholy', 'lovely', 'dancing with your ghost', 'death bed',
'past lives', 'cry for me', 'không hẹn gặp lại', 'chưa chắc', 'điều anh biết', 'chúng ta không thuộc về nhau',
'huan back home', 'buồn', 'khóc', 'cô đơn', 'đau lòng', 'mất mát', 'chia tay', 'lặng lẽ', 'nước mắt',
'sorrow', 'heartbreak', 'tears', 'regret', 'miss you', 'alone', 'broken', 'dark', 'rainy', 'blue',
'đáy biển', 'vây giữ', 'mười năm nhân gian', 'nhân sinh quán', 'quan sơn tửu', 'thăm cố tri', 'kiếp sau chắc sẽ không còn gặp được anh',
'người yêu bỏ lỡ', 'intro real love', 'in the end', '花火が瞬く夜に', 'melancholy', 'bá hổ thuyết', '海底一支榴莲', '下辈子还要和你成个家',
'送给未来的你', '刚好遇见你', 'my sunset', 'lifeline', 'seasons', 'breathe', 'love is gone', 'monody', 'steort heal', 'close the sun',
'shadow of the sun', 'way back home', 'that girl', 'pretty girl', 'dream save me', 'alan play', 'death bed',
'cry for me', 'không hẹn gặp lại', 'chưa chắc remix', 'đường tôi chở em về', 'nụ cười của cô ấy', 'chill 39', 'call of silence',
'alone', 'faded', 'spectre', 'on my way', 'so far away'
],
energetic: [
'energy', 'fight', 'legend', 'believer', 'rock', 'bass', 'edm', 'darkside', 'run', 'sold out', 'legendary',
'royalty', 'spectre', 'alone', 'faded', 'on my way', 'normal no more', 'collapsing world', 'mạnh mẽ',
'sôi động', 'thể thao', 'gym', 'năng động', 'phấn khích', 'bùng nổ', 'power', 'strong', 'intense',
'workout', 'rush', 'storm', 'thunder', 'blast', 'epic', 'victory', 'champions',
'địa ngục trần gian', 'darkside', 'legendary', 'believer', 'lost sky', 'tướng quân', 'candyland', 'nevada',
'vicetone', 'legends never die', 'top edm', 'his theme', 'alan play', 'dream save me', 'run free', 'fight',
'pixels', 'so far away', 'sold out', 'normal no more', 'collapsing world', 'spectre', 'alone', 'faded',
'on my way', 'royalty', 'closer', 'dusk till dawn', 'shape of you', 'summer', 'vacation', 'năng lượng',
'đế vương', 'top nhac', 'nova', 'first date', 'mood', 'psycho', 'yami', 'return', 'i want you to know'
],
calm: [
'chill', 'calm', 'peace', 'night', 'morning', 'acoustic', 'piano', 'rain', 'windy', 'soul', 'my soul',
'golden hour', 'origin', 'my sunset', 'lifeline', 'seasons', 'breathe', 'china', 'flower day', 'before spring ends',
'forest mixtape', 'komorebi', '城南花已开', '告白之夜', '幻昼', '青空', 'bình yên', 'thư giãn', 'nhẹ nhàng',
'tĩnh lặng', 'thiền', 'yên tĩnh', 'relax', 'soothing', 'gentle', 'soft', 'tender', 'peaceful', 'meditation',
'the right path', 'glass animals', 'chill 1', 'chill 2', 'chill 3', 'chill 4', 'chill 5', 'chill 6', 'chill 7', 'chill 8',
'chill 9', 'chill 12', 'chill 13', 'chill 14', 'chill 15', 'chill 16', 'chill 17', 'chill 18', 'chill 19', 'chill 20',
'chill 21', 'chill 22', 'chill 23', 'chill 24', 'chill 25', 'chill 26', 'chill 27', 'chill 28', 'chill 29', 'chill 30',
'chill 31', 'chill 32', 'chill 33', 'chill 34', 'chill 35', 'chill 36', 'chill 37', 'chill 38', 'chill 39', 'chill 40', 'chill 41',
'windy hill', 'past lives', 'lovely', 'my soul', 'golden hour', 'origin', 'my sunset', 'lifeline', 'seasons', 'breathe',
'china-x', 'china p', 'china rain', 'komorebi', 'flower day', 'before spring ends', 'forest mixtape', '所念皆星河', '城南花已开', '告白之夜', '幻昼', '青空',
'moonlight', 'night cruising', 'beyond the sea is freedom', 'gặp gỡ đã là quẻ thượng thượng', '小红书02'
],
angry: [
'angry', 'rage', 'metal', 'hard', 'bass boost', 'rock mạnh', 'legendary', 'believer', 'darkside', 'fight',
'sold out', 'psycho', 'normal no more', 'collapsing world', 'tức giận', 'nổi khùng', 'bực bội', 'căng thẳng',
'giận dữ', 'fury', 'wrath', 'storm', 'heavy', 'aggressive', 'rebel', 'defiant', 'break', 'smash', 'war',
'địa ngục trần gian', 'darkside', 'legendary', 'believer', 'lost sky', 'tướng quân', 'đế vương', 'top edm',
'psycho', 'normal no more', 'collapsing world', 'sold out', 'fight', 'rock', 'bass boost', 'hard', 'metal',
'rage', 'fury', 'storm', 'thunder', 'blast', 'epic', 'war', 'rebel', 'smash', 'break'
],
lonely: [
'lonely', 'alone', 'without you', 'missing', 'far away', 'shadow', 'cry for me', 'death bed', 'past lives',
'dancing with your ghost', 'lovely', 'chưa chắc', 'điều anh biết', 'chúng ta không thuộc về nhau', 'cô đơn',
'trống vắng', 'thiếu vắng', 'nhớ nhung', 'xa vắng', 'lẻ loi', 'solitude', 'abandoned', 'isolated',
'empty', 'hollow', 'lost', 'wandering', 'ghost', 'midnight', 'cold',
'huan back home', 'way back home', 'that girl', 'pretty girl', 'dream save me', 'alan play', 'death bed',
'cry for me', 'không hẹn gặp lại', 'chưa chắc remix', 'đường tôi chở em về', 'nụ cười của cô ấy', 'chill 39', 'call of silence',
'alone', 'faded', 'spectre', 'on my way', 'so far away', 'shadow of the sun', 'love is gone', 'monody', 'steort heal', 'close the sun',
'my sunset', 'lifeline', 'seasons', 'breathe', 'intro real love', 'in the end', '花火が瞬く夜に', 'melancholy'
],
romantic: [
'love', 'romantic', 'girl', 'boy', 'kiss', 'valentine', 'you', 'i want you', 'shape of you', 'closer',
'dusk till dawn', 'nevada', 'on & on', 'first date', 'mood', 'royalty', 'pretty girl', 'that girl',
'way back home', 'lãng mạn', 'yêu', 'tình yêu', 'ngọt ngào', 'bên nhau', 'say đắm', 'heart', 'sweet',
'passion', 'embrace', 'forever', 'together', 'darling', 'cherish', 'adore',
'cưới thôi', 'nắng lung linh', '100 years love', 'phonecert mashup', 'mây x gió', 'kẹo bông ngòn', 'anh thanh niên',
'i want you to know', 'nothing on you', 'the way still love you', 'closer', 'dusk till dawn', 'shape of you',
'summer', 'vacation', 'first date', 'mood', 'royalty', 'pretty girl', 'that girl', 'way back home', 'nevada',
'on & on', 'love is gone', 'cry for me', 'đường tôi chở em về', 'nụ cười của cô ấy', 'gặp gỡ đã là quẻ thượng thượng'
],
focused: [
'focus', 'study', 'instrumental', 'piano', 'lo-fi', 'lofi', 'beat', 'china', 'origin', 'my soul',
'golden hour', 'komorebi', 'flower day', 'before spring ends', 'forest mixtape', 'nhạc không lời',
'piano', 'melancholy', 'my sunset', 'tập trung', 'học bài', 'làm việc', 'thi cử', 'không lời',
'concentrate', 'work', 'reading', 'coding', 'ambient', 'background', 'study beats', 'deep work',
'the right path', 'glass animals', 'chill 1', 'chill 2', 'chill 3', 'chill 4', 'chill 5', 'chill 6', 'chill 7', 'chill 8',
'chill 9', 'chill 12', 'chill 13', 'chill 14', 'chill 15', 'chill 16', 'chill 17', 'chill 18', 'chill 19', 'chill 20',
'windy hill', 'past lives', 'lovely', 'my soul', 'golden hour', 'origin', 'my sunset', 'lifeline', 'seasons', 'breathe',
'china-x', 'china p', 'china rain', 'komorebi', 'flower day', 'before spring ends', 'forest mixtape', '所念皆星河', '城南花已开', '告白之夜', '幻昼', '青空',
'night cruising', 'beyond the sea is freedom'
]
};
// Lưu trữ từ khóa hiện tại cho mỗi cảm xúc (sẽ thay đổi khi reroll)
let currentKeywords = {};
let currentEmotion = null;
let currentEmotionSongs = [];
// Hàm khởi tạo lại từ khóa ngẫu nhiên cho một emotion (lấy random 8-12 từ từ master)
function randomizeKeywordsForEmotion(emotionId) {
const master = masterKeywords[emotionId];
if (!master) return [];
const num = Math.floor(Math.random() * 8) + 8; // 8-15 từ
const shuffled = [...master];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled.slice(0, num);
}
// Hàm lọc bài hát theo danh sách từ khóa hiện tại của một emotion
function filterSongsByCurrentKeywords(emotionId) {
if (!currentKeywords[emotionId]) {
currentKeywords[emotionId] = randomizeKeywordsForEmotion(emotionId);
}
const keywords = currentKeywords[emotionId];
const lowerKeywords = keywords.map(k => k.toLowerCase());
return musicList.filter(song => {
if (!song.name) return false;
const lowerName = song.name.toLowerCase();
return lowerKeywords.some(keyword => lowerName.includes(keyword));
});
}
// Hàm gợi ý và phát bài ngẫu nhiên từ emotion đã chọn
function suggestAndPlayByEmotion(emotionId, showNoti = true) {
const emotionName = masterKeywords[emotionId] ?
(emotionId === 'happy' ? '😊 Vui vẻ' :
emotionId === 'sad' ? '😢 Buồn' :
emotionId === 'energetic' ? '⚡ Năng động' :
emotionId === 'calm' ? '🧘 Bình yên' :
emotionId === 'angry' ? '😤 Tức giận' :
emotionId === 'lonely' ? '💔 Cô đơn' :
emotionId === 'romantic' ? '💕 Lãng mạn' :
'🎯 Tập trung') : emotionId;
const songs = filterSongsByCurrentKeywords(emotionId);
if (songs.length === 0) {
alert(`😢 Không tìm thấy bài hát nào phù hợp với cảm xúc "${emotionName}". Hãy thử "Reroll từ khóa" hoặc cảm xúc khác!`);
return false;
}
currentEmotion = emotionId;
currentEmotionSongs = songs;
const randomIndex = Math.floor(Math.random() * songs.length);
const selectedSong = songs[randomIndex];
const originalButtons = musicItemContainer.querySelectorAll('button');
let targetButton = null;
for (let i = 0; i < originalButtons.length; i++) {
if (originalButtons[i].textContent.includes(selectedSong.name)) {
targetButton = originalButtons[i];
break;
}
}
playMusic(selectedSong, targetButton);
if (showNoti) {
const msg = document.createElement('div');
msg.textContent = `✨ Gợi ý cho ${emotionName}: "${selectedSong.name}" - Chúc bạn luôn an nhiên! ✨`;
msg.style.cssText = 'position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,0.8); color:#CE93D8; padding:12px 20px; border-radius:40px; font-size:14px; z-index:1000001; backdrop-filter:blur(10px); border:1px solid #CE93D8; white-space:nowrap;';
document.body.appendChild(msg);
setTimeout(() => msg.remove(), 4000);
}
return true;
}
// Hàm cập nhật số lượng bài hết tìm thấy cho mỗi emotion (hiển thị trên nút)
function updateSongCounts() {
for (const [id, btn] of emotionButtonsMap) {
const count = filterSongsByCurrentKeywords(id).length;
btn.textContent = `${emotionDisplayNames[id]} (${count})`;
}
}
// Tạo grid các nút cảm xúc
const emotionGrid = document.createElement('div');
emotionGrid.style.display = 'grid';
emotionGrid.style.gridTemplateColumns = 'repeat(2, 1fr)';
emotionGrid.style.gap = '8px';
emotionGrid.style.marginBottom = '12px';
const emotionDisplayNames = {
happy: '😊 Vui vẻ',
sad: '😢 Buồn',
energetic: '⚡ Năng động',
calm: '🧘 Bình yên',
angry: '😤 Tức giận',
lonely: '💔 Cô đơn',
romantic: '💕 Lãng mạn',
focused: '🎯 Tập trung'
};
const emotionButtonsMap = new Map();
for (const [id, name] of Object.entries(emotionDisplayNames)) {
const btn = document.createElement('button');
btn.textContent = name;
btn.style.padding = '8px';
btn.style.backgroundColor = 'rgba(156, 39, 176, 0.3)';
btn.style.border = '1px solid rgba(156, 39, 176, 0.5)';
btn.style.borderRadius = '30px';
btn.style.cursor = 'pointer';
btn.style.fontSize = '12px';
btn.style.fontWeight = 'bold';
btn.style.transition = 'all 0.2s';
btn.addEventListener('mouseenter', () => {
btn.style.backgroundColor = 'rgba(156, 39, 176, 0.6)';
btn.style.transform = 'scale(1.02)';
});
btn.addEventListener('mouseleave', () => {
btn.style.backgroundColor = 'rgba(156, 39, 176, 0.3)';
btn.style.transform = 'scale(1)';
});
btn.addEventListener('click', () => {
suggestAndPlayByEmotion(id);
});
emotionGrid.appendChild(btn);
emotionButtonsMap.set(id, btn);
}
emotionContainer.appendChild(emotionGrid);
// Khởi tạo từ khóa ngẫu nhiên cho tất cả emotion
for (const id of Object.keys(emotionDisplayNames)) {
currentKeywords[id] = randomizeKeywordsForEmotion(id);
}
updateSongCounts();
// Nút điều khiển: Reroll từ khóa + Random cảm xúc + Gợi ý khác
const controlPanel = document.createElement('div');
controlPanel.style.display = 'flex';
controlPanel.style.gap = '8px';
controlPanel.style.marginBottom = '12px';
controlPanel.style.flexWrap = 'wrap';
const rerollKeywordsBtn = document.createElement('button');
rerollKeywordsBtn.textContent = '🔄 Reroll từ khóa';
rerollKeywordsBtn.style.flex = '1';
rerollKeywordsBtn.style.padding = '8px';
rerollKeywordsBtn.style.backgroundColor = 'rgba(255, 152, 0, 0.3)';
rerollKeywordsBtn.style.border = '1px solid rgba(255, 152, 0, 0.5)';
rerollKeywordsBtn.style.borderRadius = '30px';
rerollKeywordsBtn.style.fontSize = '12px';
rerollKeywordsBtn.addEventListener('click', () => {
for (const id of Object.keys(emotionDisplayNames)) {
currentKeywords[id] = randomizeKeywordsForEmotion(id);
}
updateSongCounts();
const msg = document.createElement('div');
msg.textContent = '🔄 Đã reroll từ khóa cho tất cả cảm xúc! Hãy thử lại gợi ý.';
msg.style.cssText = 'position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,0.8); color:#FF9800; padding:10px 20px; border-radius:40px; font-size:13px; z-index:1000001; backdrop-filter:blur(10px); white-space:nowrap;';
document.body.appendChild(msg);
setTimeout(() => msg.remove(), 3000);
});
controlPanel.appendChild(rerollKeywordsBtn);
const randomEmotionBtn = document.createElement('button');
randomEmotionBtn.textContent = '🎲 Random cảm xúc';
randomEmotionBtn.style.flex = '1';
randomEmotionBtn.style.padding = '8px';
randomEmotionBtn.style.backgroundColor = 'rgba(76, 175, 80, 0.3)';
randomEmotionBtn.style.border = '1px solid rgba(76, 175, 80, 0.5)';
randomEmotionBtn.style.borderRadius = '30px';
randomEmotionBtn.style.fontSize = '12px';
randomEmotionBtn.addEventListener('click', () => {
const ids = Object.keys(emotionDisplayNames);
const randomId = ids[Math.floor(Math.random() * ids.length)];
suggestAndPlayByEmotion(randomId);
});
controlPanel.appendChild(randomEmotionBtn);
const anotherSuggestionBtn = document.createElement('button');
anotherSuggestionBtn.textContent = '🎲 Gợi ý khác (cùng cảm xúc)';
anotherSuggestionBtn.style.flex = '1';
anotherSuggestionBtn.style.padding = '8px';
anotherSuggestionBtn.style.backgroundColor = 'rgba(255, 193, 7, 0.3)';
anotherSuggestionBtn.style.border = '1px solid rgba(255, 193, 7, 0.5)';
anotherSuggestionBtn.style.borderRadius = '30px';
anotherSuggestionBtn.style.fontSize = '12px';
anotherSuggestionBtn.addEventListener('click', () => {
if (!currentEmotion) {
alert('Hãy chọn một cảm xúc trước bằng cách nhấn vào nút cảm xúc bên trên!');
return;
}
if (currentEmotionSongs.length <= 1) {
alert('Chỉ có một bài cho cảm xúc này, hãy thử "Reroll từ khóa" hoặc chọn cảm xúc khác!');
return;
}
let newSongs = currentEmotionSongs.filter(s => s.name !== (currentMusic?.name));
if (newSongs.length === 0) newSongs = currentEmotionSongs;
const randomIndex = Math.floor(Math.random() * newSongs.length);
const selectedSong = newSongs[randomIndex];
const originalButtons = musicItemContainer.querySelectorAll('button');
let targetButton = null;
for (let i = 0; i < originalButtons.length; i++) {
if (originalButtons[i].textContent.includes(selectedSong.name)) {
targetButton = originalButtons[i];
break;
}
}
playMusic(selectedSong, targetButton);
const emotionName = emotionDisplayNames[currentEmotion] || 'cảm xúc';
const msg = document.createElement('div');
msg.textContent = `🎵 Gợi ý khác cho ${emotionName}: "${selectedSong.name}" - Thư giãn nhé! 🎵`;
msg.style.cssText = 'position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,0.8); color:#FFD54F; padding:12px 20px; border-radius:40px; font-size:14px; z-index:1000001; backdrop-filter:blur(10px); border:1px solid #FFD54F; white-space:nowrap;';
document.body.appendChild(msg);
setTimeout(() => msg.remove(), 4000);
});
controlPanel.appendChild(anotherSuggestionBtn);
emotionContainer.appendChild(controlPanel);
// Ô nhập cảm xúc tự do (giữ nguyên nhưng cải tiến)
const customEmotionDiv = document.createElement('div');
customEmotionDiv.style.marginTop = '12px';
customEmotionDiv.style.display = 'flex';
customEmotionDiv.style.gap = '8px';
const customInput = document.createElement('input');
customInput.type = 'text';
customInput.placeholder = 'Nhập cảm xúc của bạn (vd: hạnh phúc, mệt mỏi, thư giãn...)';
customInput.style.flex = '1';
customInput.style.padding = '8px';
customInput.style.backgroundColor = 'rgba(255,255,255,0.1)';
customInput.style.border = '1px solid rgba(255,255,255,0.2)';
customInput.style.borderRadius = '30px';
customInput.style.color = '#fff';
customInput.style.fontSize = '12px';
const customSuggestBtn = document.createElement('button');
customSuggestBtn.textContent = '🔍 Gợi ý';
customSuggestBtn.style.padding = '8px 16px';
customSuggestBtn.style.backgroundColor = 'rgba(33, 150, 243, 0.3)';
customSuggestBtn.style.border = '1px solid rgba(33, 150, 243, 0.5)';
customSuggestBtn.style.borderRadius = '30px';
customSuggestBtn.style.cursor = 'pointer';
customSuggestBtn.style.fontSize = '12px';
customEmotionDiv.appendChild(customInput);
customEmotionDiv.appendChild(customSuggestBtn);
emotionContainer.appendChild(customEmotionDiv);
customSuggestBtn.addEventListener('click', () => {
let raw = customInput.value.trim();
if (!raw) {
alert('Vui lòng nhập cảm xúc (ví dụ: vui, buồn, yêu đời...)');
return;
}
raw = raw.toLowerCase();
// Tìm emotion gần nhất dựa trên tên hoặc từ khóa trong master
let bestMatch = null;
for (const [id, data] of Object.entries(masterKeywords)) {
const displayName = emotionDisplayNames[id].toLowerCase();
if (displayName.includes(raw) || id.includes(raw)) {
bestMatch = id;
break;
}
if (data.some(k => k.toLowerCase().includes(raw))) {
bestMatch = id;
break;
}
}
if (bestMatch) {
suggestAndPlayByEmotion(bestMatch);
} else {
// Tìm bài hát có tên chứa từ khóa
const matchedSongs = musicList.filter(song => song.name && song.name.toLowerCase().includes(raw));
if (matchedSongs.length === 0) {
alert(`Không tìm thấy bài hát nào liên quan đến "${raw}". Hãy thử từ khóa khác!`);
return;
}
const randomIndex = Math.floor(Math.random() * matchedSongs.length);
const selectedSong = matchedSongs[randomIndex];
const originalButtons = musicItemContainer.querySelectorAll('button');
let targetButton = null;
for (let i = 0; i < originalButtons.length; i++) {
if (originalButtons[i].textContent.includes(selectedSong.name)) {
targetButton = originalButtons[i];
break;
}
}
playMusic(selectedSong, targetButton);
const msg = document.createElement('div');
msg.textContent = `💖 Gợi ý theo "${raw}": "${selectedSong.name}" - Hy vọng bạn sẽ thấy dễ chịu hơn! 💖`;
msg.style.cssText = 'position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,0.8); color:#81D4FA; padding:12px 20px; border-radius:40px; font-size:14px; z-index:1000001; backdrop-filter:blur(10px); border:1px solid #81D4FA; white-space:nowrap;';
document.body.appendChild(msg);
setTimeout(() => msg.remove(), 4000);
}
customInput.value = '';
});
// Chèn emotionContainer vào menu (sau custom EQ container)
const customEqContainer = document.querySelector('#musicMenuContainer > div:nth-child(3)');
if (customEqContainer && customEqContainer.parentNode) {
customEqContainer.insertAdjacentElement('afterend', emotionContainer);
} else {
const musicItem = document.getElementById('musicMenuContainer')?.querySelector('div:last-child');
if (musicItem) musicItem.parentNode.insertBefore(emotionContainer, musicItem);
}
// ==================== VIRTUAL PIANO (BÀN PHÍM PIANO ẢO) ====================
(function() {
if (document.getElementById('virtualPianoContainer')) return;
const pianoContainer = document.createElement('div');
pianoContainer.id = 'virtualPianoContainer';
pianoContainer.style.marginTop = '15px';
pianoContainer.style.padding = '10px';
pianoContainer.style.backgroundColor = 'rgba(0,0,0,0.1)';
pianoContainer.style.borderRadius = '12px';
pianoContainer.style.border = '1px solid #FF9800';
pianoContainer.style.display = 'none';
const pianoHeader = document.createElement('div');
pianoHeader.style.display = 'flex';
pianoHeader.style.justifyContent = 'space-between';
pianoHeader.style.alignItems = 'center';
pianoHeader.style.marginBottom = '10px';
const pianoTitle = document.createElement('span');
pianoTitle.textContent = '🎹 VIRTUAL PIANO (Click hoặc phím: A S D F G H J K L ; W E R T Y U I)';
pianoTitle.style.fontSize = '11px';
pianoTitle.style.fontWeight = 'bold';
pianoTitle.style.color = '#FFB74D';
const closePianoBtn = document.createElement('button');
closePianoBtn.textContent = '✖';
closePianoBtn.style.background = 'none';
closePianoBtn.style.border = 'none';
closePianoBtn.style.color = '#fff';
closePianoBtn.style.cursor = 'pointer';
closePianoBtn.style.fontSize = '16px';
closePianoBtn.addEventListener('click', () => {
pianoContainer.style.display = 'none';
enablePianoBtn.textContent = '🎹 BẬT PIANO ẢO';
enablePianoBtn.style.backgroundColor = 'rgba(255, 152, 0, 0.1)';
if (pianoEnabled) deactivatePiano();
});
pianoHeader.appendChild(pianoTitle);
pianoHeader.appendChild(closePianoBtn);
pianoContainer.appendChild(pianoHeader);
// Định nghĩa các nốt nhạc (tần số) và phím tắt
const pianoKeys = [
{ note: 'C', freq: 261.63, key: 'A', type: 'white' },
{ note: 'C#', freq: 277.18, key: 'W', type: 'black' },
{ note: 'D', freq: 293.66, key: 'S', type: 'white' },
{ note: 'D#', freq: 311.13, key: 'E', type: 'black' },
{ note: 'E', freq: 329.63, key: 'D', type: 'white' },
{ note: 'F', freq: 349.23, key: 'F', type: 'white' },
{ note: 'F#', freq: 369.99, key: 'R', type: 'black' },
{ note: 'G', freq: 392.00, key: 'G', type: 'white' },
{ note: 'G#', freq: 415.30, key: 'T', type: 'black' },
{ note: 'A', freq: 440.00, key: 'H', type: 'white' },
{ note: 'A#', freq: 466.16, key: 'Y', type: 'black' },
{ note: 'B', freq: 493.88, key: 'J', type: 'white' },
{ note: 'C2', freq: 523.25, key: 'K', type: 'white' },
{ note: 'C#2', freq: 554.37, key: 'U', type: 'black' },
{ note: 'D2', freq: 587.33, key: 'L', type: 'white' },
{ note: 'D#2', freq: 622.25, key: 'I', type: 'black' },
{ note: 'E2', freq: 659.25, key: ';', type: 'white' }
];
const keysRow = document.createElement('div');
keysRow.style.display = 'flex';
keysRow.style.position = 'relative';
keysRow.style.height = '150px';
keysRow.style.marginBottom = '10px';
let audioCtx = null;
let pianoVolume = 0.3;
function playTone(freq, duration = 0.6) {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume();
}
const now = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.value = freq;
gain.gain.value = pianoVolume;
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
gain.gain.exponentialRampToValueAtTime(0.0001, now + duration);
osc.stop(now + duration);
}
// Tạo các phím
pianoKeys.forEach((p) => {
const keyDiv = document.createElement('div');
keyDiv.className = `piano-key ${p.type}`;
keyDiv.textContent = p.note;
keyDiv.style.display = 'flex';
keyDiv.style.alignItems = 'center';
keyDiv.style.justifyContent = 'center';
keyDiv.style.fontSize = '12px';
keyDiv.style.fontWeight = 'bold';
keyDiv.style.cursor = 'pointer';
keyDiv.style.userSelect = 'none';
if (p.type === 'white') {
keyDiv.style.width = '40px';
keyDiv.style.height = '150px';
keyDiv.style.backgroundColor = '#fff';
keyDiv.style.color = '#333';
keyDiv.style.border = '1px solid #ccc';
keyDiv.style.borderRadius = '0 0 8px 8px';
keyDiv.style.marginRight = '2px';
} else {
keyDiv.style.width = '28px';
keyDiv.style.height = '90px';
keyDiv.style.backgroundColor = '#333';
keyDiv.style.color = '#fff';
keyDiv.style.border = '1px solid #111';
keyDiv.style.borderRadius = '0 0 6px 6px';
keyDiv.style.position = 'relative';
keyDiv.style.marginLeft = '-14px';
keyDiv.style.marginRight = '-14px';
keyDiv.style.zIndex = '2';
}
keyDiv.style.transition = '0.1s';
keyDiv.addEventListener('mousedown', (e) => {
e.stopPropagation();
playTone(p.freq);
keyDiv.style.transform = 'scale(0.95)';
keyDiv.style.opacity = '0.8';
setTimeout(() => {
keyDiv.style.transform = '';
keyDiv.style.opacity = '';
}, 150);
});
keysRow.appendChild(keyDiv);
p.element = keyDiv;
});
pianoContainer.appendChild(keysRow);
// Volume slider cho piano
const pianoVolDiv = document.createElement('div');
pianoVolDiv.style.marginTop = '8px';
const pianoVolLabel = document.createElement('label');
pianoVolLabel.textContent = 'Âm lượng piano:';
pianoVolLabel.style.fontSize = '11px';
const pianoVolSlider = createSlider(0, 100, 30);
pianoVolSlider.addEventListener('input', (e) => {
pianoVolume = e.target.value / 100;
});
pianoVolDiv.appendChild(pianoVolLabel);
pianoVolDiv.appendChild(pianoVolSlider);
pianoContainer.appendChild(pianoVolDiv);
let pianoEnabled = false;
function handleKeyDown(e) {
if (!pianoEnabled) return;
const key = e.key.toUpperCase();
const found = pianoKeys.find(p => p.key === key);
if (found) {
e.preventDefault();
playTone(found.freq);
const el = found.element;
el.style.transform = 'scale(0.95)';
el.style.opacity = '0.8';
setTimeout(() => {
if (el) {
el.style.transform = '';
el.style.opacity = '';
}
}, 150);
}
}
function activatePiano() {
pianoEnabled = true;
window.addEventListener('keydown', handleKeyDown);
}
function deactivatePiano() {
pianoEnabled = false;
window.removeEventListener('keydown', handleKeyDown);
}
// Tìm nút để thêm vào menu (trong custom EQ container)
const customEq = document.querySelector('#musicMenuContainer > div:nth-child(3)');
if (customEq) {
const enablePianoBtn = document.createElement('button');
enablePianoBtn.textContent = '🎹 BẬT PIANO ẢO';
enablePianoBtn.style.backgroundColor = 'rgba(255, 152, 0, 0.1)';
enablePianoBtn.style.border = '1px solid rgba(255, 152, 0, 0.1)';
enablePianoBtn.style.marginTop = '10px';
enablePianoBtn.addEventListener('click', () => {
if (pianoContainer.style.display === 'none') {
pianoContainer.style.display = 'block';
enablePianoBtn.textContent = '🎹 TẮT PIANO ẢO';
enablePianoBtn.style.backgroundColor = 'rgba(255, 152, 0, 0.1)';
activatePiano();
} else {
pianoContainer.style.display = 'none';
enablePianoBtn.textContent = '🎹 BẬT PIANO ẢO';
enablePianoBtn.style.backgroundColor = 'rgba(255, 152, 0, 0.1)';
deactivatePiano();
}
});
customEq.appendChild(enablePianoBtn);
customEq.appendChild(pianoContainer);
} else {
// Fallback: thêm vào cuối menu
const menu = document.getElementById('musicMenuContainer');
if (menu) {
const fallbackBtn = createButton('🎹 BẬT PIANO ẢO', 'rgba(255,152,0,0.1)');
fallbackBtn.addEventListener('click', () => {
if (pianoContainer.style.display === 'none') {
pianoContainer.style.display = 'block';
fallbackBtn.textContent = '🎹 TẮT PIANO ẢO';
activatePiano();
} else {
pianoContainer.style.display = 'none';
fallbackBtn.textContent = '🎹 BẬT PIANO ẢO';
deactivatePiano();
}
});
menu.appendChild(fallbackBtn);
menu.appendChild(pianoContainer);
}
}
})();
// ==================== NIGHT MODE (CHẾ ĐỘ TỐI GIAO DIỆN) ====================
(function() {
let nightModeActive = false;
// Tạo style cho night mode
const nightModeStyle = document.createElement('style');
nightModeStyle.id = 'nightModeStyle';
nightModeStyle.textContent = `
/* Night mode cho toàn trang */
body.night-mode {
background-color: #121212 !important;
color: #e0e0e0 !important;
}
body.night-mode * {
border-color: #333 !important;
}
body.night-mode a {
color: #bb86fc !important;
}
body.night-mode input, body.night-mode select, body.night-mode textarea {
background-color: #2d2d2d !important;
color: #e0e0e0 !important;
border-color: #444 !important;
}
body.night-mode button {
background-color: #333 !important;
color: #e0e0e0 !important;
}
/* Night mode cho menu */
#musicMenuContainer.night-mode {
background: rgba(0, 0, 0, 0.9) !important;
backdrop-filter: blur(15px) !important;
border: 1px solid #333 !important;
color: #e0e0e0 !important;
}
#musicMenuContainer.night-mode button {
background: rgba(50, 50, 50, 0.8) !important;
border-color: #555 !important;
color: #ddd !important;
}
#musicMenuContainer.night-mode button:hover {
background: rgba(100, 100, 100, 0.8) !important;
}
#musicMenuContainer.night-mode input,
#musicMenuContainer.night-mode select {
background: #2a2a2a !important;
color: #eee !important;
border-color: #555 !important;
}
#musicMenuContainer.night-mode .piano-key.white {
background-color: #ddd !important;
color: #000 !important;
}
#musicMenuContainer.night-mode .piano-key.black {
background-color: #111 !important;
color: #fff !important;
}
`;
document.head.appendChild(nightModeStyle);
// Tìm nút trong custom EQ container để thêm nút Night Mode
const customEqNight = document.querySelector('#musicMenuContainer > div:nth-child(3)');
if (customEqNight) {
const nightModeBtn = document.createElement('button');
nightModeBtn.textContent = '🌙 BẬT NIGHT MODE';
nightModeBtn.style.backgroundColor = 'rgba(33, 33, 33, 0.7)';
nightModeBtn.style.border = '1px solid #555';
nightModeBtn.style.marginTop = '10px';
nightModeBtn.addEventListener('click', () => {
nightModeActive = !nightModeActive;
if (nightModeActive) {
document.body.classList.add('night-mode');
document.getElementById('musicMenuContainer').classList.add('night-mode');
nightModeBtn.textContent = '☀️ TẮT NIGHT MODE';
nightModeBtn.style.backgroundColor = 'rgba(255, 165, 0, 0.3)';
} else {
document.body.classList.remove('night-mode');
document.getElementById('musicMenuContainer').classList.remove('night-mode');
nightModeBtn.textContent = '🌙 BẬT NIGHT MODE';
nightModeBtn.style.backgroundColor = 'rgba(33, 33, 33, 0.7)';
}
});
customEqNight.appendChild(nightModeBtn);
} else {
// Fallback: thêm vào cuối menu
const menu = document.getElementById('musicMenuContainer');
if (menu) {
const fallbackNightBtn = createButton('🌙 BẬT NIGHT MODE', 'rgba(33,33,33,0.7)');
fallbackNightBtn.addEventListener('click', () => {
nightModeActive = !nightModeActive;
if (nightModeActive) {
document.body.classList.add('night-mode');
menu.classList.add('night-mode');
fallbackNightBtn.textContent = '☀️ TẮT NIGHT MODE';
} else {
document.body.classList.remove('night-mode');
menu.classList.remove('night-mode');
fallbackNightBtn.textContent = '🌙 BẬT NIGHT MODE';
}
});
menu.appendChild(fallbackNightBtn);
}
}
})();
// ==================== THÊM: QUẢN LÝ HIỆU ỨNG HÌNH ẢNH (MƯA, TUYẾT, BONG BÓNG, SAO BĂNG, ĐOM ĐÓM, VŨ TRỤ, GALAXY, HỐ ĐEN) ====================
class VisualEffectsManager {
constructor() {
this.canvas = null;
this.ctx = null;
this.width = window.innerWidth;
this.height = window.innerHeight;
this.activeEffects = new Set(); // lưu tên các hiệu ứng đang bật
this.animationId = null;
this.lastTimestamp = 0;
this.particles = {}; // lưu dữ liệu cho từng hiệu ứng
this.initCanvas();
window.addEventListener('resize', () => this.resizeCanvas());
}
initCanvas() {
this.canvas = document.createElement('canvas');
this.canvas.id = 'visualEffectsCanvas';
this.canvas.style.position = 'fixed';
this.canvas.style.top = '0';
this.canvas.style.left = '0';
this.canvas.style.width = '100%';
this.canvas.style.height = '100%';
this.canvas.style.zIndex = '0'; // trên background mood lighting (-1) nhưng dưới menu (9999)
this.canvas.style.pointerEvents = 'none';
this.canvas.style.display = 'block';
this.resizeCanvas();
document.body.insertBefore(this.canvas, document.body.firstChild);
this.ctx = this.canvas.getContext('2d');
this.startAnimation();
}
resizeCanvas() {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.canvas.width = this.width;
this.canvas.height = this.height;
// reset lại các particle khi resize (tránh lỗi vị trí)
for (let effect of this.activeEffects) {
if (this.particles[effect] && typeof this.resetEffect === 'function') {
this.resetEffect(effect);
}
}
}
// Khởi tạo dữ liệu cho từng hiệu ứng
initEffectData(effectName) {
switch(effectName) {
case 'rain':
this.particles.rain = [];
for (let i = 0; i < 200; i++) {
this.particles.rain.push({
x: Math.random() * this.width,
y: Math.random() * this.height,
length: Math.random() * 15 + 10,
speed: Math.random() * 8 + 4,
opacity: Math.random() * 0.5 + 0.3
});
}
break;
case 'snow':
this.particles.snow = [];
for (let i = 0; i < 150; i++) {
this.particles.snow.push({
x: Math.random() * this.width,
y: Math.random() * this.height,
radius: Math.random() * 4 + 2,
speed: Math.random() * 2 + 1,
opacity: Math.random() * 0.6 + 0.4
});
}
break;
case 'bubbles':
this.particles.bubbles = [];
for (let i = 0; i < 60; i++) {
this.particles.bubbles.push({
x: Math.random() * this.width,
y: Math.random() * this.height,
radius: Math.random() * 15 + 5,
speed: Math.random() * 1.5 + 0.5,
wobble: Math.random() * Math.PI * 2,
wobbleSpeed: Math.random() * 0.05 + 0.02
});
}
break;
case 'shootingStars':
this.particles.shootingStars = [];
// chỉ tạo một vài ngôi sao băng, mỗi lần bay xong sẽ tạo lại
for (let i = 0; i < 3; i++) {
this.spawnShootingStar();
}
break;
case 'fireflies':
this.particles.fireflies = [];
for (let i = 0; i < 80; i++) {
this.particles.fireflies.push({
x: Math.random() * this.width,
y: Math.random() * this.height,
radius: Math.random() * 3 + 1.5,
speedX: (Math.random() - 0.5) * 1.2,
speedY: (Math.random() - 0.5) * 0.8,
angle: Math.random() * Math.PI * 2,
flicker: Math.random() * Math.PI * 2,
flickerSpeed: Math.random() * 0.05 + 0.02
});
}
break;
case 'universe':
this.particles.universe = [];
for (let i = 0; i < 400; i++) {
this.particles.universe.push({
x: Math.random() * this.width,
y: Math.random() * this.height,
radius: Math.random() * 2 + 0.5,
alpha: Math.random() * 0.8 + 0.2,
speedX: (Math.random() - 0.5) * 0.2,
speedY: (Math.random() - 0.5) * 0.2
});
}
break;
case 'galaxy':
this.particles.galaxy = {
stars: [],
angle: 0,
rotationSpeed: 0.002
};
for (let i = 0; i < 600; i++) {
const radius = Math.pow(Math.random(), 1.5) * Math.min(this.width, this.height) * 0.4;
const angle = Math.random() * Math.PI * 2;
this.particles.galaxy.stars.push({
radius: radius,
angle: angle,
size: Math.random() * 2 + 1,
brightness: Math.random() * 0.7 + 0.3,
armOffset: (Math.random() - 0.5) * 0.5
});
}
break;
case 'blackHole':
this.particles.blackHole = {
x: this.width / 2,
y: this.height / 2,
particles: [],
accretionDisk: 0
};
for (let i = 0; i < 300; i++) {
const distance = Math.random() * 300 + 50;
const angle = Math.random() * Math.PI * 2;
this.particles.blackHole.particles.push({
distance: distance,
angle: angle,
speed: (1 / (distance + 20)) * 2,
size: Math.random() * 3 + 1,
color: `hsl(${Math.random() * 60 + 20}, 100%, 60%)`
});
}
break;
default: break;
}
}
spawnShootingStar() {
if (!this.particles.shootingStars) this.particles.shootingStars = [];
this.particles.shootingStars.push({
x: Math.random() * this.width,
y: Math.random() * this.height * 0.5,
vx: (Math.random() * 8 + 6) * (Math.random() > 0.5 ? 1 : -1),
vy: Math.random() * 5 + 3,
length: Math.random() * 60 + 40,
opacity: 0.8,
active: true,
life: 1.0
});
}
resetEffect(effectName) {
if (this.particles[effectName]) delete this.particles[effectName];
this.initEffectData(effectName);
}
enableEffect(effectName) {
if (this.activeEffects.has(effectName)) return;
this.activeEffects.add(effectName);
if (!this.particles[effectName]) {
this.initEffectData(effectName);
}
}
disableEffect(effectName) {
this.activeEffects.delete(effectName);
// không xóa dữ liệu ngay, để nếu bật lại thì dùng lại
}
updateEffects(deltaTime) {
const dt = Math.min(deltaTime / 16, 2); // normalize
for (let effect of this.activeEffects) {
switch(effect) {
case 'rain':
for (let drop of this.particles.rain) {
drop.y += drop.speed * dt;
if (drop.y > this.height) {
drop.y = -drop.length;
drop.x = Math.random() * this.width;
}
}
break;
case 'snow':
for (let flake of this.particles.snow) {
flake.y += flake.speed * dt;
if (flake.y > this.height) {
flake.y = -flake.radius;
flake.x = Math.random() * this.width;
}
}
break;
case 'bubbles':
for (let bubble of this.particles.bubbles) {
bubble.y -= bubble.speed * dt;
bubble.wobble += bubble.wobbleSpeed * dt;
if (bubble.y + bubble.radius < 0) {
bubble.y = this.height + bubble.radius;
bubble.x = Math.random() * this.width;
}
}
break;
case 'shootingStars':
for (let i = 0; i < this.particles.shootingStars.length; i++) {
const star = this.particles.shootingStars[i];
star.x += star.vx * dt;
star.y += star.vy * dt;
star.life -= 0.01 * dt;
if (star.x < -200 || star.x > this.width + 200 || star.y > this.height + 200 || star.life <= 0) {
this.particles.shootingStars.splice(i,1);
i--;
if (Math.random() < 0.02) this.spawnShootingStar(); // tạo mới ngẫu nhiên
}
}
if (this.particles.shootingStars.length < 2 && Math.random() < 0.01) this.spawnShootingStar();
break;
case 'fireflies':
for (let ff of this.particles.fireflies) {
ff.x += ff.speedX * dt;
ff.y += ff.speedY * dt;
ff.angle += 0.02 * dt;
ff.flicker += ff.flickerSpeed * dt;
if (ff.x < -20) ff.x = this.width + 20;
if (ff.x > this.width + 20) ff.x = -20;
if (ff.y < -20) ff.y = this.height + 20;
if (ff.y > this.height + 20) ff.y = -20;
}
break;
case 'universe':
for (let star of this.particles.universe) {
star.x += star.speedX * dt;
star.y += star.speedY * dt;
if (star.x < 0) star.x = this.width;
if (star.x > this.width) star.x = 0;
if (star.y < 0) star.y = this.height;
if (star.y > this.height) star.y = 0;
}
break;
case 'galaxy':
if (this.particles.galaxy) {
this.particles.galaxy.angle += this.particles.galaxy.rotationSpeed * dt;
}
break;
case 'blackHole':
if (this.particles.blackHole) {
this.particles.blackHole.accretionDisk += 0.02 * dt;
for (let p of this.particles.blackHole.particles) {
p.angle += p.speed * dt;
// kéo dần vào trung tâm
p.distance -= 0.5 * dt;
if (p.distance < 5) {
p.distance = Math.random() * 300 + 50;
p.angle = Math.random() * Math.PI * 2;
}
}
}
break;
}
}
}
drawEffects() {
if (!this.ctx) return;
this.ctx.clearRect(0, 0, this.width, this.height);
for (let effect of this.activeEffects) {
switch(effect) {
case 'rain':
this.ctx.save();
this.ctx.strokeStyle = 'rgba(174, 194, 224, 0.6)';
this.ctx.lineWidth = 1.5;
for (let drop of this.particles.rain) {
this.ctx.beginPath();
this.ctx.moveTo(drop.x, drop.y);
this.ctx.lineTo(drop.x, drop.y + drop.length);
this.ctx.stroke();
}
this.ctx.restore();
break;
case 'snow':
for (let flake of this.particles.snow) {
this.ctx.beginPath();
this.ctx.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2);
this.ctx.fillStyle = `rgba(255, 255, 255, ${flake.opacity})`;
this.ctx.fill();
}
break;
case 'bubbles':
for (let bubble of this.particles.bubbles) {
const offsetX = Math.sin(bubble.wobble) * 3;
this.ctx.beginPath();
this.ctx.arc(bubble.x + offsetX, bubble.y, bubble.radius, 0, Math.PI * 2);
this.ctx.strokeStyle = 'rgba(173, 216, 230, 0.6)';
this.ctx.lineWidth = 1;
this.ctx.stroke();
this.ctx.fillStyle = 'rgba(173, 216, 230, 0.1)';
this.ctx.fill();
}
break;
case 'shootingStars':
for (let star of this.particles.shootingStars) {
const gradient = this.ctx.createLinearGradient(star.x, star.y, star.x - star.vx, star.y - star.vy);
gradient.addColorStop(0, `rgba(255, 255, 200, ${star.life * 0.8})`);
gradient.addColorStop(1, `rgba(255, 255, 200, 0)`);
this.ctx.beginPath();
this.ctx.moveTo(star.x, star.y);
this.ctx.lineTo(star.x - star.vx * 2, star.y - star.vy * 2);
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = gradient;
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.arc(star.x, star.y, 2, 0, Math.PI*2);
this.ctx.fillStyle = `rgba(255, 255, 180, ${star.life})`;
this.ctx.fill();
}
break;
case 'fireflies':
for (let ff of this.particles.fireflies) {
const glow = Math.sin(ff.flicker) * 0.5 + 0.5;
this.ctx.beginPath();
this.ctx.arc(ff.x, ff.y, ff.radius * (0.7 + glow * 0.5), 0, Math.PI * 2);
this.ctx.fillStyle = `rgba(255, 220, 100, ${0.4 + glow * 0.4})`;
this.ctx.fill();
this.ctx.shadowBlur = 8;
this.ctx.shadowColor = '#ffcc44';
this.ctx.fill();
this.ctx.shadowBlur = 0;
}
break;
case 'universe':
for (let star of this.particles.universe) {
this.ctx.beginPath();
this.ctx.arc(star.x, star.y, star.radius, 0, Math.PI*2);
this.ctx.fillStyle = `rgba(255, 255, 200, ${star.alpha})`;
this.ctx.fill();
}
break;
case 'galaxy':
const cx = this.width/2, cy = this.height/2;
const maxRad = Math.min(this.width, this.height) * 0.45;
for (let star of this.particles.galaxy.stars) {
const rad = star.radius + star.armOffset * 40;
const angle = star.angle + this.particles.galaxy.angle;
const x = cx + Math.cos(angle) * rad;
const y = cy + Math.sin(angle) * rad * 0.6; // hơi dẹt
if (x>0 && x<this.width && y>0 && y<this.height) {
this.ctx.beginPath();
this.ctx.arc(x, y, star.size, 0, Math.PI*2);
const hue = (angle * 50 + star.brightness * 100) % 360;
this.ctx.fillStyle = `hsla(${hue}, 80%, 60%, ${star.brightness * 0.8})`;
this.ctx.fill();
}
}
// thêm tâm sáng
const grad = this.ctx.createRadialGradient(cx, cy, 0, cx, cy, 50);
grad.addColorStop(0, 'rgba(255,255,200,0.9)');
grad.addColorStop(1, 'rgba(255,200,100,0)');
this.ctx.fillStyle = grad;
this.ctx.fillRect(cx-60, cy-60, 120, 120);
break;
case 'blackHole':
const bhx = this.particles.blackHole.x, bhy = this.particles.blackHole.y;
// vẽ đĩa bồi tụ
for (let p of this.particles.blackHole.particles) {
const x = bhx + Math.cos(p.angle) * p.distance;
const y = bhy + Math.sin(p.angle) * p.distance * 0.8;
this.ctx.beginPath();
this.ctx.arc(x, y, p.size, 0, Math.PI*2);
this.ctx.fillStyle = p.color;
this.ctx.fill();
}
// hố đen
const gradBH = this.ctx.createRadialGradient(bhx, bhy, 0, bhx, bhy, 40);
gradBH.addColorStop(0, 'black');
gradBH.addColorStop(0.7, '#111');
gradBH.addColorStop(1, 'rgba(0,0,0,0)');
this.ctx.fillStyle = gradBH;
this.ctx.fillRect(bhx-50, bhy-50, 100, 100);
// hiệu ứng thấu kính hấp dẫn (vòng tròn méo)
this.ctx.beginPath();
this.ctx.arc(bhx, bhy, 35, 0, Math.PI*2);
this.ctx.strokeStyle = 'rgba(255,100,50,0.5)';
this.ctx.lineWidth = 2;
this.ctx.stroke();
break;
}
}
}
startAnimation() {
let lastTime = performance.now();
const animate = (now) => {
const delta = Math.min(100, now - lastTime);
lastTime = now;
if (this.activeEffects.size > 0) {
this.updateEffects(delta);
this.drawEffects();
} else {
this.ctx.clearRect(0, 0, this.width, this.height);
}
this.animationId = requestAnimationFrame(animate);
};
this.animationId = requestAnimationFrame(animate);
}
stop() {
if (this.animationId) cancelAnimationFrame(this.animationId);
if (this.canvas) this.canvas.remove();
}
}
// Khởi tạo quản lý hiệu ứng
const visualEffects = new VisualEffectsManager();
// Thêm bảng điều khiển hiệu ứng vào menu chính
const effectsContainer = document.createElement('div');
effectsContainer.style.marginTop = '15px';
effectsContainer.style.padding = '12px';
effectsContainer.style.backgroundColor = 'rgba(0, 150, 255, 0.15)';
effectsContainer.style.borderRadius = '16px';
effectsContainer.style.border = '1px solid rgba(0, 150, 255, 0.3)';
effectsContainer.style.backdropFilter = 'blur(8px)';
const effectsTitle = document.createElement('div');
effectsTitle.textContent = '✨ HIỆU ỨNG HÌNH ẢNH ✨';
effectsTitle.style.fontWeight = 'bold';
effectsTitle.style.textAlign = 'center';
effectsTitle.style.marginBottom = '12px';
effectsTitle.style.color = '#42A5F5';
effectsContainer.appendChild(effectsTitle);
const effectsGrid = document.createElement('div');
effectsGrid.style.display = 'grid';
effectsGrid.style.gridTemplateColumns = 'repeat(2, 1fr)';
effectsGrid.style.gap = '8px';
const effectList = [
{ id: 'rain', name: '🌧️ Mưa', default: false },
{ id: 'snow', name: '❄️ Tuyết', default: false },
{ id: 'bubbles', name: '🫧 Bong bóng', default: false },
{ id: 'shootingStars', name: '☄️ Sao băng', default: false },
{ id: 'fireflies', name: '✨ Đom đóm', default: false },
{ id: 'universe', name: '🌌 Vũ trụ', default: false },
{ id: 'galaxy', name: '🌀 Galaxy', default: false },
{ id: 'blackHole', name: '⚫ Hố đen', default: false }
];
effectList.forEach(effect => {
const btn = document.createElement('button');
btn.textContent = effect.name;
btn.style.backgroundColor = 'rgba(33, 150, 243, 0.2)';
btn.style.border = '1px solid rgba(33, 150, 243, 0.4)';
btn.style.borderRadius = '30px';
btn.style.padding = '6px';
btn.style.fontSize = '11px';
btn.style.cursor = 'pointer';
let isActive = false;
btn.addEventListener('click', () => {
isActive = !isActive;
if (isActive) {
visualEffects.enableEffect(effect.id);
btn.style.backgroundColor = 'rgba(76, 175, 80, 0.4)';
btn.style.borderColor = '#4CAF50';
} else {
visualEffects.disableEffect(effect.id);
btn.style.backgroundColor = 'rgba(33, 150, 243, 0.2)';
btn.style.borderColor = 'rgba(33, 150, 243, 0.4)';
}
});
effectsGrid.appendChild(btn);
});
effectsContainer.appendChild(effectsGrid);
// Chèn vào menu sau emotion container (tìm vị trí)
const menuMain = document.getElementById('musicMenuContainer');
if (menuMain) {
// Tìm emotion container (có chứa "CẢM XÚC HIỆN TẠI")
const emotionDiv = Array.from(menuMain.children).find(child => child.innerText.includes('CẢM XÚC HIỆN TẠI'));
if (emotionDiv) {
emotionDiv.insertAdjacentElement('afterend', effectsContainer);
} else {
menuMain.appendChild(effectsContainer);
}
}
// ==================== THƯ VIỆN THƠ THEO CẢM XÚC ====================
const poetryLibrary = {
happy: [
{ text: "Hãy hát lên, hỡi nàng thơ! Cuộc đời là một bài ca bất tận.", author: "Walt Whitman" },
{ text: "Niềm vui bất chợt như ánh nắng ban mai, sưởi ấm tâm hồn ta từng khoảnh khắc.", author: "Rabindranath Tagore" },
{ text: "Dẫu biết đời là bể khổ, ta vẫn chọn cách nở nụ cười như hoa.", author: "Nguyễn Du" },
{ text: "Vui sướng tột cùng là được sống, được yêu và được hy vọng.", author: "Victor Hugo" },
{ text: "Mùa xuân đến, lòng người cũng như đất trời, bỗng dưng tươi mới lạ.", author: "Xuân Diệu" }
],
sad: [
{ text: "Cuộc đời là một câu chuyện kể bởi một thằng ngốc, đầy ồn ào và giận dữ, chẳng có ý nghĩa gì.", author: "William Shakespeare" },
{ text: "Nỗi buồn lặng lẽ như mưa dầm thấm vào từng kẽ nứt của tâm hồn.", author: "Emily Dickinson" },
{ text: "Người buồn cảnh có vui đâu bao giờ.", author: "Nguyễn Du" },
{ text: "Tôi khóc, vì tôi không có đôi hài để đi, cho đến khi tôi thấy một người không có chân.", author: "Helen Keller" },
{ text: "Trái tim tan vỡ là thứ âm nhạc duy nhất không cần nhạc trưởng.", author: "Fyodor Dostoevsky" }
],
energetic: [
{ text: "Hãy đứng dậy và bước tiếp, vì bình minh chỉ dành cho những ai dám vượt qua bóng tối.", author: "Rumi" },
{ text: "Đừng đi theo lối mòn, hãy băng qua rừng rậm và tạo ra con đường riêng.", author: "Walt Whitman" },
{ text: "Sức mạnh không đến từ thể xác, mà từ một ý chí không bao giờ đầu hàng.", author: "Mahatma Gandhi" },
{ text: "Lửa thử vàng, gian nan thử sức.", author: "Nguyễn Bỉnh Khiêm" },
{ text: "Đứng dậy! Hỡi những linh hồn dũng cảm! Tương lai đang chờ phía trước.", author: "Victor Hugo" }
],
calm: [
{ text: "Im lặng là bài hát của tâm hồn, chỉ những ai biết lắng nghe mới thấu hiểu.", author: "Lão Tử" },
{ text: "Hãy để tâm hồn tựa mặt hồ yên ả, dẫu sóng gió vẫn giữ được sự trong trẻo.", author: "Rabindranath Tagore" },
{ text: "An nhiên giữa dòng đời vội vã, như đóa sen vươn lên từ bùn lầy.", author: "Hồ Xuân Hương" },
{ text: "Tĩnh tâm để thấy, thấy để hiểu, hiểu để thương.", author: "Thích Nhất Hạnh" },
{ text: "Cành cây khẽ đung đưa, lá rơi nhẹ nhàng – thiên nhiên dạy ta bài học về sự buông bỏ.", author: "Matsuo Bashō" }
],
angry: [
{ text: "Cơn thịnh nộ như ngọn lửa thiêu rụi mọi lý trí, nhưng tro tàn để lại là cô đơn.", author: "William Shakespeare" },
{ text: "Người giận dữ luôn mở miệng nhưng lại bịt tai.", author: "Khổng Tử" },
{ text: "Hận thù là gánh nặng mà chỉ kẻ mang nó mới cảm thấy.", author: "Lão Tử" },
{ text: "Giận dữ là trừng phạt bản thân vì sai lầm của người khác.", author: "Alexander Pope" },
{ text: "Dẫu căm hờn có lớn đến đâu, hãy để nó tan biến như mây khói.", author: "Rumi" }
],
lonely: [
{ text: "Cô đơn là căn phòng không cửa sổ, nơi tiếng vọng duy nhất là nhịp tim mình.", author: "Emily Dickinson" },
{ text: "Ta như con tằm nhả tơ, quấn mình trong nỗi cô đơn.", author: "Nguyễn Du" },
{ text: "Nỗi buồn của người cô độc giống như bóng đêm không trăng sao.", author: "Fyodor Dostoevsky" },
{ text: "Khi một người đi xa, họ để lại khoảng trống lớn hơn cả bản thân họ.", author: "Pablo Neruda" },
{ text: "Trong vũ trụ bao la, mỗi chúng ta đều là một hạt bụi cô đơn.", author: "Rabindranath Tagore" }
],
romantic: [
{ text: "Em là đóa hồng đẹp nhất, còn anh là giọt sương mai long lanh trên cánh hoa.", author: "William Shakespeare" },
{ text: "Yêu là để mắt thấy nhau trong mọi sự vật, kể cả khi cách xa ngàn trùng.", author: "Pablo Neruda" },
{ text: "Trăm năm trong cõi người ta, chữ tình chữ tài khéo là ghét nhau.", author: "Nguyễn Du" },
{ text: "Tình yêu là sự kết hợp giữa lý trí và điên rồ, giữa thực tế và mộng mơ.", author: "Victor Hugo" },
{ text: "Anh yêu em không chỉ vì em là em, mà vì khi ở bên em, anh được là anh.", author: "Elizabeth Barrett Browning" }
],
focused: [
{ text: "Hãy làm việc như thể bạn không cần tiền, yêu như thể bạn chưa từng tổn thương.", author: "William Shakespeare" },
{ text: "Đừng đếm ngày, hãy làm cho mỗi ngày trở nên đáng nhớ.", author: "Rabindranath Tagore" },
{ text: "Sự tập trung là chiếc chìa khóa mở cánh cửa thành công.", author: "Lão Tử" },
{ text: "Không có đường tắt đến bất kỳ nơi nào đáng đến.", author: "Beverly Sills" },
{ text: "Mài sắt nên kim, chỉ cần kiên trì.", author: "Tục ngữ Việt Nam" }
]
};
function getRandomPoetry(emotionId) {
const poems = poetryLibrary[emotionId] || poetryLibrary.calm;
if (!poems.length) return { text: "Âm nhạc là liều thuốc của tâm hồn.", author: "Plato" };
return poems[Math.floor(Math.random() * poems.length)];
}
function showPoetryNotification(poem, songName) {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
bottom: 100px;
right: 20px;
max-width: 320px;
background: linear-gradient(135deg, rgba(0,0,0,0.85), rgba(50,50,70,0.9));
backdrop-filter: blur(12px);
color: #f0e6d0;
padding: 16px 20px;
border-radius: 24px;
z-index: 1000001;
font-family: 'Georgia', serif;
font-size: 14px;
line-height: 1.5;
border-left: 5px solid #ffb347;
box-shadow: 0 10px 25px rgba(0,0,0,0.3);
animation: fadeInUp 0.4s ease-out;
pointer-events: none;
`;
notification.style.cssText = `
position: fixed;
bottom: 100px;
right: 20px;
max-width: 320px;
background: linear-gradient(135deg, rgba(0,0,0,0.85), rgba(50,50,70,0.9));
backdrop-filter: blur(12px);
color: #f0e6d0;
padding: 16px 20px;
border-radius: 24px;
z-index: 1000001;
font-family: 'Segoe UI', 'Roboto', 'Noto Sans', 'Arial', sans-serif;
font-size: 14px;
line-height: 1.5;
border-left: 5px solid #ffb347;
box-shadow: 0 10px 25px rgba(0,0,0,0.3);
animation: fadeInUp 0.4s ease-out;
pointer-events: none;
`;
notification.innerHTML = `
<div style="font-style: italic; margin-bottom: 8px;">“${poem.text}”</div>
<div style="text-align: right; font-size: 12px; color: #ffb347;">— ${poem.author}</div>
<div style="font-size: 10px; margin-top: 8px; color: #aaa;">🎵 Đang nghe: ${songName}</div>
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'fadeOutDown 0.3s ease-in forwards';
setTimeout(() => notification.remove(), 300);
}, 8000);
}
// Thêm keyframes nếu chưa có
if (!document.querySelector('#poetryKeyframes')) {
const poetryStyle = document.createElement('style');
poetryStyle.id = 'poetryKeyframes';
poetryStyle.textContent = `
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeOutDown {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(30px); }
}
`;
document.head.appendChild(poetryStyle);
}
// Biến trạng thái
let poetrySuggestionEnabled = false;
// Tạo nút bật/tắt và chèn vào emotionContainer
const poetryToggleContainer = document.createElement('div');
poetryToggleContainer.style.marginTop = '12px';
poetryToggleContainer.style.textAlign = 'center';
const poetryToggleBtn = document.createElement('button');
poetryToggleBtn.textContent = '📜 BẬT GỢI Ý THƠ KHI CHỌN NHẠC';
poetryToggleBtn.style.backgroundColor = 'rgba(255, 193, 7, 0.2)';
poetryToggleBtn.style.border = '1px solid rgba(255, 193, 7, 0.4)';
poetryToggleBtn.style.borderRadius = '30px';
poetryToggleBtn.style.padding = '8px 12px';
poetryToggleBtn.style.fontSize = '12px';
poetryToggleBtn.style.width = '100%';
poetryToggleBtn.style.marginBottom = '8px';
poetryToggleBtn.addEventListener('click', () => {
poetrySuggestionEnabled = !poetrySuggestionEnabled;
poetryToggleBtn.textContent = poetrySuggestionEnabled ? '📜 TẮT GỢI Ý THƠ' : '📜 BẬT GỢI Ý THƠ KHI CHỌN NHẠC';
poetryToggleBtn.style.backgroundColor = poetrySuggestionEnabled ? 'rgba(76, 175, 80, 0.3)' : 'rgba(255, 193, 7, 0.2)';
const msg = document.createElement('div');
msg.textContent = poetrySuggestionEnabled ? '✨ Đã bật gợi ý thơ! Chọn bài hát sẽ nhận được câu thơ hay.' : '📖 Đã tắt gợi ý thơ.';
msg.style.cssText = 'position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,0.7); color:#FFD966; padding:8px 16px; border-radius:30px; font-size:12px; z-index:1000001;';
document.body.appendChild(msg);
setTimeout(() => msg.remove(), 2000);
});
poetryToggleContainer.appendChild(poetryToggleBtn);
// Tìm emotionContainer (có chứa "CẢM XÚC HIỆN TẠI")
const emotionContainerDiv = Array.from(document.querySelectorAll('#musicMenuContainer > div')).find(div => div.innerText.includes('CẢM XÚC HIỆN TẠI'));
if (emotionContainerDiv) {
emotionContainerDiv.appendChild(poetryToggleContainer);
} else {
// fallback: thêm vào cuối menu
const menuMain = document.getElementById('musicMenuContainer');
if (menuMain) menuMain.appendChild(poetryToggleContainer);
}
// Ghi đè hàm playMusic toàn cục
const originalPlayMusic = window.playMusic || (typeof playMusic !== 'undefined' ? playMusic : null);
if (originalPlayMusic) {
window.playMusic = function(music, musicButton) {
originalPlayMusic(music, musicButton);
if (poetrySuggestionEnabled) {
let emotion = window.currentEmotion; // từ phần emotion script
if (typeof currentEmotion !== 'undefined') emotion = currentEmotion;
if (!emotion || !poetryLibrary[emotion]) emotion = 'calm';
const poem = getRandomPoetry(emotion);
showPoetryNotification(poem, music.name);
}
};
// Đảm bảo hàm playMusic trong scope cũng được cập nhật (nếu cần)
if (typeof playMusic !== 'undefined' && playMusic !== window.playMusic) {
// Gán lại cho biến playMusic toàn cục (trong trường hợp nó được gọi trực tiếp)
window.playMusic = playMusic = window.playMusic;
}
} else {
console.warn("Không tìm thấy hàm playMusic gốc, tính năng thơ sẽ không hoạt động.");
}
})();
// ==================== PHẦN 2: MENU SỐ 2 (GIỮ NGUYÊN) ====================
(function () {
'use strict';
const menu = document.createElement('div');
menu.id = 'music-menu';
menu.style.position = 'fixed';
menu.style.top = '0';
menu.style.right = '-300px';
menu.style.width = '250px';
menu.style.height = '100%';
menu.style.backgroundColor = 'rgba(0, 0, 0, 0.25)';
menu.style.color = '#fff';
menu.style.fontFamily = 'Arial, sans-serif';
menu.style.padding = '20px';
menu.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.25)';
menu.style.transition = 'right 0.5s ease';
menu.style.zIndex = '10000';
menu.innerHTML = `
<h2 style="text-align: center;" id="music-menu-title">Hudební menu</h2>
<label for="music-url" id="music-url-label">URL písničky (YouTube):</label>
<input id="music-url" type="text" style="width: 100%; margin-bottom: 10px; padding: 5px;" placeholder="cho link ytb vào đây....">
<label for="start-time" id="start-time-label">Start Time Seconds...):</label>
<input id="start-time" type="number" style="width: 100%; margin-bottom: 10px; padding: 5px;" placeholder="giây...">
<!-- ==================== THÊM NÚT LOOP CHO MENU 2 ==================== -->
<button id="loop-menu2" style="width: 100%; padding: 10px; margin-bottom: 10px; background-color: #9E9E9E; color: white; border: none; cursor: pointer; font-size: 16px; font-weight: bold; border-radius: 5px;">➡️ Không lặp</button>
<button id="play-music" style="width: 100%; padding: 10px; margin-bottom: 10px; background-color: #4CAF50; color: white; border: none; cursor: pointer; font-size: 16px; font-weight: bold; border-radius: 5px;">Přehrát písničku</button>
<button id="start-video" style="width: 100%; padding: 10px; margin-bottom: 10px; background-color: #2196F3; color: white; border: none; cursor: pointer; font-size: 16px; font-weight: bold; border-radius: 5px;">START VIDEO</button>
<button id="stop-music" style="width: 100%; padding: 10px; background-color: #f44336; color: white; border: none; cursor: pointer; font-size: 16px; font-weight: bold; border-radius: 5px;">Zastavit</button>
<div id="video-container" style="margin-top: 20px; display: none;">
<iframe id="video-frame" width="100%" height="200" style="border: none;" src="" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<div id="player" style="margin-top: 20px; display: none;"></div>
<div id="warning" style="margin-top: 20px; color: red; font-weight: bold;">
<p id="adblock-warning">POUŽITÍ ADBLOCKU VEDE K NEFUNKČNOSTI VYPNITE HO</p>
<label for="language-select" style="color: white;">Language:</label>
<select id="language-select" style="width: 100%; padding: 5px;">
<option value="cz">Čeština</option>
<option value="en">English</option>
<option value="ru">Русский</option>
<option value="vi">Tiếng Việt</option>
<option value="ar">العربية</option>
</select>
</div>
<div style="margin-top: 20px; text-align: center; font-weight: bold; color: #FFEB3B;">
Script By <a href="https://www.youtube.com/@RektByMateX" target="_blank" style="color: #FF4081; font-weight: bold; text-decoration: underline;">RektByMateX</a>
</div>
<div style="margin-top: 20px; text-align: center; font-weight: bold; color: #FFEB2B;">
truy cập nhanh (quick access)
</div>
<div style="margin-top: 20px; text-align: center; font-weight: bold; color: #FFEB3B;">
1 <a href="https://www.youtube.com" target="_blank" style="color: #FF4081; font-weight: bold; text-decoration: underline;">youtube</a>
</div>
<div style="margin-top: 20px; text-align: center; font-weight: bold; color: #FFEB3B;">
2 <a href="https://translate.google.com" target="_blank" style="color: #FF4081; font-weight: bold; text-decoration: underline;">translate.google</a>
</div>
<div style="margin-top: 20px; text-align: center; font-weight: bold; color: #FFEB3B;">
3 <a href="https://facebook.com" target="_blank" style="color: #FF4081; font-weight: bold; text-decoration: underline;">facebook</a>
</div>
<div style="margin-top: 20px; text-align: center; font-weight: bold; color: #FFEB3B;">
4 <a href="https://discord.com/channels/@me" target="_blank" style="color: #FF4081; font-weight: bold; text-decoration: underline;">discord</a>
</div>
<div style="margin-top: 20px; text-align: center; font-weight: bold; color: #FFEB3B;">
5 <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley" target="_blank" style="color: #FF4081; font-weight: bold; text-decoration: underline;">hmmmmmm</a>
</div>
`;
document.body.appendChild(menu);
let menuVisible = false;
let loopMode2 = 'none';
let currentVideoId2 = null;
function toggleMenu() {
menu.style.right = menuVisible ? '-300px' : '0';
menuVisible = !menuVisible;
console.log('Menu visible:', menuVisible);
}
const loopBtn2 = document.getElementById('loop-menu2');
loopBtn2.addEventListener('click', () => {
if (loopMode2 === 'none') {
loopMode2 = 'single';
loopBtn2.textContent = '🔂 Lặp bài này';
loopBtn2.style.backgroundColor = '#4CAF50';
} else {
loopMode2 = 'none';
loopBtn2.textContent = '➡️ Không lặp';
loopBtn2.style.backgroundColor = '#9E9E9E';
}
});
let player;
function createYouTubePlayer(videoId, startTime) {
currentVideoId2 = videoId;
if (!player) {
player = new YT.Player('player', {
height: '0',
width: '0',
videoId: videoId,
playerVars: {
autoplay: 1,
start: startTime,
vq: 'hd1080',
iv_load_policy: 3
},
events: {
onReady: (event) => {
event.target.playVideo();
setTimeout(() => {
try {
const iframe = player.getIframe();
const videoEl = iframe.contentDocument?.querySelector('video');
if (videoEl) {
audioEngine.connectToVideo(videoEl);
}
} catch (e) {}
}, 2000);
},
onStateChange: (event) => {
if (event.data === YT.PlayerState.ENDED) {
if (loopMode2 === 'single' && currentVideoId2) {
player.loadVideoById(currentVideoId2, startTime);
}
}
}
}
});
} else {
player.loadVideoById(videoId, startTime);
}
}
function stopYouTubePlayer() {
if (player) {
player.stopVideo();
}
const iframe = document.getElementById('video-frame');
iframe.src = '';
document.getElementById('player').style.display = 'none';
document.getElementById('video-container').style.display = 'none';
}
document.getElementById('play-music').addEventListener('click', () => {
const url = document.getElementById('music-url').value;
const startTime = parseInt(document.getElementById('start-time').value) || 0;
const videoId = extractYouTubeVideoId(url);
if (videoId) {
createYouTubePlayer(videoId, startTime);
document.getElementById('player').style.display = 'block';
document.getElementById('video-container').style.display = 'none';
} else {
alert('Neplatný YouTube odkaz.');
}
});
document.getElementById('start-video').addEventListener('click', () => {
const url = document.getElementById('music-url').value;
const startTime = parseInt(document.getElementById('start-time').value) || 0;
const videoId = extractYouTubeVideoId(url);
if (videoId) {
const iframe = document.getElementById('video-frame');
iframe.src = `https://www.youtube.com/embed/${videoId}?start=${startTime}&autoplay=1`;
document.getElementById('video-container').style.display = 'block';
document.getElementById('player').style.display = 'none';
} else {
alert('Neplatný YouTube odkaz.');
}
});
document.getElementById('stop-music').addEventListener('click', () => {
stopYouTubePlayer();
});
document.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === '[') {
toggleMenu();
}
if (e.key.toLowerCase() === '`') {
toggleMenu();
}
});
function extractYouTubeVideoId(url) {
const match = url.match(/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/);
return match ? match[1] : null;
}
const script = document.createElement('script');
script.src = 'https://www.youtube.com/iframe_api';
document.head.appendChild(script);
document.getElementById('language-select').addEventListener('change', (e) => {
changeLanguage(e.target.value);
});
function changeLanguage(language) {
const translations = {
cz: {
'music-menu-title': 'Hudební menu',
'music-url-label': 'URL písničky (YouTube):',
'start-time-label': 'Začátek (sekundy):',
'play-music': 'Přehrát písničku',
'start-video': 'Spustit Video',
'stop-music': 'Zastavit',
'adblock-warning': 'POUŽITÍ ADBLOCKU VEDE K NEFUNKČNOSTI VYPNITE HO',
'language-btn': 'Vyberte jazyk'
},
en: {
'music-menu-title': 'Music Menu',
'music-url-label': 'Song URL (YouTube):',
'start-time-label': 'Start time (seconds):',
'play-music': 'Play Song',
'start-video': 'START VIDEO',
'stop-music': 'Stop',
'adblock-warning': 'USING ADBLOCK CAUSES FAILURE, PLEASE DISABLE IT',
'language-btn': 'Languages'
},
ru: {
'music-menu-title': 'Музыкальное меню',
'music-url-label': 'URL песни (YouTube):',
'start-time-label': 'Время начала (секунды):',
'play-music': 'Воспроизвести песню',
'start-video': 'Запустить видео',
'stop-music': 'Остановить',
'adblock-warning': 'Использование Adblock вызывает сбой, пожалуйста, отключите его',
'language-btn': 'Языки'
},
vi: {
'music-menu-title': 'Menu Nhạc',
'music-url-label': 'URL bài hát (YouTube):',
'start-time-label': 'Thời gian bắt đầu (giây):',
'play-music': 'Phát bài hát',
'start-video': 'Bắt đầu Video',
'stop-music': 'Dừng',
'adblock-warning': 'SỬ DỤNG ADBLOCK GÂY LỖI, VUI LÒNG TẮT NÓ',
'language-btn': 'Ngôn ngữ'
},
ar: {
'music-menu-title': 'قائمة الموسيقى',
'music-url-label': 'رابط الأغنية (YouTube):',
'start-time-label': 'وقت البداية (ثواني):',
'play-music': 'تشغيل الأغنية',
'start-video': 'تشغيل الفيديو',
'stop-music': 'إيقاف',
'adblock-warning': 'استخدام Adblock يؤدي إلى عطل ، يرجى تعطيله',
'language-btn': 'اللغات'
}
};
const text = translations[language] || translations.en;
document.getElementById('music-menu-title').textContent = text['music-menu-title'];
document.getElementById('music-url-label').textContent = text['music-url-label'];
document.getElementById('start-time-label').textContent = text['start-time-label'];
document.getElementById('play-music').textContent = text['play-music'];
document.getElementById('start-video').textContent = text['start-video'];
document.getElementById('stop-music').textContent = text['stop-music'];
document.getElementById('adblock-warning').textContent = text['adblock-warning'];
document.getElementById('language-btn').textContent = text['language-btn'];
}
})();
// ==================== PHẦN 3: TABLE GRADIENT (GIỮ NGUYÊN) ====================
(function () {
'use strict'
const POLL_INTERVAL = 1000
const HUE_RANGE = 120
const SATURATION = '80%'
const LIGHTNESS = '88%'
function applyGradientToColumn(table, column) {
const rowCount = table.rows.length
Array.from(table.rows).forEach((row, index) => {
const cell = row.cells[column]
if (!cell) return
const hue = (index / (rowCount - 1)) * HUE_RANGE
cell.style.backgroundColor = `hsl(${HUE_RANGE - hue}, ${SATURATION}, ${LIGHTNESS})`
})
}
function initializeTable(table) {
if (table.hasAttribute('data-gradient-initialized')) return
table.setAttribute('data-gradient-initialized', 'true')
const columnCount = table.rows[0]?.cells.length || 0
for (let col = 0; col < columnCount; col++) {
applyGradientToColumn(table, col)
}
}
function initializeTables() {
document.querySelectorAll('table:not([data-gradient-initialized])').forEach(initializeTable)
}
initializeTables()
setInterval(initializeTables, POLL_INTERVAL)
})();
// ==================== PHẦN 4: FACEBOOK DOWNLOADER (GIỮ NGUYÊN) ====================
(function () {
'use strict';
function getOverlapScore(el) {
var rect = el.getBoundingClientRect();
return (
Math.min(
rect.bottom,
window.innerHeight || document.documentElement.clientHeight
) - Math.max(0, rect.top)
);
}
function getVideoIdFromVideoElement(video) {
try {
for (let k in video.parentElement) {
if (k.startsWith("__reactProps")) {
return video.parentElement[k].children.props.videoFBID;
}
}
} catch (e) {
return null;
}
}
async function getWatchingVideoId() {
let allVideos = Array.from(document.querySelectorAll("video"));
let result = [];
for (let video of allVideos) {
let videoId = getVideoIdFromVideoElement(video);
if (videoId) {
result.push({
videoId,
overlapScore: getOverlapScore(video),
playing: !!(
video.currentTime > 0 &&
!video.paused &&
!video.ended &&
video.readyState > 2
),
});
}
}
let playingVideo = result.find((_) => _.playing);
if (playingVideo) return [playingVideo.videoId];
return result
.filter((_) => _.videoId && (_.overlapScore > 0 || _.playing))
.sort((a, b) => b.overlapScore - a.overlapScore)
.map((_) => _.videoId);
}
async function getVideoUrlFromVideoId(videoId) {
let dtsg = await getDtsg();
try {
return await getLinkFbVideo2(videoId, dtsg);
} catch (e) {
return await getLinkFbVideo1(videoId, dtsg);
}
}
async function getLinkFbVideo2(videoId, dtsg) {
let res = await fetch(
"https://www.facebook.com/video/video_data_async/?video_id=" + videoId,
{
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: stringifyVariables({
__a: "1",
fb_dtsg: dtsg,
}),
}
);
let text = await res.text();
text = text.replace("for (;;);", "");
let json = JSON.parse(text);
const { hd_src, hd_src_no_ratelimit, sd_src, sd_src_no_ratelimit } =
json?.payload || {};
return hd_src_no_ratelimit || hd_src || sd_src_no_ratelimit || sd_src;
}
async function getLinkFbVideo1(videoId, dtsg) {
let res = await fetchGraphQl("5279476072161634", {
UFI2CommentsProvider_commentsKey: "CometTahoeSidePaneQuery",
caller: "CHANNEL_VIEW_FROM_PAGE_TIMELINE",
displayCommentsContextEnableComment: null,
displayCommentsContextIsAdPreview: null,
displayCommentsContextIsAggregatedShare: null,
displayCommentsContextIsStorySet: null,
displayCommentsFeedbackContext: null,
feedbackSource: 41,
feedLocation: "TAHOE",
focusCommentID: null,
privacySelectorRenderLocation: "COMET_STREAM",
renderLocation: "video_channel",
scale: 1,
streamChainingSection: !1,
useDefaultActor: !1,
videoChainingContext: null,
videoID: videoId,
}, dtsg);
let text = await res.text();
let a = JSON.parse(text.split("\n")[0]),
link = a.data.video.playable_url_quality_hd || a.data.video.playable_url;
return link;
}
function fetchGraphQl(doc_id, variables, dtsg) {
return fetch("https://www.facebook.com/api/graphql/", {
method: "POST",
headers: {
"content-type": "application/x-www-form-urlencoded",
},
body: stringifyVariables({
doc_id: doc_id,
variables: JSON.stringify(variables),
fb_dtsg: dtsg,
server_timestamps: !0,
}),
});
}
function stringifyVariables(d, e) {
let f = [],
a;
for (a in d)
if (d.hasOwnProperty(a)) {
let g = e ? e + "[" + a + "]" : a,
b = d[a];
f.push(
null !== b && "object" == typeof b
? stringifyVariables(b, g)
: encodeURIComponent(g) + "=" + encodeURIComponent(b)
);
}
return f.join("&");
}
async function getDtsg() {
return require("DTSGInitialData").token;
}
function downloadURL(url, name) {
var link = document.createElement("a");
link.target = "_blank";
link.download = name;
link.href = url;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
async function downloadWatchingVideo() {
try {
let listVideoId = await getWatchingVideoId();
if (!listVideoId?.length > 0) throw Error("No video found in the page");
console.log(listVideoId)
for (let videoId of listVideoId) {
let videoUrl = await getVideoUrlFromVideoId(videoId);
if (videoUrl) downloadURL(videoUrl, "fb_video.mp4");
}
} catch (e) {
alert("ERROR: " + e);
}
}
function resisterMenuCommand() {
GM_registerMenuCommand("Download watching video", downloadWatchingVideo);
}
resisterMenuCommand();
})();
// ==================== THÊM STYLE CHO MENU ====================
const style = document.createElement('style');
style.textContent = `
/* Glassmorphism effect cho menu */
#musicMenuContainer {
position: fixed !important;
top: 50% !important;
left: 0 !important;
transform: translateY(-50%) !important;
width: 300px !important;
max-height: 80vh !important;
overflow-y: auto !important;
background: rgba(20, 30, 40, 0.25) !important;
backdrop-filter: blur(15px) !important;
-webkit-backdrop-filter: blur(15px) !important;
border: 1px solid rgba(255, 255, 255, 0.15) !important;
border-left: none !important;
border-radius: 0 20px 20px 0 !important;
color: #fff !important;
padding: 25px 20px !important;
z-index: 999999 !important;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.05) inset !important;
transition: left 0.4s cubic-bezier(0.2, 0.9, 0.3, 1) !important;
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif !important;
scrollbar-width: thin !important;
scrollbar-color: rgba(76, 175, 80, 0.5) rgba(255, 255, 255, 0.1) !important;
}
/* Tùy chỉnh scrollbar */
#musicMenuContainer::-webkit-scrollbar {
width: 5px !important;
}
#musicMenuContainer::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05) !important;
border-radius: 10px !important;
}
#musicMenuContainer::-webkit-scrollbar-thumb {
background: rgba(76, 175, 80, 0.25) !important;
border-radius: 10px !important;
transition: 0.3s !important;
}
#musicMenuContainer::-webkit-scrollbar-thumb:hover {
background: rgba(76, 175, 80, 0.25) !important;
}
/* Menu title */
#musicMenuContainer h2 {
font-size: 14px !important;
font-weight: 500 !important;
text-align: center !important;
margin: 0 0 20px 0 !important;
padding: 10px !important;
background: rgba(255, 255, 255, 0.08) !important;
border-radius: 12px !important;
color: rgba(255, 255, 255, 0.25) !important;
letter-spacing: 0.5px !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
backdrop-filter: blur(5px) !important;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2) !important;
}
/* Mood container */
#musicMenuContainer > div:nth-child(2) {
background: rgba(76, 175, 80, 0.15) !important;
border: 1px solid rgba(76, 175, 80, 0.3) !important;
border-radius: 16px !important;
padding: 15px !important;
margin-bottom: 20px !important;
backdrop-filter: blur(8px) !important;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2) !important;
}
/* Current mood display */
#currentMood {
background: rgba(0, 0, 0, 0.3) !important;
border: 1px solid rgba(76, 175, 80, 0.3) !important;
border-radius: 30px !important;
padding: 10px 15px !important;
margin-bottom: 15px !important;
font-size: 13px !important;
text-align: center !important;
color: #fff !important;
backdrop-filter: blur(5px) !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2) !important;
}
/* Mood grid */
#musicMenuContainer > div:nth-child(2) > div:last-child {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 8px !important;
}
/* Mood buttons */
#musicMenuContainer > div:nth-child(2) button {
background: rgba(33, 150, 243, 0.2) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
border-radius: 12px !important;
padding: 10px 5px !important;
font-size: 11px !important;
font-weight: 500 !important;
color: rgba(255, 255, 255, 0.9) !important;
cursor: pointer !important;
transition: all 0.3s cubic-bezier(0.2, 0.9, 0.3, 0.25) !important;
backdrop-filter: blur(5px) !important;
text-transform: uppercase !important;
letter-spacing: 0.5px !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2) !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
#musicMenuContainer > div:nth-child(2) button:hover {
background: rgba(33, 150, 243, 0.4) !important;
transform: translateY(-2px) scale(1.02) !important;
border-color: rgba(255, 255, 255, 0.3) !important;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3) !important;
}
#musicMenuContainer > div:nth-child(2) button:active {
transform: translateY(0) scale(0.98) !important;
}
/* Custom EQ container */
#musicMenuContainer > div:nth-child(3) {
background: rgba(33, 150, 243, 0.15) !important;
border: 1px solid rgba(33, 150, 243, 0.3) !important;
border-radius: 16px !important;
padding: 15px !important;
margin-bottom: 20px !important;
backdrop-filter: blur(8px) !important;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2) !important;
}
/* EQ labels */
#musicMenuContainer label {
font-size: 12px !important;
font-weight: 500 !important;
color: rgba(255, 255, 255, 0.7) !important;
display: block !important;
margin-bottom: 5px !important;
letter-spacing: 0.3px !important;
}
/* Range sliders */
#musicMenuContainer input[type="range"] {
width: 100% !important;
height: 5px !important;
background: rgba(255, 255, 255, 0.1) !important;
border-radius: 10px !important;
outline: none !important;
-webkit-appearance: none !important;
margin-bottom: 15px !important;
cursor: pointer !important;
}
#musicMenuContainer input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none !important;
width: 18px !important;
height: 18px !important;
background: linear-gradient(135deg, #4CAF50, #45a049) !important;
border-radius: 50% !important;
border: 2px solid rgba(255, 255, 255, 0.25) !important;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important;
cursor: pointer !important;
transition: 0.2s !important;
}
#musicMenuContainer input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2) !important;
background: linear-gradient(135deg, #66BB6A, #4CAF50) !important;
}
/* Volume slider specific */
#musicMenuContainer input[type="range"]#volumeSlider {
background: linear-gradient(90deg, #4CAF50, #8BC34A) !important;
}
/* Buttons */
#musicMenuContainer button {
width: 100% !important;
padding: 12px !important;
margin-bottom: 10px !important;
background: rgba(255, 255, 255, 0.1) !important;
border: 1px solid rgba(255, 255, 255, 0.15) !important;
border-radius: 12px !important;
color: white !important;
font-size: 13px !important;
font-weight: 500 !important;
cursor: pointer !important;
transition: all 0.3s cubic-bezier(0.2, 0.9, 0.3, 0.25) !important;
backdrop-filter: blur(8px) !important;
text-transform: uppercase !important;
letter-spacing: 0.5px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2) !important;
}
#musicMenuContainer button:hover {
background: rgba(255, 255, 255, 0.2) !important;
transform: translateY(-2px) !important;
border-color: rgba(255, 255, 255, 0.3) !important;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3) !important;
}
#musicMenuContainer button:active {
transform: translateY(0) !important;
}
/* Special buttons */
#musicMenuContainer button:nth-child(8) { /* Mute button */
background: rgba(255, 165, 0, 0.2) !important;
border-color: rgba(255, 165, 0, 0.3) !important;
}
#musicMenuContainer button:nth-child(8):hover {
background: rgba(255, 165, 0, 0.3) !important;
}
#musicMenuContainer button:nth-child(11) { /* Loop button */
background: rgba(128, 128, 128, 0.2) !important;
border-color: rgba(255, 255, 255, 0.15) !important;
}
#musicMenuContainer button:nth-child(13) { /* Pause button */
background: rgba(0, 255, 0, 0.15) !important;
border-color: rgba(0, 255, 0, 0.2) !important;
}
#musicMenuContainer button:nth-child(13):hover {
background: rgba(0, 255, 0, 0.25) !important;
}
#musicMenuContainer button:nth-child(14) { /* Random button */
background: rgba(255, 0, 255, 0.15) !important;
border-color: rgba(255, 0, 255, 0.2) !important;
}
#musicMenuContainer button:nth-child(15) { /* Stop button */
background: rgba(255, 0, 0, 0.15) !important;
border-color: rgba(255, 0, 0, 0.2) !important;
}
/* Progress bar */
#musicMenuContainer input[type="range"]#progressBar {
margin-bottom: 5px !important;
}
/* Duration display */
#musicMenuContainer > div:nth-child(10) {
font-size: 12px !important;
color: rgba(255, 255, 255, 0.6) !important;
text-align: center !important;
margin-bottom: 15px !important;
padding: 8px !important;
background: rgba(0, 0, 0, 0.2) !important;
border-radius: 20px !important;
backdrop-filter: blur(5px) !important;
}
/* Music list container */
#musicMenuContainer > div:last-child {
margin-top: 20px !important;
}
/* Music item buttons */
#musicMenuContainer > div:last-child button {
background: rgba(0, 255, 255, 0.1) !important;
border-color: rgba(0, 255, 255, 0.15) !important;
font-size: 12px !important;
padding: 10px !important;
}
#musicMenuContainer > div:last-child button:hover {
background: rgba(0, 255, 255, 0.2) !important;
}
/* Speed select dropdown */
#musicMenuContainer select {
width: 100% !important;
padding: 12px !important;
margin-bottom: 15px !important;
background: rgba(255, 255, 255, 0.1) !important;
border: 1px solid rgba(255, 255, 255, 0.15) !important;
border-radius: 12px !important;
color: white !important;
font-size: 13px !important;
cursor: pointer !important;
backdrop-filter: blur(8px) !important;
outline: none !important;
transition: 0.3s !important;
}
#musicMenuContainer select:hover {
background: rgba(255, 255, 255, 0.15) !important;
border-color: rgba(255, 255, 255, 0.25) !important;
}
#musicMenuContainer select option {
background: rgba(20, 30, 40, 0.95) !important;
color: white !important;
padding: 10px !important;
}
/* Reconnect button */
#musicMenuContainer button:last-of-type {
background: rgba(255, 140, 0, 0.2) !important;
border-color: rgba(255, 140, 0, 0.3) !important;
margin-top: 5px !important;
}
#musicMenuContainer button:last-of-type:hover {
background: rgba(255, 140, 0, 0.3) !important;
}
/* Animation cho các elements */
@keyframes glow {
0% { box-shadow: 0 0 5px rgba(76, 175, 80, 0.2); }
50% { box-shadow: 0 0 20px rgba(76, 175, 80, 0.4); }
100% { box-shadow: 0 0 5px rgba(76, 175, 80, 0.2); }
}
#currentMood {
animation: glow 3s infinite !important;
}
/* Smooth transitions cho tất cả */
#musicMenuContainer * {
transition: all 0.3s ease !important;
}
/* Glass effect cho các containers */
.glass-panel {
background: rgba(255, 255, 255, 0.05) !important;
backdrop-filter: blur(10px) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
border-radius: 16px !important;
}
`;
document.head.appendChild(style);
// Thêm animation cho menu khi mở/đóng
const menuObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'style') {
const menu = document.getElementById('musicMenuContainer');
if (menu && menu.style.left === '0px') {
menu.style.animation = 'slideIn 0.4s cubic-bezier(0.2, 0.9, 0.3, 1)';
} else {
menu.style.animation = 'slideOut 0.4s cubic-bezier(0.2, 0.9, 0.3, 1)';
}
}
});
});
// Thêm keyframe animations
const animStyle = document.createElement('style');
animStyle.textContent = `
@keyframes slideIn {
from {
left: -300px;
opacity: 0;
}
to {
left: 0;
opacity: 1;
}
}
@keyframes slideOut {
from {
left: 0;
opacity: 1;
}
to {
left: -300px;
opacity: 0;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Áp dụng animation cho các phần tử con khi menu mở */
#musicMenuContainer[style*="left: 0px"] > * {
animation: fadeInUp 0.5s ease forwards;
opacity: 0;
}
#musicMenuContainer[style*="left: 0px"] > *:nth-child(1) { animation-delay: 0.1s; }
#musicMenuContainer[style*="left: 0px"] > *:nth-child(2) { animation-delay: 0.15s; }
#musicMenuContainer[style*="left: 0px"] > *:nth-child(3) { animation-delay: 0.2s; }
#musicMenuContainer[style*="left: 0px"] > *:nth-child(4) { animation-delay: 0.25s; }
#musicMenuContainer[style*="left: 0px"] > *:nth-child(5) { animation-delay: 0.3s; }
#musicMenuContainer[style*="left: 0px"] > *:nth-child(6) { animation-delay: 0.35s; }
#musicMenuContainer[style*="left: 0px"] > *:nth-child(7) { animation-delay: 0.4s; }
#musicMenuContainer[style*="left: 0px"] > *:nth-child(8) { animation-delay: 0.45s; }
#musicMenuContainer[style*="left: 0px"] > *:nth-child(9) { animation-delay: 0.5s; }
#musicMenuContainer[style*="left: 0px"] > *:nth-child(10) { animation-delay: 0.55s; }
`;
document.head.appendChild(animStyle);
// Observe menu để thêm animation
setTimeout(() => {
const menu = document.getElementById('musicMenuContainer');
if (menu) {
menuObserver.observe(menu, { attributes: true });
}
}, 1000);