Actualización a pagina estática
This commit is contained in:
525
index.html
525
index.html
@@ -1,265 +1,316 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { User, MessageSquare, Palette, Smartphone, X, ChevronRight, ExternalLink } from 'lucide-react';
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>Telegram Mini App</title>
|
||||
|
||||
// --- COMPONENTE PRINCIPAL ---
|
||||
export default function App() {
|
||||
const [userData, setUserData] = useState(null);
|
||||
const [platformData, setPlatformData] = useState(null);
|
||||
const [themeData, setThemeData] = useState(null);
|
||||
const [chatData, setChatData] = useState(null);
|
||||
<!-- 1. Cargar Tailwind CSS (Estilos modernos) -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
// Estado para el Modal
|
||||
const [selectedCategory, setSelectedCategory] = useState(null);
|
||||
<!-- 2. Cargar React y ReactDOM -->
|
||||
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||
|
||||
useEffect(() => {
|
||||
const tg = window.Telegram?.WebApp;
|
||||
<!-- 3. Cargar Babel (Para que el navegador entienda JSX) -->
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
|
||||
if (tg && tg.initDataUnsafe && Object.keys(tg.initDataUnsafe).length > 0) {
|
||||
// --- ENTORNO REAL DE TELEGRAM ---
|
||||
tg.ready();
|
||||
tg.expand();
|
||||
<!-- 4. Script de Telegram Web App -->
|
||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||
|
||||
// Configurar colores de la barra de estado de Telegram
|
||||
if (tg.setHeaderColor) tg.setHeaderColor('#3d0091');
|
||||
if (tg.setBackgroundColor) tg.setBackgroundColor('#121212');
|
||||
<style>
|
||||
/* Ajustes finos para scrollbar y selección */
|
||||
body {
|
||||
background-color: #121212;
|
||||
overscroll-behavior: none; /* Evita rebote en iOS */
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1e1e1e;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #3d0091;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
setUserData(tg.initDataUnsafe.user);
|
||||
setChatData({
|
||||
start_param: tg.initDataUnsafe.start_param || 'N/A',
|
||||
chat_type: tg.initDataUnsafe.chat_type || 'private',
|
||||
chat_instance: tg.initDataUnsafe.chat_instance || 'N/A',
|
||||
});
|
||||
setThemeData(tg.themeParams);
|
||||
setPlatformData({
|
||||
platform: tg.platform,
|
||||
version: tg.version,
|
||||
viewportHeight: tg.viewportHeight,
|
||||
colorScheme: tg.colorScheme
|
||||
});
|
||||
<!-- CÓDIGO DE LA APLICACIÓN -->
|
||||
<script type="text/babel">
|
||||
const { useState, useEffect } = React;
|
||||
|
||||
} else {
|
||||
// --- MODO DESARROLLO (MOCK DATA) ---
|
||||
console.log("Modo desarrollo: Cargando datos Mock");
|
||||
setUserData({
|
||||
id: 987654321,
|
||||
first_name: "Dev",
|
||||
last_name: "Tester",
|
||||
username: "dev_tester",
|
||||
language_code: "en",
|
||||
is_premium: false,
|
||||
photo_url: null
|
||||
});
|
||||
setChatData({
|
||||
start_param: "debug_mode_on",
|
||||
chat_type: "supergroup",
|
||||
chat_instance: "1122334455"
|
||||
});
|
||||
setChatData({
|
||||
start_param: "referral_source_ads",
|
||||
chat_type: "private",
|
||||
chat_instance: "84758473829102"
|
||||
});
|
||||
setThemeData({
|
||||
bg_color: "#121212",
|
||||
text_color: "#ffffff",
|
||||
hint_color: "#999999",
|
||||
link_color: "#7c4dff",
|
||||
button_color: "#3d0091",
|
||||
button_text_color: "#ffffff"
|
||||
});
|
||||
setPlatformData({
|
||||
platform: "ios",
|
||||
version: "7.0",
|
||||
viewportHeight: 640,
|
||||
colorScheme: "dark"
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
// --- ICONOS SVG (Reemplazo de Lucide para no usar imports) ---
|
||||
const IconUser = ({ className }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||||
);
|
||||
const IconChat = ({ className }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
);
|
||||
const IconPalette = ({ className }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><circle cx="13.5" cy="6.5" r=".5"/><circle cx="17.5" cy="10.5" r=".5"/><circle cx="8.5" cy="7.5" r=".5"/><circle cx="6.5" cy="12.5" r=".5"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/></svg>
|
||||
);
|
||||
const IconSystem = ({ className }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><rect width="14" height="20" x="5" y="2" rx="2" ry="2"/><path d="M12 18h.01"/></svg>
|
||||
);
|
||||
const IconX = ({ className }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
);
|
||||
const IconChevron = ({ className }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="m9 18 6-6-6-6"/></svg>
|
||||
);
|
||||
const IconExternal = ({ className }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
||||
);
|
||||
|
||||
// Función para cerrar el modal
|
||||
const closeModal = () => setSelectedCategory(null);
|
||||
// --- COMPONENTE PRINCIPAL ---
|
||||
function App() {
|
||||
const [userData, setUserData] = useState(null);
|
||||
const [platformData, setPlatformData] = useState(null);
|
||||
const [themeData, setThemeData] = useState(null);
|
||||
const [chatData, setChatData] = useState(null);
|
||||
const [selectedCategory, setSelectedCategory] = useState(null);
|
||||
|
||||
// Renderizado de lista de detalles
|
||||
const renderDetails = (data) => {
|
||||
if (!data) return <p className="text-gray-400">Sin datos disponibles.</p>;
|
||||
return Object.entries(data).map(([key, value]) => (
|
||||
<div key={key} className="flex flex-col py-3 border-b border-white/10 last:border-0">
|
||||
<span className="text-xs font-semibold text-purple-300 uppercase tracking-wider mb-1">
|
||||
{key.replace(/_/g, ' ')}
|
||||
</span>
|
||||
<span className="text-white text-sm break-all font-mono bg-black/20 p-2 rounded">
|
||||
{value !== null && value !== undefined ? value.toString() : 'N/A'}
|
||||
</span>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
useEffect(() => {
|
||||
const tg = window.Telegram?.WebApp;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#121212] text-white font-sans selection:bg-purple-500/30">
|
||||
// Verificar si initDataUnsafe tiene datos reales (más fiable que solo checkear el objeto)
|
||||
const isTelegram = tg && tg.initDataUnsafe && Object.keys(tg.initDataUnsafe).length > 0 && tg.initDataUnsafe.user;
|
||||
|
||||
{/* --- HEADER CURVO --- */}
|
||||
<div className="relative bg-[#3d0091] pt-8 pb-12 rounded-b-[40px] shadow-2xl shadow-purple-900/20">
|
||||
<div className="flex flex-col items-center justify-center px-4">
|
||||
{/* Avatar con anillo animado */}
|
||||
<div className="relative group cursor-pointer">
|
||||
<div className="absolute -inset-1 bg-gradient-to-r from-pink-500 to-purple-600 rounded-full opacity-75 group-hover:opacity-100 blur transition duration-200"></div>
|
||||
<div className="relative w-24 h-24 rounded-full bg-[#1e1e1e] border-4 border-[#3d0091] flex items-center justify-center overflow-hidden">
|
||||
{userData?.photo_url ? (
|
||||
<img src={userData.photo_url} alt="User" className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<span className="text-3xl font-bold text-white">
|
||||
{userData?.first_name ? userData.first_name[0] : 'U'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
if (isTelegram) {
|
||||
// --- ENTORNO REAL DE TELEGRAM ---
|
||||
tg.ready();
|
||||
tg.expand();
|
||||
|
||||
<h1 className="mt-4 text-2xl font-bold tracking-tight text-white">
|
||||
Hola, {userData?.first_name || 'Usuario'}
|
||||
</h1>
|
||||
// Configurar colores de la barra de estado
|
||||
if (tg.setHeaderColor) tg.setHeaderColor('#3d0091');
|
||||
if (tg.setBackgroundColor) tg.setBackgroundColor('#121212');
|
||||
|
||||
{userData?.username && (
|
||||
<div className="mt-2 px-3 py-1 bg-white/10 backdrop-blur-md rounded-full border border-white/10 text-sm font-medium text-purple-200">
|
||||
@{userData.username}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
setUserData(tg.initDataUnsafe.user);
|
||||
setChatData({
|
||||
start_param: tg.initDataUnsafe.start_param || 'N/A',
|
||||
chat_type: tg.initDataUnsafe.chat_type || 'private',
|
||||
chat_instance: tg.initDataUnsafe.chat_instance || 'N/A',
|
||||
});
|
||||
setThemeData(tg.themeParams);
|
||||
setPlatformData({
|
||||
platform: tg.platform,
|
||||
version: tg.version,
|
||||
viewportHeight: tg.viewportHeight,
|
||||
colorScheme: tg.colorScheme
|
||||
});
|
||||
|
||||
{/* --- CONTENIDO PRINCIPAL --- */}
|
||||
<div className="px-6 -mt-6 pb-10 relative z-10">
|
||||
<div className="text-center mb-4 opacity-75 text-xs font-medium uppercase tracking-widest text-purple-200">
|
||||
Panel de Control
|
||||
</div>
|
||||
} else {
|
||||
// --- MODO DESARROLLO (MOCK DATA) ---
|
||||
console.log("Modo Web/Dev: Cargando datos falsos");
|
||||
setUserData({
|
||||
id: 999888777,
|
||||
first_name: "Dev",
|
||||
last_name: "Tester",
|
||||
username: "dev_tester",
|
||||
language_code: "es",
|
||||
is_premium: true,
|
||||
photo_url: null
|
||||
});
|
||||
setChatData({
|
||||
start_param: "debug_mode",
|
||||
chat_type: "supergroup",
|
||||
chat_instance: "abc-123-xyz"
|
||||
});
|
||||
setThemeData({
|
||||
bg_color: "#121212",
|
||||
text_color: "#ffffff",
|
||||
hint_color: "#999999",
|
||||
link_color: "#7c4dff",
|
||||
button_color: "#3d0091",
|
||||
button_text_color: "#ffffff"
|
||||
});
|
||||
setPlatformData({
|
||||
platform: "tdesktop",
|
||||
version: "8.0",
|
||||
viewportHeight: 800,
|
||||
colorScheme: "dark"
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
{/* GRID 2x2 - USO DE TAILWIND GRID QUE ES MUCHO MÁS ROBUSTO */}
|
||||
<div className="grid grid-cols-2 gap-4 w-full max-w-md mx-auto">
|
||||
const closeModal = () => setSelectedCategory(null);
|
||||
|
||||
{/* TARJETA 1: USUARIO */}
|
||||
<CardButton
|
||||
icon={<User size={32} className="text-purple-400" />}
|
||||
label="USUARIO"
|
||||
color="bg-purple-500/10"
|
||||
borderColor="border-purple-500/20"
|
||||
onClick={() => setSelectedCategory({ title: 'Datos de Usuario', data: userData, icon: <User /> })}
|
||||
/>
|
||||
const renderDetails = (data) => {
|
||||
if (!data) return <p className="text-gray-400">Sin datos disponibles.</p>;
|
||||
return Object.entries(data).map(([key, value]) => (
|
||||
<div key={key} className="flex flex-col py-3 border-b border-white/10 last:border-0">
|
||||
<span className="text-xs font-semibold text-purple-300 uppercase tracking-wider mb-1">
|
||||
{key.replace(/_/g, ' ')}
|
||||
</span>
|
||||
<span className="text-white text-sm break-all font-mono bg-black/20 p-2 rounded border border-white/5">
|
||||
{value !== null && value !== undefined ? value.toString() : 'N/A'}
|
||||
</span>
|
||||
</div>
|
||||
));
|
||||
};
|
||||
|
||||
{/* TARJETA 2: CHAT */}
|
||||
<CardButton
|
||||
icon={<MessageSquare size={32} className="text-pink-400" />}
|
||||
label="CHAT"
|
||||
color="bg-pink-500/10"
|
||||
borderColor="border-pink-500/20"
|
||||
onClick={() => setSelectedCategory({ title: 'Contexto Chat', data: chatData, icon: <MessageSquare /> })}
|
||||
/>
|
||||
return (
|
||||
<div className="min-h-screen bg-[#121212] text-white font-sans selection:bg-purple-500/30 pb-10">
|
||||
|
||||
{/* TARJETA 3: TEMA */}
|
||||
<CardButton
|
||||
icon={<Palette size={32} className="text-emerald-400" />}
|
||||
label="TEMA"
|
||||
color="bg-emerald-500/10"
|
||||
borderColor="border-emerald-500/20"
|
||||
onClick={() => setSelectedCategory({ title: 'Apariencia', data: themeData, icon: <Palette /> })}
|
||||
/>
|
||||
{/* HEADER */}
|
||||
<div className="relative bg-[#3d0091] pt-8 pb-12 rounded-b-[40px] shadow-2xl shadow-purple-900/20">
|
||||
<div className="flex flex-col items-center justify-center px-4">
|
||||
<div className="relative group cursor-pointer">
|
||||
<div className="absolute -inset-1 bg-gradient-to-r from-pink-500 to-purple-600 rounded-full opacity-75 blur transition duration-200"></div>
|
||||
<div className="relative w-24 h-24 rounded-full bg-[#1e1e1e] border-4 border-[#3d0091] flex items-center justify-center overflow-hidden">
|
||||
{userData?.photo_url ? (
|
||||
<img src={userData.photo_url} alt="User" className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<span className="text-3xl font-bold text-white">
|
||||
{userData?.first_name ? userData.first_name[0] : 'U'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TARJETA 4: SISTEMA */}
|
||||
<CardButton
|
||||
icon={<Smartphone size={32} className="text-orange-400" />}
|
||||
label="SISTEMA"
|
||||
color="bg-orange-500/10"
|
||||
borderColor="border-orange-500/20"
|
||||
onClick={() => setSelectedCategory({ title: 'Dispositivo', data: platformData, icon: <Smartphone /> })}
|
||||
/>
|
||||
<h1 className="mt-4 text-2xl font-bold tracking-tight text-white">
|
||||
Hola, {userData?.first_name || 'Usuario'}
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
{userData?.username && (
|
||||
<div className="mt-2 px-3 py-1 bg-white/10 backdrop-blur-md rounded-full border border-white/10 text-sm font-medium text-purple-200">
|
||||
@{userData.username}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Botón de documentación */}
|
||||
<div className="mt-8 text-center">
|
||||
<button
|
||||
onClick={() => window.open('https://core.telegram.org/bots/webapps', '_blank')}
|
||||
className="inline-flex items-center gap-2 text-sm text-gray-500 hover:text-purple-400 transition-colors"
|
||||
>
|
||||
<span>Documentación API</span>
|
||||
<ExternalLink size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* CONTENIDO PRINCIPAL */}
|
||||
<div className="px-6 -mt-6 relative z-10">
|
||||
<div className="text-center mb-4 opacity-75 text-xs font-medium uppercase tracking-widest text-purple-200 drop-shadow-md">
|
||||
Panel de Control
|
||||
</div>
|
||||
|
||||
{/* --- MODAL PERSONALIZADO (Sin librerías pesadas) --- */}
|
||||
{selectedCategory && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 animate-in fade-in duration-200">
|
||||
{/* Overlay oscuro */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
||||
onClick={closeModal}
|
||||
></div>
|
||||
{/* GRID 2x2 - USO DE TAILWIND GRID PURO */}
|
||||
<div className="grid grid-cols-2 gap-4 w-full max-w-md mx-auto">
|
||||
|
||||
{/* Contenido del Modal */}
|
||||
<div className="relative w-full max-w-sm bg-[#1e1e1e] rounded-2xl shadow-2xl border border-white/10 overflow-hidden transform transition-all scale-100">
|
||||
<CardButton
|
||||
icon={<IconUser className="w-8 h-8 text-purple-400" />}
|
||||
label="USUARIO"
|
||||
color="bg-purple-500/10"
|
||||
borderColor="border-purple-500/20"
|
||||
onClick={() => setSelectedCategory({ title: 'Datos de Usuario', data: userData, icon: <IconUser className="w-6 h-6" /> })}
|
||||
/>
|
||||
|
||||
{/* Cabecera del Modal */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-white/10 bg-[#252525]">
|
||||
<div className="flex items-center gap-3 text-white">
|
||||
<div className="p-2 bg-white/5 rounded-lg text-purple-400">
|
||||
{selectedCategory.icon}
|
||||
<CardButton
|
||||
icon={<IconChat className="w-8 h-8 text-pink-400" />}
|
||||
label="CHAT"
|
||||
color="bg-pink-500/10"
|
||||
borderColor="border-pink-500/20"
|
||||
onClick={() => setSelectedCategory({ title: 'Contexto Chat', data: chatData, icon: <IconChat className="w-6 h-6" /> })}
|
||||
/>
|
||||
|
||||
<CardButton
|
||||
icon={<IconPalette className="w-8 h-8 text-emerald-400" />}
|
||||
label="TEMA"
|
||||
color="bg-emerald-500/10"
|
||||
borderColor="border-emerald-500/20"
|
||||
onClick={() => setSelectedCategory({ title: 'Apariencia', data: themeData, icon: <IconPalette className="w-6 h-6" /> })}
|
||||
/>
|
||||
|
||||
<CardButton
|
||||
icon={<IconSystem className="w-8 h-8 text-orange-400" />}
|
||||
label="SISTEMA"
|
||||
color="bg-orange-500/10"
|
||||
borderColor="border-orange-500/20"
|
||||
onClick={() => setSelectedCategory({ title: 'Dispositivo', data: platformData, icon: <IconSystem className="w-6 h-6" /> })}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
{/* BOTÓN DOCUMENTACIÓN */}
|
||||
<div className="mt-8 text-center">
|
||||
<button
|
||||
onClick={() => window.open('https://core.telegram.org/bots/webapps', '_blank')}
|
||||
className="inline-flex items-center gap-2 text-sm text-gray-500 hover:text-purple-400 transition-colors"
|
||||
>
|
||||
<span>Documentación API</span>
|
||||
<IconExternal className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* MODAL */}
|
||||
{selectedCategory && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 animate-[fadeIn_0.2s_ease-out]">
|
||||
<div
|
||||
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
||||
onClick={closeModal}
|
||||
></div>
|
||||
|
||||
<div className="relative w-full max-w-sm bg-[#1e1e1e] rounded-2xl shadow-2xl border border-white/10 overflow-hidden transform scale-100 animate-[zoomIn_0.2s_ease-out]">
|
||||
<div className="flex items-center justify-between p-4 border-b border-white/10 bg-[#252525]">
|
||||
<div className="flex items-center gap-3 text-white">
|
||||
<div className="p-2 bg-white/5 rounded-lg text-purple-400">
|
||||
{selectedCategory.icon}
|
||||
</div>
|
||||
<h3 className="font-bold text-lg">{selectedCategory.title}</h3>
|
||||
</div>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="p-2 text-gray-400 hover:text-white hover:bg-white/10 rounded-full transition-colors"
|
||||
>
|
||||
<IconX className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-4 max-h-[60vh] overflow-y-auto custom-scrollbar">
|
||||
{renderDetails(selectedCategory.data)}
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-[#252525] border-t border-white/10">
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="w-full py-3 bg-[#3d0091] hover:bg-[#4d00b1] text-white font-bold rounded-xl transition-all active:scale-[0.98]"
|
||||
>
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="font-bold text-lg">{selectedCategory.title}</h3>
|
||||
</div>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="p-2 text-gray-400 hover:text-white hover:bg-white/10 rounded-full transition-colors"
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
{/* Cuerpo del Modal */}
|
||||
<div className="p-4 max-h-[60vh] overflow-y-auto">
|
||||
{renderDetails(selectedCategory.data)}
|
||||
</div>
|
||||
// --- COMPONENTE TARJETA ---
|
||||
function CardButton({ icon, label, color, borderColor, onClick }) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`
|
||||
relative group flex flex-col items-center justify-center
|
||||
aspect-square w-full rounded-2xl
|
||||
${color} border ${borderColor}
|
||||
hover:brightness-125 active:scale-95
|
||||
transition-all duration-200 ease-out
|
||||
`}
|
||||
>
|
||||
<div className="mb-3 transform group-hover:scale-110 transition-transform duration-200">
|
||||
{icon}
|
||||
</div>
|
||||
<span className="text-xs font-bold tracking-wider text-white/90">
|
||||
{label}
|
||||
</span>
|
||||
|
||||
{/* Pie del Modal */}
|
||||
<div className="p-4 bg-[#252525] border-t border-white/10">
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="w-full py-3 bg-[#3d0091] hover:bg-[#4d00b1] text-white font-bold rounded-xl transition-all active:scale-[0.98]"
|
||||
>
|
||||
Cerrar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute top-3 right-3 opacity-0 group-hover:opacity-50 transition-opacity">
|
||||
<IconChevron className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// --- COMPONENTE DE TARJETA REUTILIZABLE ---
|
||||
// Este componente garantiza la forma cuadrada con 'aspect-square'
|
||||
function CardButton({ icon, label, color, borderColor, onClick }) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`
|
||||
relative group flex flex-col items-center justify-center
|
||||
aspect-square w-full rounded-2xl
|
||||
${color} border ${borderColor}
|
||||
hover:brightness-125 active:scale-95
|
||||
transition-all duration-200 ease-out
|
||||
`}
|
||||
>
|
||||
<div className="mb-3 transform group-hover:scale-110 transition-transform duration-200">
|
||||
{icon}
|
||||
</div>
|
||||
<span className="text-xs font-bold tracking-wider text-white/90">
|
||||
{label}
|
||||
</span>
|
||||
|
||||
{/* Indicador sutil de flecha */}
|
||||
<div className="absolute top-3 right-3 opacity-0 group-hover:opacity-50 transition-opacity">
|
||||
<ChevronRight size={16} className="text-white" />
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(<App />);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user