gfds / src /App.tsx
Zestysson
Deploy Glizh premium landing page and live player simulator
0ba4f29
Raw
History Blame Contribute Delete
93.3 kB
import React, { useState, useEffect, useRef } from 'react';
import {
Disc,
Play,
Pause,
SkipForward,
SkipBack,
Volume2,
Volume1,
VolumeX,
Plus,
Trash2,
Wand2,
Terminal,
HelpCircle,
AlertTriangle,
Layers,
Cpu,
Tv,
ListMusic,
CheckCircle,
ExternalLink,
ChevronRight,
ShieldCheck,
Zap,
Music,
Maximize2,
LogOut,
Send,
Sliders,
AudioLines,
Sparkles,
Info,
X,
MessageSquare
} from 'lucide-react';
import { SongTrack, BotStats, DiscordCommand, MockDiscordMessage, INITIAL_QUEUE, BOT_COMMANDS } from './types';
// Browser Sound Synthesizer Engine
class WebMusicSynth {
private ctx: AudioContext | null = null;
private activeOscillators: OscillatorNode[] = [];
private mainGainNode: GainNode | null = null;
init() {
if (!this.ctx) {
const AudioContextClass = window.AudioContext || (window as any).webkitAudioContext;
if (AudioContextClass) {
this.ctx = new AudioContextClass();
}
}
}
// Plays a synth-based Airhorn blow
playAirhorn() {
this.init();
if (!this.ctx) return;
const now = this.ctx.currentTime;
const gain = this.ctx.createGain();
gain.connect(this.ctx.destination);
gain.gain.setValueAtTime(0, now);
gain.gain.linearRampToValueAtTime(0.3, now + 0.05);
gain.gain.linearRampToValueAtTime(0.2, now + 0.2);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.8);
// Create classic double oscillator detuned discord airhorn
const frequencies = [220, 222, 330, 440];
frequencies.forEach(f => {
const osc = this.ctx!.createOscillator();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(f, now);
osc.frequency.linearRampToValueAtTime(f - 5, now + 0.6);
const filter = this.ctx!.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(1000, now);
osc.connect(filter);
filter.connect(gain);
osc.start(now);
osc.stop(now + 0.8);
});
}
// Plays laser synth noise
playLaser() {
this.init();
if (!this.ctx) return;
const now = this.ctx.currentTime;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(1200, now);
osc.frequency.exponentialRampToValueAtTime(80, now + 0.45);
gain.gain.setValueAtTime(0.25, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.45);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start(now);
osc.stop(now + 0.5);
}
// Plays beautiful deep major chord major sweep pad
playSynthPad() {
this.init();
if (!this.ctx) return;
const now = this.ctx.currentTime;
const chords = [196.00, 246.94, 293.66, 392.00]; // G3, B3, D4, G4 major chord
const masterGain = this.ctx.createGain();
masterGain.gain.setValueAtTime(0, now);
masterGain.gain.linearRampToValueAtTime(0.3, now + 0.2);
masterGain.gain.exponentialRampToValueAtTime(0.001, now + 2.5);
masterGain.connect(this.ctx.destination);
chords.forEach((freq, idx) => {
const osc = this.ctx!.createOscillator();
const oscGain = this.ctx!.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq + (idx * 0.4), now);
oscGain.gain.setValueAtTime(0.08, now);
osc.connect(oscGain);
oscGain.connect(masterGain);
osc.start(now);
osc.stop(now + 2.6);
});
}
// Toggle sustained rain background noise
toggleRain(enable: boolean) {
this.init();
if (!this.ctx) return;
if (!enable) {
if (this.mainGainNode) {
try {
this.mainGainNode.gain.setValueAtTime(this.mainGainNode.gain.value, this.ctx.currentTime);
this.mainGainNode.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.8);
} catch {}
}
return;
}
const now = this.ctx.currentTime;
this.mainGainNode = this.ctx.createGain();
this.mainGainNode.gain.setValueAtTime(0, now);
this.mainGainNode.gain.linearRampToValueAtTime(0.25, now + 1.0);
this.mainGainNode.connect(this.ctx.destination);
const bufferSize = 2 * this.ctx.sampleRate;
const noiseBuffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
const output = noiseBuffer.getChannelData(0);
let lastOut = 0.0;
for (let i = 0; i < bufferSize; i++) {
const white = Math.random() * 2 - 1;
output[i] = (lastOut + (0.02 * white)) / 1.02;
lastOut = output[i];
output[i] *= 3.5;
}
const noiseSource = this.ctx.createBufferSource();
noiseSource.buffer = noiseBuffer;
noiseSource.loop = true;
const lowpass = this.ctx.createBiquadFilter();
lowpass.type = 'lowpass';
lowpass.frequency.setValueAtTime(700, now);
noiseSource.connect(lowpass);
lowpass.connect(this.mainGainNode);
noiseSource.start(now);
}
}
export default function App() {
const [activeTab, setActiveTab] = useState<'home' | 'dashboard' | 'console'>('home');
const [joinedChannel, setJoinedChannel] = useState(false);
const [guildName, setGuildName] = useState('Mi Servidor Principal');
// Music Player state variables
const [isPlaying, setIsPlaying] = useState(true);
const [songQueue, setSongQueue] = useState<SongTrack[]>(INITIAL_QUEUE);
const [currentTrackIndex, setCurrentTrackIndex] = useState(0);
const [playProgress, setPlayProgress] = useState(15); // offset ratio (seconds)
const [volume, setVolume] = useState(90);
const [bassboostMode, setBassboostMode] = useState<'Off' | 'Bass+ Low'| 'Glizh Heavy'>('Off');
const [activeFilter, setActiveFilter] = useState<'None' | 'Nightcore' | 'Vaporwave' | 'Lo-Fi Chill'>('None');
// Interactive Custom Adding state
const [manualTitle, setManualTitle] = useState('');
const [manualArtist, setManualArtist] = useState('');
const [geminiSearchQuery, setGeminiSearchQuery] = useState('');
const [isAiSuggesting, setIsAiSuggesting] = useState(false);
// Sound effects state
const synthRef = useRef<WebMusicSynth | null>(null);
const [isRainEnabled, setIsRainEnabled] = useState(false);
// Stats Counters
const [botStats, setBotStats] = useState<BotStats>({
guilds: 18249,
users: 489210,
voiceChannels: 3412,
ping: 18,
uptime: '28d 4h 12m'
});
// Discord Chat Terminal Simulator states
const [discordChat, setDiscordChat] = useState<MockDiscordMessage[]>([
{
id: 'msg-1',
author: { username: 'cristophergamer', avatar: '🎮', isBot: false, colorClass: 'text-amber-400 font-bold' },
content: 'Buenas gente, ¿qué bot de música vamos a usar hoy? Hay que armar una sesión chill.',
timestamp: 'Hoy a las 02:01 AM'
},
{
id: 'msg-2',
author: { username: 'mandy_xx', avatar: '🐱', isBot: false, colorClass: 'text-rose-450 font-bold' },
content: '¡Usemos Glizh! Suena muchísimo más limpio que los otros bots y tiene unos graves brutales.',
timestamp: 'Hoy a las 02:02 AM'
},
{
id: 'msg-3',
author: { username: 'Glizh', avatar: '👑', isBot: true, colorClass: 'text-yellow-400 font-bold' },
timestamp: 'Hoy a las 02:02 AM',
embed: {
title: '🎶 Glizh Sound System v4.8 Activo',
description: '¡Gracias por llamarme! Estoy escuchando en el canal de voz **🔊 General Ambient**. Puedes invitar a tus amigos o usar `/play` directamente aquí.',
color: 'border-amber-500',
fields: [
{ name: 'DSP Core', value: '🟢 Ultra Stereo Link (320kbps)', inline: true },
{ name: 'Filtro Activo', value: '✨ Glizh ClearAudio v4', inline: true },
],
footer: 'Listo | Comandos de barra activos'
}
}
]);
const [consoleInput, setConsoleInput] = useState('');
const [isConsoleGenerating, setIsConsoleGenerating] = useState(false);
// Discord Authorization Gateway Dialog
const [showInviteModal, setShowInviteModal] = useState(false);
const [inviteServerName, setInviteServerName] = useState('Mi Servidor Dorado');
const [inviteCompleted, setInviteCompleted] = useState(false);
// Floating Assistant state variables
const [isAssistantOpen, setIsAssistantOpen] = useState(false);
const [assistantHistory, setAssistantHistory] = useState<{ sender: 'user' | 'bot'; text: string }[]>([
{ sender: 'bot', text: '¡Hola! Soy Glizh AI, el asistente virtual integrado. Pregúntame lo que quieras sobre comandos de música, filtros de audio DSP, o cómo solucionar problemas con Lavalink. ✨' }
]);
const [assistantInput, setAssistantInput] = useState('');
const [isAssistantLoading, setIsAssistantLoading] = useState(false);
const assistantEndRef = useRef<HTMLDivElement | null>(null);
const activeTrack: SongTrack | undefined = songQueue[currentTrackIndex];
// Auto scroll assistant chat to bottom
useEffect(() => {
if (isAssistantOpen) {
assistantEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}
}, [assistantHistory, isAssistantOpen]);
// Tick playback timer every second
useEffect(() => {
let interval: any = null;
if (isPlaying && activeTrack) {
interval = setInterval(() => {
setPlayProgress(prev => {
const rawDuration = activeTrack.duration;
const [m, s] = rawDuration.split(':').map(Number);
const totalSecs = (m * 60) + s;
if (prev >= totalSecs) {
handleSkipForward();
return 0;
}
return prev + 1;
});
}, 1000);
}
return () => clearInterval(interval);
}, [isPlaying, currentTrackIndex, songQueue]);
// Audio synths lazy initializer
const getSynth = () => {
if (!synthRef.current) {
synthRef.current = new WebMusicSynth();
}
return synthRef.current;
};
// Convert seconds back to readable time string MM:SS
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};
// Skip tracks functions
const handleSkipForward = () => {
if (songQueue.length === 0) return;
setPlayProgress(0);
setCurrentTrackIndex(prev => (prev + 1) % songQueue.length);
};
const handleSkipBack = () => {
setPlayProgress(0);
setCurrentTrackIndex(prev => (prev - 1 + songQueue.length) % songQueue.length);
};
const handleTogglePlay = () => {
setIsPlaying(prev => !prev);
getSynth().playSynthPad();
};
const handleRemoveTrack = (id: string, idx: number) => {
if (songQueue.length <= 1) {
alert("¡Glizh debe mantener al menos 1 canción en la cola para no detener la música!");
return;
}
setSongQueue(prev => prev.filter(t => t.id !== id));
if (idx === currentTrackIndex) {
setCurrentTrackIndex(0);
setPlayProgress(0);
} else if (idx < currentTrackIndex) {
setCurrentTrackIndex(prev => Math.max(0, prev - 1));
}
};
// Adds raw manual tracks
const handleAddManualTrack = () => {
if (!manualTitle.trim()) return;
const newT: SongTrack = {
id: `manual-${Date.now()}`,
title: manualTitle,
artist: manualArtist.trim() || 'Artista Desconocido',
duration: '03:15',
genre: 'Cola Manual',
requester: 'Tú'
};
setSongQueue(prev => [...prev, newT]);
setManualTitle('');
setManualArtist('');
};
// Suggest custom playlist using Gemini
const handleGeminiPlaylistSuggest = async () => {
if (!geminiSearchQuery.trim()) {
alert("Por favor escribe un ambiente o género. E.g. 'reggaeton viejo para bailar' o 'bachata romantica'");
return;
}
setIsAiSuggesting(true);
try {
const response = await fetch('/api/music/suggest', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: geminiSearchQuery })
});
const data = await response.json();
if (data.error) throw new Error(data.error);
if (data.tracks && Array.isArray(data.tracks)) {
const parsedTracks: SongTrack[] = data.tracks.map((t: any, index: number) => ({
id: `ai-suggest-${Date.now()}-${index}`,
title: t.title || 'Melodía Dinámica',
artist: t.artist || 'Glizh AI Studio',
duration: t.duration || '03:00',
genre: t.genre || 'Género sugerido',
requester: 'Asistente Glizh'
}));
setSongQueue(prev => [...prev, ...parsedTracks]);
const discordEvent: MockDiscordMessage = {
id: `disc-event-${Date.now()}`,
author: { username: 'Glizh', avatar: '👑', isBot: true, colorClass: 'text-yellow-400 font-bold' },
timestamp: 'Hoy a las ' + new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
embed: {
title: `💿 Cola generada por IA cargada: "${geminiSearchQuery}"`,
description: `¡El motor inteligente analizó el prompt y añadió **${parsedTracks.length} canciones** a la cola de reproducción!`,
color: 'border-yellow-500',
fields: parsedTracks.map((t, i) => ({
name: `${i+1}. ${t.title}`,
value: `de *${t.artist}* • Duración: ${t.duration}`
})),
footer: 'Recomendador Inteligente Glizh | Gemini API'
}
};
setDiscordChat(prev => [...prev, discordEvent]);
setGeminiSearchQuery('');
alert(`¡Glizh cargó exitosamente ${parsedTracks.length} canciones en la cola! Compruébalo en la consola o el reproductor.`);
}
} catch (e: any) {
console.error(e);
alert(`No se pudo generar la recomendación: ${e.message || 'Verifica la clave de Gemini en el servidor.'}`);
} finally {
setIsAiSuggesting(false);
}
};
// Pre-configured "Music Themes" for rapid testing
const loadPresetTheme = (theme: 'retro' | 'workout' | 'jazz') => {
let preset: SongTrack[] = [];
if (theme === 'retro') {
preset = [
{ id: `retro-1-${Date.now()}`, title: 'Provenza', artist: 'KAROL G', duration: '03:27', genre: 'Reggaeton', requester: 'reggaeton_lover' },
{ id: `retro-2-${Date.now()}`, title: 'La Bachata', artist: 'Manuel Turizo', duration: '02:42', genre: 'Bachata', requester: 'reggaeton_lover' },
{ id: `retro-3-${Date.now()}`, title: 'Un Preview', artist: 'Bad Bunny', duration: '02:44', genre: 'Reggaeton/Trap', requester: 'you' }
];
} else if (theme === 'workout') {
preset = [
{ id: `work-1-${Date.now()}`, title: 'Trap De La Calle', artist: 'Myke Towers', duration: '03:15', genre: 'Trap Latino', requester: 'shreed_33' },
{ id: `work-2-${Date.now()}`, title: 'La Jeepeta', artist: 'Anuel AA', duration: '03:45', genre: 'Trap Latino', requester: 'shreed_33' }
];
} else {
preset = [
{ id: `jazz-1-${Date.now()}`, title: 'Amargura', artist: 'KAROL G', duration: '02:50', genre: 'Reggaeton', requester: 'zesty_fans' },
{ id: `jazz-2-${Date.now()}`, title: 'Ella Baila Sola', artist: 'Eslabon Armado & Peso Pluma', duration: '02:44', genre: 'Regional Mexicano', requester: 'you' }
];
}
setSongQueue(prev => [...prev, ...preset]);
getSynth().playSynthPad();
};
// Soundboard instant click SFX triggers
const triggerSFX = (type: 'airhorn' | 'laser' | 'pad') => {
const synth = getSynth();
if (type === 'airhorn') synth.playAirhorn();
if (type === 'laser') synth.playLaser();
if (type === 'pad') synth.playSynthPad();
};
const handleToggleRainAmbient = () => {
const nextState = !isRainEnabled;
setIsRainEnabled(nextState);
getSynth().toggleRain(nextState);
};
// Submit Slash command in simulated terminal chat
const handleSendConsole = async (overrideMsg?: string) => {
const actualInput = overrideMsg || consoleInput;
if (!actualInput.trim()) return;
const userMessage: MockDiscordMessage = {
id: `discord-usr-${Date.now()}`,
author: { username: 'tú', avatar: '⚡', isBot: false, colorClass: 'text-amber-400 font-bold' },
content: actualInput,
timestamp: 'Hoy a las ' + new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})
};
setDiscordChat(prev => [...prev, userMessage]);
if (!overrideMsg) setConsoleInput('');
setIsConsoleGenerating(true);
const cmdClean = actualInput.toLowerCase().trim();
if (cmdClean.startsWith('/play ')) {
const targetQuery = actualInput.substring(6);
setTimeout(() => {
const newTrack: SongTrack = {
id: `cmd-added-${Date.now()}`,
title: targetQuery.charAt(0).toUpperCase() + targetQuery.slice(1),
artist: 'Artista Encontrado',
duration: '03:10',
genre: 'Vía Terminal',
requester: 'tú'
};
setSongQueue(prev => [...prev, newTrack]);
setDiscordChat(prev => [...prev, {
id: `discord-bot-${Date.now()}`,
author: { username: 'Glizh', avatar: '👑', isBot: true, colorClass: 'text-yellow-400 font-bold' },
timestamp: 'Hoy a las ' + new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
embed: {
title: `🔊 Añadido a la cola`,
description: `Se añadió **${newTrack.title}** de *${newTrack.artist}* en la posición **#${songQueue.length + 1}**`,
color: 'border-yellow-500',
fields: [
{ name: 'Tamaño de cola', value: `${songQueue.length + 1} canciones`, inline: true },
{ name: 'Reproducción aproximada', value: `En aprox ${(songQueue.length) * 3} mins`, inline: true }
],
footer: 'Glizh Music Engine | Activo'
}
}]);
setIsConsoleGenerating(false);
}, 500);
} else if (cmdClean === '/skip') {
setTimeout(() => {
handleSkipForward();
setDiscordChat(prev => [...prev, {
id: `discord-bot-${Date.now()}`,
author: { username: 'Glizh', avatar: '👑', isBot: true, colorClass: 'text-yellow-400 font-bold' },
timestamp: 'Hoy a las ' + new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
embed: {
title: '⏭️ Canción Saltada',
description: `Ahora suena: **${songQueue[(currentTrackIndex + 1) % songQueue.length]?.title || 'Ninguna'}**`,
color: 'border-amber-500'
}
}]);
setIsConsoleGenerating(false);
}, 400);
} else if (cmdClean === '/queue') {
setTimeout(() => {
setDiscordChat(prev => [...prev, {
id: `discord-bot-${Date.now()}`,
author: { username: 'Glizh', avatar: '👑', isBot: true, colorClass: 'text-yellow-400 font-bold' },
timestamp: 'Hoy a las ' + new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
embed: {
title: '📜 Lista de Canciones en Espera',
description: songQueue.map((t, i) => `${i === currentTrackIndex ? '➔ 🔊' : `• [${i+1}]`} **${t.title}** - *${t.artist}* (${t.duration})`).join('\n'),
color: 'border-yellow-500',
footer: 'Glizh Bot | Usa /skip para saltar'
}
}]);
setIsConsoleGenerating(false);
}, 400);
} else if (cmdClean === '/lyrics') {
setTimeout(() => {
setDiscordChat(prev => [...prev, {
id: `discord-bot-${Date.now()}`,
author: { username: 'Glizh', avatar: '👑', isBot: true, colorClass: 'text-yellow-400 font-bold' },
timestamp: 'Hoy a las ' + new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
embed: {
title: `🎵 Letra de: "${activeTrack?.title || 'Nada Sonando'}"`,
description: `*(Pre-loaded lyrics excerpt)*\n\n[00:12] Baby, te vi en Provenza...\n[00:25] Hace rato que no sé de ti...\n[00:44] Aunque ande con otra, no te olvido...\n\n(Sincronización de letra en vivo activa)`,
color: 'border-yellow-500'
}
}]);
setIsConsoleGenerating(false);
}, 550);
} else if (cmdClean.startsWith('/volume ')) {
const volVal = parseInt(cmdClean.substring(8)) || 100;
setVolume(Math.min(150, Math.max(0, volVal)));
setTimeout(() => {
setDiscordChat(prev => [...prev, {
id: `discord-bot-${Date.now()}`,
author: { username: 'Glizh', avatar: '👑', isBot: true, colorClass: 'text-yellow-400 font-bold' },
timestamp: 'Hoy a las ' + new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
embed: {
title: `🔊 Volumen Ajustado`,
description: `Se modificó la escala del volumen maestro a **${volVal}%**.`,
color: 'border-yellow-500'
}
}]);
setIsConsoleGenerating(false);
}, 400);
} else if (cmdClean.startsWith('/bassboost ')) {
const boostVal = cmdClean.substring(11).toUpperCase();
setTimeout(() => {
setDiscordChat(prev => [...prev, {
id: `discord-bot-${Date.now()}`,
author: { username: 'Glizh', avatar: '👑', isBot: true, colorClass: 'text-yellow-400 font-bold' },
timestamp: 'Hoy a las ' + new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
embed: {
title: `⚡ Filtro Bassboost Aplicado`,
description: `Umbral dinámico configurado a: **${boostVal}**`,
color: 'border-yellow-500'
}
}]);
setIsConsoleGenerating(false);
}, 400);
} else {
try {
const response = await fetch('/api/music/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: actualInput })
});
const data = await response.json();
if (data.error) throw new Error(data.error);
setDiscordChat(prev => [...prev, {
id: `discord-bot-${Date.now()}`,
author: { username: 'Glizh', avatar: '👑', isBot: true, colorClass: 'text-yellow-400 font-bold' },
timestamp: 'Hoy a las ' + new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
embed: {
title: 'Sala de Chat Interactiva Glizh',
description: data.text || '¡Alto y claro! El bot de música Glizh está listo.',
color: 'border-yellow-500'
}
}]);
} catch (err: any) {
setDiscordChat(prev => [...prev, {
id: `discord-bot-${Date.now()}`,
author: { username: 'Glizh', avatar: '👑', isBot: true, colorClass: 'text-yellow-400 font-bold' },
timestamp: 'Hoy a las ' + new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
content: `🎸 ¡Glizh está online! Prueba comandos como \`/play provenza\` o \`/queue\` en la consola del bot.`
}]);
} finally {
setIsConsoleGenerating(false);
}
}
};
// Submit Simulated Invite
const handleSimulateInviteSubmit = () => {
setInviteCompleted(true);
setTimeout(() => {
setShowInviteModal(false);
setGuildName(inviteServerName);
setInviteCompleted(false);
alert(`¡Éxito! Glizh Bot se ha enlazado al servidor de Discord "${inviteServerName}".`);
}, 1200);
};
// Assistant chatbot trigger
const handleSendAssistant = async () => {
if (!assistantInput.trim()) return;
const userText = assistantInput;
setAssistantHistory(prev => [...prev, { sender: 'user', text: userText }]);
setAssistantInput('');
setIsAssistantLoading(true);
try {
const response = await fetch('/api/music/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: userText })
});
const data = await response.json();
if (data.error) throw new Error(data.error);
setAssistantHistory(prev => [...prev, { sender: 'bot', text: data.text || 'He recibido tu solicitud.' }]);
} catch (e) {
setAssistantHistory(prev => [...prev, { sender: 'bot', text: '¡Hola! Estoy listo para sonar. Activa tu GEMINI_API_KEY para respuestas inteligentes.' }]);
} finally {
setIsAssistantLoading(false);
}
};
return (
<div className="flex flex-col min-h-screen w-full bg-[#070709] text-[#f4f4f5] font-sans antialiased overflow-x-hidden relative select-none">
{/* Dynamic Background Ambient Gold Glow */}
<div className="absolute top-[-10%] left-[25%] w-[500px] h-[500px] rounded-full bg-amber-500/5 glow-ambient pointer-events-none z-0" />
<div className="absolute bottom-[20%] right-[-10%] w-[450px] h-[450px] rounded-full bg-yellow-500/5 glow-ambient pointer-events-none z-0" />
{/* Website Top Header Navbar */}
<nav className="sticky top-0 w-full z-40 bg-[#070709]/85 backdrop-blur-md border-b border-white/5 px-4 sm:px-8 py-3.5 flex items-center justify-between">
<div className="flex items-center gap-2.5">
<div className="p-1.5 bg-gradient-to-tr from-amber-500 to-yellow-600 rounded-lg text-black shadow-lg shadow-amber-500/10 relative overflow-hidden">
<Music className="h-5 w-5 animate-pulse" />
<div className="absolute inset-0 bg-white/20 hover:scale-110 transition-transform" />
</div>
<div>
<div className="flex items-center gap-1.5">
<span className="font-extrabold tracking-wider text-transparent bg-clip-text bg-gradient-to-r from-amber-200 via-yellow-450 to-amber-500 text-lg sm:text-xl font-mono">
GLIZH
</span>
<span className="h-2 w-2 rounded-full bg-amber-500 animate-ping inline-block" />
</div>
<p className="text-[10px] uppercase tracking-widest text-[#a1a1aa] font-mono leading-none">
Premium Discord Sound
</p>
</div>
</div>
{/* Tab Selector Nav links */}
<div className="hidden md:flex items-center gap-1.5 px-1.5 py-1 rounded-full bg-white/5 border border-white/5">
<button
onClick={() => setActiveTab('home')}
className={`px-4 py-1.5 rounded-full text-xs font-semibold tracking-wide transition-all cursor-pointer ${
activeTab === 'home' ? 'bg-amber-500 text-black font-bold' : 'text-neutral-400 hover:text-white'
}`}
>
Inicio
</button>
<button
onClick={() => setActiveTab('dashboard')}
className={`px-4 py-1.5 rounded-full text-xs font-semibold tracking-wide transition-all cursor-pointer flex items-center gap-1 ${
activeTab === 'dashboard' ? 'bg-amber-500 text-black font-bold' : 'text-neutral-400 hover:text-white'
}`}
>
<AudioLines className="h-3.2 w-3.2" />
Reproductor en Vivo
</button>
<button
onClick={() => setActiveTab('console')}
className={`px-4 py-1.5 rounded-full text-xs font-semibold tracking-wide transition-all cursor-pointer flex items-center gap-1 ${
activeTab === 'console' ? 'bg-amber-500 text-black font-bold' : 'text-neutral-400 hover:text-white'
}`}
>
<Terminal className="h-3.2 w-3.2" />
Simulador de Chat
</button>
</div>
{/* Gateway Launch Controls */}
<div className="flex items-center gap-3">
<button
onClick={() => setShowInviteModal(true)}
className="px-4 py-1.5 text-xs font-bold rounded-lg shadow-lg bg-amber-500 hover:bg-amber-600 text-black transition-all cursor-pointer inline-flex items-center gap-1.5 gold-glow"
>
<Plus className="h-3.5 w-3.5" />
Agregar a Discord
</button>
<button
onClick={() => setActiveTab('dashboard')}
className="px-3.5 py-1.5 text-xs font-bold rounded-lg bg-neutral-900 hover:bg-neutral-800 text-[#f4f4f5] transition-all cursor-pointer border border-white/5 flex md:hidden items-center"
title="Reproductor en Vivo"
>
Reproductor
</button>
</div>
</nav>
{/* Main Container Content */}
<main className="flex-1 w-full flex flex-col z-10">
{/* ======================================= */}
{/* TAB 1: LANDING OVERVIEW PAGE */}
{/* ======================================= */}
{activeTab === 'home' && (
<div className="flex flex-col w-full max-w-7xl mx-auto px-4 sm:px-8 py-8 sm:py-16 space-y-16">
{/* Hero Splash Typography */}
<div className="text-center max-w-3xl mx-auto space-y-6">
<div className="inline-flex items-center gap-1.5 bg-amber-500/10 border border-amber-500/30 rounded-full px-3 py-1 text-xs text-amber-300 font-mono tracking-wide">
<Sparkles className="h-3 w-3 text-amber-400" />
Motor de Audio de Alta Fidelidad para Discord
</div>
<h1 className="text-4xl sm:text-6xl font-extrabold tracking-tight leading-tight text-white">
Música Sin Límites en <br className="hidden sm:inline" />
<span className="text-transparent bg-clip-text bg-gradient-to-r from-amber-300 via-yellow-450 to-amber-500 font-mono">
tu Servidor de Discord
</span>
</h1>
<p className="text-sm sm:text-base text-neutral-400 leading-relaxed max-w-2xl mx-auto">
Conecta a tu comunidad con <strong>Glizh</strong>. Transmisión estéreo sin pérdidas (320kbps), ecualizadores premium, filtros dinámicos, control inteligente por comandos y listas curadas por IA con Gemini.
</p>
<div className="flex flex-wrap items-center justify-center gap-4 pt-3">
<button
onClick={() => setShowInviteModal(true)}
className="px-8 py-3 bg-amber-500 hover:bg-amber-600 text-black font-bold text-sm rounded-xl transform hover:scale-[1.03] transition-all shadow-lg shadow-amber-500/25 cursor-pointer flex items-center gap-2 gold-glow"
>
<Plus className="h-4 w-4" />
Invitar a Glizh Bot
</button>
<button
onClick={() => setActiveTab('dashboard')}
className="px-8 py-3 bg-neutral-950 border border-white/5 hover:bg-neutral-900 text-[#efefe1] font-bold text-sm rounded-xl transition-all cursor-pointer flex items-center gap-2"
>
<AudioLines className="h-4 w-4 text-amber-400" />
Abrir Reproductor
</button>
<button
onClick={() => setActiveTab('console')}
className="px-5 py-3 bg-neutral-950 hover:bg-neutral-900 border border-white/5 text-neutral-400 hover:text-white font-mono text-xs rounded-xl transition-all cursor-pointer flex items-center gap-1.5"
>
<Terminal className="h-3.5 w-3.5 text-amber-400" />
Simulador de Comandos
</button>
</div>
</div>
{/* Immersive Preview Showcase */}
<div className="w-full rounded-2xl border border-white/5 p-4 sm:p-5 bg-gradient-to-b from-[#101012] to-[#08080a] shadow-2xl relative group overflow-hidden gold-border-glow">
<div className="absolute top-2 left-6 text-[10px] text-amber-500/60 font-mono tracking-widest flex items-center gap-1">
<span className="h-2 w-2 rounded-full bg-amber-500 animate-ping inline-block" />
VISTA PREVIA DEL REPRODUCTOR EN VIVO DE GLIZH
</div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-5 mt-4">
{/* Embedded Mini Player panel */}
<div className="col-span-1 lg:col-span-5 bg-black/40 border border-white/5 rounded-xl p-5 flex flex-col justify-between space-y-4">
<div className="flex items-center justify-between">
<span className="text-[10px] bg-amber-500/10 text-amber-300 font-mono py-0.5 px-2.5 rounded-full border border-amber-500/20 uppercase tracking-widest">
Sonando ahora
</span>
<div className="flex gap-1">
<span className="h-3.5 w-1 bg-amber-450 rounded-full eq-bar-1" />
<span className="h-3.5 w-1 bg-yellow-450 rounded-full eq-bar-2" />
<span className="h-3.5 w-1 bg-amber-500 rounded-full eq-bar-3" />
<span className="h-3.5 w-1 bg-yellow-500 rounded-full eq-bar-4" />
</div>
</div>
<div className="flex items-center gap-4">
<div className="w-14 h-14 rounded-full bg-gradient-to-tr from-amber-400 via-yellow-500 to-amber-600 flex items-center justify-center animate-spin" style={{ animationDuration: '8s' }}>
<Disc className="h-7 w-7 text-black/90" />
</div>
<div>
<h4 className="text-sm font-bold text-white tracking-wide">{activeTrack?.title || 'Provenza'}</h4>
<p className="text-xs text-neutral-400">{activeTrack?.artist || 'KAROL G'}</p>
<span className="text-[9px] uppercase font-mono tracking-wider text-neutral-500">Solicitado por: @cristophergamer</span>
</div>
</div>
<div className="space-y-1">
<div className="flex justify-between text-[10px] font-mono text-neutral-400">
<span>{formatTime(playProgress)}</span>
<span>{activeTrack?.duration || '03:27'}</span>
</div>
<div className="w-full bg-white/5 rounded-full h-1 relative overflow-hidden">
<div className="bg-amber-500 h-full rounded-full transition-all" style={{ width: '30%' }} />
</div>
</div>
<div className="flex justify-between items-center bg-white/2 p-2 px-3.5 rounded-lg border border-white/5">
<div className="flex items-center gap-1.5">
<Volume2 className="h-4 w-4 text-amber-400" />
<span className="text-[10px] font-mono">Vol: {volume}%</span>
</div>
<div className="h-4 w-px bg-white/10" />
<span className="text-[10px] font-mono uppercase text-amber-400">🟢 Link: Voice General</span>
</div>
</div>
{/* Embedded Mini Queue listing */}
<div className="col-span-1 lg:col-span-7 bg-black/40 border border-white/5 rounded-xl p-5 flex flex-col justify-between space-y-4">
<div>
<h4 className="text-xs font-semibold uppercase tracking-wider text-neutral-300 font-mono mb-2">Lista de reproducción entrante</h4>
<div className="space-y-1.5 max-h-[140px] overflow-y-auto pr-2">
{songQueue.slice(0, 3).map((t, index) => (
<div key={t.id} className="flex justify-between items-center text-xs p-2 rounded-lg bg-neutral-950 border border-white/5">
<div className="flex items-center gap-2">
<span className="text-[10px] uppercase font-mono text-amber-500">[{index + 1}]</span>
<span className="font-medium text-neutral-200 truncate">{t.title}</span>
</div>
<span className="text-[10px] font-mono text-neutral-500 whitespace-nowrap">{t.duration}</span>
</div>
))}
</div>
</div>
<div className="flex items-center justify-between border-t border-white/5 pt-3">
<span className="text-[10px] text-neutral-400">¿Quieres ver la cola completa o cambiar de canción?</span>
<button
onClick={() => setActiveTab('dashboard')}
className="text-xs text-black font-bold py-1 px-3 bg-amber-500 hover:bg-amber-600 rounded-md border border-amber-500/20 cursor-pointer inline-flex items-center gap-1"
>
Reproductor Activo
<ChevronRight className="h-3 w-3" />
</button>
</div>
</div>
</div>
</div>
{/* Simulated Live Statistics */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 sm:gap-6">
<div className="p-5 rounded-xl border border-white/5 bg-[#0e0e11] flex flex-col space-y-1 relative overflow-hidden group">
<div className="absolute top-0 left-0 w-1 h-full bg-amber-500 opacity-0 group-hover:opacity-100 transition-all" />
<span className="text-xs font-mono uppercase tracking-widest text-neutral-500">Servidores Activos</span>
<span className="text-2xl sm:text-3xl font-extrabold tracking-tight text-white">{botStats.guilds.toLocaleString()}</span>
<p className="text-[10px] text-neutral-400">Comunidades de Discord activas</p>
</div>
<div className="p-5 rounded-xl border border-white/5 bg-[#0e0e11] flex flex-col space-y-1 relative overflow-hidden group">
<div className="absolute top-0 left-0 w-1 h-full bg-yellow-500 opacity-0 group-hover:opacity-100 transition-all" />
<span className="text-xs font-mono uppercase tracking-widest text-neutral-500">Oyentes</span>
<span className="text-2xl sm:text-3xl font-extrabold tracking-tight text-white">{(botStats.users / 1000).toFixed(0)}k+</span>
<p className="text-[10px] text-neutral-400">Usuarios únicos en streaming hoy</p>
</div>
<div className="p-5 rounded-xl border border-white/5 bg-[#0e0e11] flex flex-col space-y-1 relative overflow-hidden group">
<div className="absolute top-0 left-0 w-1 h-full bg-amber-600 opacity-0 group-hover:opacity-100 transition-all" />
<span className="text-xs font-mono uppercase tracking-widest text-neutral-500">Latencia</span>
<span className="text-2xl sm:text-3xl font-extrabold tracking-tight text-amber-400">{botStats.ping}ms</span>
<p className="text-[10px] text-neutral-400">Excelente conexión y carga de buffer</p>
</div>
<div className="p-5 rounded-xl border border-white/5 bg-[#0e0e11] flex flex-col space-y-1 relative overflow-hidden group">
<div className="absolute top-0 left-0 w-1 h-full bg-yellow-600 opacity-0 group-hover:opacity-100 transition-all" />
<span className="text-xs font-mono uppercase tracking-widest text-neutral-500">Uptime</span>
<span className="text-2xl sm:text-3xl font-extrabold tracking-tight text-white">{botStats.uptime}</span>
<p className="text-[10px] text-neutral-400">Estabilidad garantizada 24/7</p>
</div>
</div>
{/* Highlight Feature Bento grid */}
<div className="space-y-6">
<div className="text-center">
<h3 className="text-xl sm:text-3xl font-bold tracking-tight text-white">Diseñado Para La Excelencia Auditiva</h3>
<p className="text-xs sm:text-sm text-neutral-400 max-w-lg mx-auto mt-1">Glizh elimina los saltos de audio y el lag para ofrecer una experiencia musical de primera clase.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="p-6 rounded-xl border border-white/5 bg-neutral-900/60 hover:bg-neutral-900 transition-all flex flex-col space-y-3">
<div className="p-2.5 bg-amber-500/10 border border-amber-500/20 rounded-lg text-amber-400 w-fit">
<AudioLines className="h-5 w-5" />
</div>
<h4 className="text-sm font-bold text-white tracking-wide">Ecualizador Premium & Bassboost</h4>
<p className="text-xs text-neutral-400 leading-relaxed">
Ajusta los bajos de forma masiva o aplica filtros dinámicos (Vaporwave, Nightcore, Lo-Fi) en tiempo real con comandos sencillos.
</p>
</div>
<div className="p-6 rounded-xl border border-white/5 bg-neutral-900/60 hover:bg-neutral-900 transition-all flex flex-col space-y-3">
<div className="p-2.5 bg-yellow-500/10 border border-yellow-500/20 rounded-lg text-yellow-405 w-fit">
<Zap className="h-5 w-5" />
</div>
<h4 className="text-sm font-bold text-white tracking-wide">Audio Lossless a 320kbps</h4>
<p className="text-xs text-neutral-400 leading-relaxed">
Transmisión de sonido sin pérdidas directamente a tus canales de voz, esquivando los filtros normales de compresión de Discord.
</p>
</div>
<div className="p-6 rounded-xl border border-white/5 bg-neutral-900/60 hover:bg-neutral-900 transition-all flex flex-col space-y-3">
<div className="p-2.5 bg-amber-600/10 border border-amber-600/20 rounded-lg text-amber-400 w-fit">
<Wand2 className="h-5 w-5" />
</div>
<h4 className="text-sm font-bold text-white tracking-wide">Co-Piloto Inteligente Gemini</h4>
<p className="text-xs text-neutral-400 leading-relaxed">
Usa lenguaje natural directamente en el simulador para que Gemini AI genere y ponga en cola playlists personalizadas según tu estado de ánimo.
</p>
</div>
</div>
</div>
{/* Immersive Guild Switch Mock Selector */}
<div className="p-6 rounded-xl bg-gradient-to-tr from-[#0f0e11] to-[#08080a] border border-white/5 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="space-y-1 text-center sm:text-left">
<span className="text-[10px] uppercase tracking-widest text-amber-500 font-mono font-bold block">Conexión del simulador</span>
<h4 className="text-lg font-bold text-white">🔊 Servidor enlazado: <span className="text-amber-300 font-mono italic">"{guildName}"</span></h4>
<p className="text-xs text-neutral-400">¿Quieres enlazar el reproductor virtual a otro servidor de tu propiedad?</p>
</div>
<div className="flex gap-2">
<button
onClick={() => setShowInviteModal(true)}
className="px-5 py-2 rounded-lg bg-neutral-800 hover:bg-neutral-700 text-xs font-bold text-[#f4f4f5] border border-white/5 transition-all cursor-pointer inline-flex items-center gap-1"
>
Configurar Servidores
</button>
<button
onClick={() => {
const next = prompt("Ingresa el nombre del nuevo servidor para simular conexión:", guildName);
if (next) setGuildName(next);
}}
className="px-5 py-2 rounded-lg bg-amber-500 hover:bg-amber-600 text-xs font-bold text-black transition-all cursor-pointer font-bold"
>
Cambiar Nombre
</button>
</div>
</div>
</div>
)}
{/* ======================================= */}
{/* TAB 2: INTERACTIVE WEB MUSIC DASHBOARD */}
{/* ======================================= */}
{activeTab === 'dashboard' && (
<div className="flex-1 w-full max-w-7xl mx-auto px-4 sm:px-8 py-6 grid grid-cols-1 lg:grid-cols-12 gap-6 relative">
{/* Left Column: Player controls & Active Visualizers */}
<div className="lg:col-span-7 flex flex-col space-y-6">
{/* High-Fi Music Master Player Card */}
<div className="rounded-2xl border border-white/5 p-6 bg-[#0e0e11] space-y-6 relative overflow-hidden gold-border-glow">
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-48 h-48 rounded-full bg-amber-500/5 blur-3xl pointer-events-none" />
<div className="flex items-center justify-between relative z-10">
<div className="flex items-center gap-2">
<span className="h-2 w-2 rounded-full bg-amber-500 animate-ping" />
<span className="text-[10px] font-mono uppercase tracking-widest text-neutral-400">
Canal de Voz Conectado #01
</span>
</div>
<div className="flex items-center gap-2">
<span className={`text-[9px] uppercase font-mono tracking-wider px-2 py-0.5 rounded border ${
bassboostMode === 'Off' ? 'bg-white/5 text-neutral-500 border-white/5' : 'bg-amber-500/10 text-amber-400 border-amber-500/20'
}`}>
Bajos: {bassboostMode}
</span>
<span className={`text-[9px] uppercase font-mono tracking-wider px-2 py-0.5 rounded border ${
activeFilter === 'None' ? 'bg-white/5 text-neutral-500 border-white/5' : 'bg-yellow-500/10 text-yellow-450 border-yellow-500/20'
}`}>
Filtro: {activeFilter}
</span>
</div>
</div>
{/* Disc and track core info */}
<div className="flex flex-col sm:flex-row items-center gap-6 relative z-10">
<div className="relative shrink-0">
<div className={`w-28 h-28 rounded-full bg-gradient-to-tr from-amber-400 via-yellow-500 to-amber-600 flex items-center justify-center shadow-lg border-4 border-black/40 ${
isPlaying ? 'animate-spin' : ''
}`} style={{ animationDuration: '6s' }}>
<Disc className="h-12 w-12 text-black/90" />
</div>
{isPlaying && (
<div className="absolute inset-0 rounded-full border-2 border-dashed border-amber-500/30 animate-ping pointer-events-none" style={{ animationDuration: '3s' }} />
)}
</div>
<div className="flex-1 text-center sm:text-left space-y-1 min-w-0">
<span className="text-[10px] uppercase font-mono bg-amber-500/10 py-0.5 px-2.5 rounded text-amber-300 border border-amber-500/20 w-fit block mx-auto sm:mx-0">
{activeTrack?.genre || 'Música Latina'}
</span>
<h3 className="text-xl sm:text-2xl font-black text-white tracking-wide truncate">
{activeTrack ? activeTrack.title : 'Glizh Desconectado'}
</h3>
<p className="text-sm text-neutral-400">
de <strong className="text-neutral-300 font-medium">{activeTrack ? activeTrack.artist : 'Carga pistas para empezar'}</strong>
</p>
{activeTrack?.requester && (
<p className="text-[10px] text-neutral-500 font-mono uppercase">
Solicitado por: <span className="text-neutral-400">@{activeTrack.requester}</span>
</p>
)}
</div>
</div>
{/* Timeline Progress Bar controls */}
<div className="space-y-2 relative z-10">
<div className="w-full relative">
<input
type="range"
min="0"
max={activeTrack ? (Number(activeTrack.duration.split(':')[0]) * 60 + Number(activeTrack.duration.split(':')[1])) : 200}
value={playProgress}
onChange={(e) => setPlayProgress(Number(e.target.value))}
className="w-full h-1 bg-white/5 rounded-lg appearance-none cursor-pointer accent-amber-500 outline-none"
/>
</div>
<div className="flex justify-between text-xs font-mono text-neutral-400">
<span>{formatTime(playProgress)}</span>
<span>{activeTrack ? activeTrack.duration : '00:00'}</span>
</div>
</div>
{/* Primary Music Controls Buttons Row */}
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-white/5 pt-5 relative z-10">
<div className="flex items-center gap-3">
<button
onClick={handleSkipBack}
className="p-2.5 rounded-lg bg-neutral-900 hover:bg-neutral-800 text-white transition-all cursor-pointer"
title="Canción anterior"
>
<SkipBack className="h-4.5 w-4.5 text-amber-450" />
</button>
<button
onClick={handleTogglePlay}
className="p-4 rounded-full bg-amber-500 hover:bg-amber-600 text-black shadow-md transform hover:scale-[1.05] transition-all cursor-pointer gold-glow"
title={isPlaying ? 'Pausar' : 'Reanudar'}
>
{isPlaying ? <Pause className="h-6 w-6" /> : <Play className="h-6 w-6" />}
</button>
<button
onClick={handleSkipForward}
className="p-2.5 rounded-lg bg-neutral-900 hover:bg-neutral-800 text-white transition-all cursor-pointer"
title="Siguiente canción"
>
<SkipForward className="h-4.5 w-4.5 text-amber-450" />
</button>
</div>
{/* Volume Slider scale controls */}
<div className="flex items-center gap-3 w-full sm:w-auto shrink-0 bg-white/3 p-2 px-3 rounded-lg border border-white/5">
<button
onClick={() => setVolume(prev => prev === 0 ? 90 : 0)}
className="text-neutral-400 hover:text-white"
>
{volume === 0 ? <VolumeX className="h-4 w-4" /> : volume < 50 ? <Volume1 className="h-4 w-4" /> : <Volume2 className="h-4 w-4" />}
</button>
<span className="text-xs font-mono text-neutral-300 w-8 text-right shrink-0">{volume}%</span>
<input
type="range"
min="0"
max="150"
value={volume}
onChange={(e) => setVolume(Number(e.target.value))}
className="w-24 h-1 bg-white/10 rounded-lg appearance-none cursor-pointer accent-amber-500 outline-none"
/>
</div>
</div>
{/* Animated EQ Bars Gold */}
<div className="flex items-center gap-1 absolute bottom-4 right-6 pointer-events-none opacity-40">
<span className={`w-1 bg-amber-500 rounded-full ${isPlaying ? 'h-5 eq-bar-1' : 'h-1 transition-all'}`} />
<span className={`w-1 bg-yellow-400 rounded-full ${isPlaying ? 'h-8 eq-bar-2' : 'h-1 transition-all'}`} style={{ animationDelay: '0.2s' }} />
<span className={`w-1 bg-amber-600 rounded-full ${isPlaying ? 'h-6 eq-bar-3' : 'h-1 transition-all'}`} style={{ animationDelay: '0.4s' }} />
<span className={`w-1 bg-yellow-500 rounded-full ${isPlaying ? 'h-7 eq-bar-4' : 'h-1 transition-all'}`} style={{ animationDelay: '0.1s' }} />
<span className={`w-1 bg-amber-400 rounded-full ${isPlaying ? 'h-4 eq-bar-1' : 'h-1 transition-all'}`} style={{ animationDelay: '0.5s' }} />
</div>
</div>
{/* Stateful EQ Filter Tuner deck */}
<div className="rounded-2xl border border-white/5 p-5 bg-[#0e0e11] space-y-4">
<div className="flex items-center justify-between">
<h4 className="text-xs font-bold font-mono tracking-widest text-[#a1a1aa] uppercase flex items-center gap-1.5">
<Sliders className="h-3.8 w-3.8 text-amber-400" />
Filtros y Ecualización DSP del Canal
</h4>
<span className="text-[10px] text-neutral-500">Ajustes instantáneos</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Bassboost tuner */}
<div className="p-3 bg-black/30 rounded-xl border border-white/5 space-y-2">
<span className="text-[10px] font-semibold text-neutral-400 uppercase tracking-wide block">Realce de Bajos (Equalizer)</span>
<div className="grid grid-cols-3 gap-1.5">
{(['Off', 'Bass+ Low', 'Glizh Heavy'] as const).map(b => (
<button
key={b}
onClick={() => {
setBassboostMode(b);
triggerSFX('pad');
}}
className={`py-1 text-[10px] font-mono rounded border capitalize transition-all cursor-pointer ${
bassboostMode === b ? 'bg-amber-500/10 text-amber-400 border-amber-500/20 font-bold' : 'bg-neutral-900 text-neutral-500 border-white/5 hover:text-white'
}`}
>
{b.split(' ').pop()}
</button>
))}
</div>
</div>
{/* High pass layout */}
<div className="p-3 bg-black/30 rounded-xl border border-white/5 space-y-2">
<span className="text-[10px] font-semibold text-neutral-400 uppercase tracking-wide block">Filtros Activos DSP</span>
<div className="grid grid-cols-4 gap-1">
{(['None', 'Nightcore', 'Vaporwave', 'Lo-Fi Chill'] as const).map(f => (
<button
key={f}
onClick={() => {
setActiveFilter(f);
triggerSFX('laser');
}}
className={`py-1 text-[9px] font-mono rounded border capitalize transition-all cursor-pointer truncate ${
activeFilter === f ? 'bg-yellow-500/10 text-yellow-450 border-yellow-500/20 font-bold' : 'bg-neutral-900 text-neutral-500 border-white/5 hover:text-white'
}`}
title={`Aplicar filtro ${f}`}
>
{f.split(' ')[0]}
</button>
))}
</div>
</div>
</div>
</div>
{/* Browser Interactive Soundboard (Plays actual sounds via Synthesizer API) */}
<div className="rounded-2xl border border-white/5 p-5 bg-[#0e0e11] space-y-4">
<div className="flex justify-between items-center">
<div>
<h4 className="text-xs font-bold font-mono tracking-widest text-[#a1a1aa] uppercase flex items-center gap-1.5">
<AudioLines className="h-3.8 w-3.8 text-amber-500" />
Efectos de Sonido Directos
</h4>
<p className="text-[10px] text-neutral-500 mt-1">Efectos sintetizados directamente en tu navegador</p>
</div>
<span className="text-[10px] px-2.5 py-0.5 rounded-full bg-amber-500/10 text-amber-300 font-mono uppercase text-right">Sin Latencia</span>
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<button
onClick={() => triggerSFX('airhorn')}
className="p-3.5 rounded-xl border border-amber-500/10 bg-amber-500/5 hover:bg-amber-500/10 hover:scale-[1.02] active:scale-[0.98] transition-all cursor-pointer flex flex-col items-center justify-center space-y-1"
>
<span className="text-xl">📢</span>
<span className="text-xs font-bold text-amber-300 font-mono">BOCINA (AIRHORN)</span>
</button>
<button
onClick={() => triggerSFX('laser')}
className="p-3.5 rounded-xl border border-yellow-500/10 bg-yellow-500/5 hover:bg-yellow-500/10 hover:scale-[1.02] active:scale-[0.98] transition-all cursor-pointer flex flex-col items-center justify-center space-y-1"
>
<span className="text-xl">👾</span>
<span className="text-xs font-bold text-yellow-450 font-mono">LASER CIBERNÉTICO</span>
</button>
<button
onClick={() => triggerSFX('pad')}
className="p-3.5 rounded-xl border border-amber-600/10 bg-amber-600/5 hover:bg-amber-600/10 hover:scale-[1.02] active:scale-[0.98] transition-all cursor-pointer flex flex-col items-center justify-center space-y-1"
>
<span className="text-xl">🎼</span>
<span className="text-xs font-bold text-amber-300 font-mono">CHORD SWEEP</span>
</button>
<button
onClick={handleToggleRainAmbient}
className={`p-3.5 rounded-xl border transition-all cursor-pointer flex flex-col items-center justify-center space-y-1 ${
isRainEnabled
? 'border-yellow-550 bg-yellow-500/10 hover:scale-[1.02] active:scale-[0.98]'
: 'border-yellow-500/10 bg-yellow-500/5 hover:bg-yellow-500/10 hover:scale-[1.02]'
}`}
>
<span className="text-xl">🌧️</span>
<span className="text-xs font-bold text-yellow-400 font-mono">{isRainEnabled ? 'PARAR LLUVIA' : 'LLUVIA AMBIENT'}</span>
</button>
</div>
</div>
</div>
{/* Right Column: Queue Manager, Manual Add, Gemini Recommender */}
<div className="lg:col-span-5 flex flex-col space-y-6">
{/* Queue List Panel */}
<div className="rounded-2xl border border-white/5 bg-[#0e0e11] flex flex-col max-h-[350px] sm:max-h-[380px] overflow-hidden">
<div className="p-4 border-b border-white/5 bg-[#070709] flex justify-between items-center shrink-0">
<div className="flex items-center gap-1.5">
<ListMusic className="h-4 w-4 text-amber-500" />
<h4 className="text-xs font-bold font-mono tracking-wider text-neutral-200 uppercase">
Canciones en la Cola ({songQueue.length})
</h4>
</div>
<button
onClick={() => {
if (window.confirm("¿Vaciar lista de reproducción?")) {
setSongQueue([songQueue[currentTrackIndex]]);
setCurrentTrackIndex(0);
}
}}
className="text-[10px] text-rose-450 hover:underline cursor-pointer"
>
Limpiar Cola
</button>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-2">
{songQueue.map((t, index) => {
const isActive = index === currentTrackIndex;
return (
<div
key={t.id}
onClick={() => {
setCurrentTrackIndex(index);
setPlayProgress(0);
}}
className={`p-3.5 rounded-xl border flex items-center justify-between gap-3 text-xs opacity-90 hover:opacity-100 cursor-pointer transition-all ${
isActive
? 'border-amber-500 bg-amber-500/10 text-white font-semibold'
: 'border-white/5 bg-black/20 hover:border-neutral-700'
}`}
>
<div className="flex items-center gap-2.5 min-w-0">
<span className="font-mono text-[10px] text-amber-500">
{isActive ? '🔊' : `[${index + 1}]`}
</span>
<div className="min-w-0">
<h5 className="text-neutral-200 truncate">{t.title}</h5>
<p className="text-[10px] text-neutral-400 truncate">de {t.artist}</p>
</div>
</div>
<div className="flex items-center gap-2 shrink-0">
{t.requester && (
<span className="text-[8px] bg-neutral-900 text-amber-400 font-mono px-1.5 py-0.5 rounded tracking-wide uppercase border border-amber-500/10">
@{t.requester}
</span>
)}
<span className="text-[10px] font-mono text-neutral-500">{t.duration}</span>
<button
onClick={(e) => {
e.stopPropagation();
handleRemoveTrack(t.id, index);
}}
className="p-1 rounded text-neutral-500 hover:text-rose-400 hover:bg-neutral-800"
title="Eliminar de la cola"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
</div>
</div>
);
})}
</div>
</div>
{/* Gemini AI Smart Playlist Spark (Suggest tracks) */}
<div className="rounded-2xl border border-white/5 p-5 bg-[#0e0e11] space-y-4">
<div className="space-y-1">
<h4 className="text-xs font-bold font-mono tracking-widest text-transparent bg-clip-text bg-gradient-to-r from-amber-300 via-yellow-450 to-amber-500 uppercase flex items-center gap-1.5">
<Wand2 className="h-4 w-4 text-amber-400 animate-pulse" />
Co-Piloto Inteligente Gemini
</h4>
<p className="text-[11px] text-neutral-400 leading-relaxed">
Escribe tu vibra o estilo musical en español (ej. "canciones de Karol G bailables" o "trap latino pesado") y deja que Gemini ponga temas realistas en la cola al instante.
</p>
</div>
<div className="space-y-3">
<div className="flex gap-2">
<input
type="text"
value={geminiSearchQuery}
onChange={(e) => setGeminiSearchQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleGeminiPlaylistSuggest()}
placeholder="Ej: reggaeton bailable del 2020..."
className="flex-1 bg-black/40 text-xs px-3 py-2.5 rounded-lg border border-white/5 outline-none focus:border-amber-500/50"
disabled={isAiSuggesting}
/>
<button
onClick={handleGeminiPlaylistSuggest}
disabled={isAiSuggesting || !geminiSearchQuery.trim()}
className="px-4 bg-amber-500 hover:bg-amber-600 text-black rounded-lg text-xs font-bold transition-all disabled:opacity-40 flex items-center gap-1 justify-center whitespace-nowrap cursor-pointer font-bold"
>
{isAiSuggesting ? 'Pensando...' : 'Generar Mix'}
</button>
</div>
{isAiSuggesting && (
<div className="flex items-center gap-2 p-2 bg-amber-500/5 border border-amber-500/10 rounded-lg text-[10px] text-amber-300 font-mono">
<span className="h-2 w-2 rounded-full bg-amber-450 animate-ping" />
<span>Generando lista de canciones mediante Gemini 3.5...</span>
</div>
)}
</div>
</div>
{/* Queue Preset Accelerators */}
<div className="rounded-2xl border border-white/5 p-4 bg-[#0e0e11] space-y-3">
<span className="text-[10px] font-mono uppercase tracking-widest text-neutral-400 block">
Paquetes de Estilo Musical Rápido
</span>
<div className="grid grid-cols-3 gap-2">
<button
onClick={() => loadPresetTheme('retro')}
className="p-2 rounded-lg border border-white/5 bg-neutral-900/60 text-amber-300 font-bold hover:text-white hover:border-amber-500 text-xs text-center cursor-pointer font-bold"
>
🔥 Reggaeton
</button>
<button
onClick={() => loadPresetTheme('workout')}
className="p-2 rounded-lg border border-white/5 bg-neutral-900/60 text-yellow-405 font-bold hover:text-white hover:border-yellow-500 text-xs text-center cursor-pointer font-bold"
>
💪 Trap Latino
</button>
<button
onClick={() => loadPresetTheme('jazz')}
className="p-2 rounded-lg border border-white/5 bg-neutral-900/60 text-amber-400 font-bold hover:text-white hover:border-amber-600 text-xs text-center cursor-pointer font-bold"
>
🎷 Bachatas/Clásicos
</button>
</div>
</div>
{/* Manual Add Item Form */}
<div className="rounded-2xl border border-white/5 p-4 bg-[#0e0e11] space-y-3">
<span className="text-[10px] font-mono uppercase tracking-widest text-neutral-400 block">
Agregar Canción Manualmente
</span>
<div className="grid grid-cols-2 gap-2">
<input
type="text"
value={manualTitle}
onChange={(e) => setManualTitle(e.target.value)}
placeholder="Título de la canción..."
className="bg-black/30 text-xs px-3 py-2 rounded-md border border-white/5 outline-none focus:border-amber-400 text-white"
/>
<input
type="text"
value={manualArtist}
onChange={(e) => setManualArtist(e.target.value)}
placeholder="Nombre del artista..."
className="bg-black/30 text-xs px-3 py-2 rounded-md border border-white/5 outline-none focus:border-amber-400 text-white"
/>
</div>
<button
onClick={handleAddManualTrack}
disabled={!manualTitle.trim()}
className="w-full py-1.5 text-xs text-black bg-amber-500 hover:bg-amber-600 font-bold rounded-lg cursor-pointer transition-all disabled:opacity-40"
>
Cargar a la Cola
</button>
</div>
</div>
</div>
)}
{/* ======================================= */}
{/* TAB 3: INTERACTIVE DISCORD CHAT CONSOLE */}
{/* ======================================= */}
{activeTab === 'console' && (
<div className="flex-1 w-full max-w-7xl mx-auto px-4 sm:px-8 py-6 grid grid-cols-1 lg:grid-cols-12 gap-6 items-stretch">
{/* Left Box: Active Command References */}
<div className="lg:col-span-4 rounded-xl border border-white/5 bg-[#0e0e11] p-5 flex flex-col space-y-4">
<div>
<h4 className="text-xs font-bold font-mono tracking-widest text-[#a1a1aa] uppercase flex items-center gap-1">
<Terminal className="h-4 w-4 text-amber-500" />
Referencia de Comandos
</h4>
<p className="text-[10px] text-neutral-500 mt-1">
Escríbelos en el simulador o haz clic en ellos para ejecutarlos automáticamente.
</p>
</div>
<div className="flex-1 overflow-y-auto space-y-2.5 pr-1">
{BOT_COMMANDS.map(cmd => (
<div
key={cmd.name}
onClick={() => {
setConsoleInput(cmd.syntax);
triggerSFX('laser');
}}
className="p-3 bg-neutral-950 hover:bg-neutral-900 border border-white/5 hover:border-amber-550 rounded-lg cursor-pointer transition-all space-y-1 block text-left"
title="Clic para pegar sintaxis"
>
<div className="flex justify-between items-center">
<span className="text-xs font-mono font-bold text-white">
/{cmd.name}
</span>
<span className="text-[8px] bg-amber-500/10 text-amber-300 font-mono uppercase px-1.5 rounded">
{cmd.category}
</span>
</div>
<p className="text-[11px] text-neutral-400 leading-relaxed">
{cmd.description}
</p>
<span className="text-[9px] font-mono text-neutral-500 block">
Sintaxis: <code className="text-neutral-300">{cmd.syntax}</code>
</span>
</div>
))}
</div>
</div>
{/* Right Box: Elegant Immersive Discord Text Client Simulator */}
<div className="lg:col-span-8 rounded-xl border border-white/5 bg-[#131416] flex flex-col h-[500px] lg:h-auto overflow-hidden">
{/* Discord Top Header simulation bar */}
<div className="p-3.5 bg-[#18191c] border-b border-black/80 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-xl text-neutral-500">#</span>
<div>
<h4 className="text-xs font-bold text-white tracking-wide font-mono">
glizh-chat-lounge
</h4>
<p className="text-[10px] text-neutral-400">
Canal de texto del simulador conectado a **"{guildName}"**
</p>
</div>
</div>
<div className="flex items-center gap-2 px-2.5 py-1 rounded bg-[#09090b]/40 text-[10px] font-mono text-amber-450 border border-white/5">
<span className="h-2 w-2 rounded-full bg-amber-500 inline-block animate-pulse" />
BOT EN: VOICE GENERAL
</div>
</div>
{/* Chat archives scroll panel */}
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-[#18191c] text-xs leading-relaxed select-text">
{discordChat.map((msg) => {
return (
<div key={msg.id} className="flex items-start gap-3 border-b border-white/2 pb-3 last:border-0">
<div className="w-9 h-9 rounded-full bg-neutral-800 flex items-center justify-center text-base shrink-0 select-none shadow">
{msg.author.avatar}
</div>
<div className="min-w-0 flex-1 space-y-1">
<div className="flex items-center gap-2">
<span className={msg.author.colorClass}>
{msg.author.username}
</span>
{msg.author.isBot && (
<span className="text-[8px] tracking-wider uppercase font-extrabold bg-amber-500 text-black px-1 relative -top-[1.2px] rounded-sm select-none">
BOT
</span>
)}
<span className="text-[9px] text-neutral-500">
{msg.timestamp}
</span>
</div>
{msg.content && (
<p className="text-neutral-300 leading-relaxed text-[12.5px] whitespace-pre-wrap select-text">
{msg.content}
</p>
)}
{/* Discord Embed design rendering */}
{msg.embed && (
<div className={`mt-2 p-3.5 rounded bg-[#0f0f12]/80 border-l-4 ${msg.embed.color || 'border-amber-500'} max-w-lg space-y-2 shadow-md relative group select-text`}>
{msg.embed.title && (
<h5 className="font-bold text-white text-[13px] tracking-wide">
{msg.embed.title}
</h5>
)}
{msg.embed.description && (
<p className="text-neutral-300 text-[11.5px] leading-relaxed whitespace-pre-wrap">
{msg.embed.description}
</p>
)}
{/* Embed Fields */}
{msg.embed.fields && msg.embed.fields.length > 0 && (
<div className="grid grid-cols-2 gap-3.5 pt-1.5 select-text">
{msg.embed.fields.map((f, i) => (
<div key={i} className="space-y-0.5">
<span className="text-[10px] text-neutral-450 font-bold uppercase tracking-wider block font-mono">
{f.name}
</span>
<span className="text-[11px] text-neutral-200">
{f.value}
</span>
</div>
))}
</div>
)}
{msg.embed.footer && (
<div className="border-t border-white/5 pt-2 mt-2 text-[8.5px] font-mono text-neutral-500 uppercase tracking-wider">
{msg.embed.footer}
</div>
)}
</div>
)}
</div>
</div>
);
})}
{/* Loading typing indicator */}
{isConsoleGenerating && (
<div className="flex items-center gap-2 text-[10px] font-mono text-neutral-400">
<span className="h-1.5 w-1.5 rounded-full bg-amber-400 animate-bounce" style={{ animationDelay: '0s' }} />
<span className="h-1.5 w-1.5 rounded-full bg-amber-400 animate-bounce" style={{ animationDelay: '0.2s' }} />
<span className="h-1.5 w-1.5 rounded-full bg-amber-400 animate-bounce" style={{ animationDelay: '0.4s' }} />
<span className="animate-pulse">Glizh está procesando el flujo de audio...</span>
</div>
)}
</div>
{/* Command preset rapid triggers bar */}
<div className="p-2 border-t border-[#101012] bg-[#1a1b1e] flex flex-wrap gap-1.5 shrink-0 select-none">
<span className="text-[9px] font-mono text-neutral-500 uppercase py-1 mr-1">
Acciones rápidas:
</span>
<button
onClick={() => handleSendConsole('/play provenza')}
className="px-2.5 py-1 rounded bg-[#2f3136] hover:bg-[#393c43] text-[10px] font-bold text-neutral-250 cursor-pointer text-left font-semibold text-white"
>
⚡ /play provenza
</button>
<button
onClick={() => handleSendConsole('/skip')}
className="px-2.5 py-1 rounded bg-[#2f3136] hover:bg-[#393c43] text-[10px] font-bold text-neutral-250 cursor-pointer text-left font-semibold text-white"
>
⏭️ /skip
</button>
<button
onClick={() => handleSendConsole('/queue')}
className="px-2.5 py-1 rounded bg-[#2f3136] hover:bg-[#393c43] text-[10px] font-bold text-neutral-250 cursor-pointer text-left font-semibold text-white"
>
📜 /queue
</button>
<button
onClick={() => handleSendConsole('/lyrics')}
className="px-2.5 py-1 rounded bg-[#2f3136] hover:bg-[#393c43] text-[10px] font-bold text-neutral-250 cursor-pointer text-left font-semibold text-white"
>
🎤 /lyrics
</button>
<button
onClick={() => handleSendConsole('/bassboost heavy')}
className="px-2.5 py-1 rounded bg-[#2f3136] hover:bg-[#393c43] text-[10px] font-bold text-neutral-250 cursor-pointer text-left font-mono text-white"
>
🔋 /bassboost heavy
</button>
</div>
{/* AI helper questions presets */}
<div className="px-3.5 py-2 border-t border-white/5 bg-[#141517] flex flex-wrap gap-1.5 shrink-0 select-none items-center">
<span className="text-[9px] font-mono text-amber-400 font-bold uppercase tracking-wider flex items-center gap-1 mr-1">
<Sparkles className="h-3 w-3 text-amber-400" />
Preguntas sugeridas:
</span>
<button
onClick={() => handleSendConsole('¿Qué filtros de audio puedo aplicar en el canal de voz?')}
className="px-2 py-1 rounded bg-amber-500/10 hover:bg-amber-500/20 text-amber-300 text-[10px] font-medium border border-amber-500/20 cursor-pointer transition-all"
>
✨ "¿Qué filtros puedo aplicar?"
</button>
<button
onClick={() => handleSendConsole('¿Cómo configuro la rotación de bloque IPv6 en Lavalink?')}
className="px-2 py-1 rounded bg-amber-500/10 hover:bg-amber-500/20 text-amber-300 text-[10px] font-medium border border-amber-500/20 cursor-pointer transition-all"
>
📡 "Configurar IPv6 en Lavalink?"
</button>
<button
onClick={() => handleSendConsole('¿Por qué tengo problemas de SocketTimeoutException con YouTube?')}
className="px-2 py-1 rounded bg-amber-500/10 hover:bg-amber-500/20 text-amber-300 text-[10px] font-medium border border-amber-500/20 cursor-pointer transition-all"
>
🔗 "Resolver errores de YouTube?"
</button>
</div>
{/* Terminal simulated Input box */}
<div className="p-4 bg-[#1e1f22] shrink-0">
<div className="flex gap-2">
<div className="flex-1 bg-[#313338] text-xs px-4 py-3 rounded-lg flex items-center justify-between border border-transparent focus-within:border-amber-500/30">
<input
type="text"
value={consoleInput}
onChange={(e) => setConsoleInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSendConsole()}
placeholder={`Enviar mensaje a #glizh-chat-lounge (o consulta al bot)...`}
className="w-full bg-transparent border-none text-[#dbdee1] outline-none placeholder-neutral-500 text-xs"
/>
</div>
<button
onClick={() => handleSendConsole()}
disabled={!consoleInput.trim() || isConsoleGenerating}
className="px-4 bg-amber-500 hover:bg-amber-600 font-bold text-xs text-black rounded-lg flex items-center justify-center transition-all cursor-pointer disabled:opacity-40 font-bold"
>
<Send className="h-4 w-4" />
</button>
</div>
</div>
</div>
</div>
)}
</main>
{/* ======================================= */}
{/* IMMERSIVE GATEWAY AUTHORIZATION DIALOG */}
{/* ======================================= */}
{showInviteModal && (
<div className="fixed inset-0 bg-black/85 backdrop-blur-sm flex items-center justify-center p-4 z-50">
<div className="w-full max-w-md rounded-xl bg-[#1e1f22] text-[#f4f4f5] border border-white/5 overflow-hidden shadow-2xl relative gold-border-glow">
{/* Top auth aesthetic header strip */}
<div className="p-5 text-center bg-gradient-to-r from-amber-600 to-yellow-600 relative">
<button
onClick={() => setShowInviteModal(false)}
className="absolute top-4 right-4 p-1 rounded-md text-black/50 hover:text-black"
>
<LogOut className="h-4.5 w-4.5" />
</button>
<div className="w-14 h-14 rounded-full bg-black/60 mx-auto flex items-center justify-center text-2xl border-2 border-white/20 select-none shadow shadow-amber-500/10">
👑
</div>
<h3 className="text-sm font-bold uppercase tracking-wider text-black mt-2 font-bold">
Glizh Bot Shard Gateway
</h3>
<p className="text-[10px] text-neutral-800 font-medium">
Autoriza la conexión de Glizh a tus servidores
</p>
</div>
{/* Simulated authorization checklists */}
<div className="p-6 space-y-4">
<div className="rounded-lg bg-[#2b2d31] p-3 text-xs space-y-2">
<span className="text-[9px] font-mono text-[#a1a1aa] uppercase font-bold block">
Seleccionar Servidor de Destino
</span>
<input
type="text"
value={inviteServerName}
onChange={(e) => setInviteServerName(e.target.value)}
placeholder="e.g. Mi Servidor Dorado"
className="w-full bg-[#1e1f22] border border-white/5 p-2 px-3 text-xs text-[#efefe1] outline-none rounded focus:border-amber-500"
/>
</div>
<div className="space-y-2.5 text-xs">
<span className="text-[9px] font-mono text-[#a1a1aa] font-bold uppercase block">
Permisos Requeridos
</span>
<div className="space-y-2 divide-y divide-white/5">
<div className="flex items-center gap-2 pt-2 first:pt-0">
<ShieldCheck className="h-4 w-4 text-amber-400 shrink-0" />
<div>
<span className="font-bold text-white block">Crear comandos de barra /</span>
<p className="text-[10px] text-neutral-400">Permite controlar colas de reproducción desde texto</p>
</div>
</div>
<div className="flex items-center gap-2 pt-2">
<CheckCircle className="h-4 w-4 text-amber-400 shrink-0" />
<div>
<span className="font-bold text-white block">Unirse a canales de voz</span>
<p className="text-[10px] text-neutral-400">Transmisión lossless directa de 320kbps</p>
</div>
</div>
<div className="flex items-center gap-2 pt-2">
<CheckCircle className="h-4 w-4 text-amber-400 shrink-0" />
<div>
<span className="font-bold text-white block">Ecualización de audio</span>
<p className="text-[10px] text-neutral-400">Modifica filtros graves o efectos Nightcore en vivo</p>
</div>
</div>
</div>
</div>
{/* Submit simulation */}
<div className="pt-4 flex gap-3">
<button
onClick={() => setShowInviteModal(false)}
className="flex-1 py-2.5 rounded-lg bg-neutral-800 hover:bg-neutral-750 font-bold text-xs text-[#a1a1aa] hover:text-white"
>
Cancelar
</button>
<button
onClick={handleSimulateInviteSubmit}
disabled={inviteCompleted}
className="flex-1 py-2.5 rounded-lg bg-amber-500 hover:bg-amber-600 font-bold text-xs text-black"
>
{inviteCompleted ? 'Enlazando Shards...' : 'Autorizar Enlace'}
</button>
</div>
</div>
</div>
</div>
)}
{/* Botón flotante del Asistente de IA Glizh */}
<div className="fixed bottom-6 right-6 z-50 flex flex-col items-end">
{isAssistantOpen && (
<div className="mb-4 w-80 sm:w-96 h-[450px] bg-[#0c0c0e]/95 backdrop-blur-md rounded-2xl border border-amber-500/35 shadow-2xl flex flex-col overflow-hidden gold-glow animate-in fade-in slide-in-from-bottom-5 duration-200">
{/* Cabecera */}
<div className="p-4 bg-gradient-to-r from-amber-500 to-yellow-600 flex justify-between items-center text-black shrink-0 font-bold">
<div className="flex items-center gap-2">
<div className="p-1.5 bg-black/10 rounded-lg">
<Sparkles className="h-4 w-4 text-black animate-pulse" />
</div>
<div>
<h4 className="font-bold text-xs uppercase tracking-wider text-black">Asistente Glizh AI</h4>
<span className="text-[9px] text-neutral-800 flex items-center gap-1 font-semibold">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500 animate-ping inline-block" /> En línea
</span>
</div>
</div>
<button
onClick={() => setIsAssistantOpen(false)}
className="p-1 rounded-md hover:bg-black/10 text-black transition-all cursor-pointer"
>
<X className="h-4 w-4" />
</button>
</div>
{/* Mensajes */}
<div className="flex-1 overflow-y-auto p-4 space-y-3">
{assistantHistory.map((msg, index) => (
<div
key={index}
className={`flex ${msg.sender === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[80%] rounded-xl p-3 text-xs leading-relaxed ${
msg.sender === 'user'
? 'bg-amber-500 text-black font-semibold rounded-tr-none'
: 'bg-neutral-900 text-neutral-200 rounded-tl-none border border-white/5'
}`}
>
{msg.text}
</div>
</div>
))}
{isAssistantLoading && (
<div className="flex justify-start">
<div className="bg-neutral-900 border border-white/5 rounded-xl rounded-tl-none p-3 text-xs flex items-center gap-1.5 text-neutral-400">
<span className="h-1.5 w-1.5 rounded-full bg-amber-450 animate-bounce" style={{ animationDelay: '0s' }} />
<span className="h-1.5 w-1.5 rounded-full bg-amber-450 animate-bounce" style={{ animationDelay: '0.2s' }} />
<span className="h-1.5 w-1.5 rounded-full bg-amber-450 animate-bounce" style={{ animationDelay: '0.4s' }} />
<span>Glizh está pensando...</span>
</div>
</div>
)}
<div ref={assistantEndRef} />
</div>
{/* Input */}
<div className="p-3 bg-[#121214] border-t border-white/5 shrink-0">
<div className="flex gap-2">
<input
type="text"
value={assistantInput}
onChange={(e) => setAssistantInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSendAssistant()}
placeholder="Pregúntale a Glizh AI..."
className="flex-1 bg-black/40 text-xs px-3 py-2.5 rounded-lg border border-white/5 outline-none focus:border-amber-500/50 text-white placeholder-neutral-500"
/>
<button
onClick={handleSendAssistant}
disabled={!assistantInput.trim() || isAssistantLoading}
className="p-2.5 bg-amber-500 hover:bg-amber-600 disabled:opacity-40 text-black rounded-lg transition-all cursor-pointer flex items-center justify-center shrink-0"
>
<Send className="h-3.5 w-3.5 text-black font-bold" />
</button>
</div>
</div>
</div>
)}
<button
onClick={() => setIsAssistantOpen(!isAssistantOpen)}
className="p-3.5 rounded-full bg-gradient-to-tr from-amber-500 to-yellow-600 hover:from-amber-600 hover:to-yellow-700 text-black shadow-lg hover:shadow-amber-500/20 transform hover:scale-105 active:scale-95 transition-all cursor-pointer flex items-center justify-center relative gold-glow"
>
{isAssistantOpen ? <X className="h-6 w-6 text-black font-bold" /> : <MessageSquare className="h-6 w-6 text-black font-bold" />}
{!isAssistantOpen && (
<span className="absolute -top-1 -right-1 flex h-3 w-3">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-3 w-3 bg-amber-500"></span>
</span>
)}
</button>
</div>
{/* Futuristic clean footer */}
<footer className="w-full py-8 border-t border-white/5 bg-[#070709] text-center text-xs text-neutral-500 font-mono tracking-wide">
<div className="max-w-7xl mx-auto px-4 space-y-2">
<div className="flex items-center justify-center gap-2 text-neutral-400">
<span>Glizh Music Inc</span>
<span></span>
<span className="text-amber-500 font-semibold">Socio Certificado de Discord</span>
</div>
<p className="text-[10px] text-neutral-600">
Esta página de inicio y simulador del bot está enlazada con Gemini 3.5.
</p>
</div>
</footer>
</div>
);
}