Spaces:
Running
Running
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Sofia Cloud - Tu espacio en la nube</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/lucide@latest"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| * { font-family: 'Inter', sans-serif; } | |
| .glass { | |
| background: rgba(255, 255, 255, 0.7); | |
| backdrop-filter: blur(10px); | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| } | |
| .glass-dark { | |
| background: rgba(17, 24, 39, 0.8); | |
| backdrop-filter: blur(10px); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .gradient-bg { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| } | |
| .file-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 40px -10px rgba(0,0,0,0.2); | |
| } | |
| .storage-bar { | |
| background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 100%); | |
| } | |
| @keyframes float { | |
| 0%, 100% { transform: translateY(0px); } | |
| 50% { transform: translateY(-10px); } | |
| } | |
| .floating { animation: float 3s ease-in-out infinite; } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 text-gray-800"> | |
| <!-- Sidebar --> | |
| <aside class="fixed left-0 top-0 h-full w-64 glass border-r border-gray-200 z-50 hidden md:block"> | |
| <div class="p-6 flex items-center gap-3 mb-6"> | |
| <div class="w-10 h-10 gradient-bg rounded-xl flex items-center justify-center text-white shadow-lg"> | |
| <i data-lucide="cloud" class="w-6 h-6"></i> | |
| </div> | |
| <div> | |
| <h1 class="font-bold text-xl text-gray-900">Sofia Cloud</h1> | |
| <p class="text-xs text-gray-500">Tu espacio seguro</p> | |
| </div> | |
| </div> | |
| <nav class="px-4 space-y-2"> | |
| <button onclick="showSection('files')" class="nav-item w-full flex items-center gap-3 px-4 py-3 rounded-xl bg-blue-50 text-blue-600 font-medium transition-all"> | |
| <i data-lucide="folder-open" class="w-5 h-5"></i> | |
| Mis Archivos | |
| </button> | |
| <button onclick="showSection('recent')" class="nav-item w-full flex items-center gap-3 px-4 py-3 rounded-xl text-gray-600 hover:bg-gray-100 transition-all"> | |
| <i data-lucide="clock" class="w-5 h-5"></i> | |
| Recientes | |
| </button> | |
| <button onclick="showSection('favorites')" class="nav-item w-full flex items-center gap-3 px-4 py-3 rounded-xl text-gray-600 hover:bg-gray-100 transition-all"> | |
| <i data-lucide="star" class="w-5 h-5"></i> | |
| Favoritos | |
| </button> | |
| <button onclick="showSection('trash')" class="nav-item w-full flex items-center gap-3 px-4 py-3 rounded-xl text-gray-600 hover:bg-gray-100 transition-all"> | |
| <i data-lucide="trash-2" class="w-5 h-5"></i> | |
| Papelera | |
| </button> | |
| </nav> | |
| <!-- Storage Widget --> | |
| <div class="absolute bottom-0 left-0 right-0 p-6"> | |
| <div class="bg-gradient-to-br from-blue-500 to-purple-600 rounded-2xl p-4 text-white"> | |
| <div class="flex items-center justify-between mb-3"> | |
| <span class="text-sm font-medium">Almacenamiento</span> | |
| <span class="text-xs bg-white/20 px-2 py-1 rounded-full">75%</span> | |
| </div> | |
| <div class="w-full bg-white/20 rounded-full h-2 mb-2"> | |
| <div class="storage-bar h-2 rounded-full" style="width: 75%"></div> | |
| </div> | |
| <p class="text-xs text-blue-100">15 GB de 20 GB usados</p> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- Main Content --> | |
| <main class="md:ml-64 min-h-screen"> | |
| <!-- Header --> | |
| <header class="sticky top-0 z-40 glass border-b border-gray-200 px-6 py-4"> | |
| <div class="flex items-center justify-between max-w-7xl mx-auto"> | |
| <div class="flex items-center gap-4 flex-1"> | |
| <button class="md:hidden p-2 hover:bg-gray-100 rounded-lg"> | |
| <i data-lucide="menu" class="w-6 h-6"></i> | |
| </button> | |
| <div class="relative flex-1 max-w-xl"> | |
| <i data-lucide="search" class="w-5 h-5 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i> | |
| <input type="text" id="searchInput" placeholder="Buscar archivos..." | |
| class="w-full pl-10 pr-4 py-2.5 bg-gray-100 border-none rounded-xl focus:ring-2 focus:ring-blue-500 focus:bg-white transition-all" | |
| oninput="filterFiles()"> | |
| </div> | |
| </div> | |
| <div class="flex items-center gap-4 ml-4"> | |
| <button onclick="createFolder()" class="hidden sm:flex items-center gap-2 px-4 py-2.5 bg-gray-100 hover:bg-gray-200 rounded-xl font-medium transition-all"> | |
| <i data-lucide="folder-plus" class="w-4 h-4"></i> | |
| Nueva Carpeta | |
| </button> | |
| <button onclick="triggerUpload()" class="flex items-center gap-2 px-4 py-2.5 gradient-bg text-white rounded-xl font-medium hover:shadow-lg transition-all"> | |
| <i data-lucide="upload-cloud" class="w-4 h-4"></i> | |
| <span class="hidden sm:inline">Subir</span> | |
| </button> | |
| <input type="file" id="fileInput" class="hidden" multiple onchange="handleFiles(this.files)"> | |
| <button class="relative p-2 hover:bg-gray-100 rounded-xl"> | |
| <i data-lucide="bell" class="w-6 h-6 text-gray-600"></i> | |
| <span class="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full"></span> | |
| </button> | |
| <img src="https://static.photos/people/200x200/42" alt="User" class="w-10 h-10 rounded-xl object-cover border-2 border-white shadow-md cursor-pointer"> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Content Area --> | |
| <div class="p-6 max-w-7xl mx-auto"> | |
| <!-- Stats Cards --> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8"> | |
| <div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <div class="p-3 bg-blue-50 rounded-xl"> | |
| <i data-lucide="files" class="w-6 h-6 text-blue-600"></i> | |
| </div> | |
| <span class="text-xs font-medium text-green-600 bg-green-50 px-2 py-1 rounded-full">+12%</span> | |
| </div> | |
| <h3 class="text-2xl font-bold text-gray-900">1,247</h3> | |
| <p class="text-sm text-gray-500">Archivos totales</p> | |
| </div> | |
| <div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <div class="p-3 bg-purple-50 rounded-xl"> | |
| <i data-lucide="hard-drive" class="w-6 h-6 text-purple-600"></i> | |
| </div> | |
| <span class="text-xs font-medium text-gray-600 bg-gray-100 px-2 py-1 rounded-full">75%</span> | |
| </div> | |
| <h3 class="text-2xl font-bold text-gray-900">15.2 GB</h3> | |
| <p class="text-sm text-gray-500">Espacio usado</p> | |
| </div> | |
| <div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <div class="p-3 bg-orange-50 rounded-xl"> | |
| <i data-lucide="share-2" class="w-6 h-6 text-orange-600"></i> | |
| </div> | |
| <span class="text-xs font-medium text-green-600 bg-green-50 px-2 py-1 rounded-full">+5%</span> | |
| </div> | |
| <h3 class="text-2xl font-bold text-gray-900">48</h3> | |
| <p class="text-sm text-gray-500">Archivos compartidos</p> | |
| </div> | |
| </div> | |
| <!-- Quick Access --> | |
| <div class="mb-8"> | |
| <h2 class="text-lg font-semibold text-gray-900 mb-4">Acceso rápido</h2> | |
| <div class="grid grid-cols-2 md:grid-cols-4 gap-4" id="quickAccess"> | |
| <!-- Generated by JS --> | |
| </div> | |
| </div> | |
| <!-- File List Header --> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h2 class="text-lg font-semibold text-gray-900">Todos los archivos</h2> | |
| <div class="flex items-center gap-2"> | |
| <button onclick="toggleView('grid')" id="gridBtn" class="p-2 rounded-lg bg-blue-50 text-blue-600"> | |
| <i data-lucide="grid-3x3" class="w-5 h-5"></i> | |
| </button> | |
| <button onclick="toggleView('list')" id="listBtn" class="p-2 rounded-lg text-gray-400 hover:bg-gray-100"> | |
| <i data-lucide="list" class="w-5 h-5"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Files Container --> | |
| <div id="filesContainer" class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4"> | |
| <!-- Files will be rendered here --> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Upload Progress Modal --> | |
| <div id="uploadModal" class="fixed bottom-4 right-4 w-80 bg-white rounded-2xl shadow-2xl border border-gray-200 p-4 hidden transform transition-all"> | |
| <div class="flex items-center justify-between mb-3"> | |
| <span class="font-medium text-gray-900">Subiendo archivos...</span> | |
| <button onclick="closeUploadModal()" class="text-gray-400 hover:text-gray-600"> | |
| <i data-lucide="x" class="w-5 h-5"></i> | |
| </button> | |
| </div> | |
| <div class="space-y-3" id="uploadList"> | |
| <!-- Upload items --> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize Lucide icons when DOM is ready | |
| document.addEventListener('DOMContentLoaded', function() { | |
| if (typeof lucide !== 'undefined') { | |
| lucide.createIcons(); | |
| } | |
| renderQuickAccess(); | |
| renderFiles(); | |
| }); | |
| // Sample data | |
| let files = [ | |
| { id: 1, name: 'Proyecto_Final.pdf', type: 'pdf', size: '2.4 MB', date: '2024-01-15', color: 'red' }, | |
| { id: 2, name: 'Fotos_Vacaciones', type: 'folder', size: '156 archivos', date: '2024-01-14', color: 'blue' }, | |
| { id: 3, name: 'Presupuesto_2024.xlsx', type: 'excel', size: '456 KB', date: '2024-01-13', color: 'green' }, | |
| { id: 4, name: 'Logo_Design.png', type: 'image', size: '3.2 MB', date: '2024-01-12', color: 'purple' }, | |
| { id: 5, name: 'Contrato_Clientes.docx', type: 'word', size: '128 KB', date: '2024-01-11', color: 'blue' }, | |
| { id: 6, name: 'Presentación_Ventas.pptx', type: 'powerpoint', size: '5.8 MB', date: '2024-01-10', color: 'orange' }, | |
| { id: 7, name: 'Backup_Sistema.zip', type: 'zip', size: '1.2 GB', date: '2024-01-09', color: 'gray' }, | |
| { id: 8, name: 'Reunión_Team.mp4', type: 'video', size: '245 MB', date: '2024-01-08', color: 'pink' }, | |
| ]; | |
| const quickAccess = [ | |
| { name: 'Documentos', icon: 'file-text', color: 'blue', count: '124' }, | |
| { name: 'Imágenes', icon: 'image', color: 'purple', count: '48' }, | |
| { name: 'Videos', icon: 'video', color: 'pink', count: '12' }, | |
| { name: 'Audio', icon: 'music', color: 'orange', count: '36' }, | |
| ]; | |
| let currentView = 'grid'; | |
| function getIcon(type) { | |
| const icons = { | |
| pdf: 'file-text', | |
| folder: 'folder', | |
| excel: 'table', | |
| word: 'file-text', | |
| powerpoint: 'presentation', | |
| image: 'image', | |
| zip: 'archive', | |
| video: 'video', | |
| audio: 'music' | |
| }; | |
| return icons[type] || 'file'; | |
| } | |
| function getColor(type) { | |
| const colors = { | |
| pdf: 'bg-red-100 text-red-600', | |
| folder: 'bg-blue-100 text-blue-600', | |
| excel: 'bg-green-100 text-green-600', | |
| word: 'bg-blue-100 text-blue-600', | |
| powerpoint: 'bg-orange-100 text-orange-600', | |
| image: 'bg-purple-100 text-purple-600', | |
| zip: 'bg-gray-100 text-gray-600', | |
| video: 'bg-pink-100 text-pink-600' | |
| }; | |
| return colors[type] || 'bg-gray-100 text-gray-600'; | |
| } | |
| function getQuickAccessColor(color) { | |
| const colorMap = { | |
| blue: 'bg-blue-100 text-blue-600', | |
| purple: 'bg-purple-100 text-purple-600', | |
| pink: 'bg-pink-100 text-pink-600', | |
| orange: 'bg-orange-100 text-orange-600' | |
| }; | |
| return colorMap[color] || 'bg-gray-100 text-gray-600'; | |
| } | |
| function renderQuickAccess() { | |
| const container = document.getElementById('quickAccess'); | |
| if (!container) return; | |
| container.innerHTML = quickAccess.map(item => ` | |
| <div class="bg-white p-4 rounded-2xl border border-gray-100 hover:shadow-md transition-all cursor-pointer group"> | |
| <div class="w-12 h-12 ${getQuickAccessColor(item.color)} rounded-xl flex items-center justify-center mb-3 group-hover:scale-110 transition-transform"> | |
| <i data-lucide="${item.icon}" class="w-6 h-6"></i> | |
| </div> | |
| <h3 class="font-medium text-gray-900 mb-1">${item.name}</h3> | |
| <p class="text-xs text-gray-500">${item.count} archivos</p> | |
| </div> | |
| `).join(''); | |
| if (typeof lucide !== 'undefined') { | |
| lucide.createIcons(); | |
| } | |
| } | |
| function renderFiles() { | |
| const container = document.getElementById('filesContainer'); | |
| if (!container) return; | |
| const searchInput = document.getElementById('searchInput'); | |
| const searchTerm = searchInput ? searchInput.value.toLowerCase() : ''; | |
| const filtered = files.filter(f => f.name.toLowerCase().includes(searchTerm)); | |
| if (currentView === 'grid') { | |
| container.className = 'grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4'; | |
| container.innerHTML = filtered.map(file => ` | |
| <div class="file-card bg-white rounded-2xl p-4 border border-gray-100 cursor-pointer group transition-all"> | |
| <div class="flex items-start justify-between mb-3"> | |
| <div class="w-12 h-12 ${getColor(file.type)} rounded-xl flex items-center justify-center"> | |
| <i data-lucide="${getIcon(file.type)}" class="w-6 h-6"></i> | |
| </div> | |
| <button onclick="event.stopPropagation(); toggleMenu(${file.id})" class="p-1 hover:bg-gray-100 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity"> | |
| <i data-lucide="more-vertical" class="w-4 h-4 text-gray-400"></i> | |
| </button> | |
| </div> | |
| <h3 class="font-medium text-gray-900 truncate mb-1" title="${file.name}">${file.name}</h3> | |
| <div class="flex items-center justify-between text-xs text-gray-500"> | |
| <span>${file.size}</span> | |
| <span>${file.date}</span> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } else { | |
| container.className = 'space-y-2'; | |
| container.innerHTML = filtered.map(file => ` | |
| <div class="file-card bg-white rounded-xl p-4 border border-gray-100 cursor-pointer flex items-center gap-4 group hover:bg-gray-50 transition-all"> | |
| <div class="w-10 h-10 ${getColor(file.type)} rounded-lg flex items-center justify-center flex-shrink-0"> | |
| <i data-lucide="${getIcon(file.type)}" class="w-5 h-5"></i> | |
| </div> | |
| <div class="flex-1 min-w-0"> | |
| <h3 class="font-medium text-gray-900 truncate">${file.name}</h3> | |
| <p class="text-xs text-gray-500">${file.date}</p> | |
| </div> | |
| <span class="text-sm text-gray-500 hidden sm:block">${file.size}</span> | |
| <button onclick="event.stopPropagation(); toggleMenu(${file.id})" class="p-2 hover:bg-gray-200 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity"> | |
| <i data-lucide="more-vertical" class="w-4 h-4 text-gray-400"></i> | |
| </button> | |
| </div> | |
| `).join(''); | |
| } | |
| if (typeof lucide !== 'undefined') { | |
| lucide.createIcons(); | |
| } | |
| } | |
| function toggleView(view) { | |
| currentView = view; | |
| const gridBtn = document.getElementById('gridBtn'); | |
| const listBtn = document.getElementById('listBtn'); | |
| if (gridBtn && listBtn) { | |
| if (view === 'grid') { | |
| gridBtn.className = 'p-2 rounded-lg bg-blue-50 text-blue-600'; | |
| listBtn.className = 'p-2 rounded-lg text-gray-400 hover:bg-gray-100'; | |
| } else { | |
| gridBtn.className = 'p-2 rounded-lg text-gray-400 hover:bg-gray-100'; | |
| listBtn.className = 'p-2 rounded-lg bg-blue-50 text-blue-600'; | |
| } | |
| } | |
| renderFiles(); | |
| } | |
| function filterFiles() { | |
| renderFiles(); | |
| } | |
| function triggerUpload() { | |
| const fileInput = document.getElementById('fileInput'); | |
| if (fileInput) fileInput.click(); | |
| } | |
| function handleFiles(selectedFiles) { | |
| const modal = document.getElementById('uploadModal'); | |
| const list = document.getElementById('uploadList'); | |
| if (!modal || !list) return; | |
| modal.classList.remove('hidden'); | |
| Array.from(selectedFiles).forEach(file => { | |
| const id = Date.now() + Math.random(); | |
| const item = document.createElement('div'); | |
| item.className = 'flex items-center gap-3 upload-item-' + id; | |
| item.innerHTML = ` | |
| <div class="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center flex-shrink-0"> | |
| <i data-lucide="file" class="w-4 h-4 text-blue-600"></i> | |
| </div> | |
| <div class="flex-1 min-w-0"> | |
| <p class="text-sm font-medium text-gray-900 truncate">${file.name}</p> | |
| <div class="w-full bg-gray-200 rounded-full h-1.5 mt-1"> | |
| <div class="bg-blue-600 h-1.5 rounded-full transition-all duration-300" style="width: 0%" id="progress-${id}"></div> | |
| </div> | |
| </div> | |
| <span class="text-xs text-gray-500" id="status-${id}">0%</span> | |
| `; | |
| list.appendChild(item); | |
| if (typeof lucide !== 'undefined') { | |
| lucide.createIcons(); | |
| } | |
| // Simulate upload | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += Math.random() * 30; | |
| if (progress >= 100) { | |
| progress = 100; | |
| clearInterval(interval); | |
| const statusEl = document.getElementById(`status-${id}`); | |
| if (statusEl) { | |
| statusEl.textContent = '✓'; | |
| statusEl.className = 'text-xs text-green-600 font-medium'; | |
| } | |
| setTimeout(() => { | |
| item.remove(); | |
| if (list.children.length === 0) closeUploadModal(); | |
| }, 1000); | |
| } | |
| const progressEl = document.getElementById(`progress-${id}`); | |
| const statusEl = document.getElementById(`status-${id}`); | |
| if (progressEl) progressEl.style.width = progress + '%'; | |
| if (statusEl) statusEl.textContent = Math.floor(progress) + '%'; | |
| }, 300); | |
| }); | |
| } | |
| function closeUploadModal() { | |
| const modal = document.getElementById('uploadModal'); | |
| if (modal) modal.classList.add('hidden'); | |
| } | |
| function createFolder() { | |
| const name = prompt('Nombre de la nueva carpeta:'); | |
| if (name && name.trim()) { | |
| files.unshift({ | |
| id: Date.now(), | |
| name: name.trim(), | |
| type: 'folder', | |
| size: '0 archivos', | |
| date: new Date().toISOString().split('T')[0], | |
| color: 'blue' | |
| }); | |
| renderFiles(); | |
| } | |
| } | |
| function showSection(section) { | |
| // Visual feedback only for demo | |
| const buttons = document.querySelectorAll('.nav-item'); | |
| buttons.forEach(btn => { | |
| btn.classList.remove('bg-blue-50', 'text-blue-600'); | |
| btn.classList.add('text-gray-600'); | |
| }); | |
| // Get the clicked button from event | |
| const clickedBtn = event ? event.currentTarget : null; | |
| if (clickedBtn) { | |
| clickedBtn.classList.remove('text-gray-600'); | |
| clickedBtn.classList.add('bg-blue-50', 'text-blue-600'); | |
| } | |
| // Here you would typically load different content based on section | |
| console.log('Section:', section); | |
| } | |
| function toggleMenu(id) { | |
| // Context menu logic - prevent errors | |
| console.log('Menu for file:', id); | |
| } | |
| </script> | |
| </body> | |
| </html> |