|
|
<!DOCTYPE html> |
|
|
<html lang="es"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>ToolLink Saver</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.tool-card:hover .tool-actions { |
|
|
opacity: 1; |
|
|
} |
|
|
|
|
|
.rating-stars input { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.rating-stars label { |
|
|
cursor: pointer; |
|
|
font-size: 1.5rem; |
|
|
color: #d1d5db; |
|
|
} |
|
|
|
|
|
.rating-stars input:checked ~ label { |
|
|
color: #f59e0b; |
|
|
} |
|
|
|
|
|
.rating-stars label:hover, |
|
|
.rating-stars label:hover ~ label { |
|
|
color: #f59e0b; |
|
|
} |
|
|
|
|
|
.tag:hover { |
|
|
transform: translateY(-2px); |
|
|
} |
|
|
|
|
|
.screenshot-container { |
|
|
background-color: #f3f4f6; |
|
|
background-image: linear-gradient(45deg, #e5e7eb 25%, transparent 25%, transparent 75%, #e5e7eb 75%, #e5e7eb), |
|
|
linear-gradient(45deg, #e5e7eb 25%, transparent 25%, transparent 75%, #e5e7eb 75%, #e5e7eb); |
|
|
background-size: 20px 20px; |
|
|
background-position: 0 0, 10px 10px; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50 min-h-screen"> |
|
|
<div class="container mx-auto px-4 py-8"> |
|
|
|
|
|
<header class="mb-10"> |
|
|
<div class="flex flex-col md:flex-row justify-between items-center"> |
|
|
<div class="mb-4 md:mb-0"> |
|
|
<h1 class="text-3xl font-bold text-indigo-700"> |
|
|
<i class="fas fa-link mr-2"></i>ToolLink Saver |
|
|
</h1> |
|
|
<p class="text-gray-600">Guarda y organiza tus herramientas favoritas</p> |
|
|
</div> |
|
|
<button id="addToolBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2 rounded-lg flex items-center transition-colors"> |
|
|
<i class="fas fa-plus mr-2"></i> Nueva Herramienta |
|
|
</button> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<main> |
|
|
|
|
|
<div class="bg-white rounded-lg shadow-md p-4 mb-8"> |
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4"> |
|
|
<div class="w-full md:w-1/2"> |
|
|
<label for="search" class="block text-sm font-medium text-gray-700 mb-1">Buscar</label> |
|
|
<div class="relative"> |
|
|
<input type="text" id="search" placeholder="Buscar herramientas..." class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> |
|
|
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> |
|
|
</div> |
|
|
</div> |
|
|
<div class="w-full md:w-1/4"> |
|
|
<label for="categoryFilter" class="block text-sm font-medium text-gray-700 mb-1">Categoría</label> |
|
|
<select id="categoryFilter" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> |
|
|
<option value="">Todas</option> |
|
|
<option value="Desarrollo">Desarrollo</option> |
|
|
<option value="Diseño">Diseño</option> |
|
|
<option value="Productividad">Productividad</option> |
|
|
<option value="Marketing">Marketing</option> |
|
|
<option value="IA">IA</option> |
|
|
</select> |
|
|
</div> |
|
|
<div class="w-full md:w-1/4"> |
|
|
<label for="tagFilter" class="block text-sm font-medium text-gray-700 mb-1">Tag</label> |
|
|
<select id="tagFilter" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> |
|
|
<option value="">Todos</option> |
|
|
|
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="toolsGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> |
|
|
|
|
|
<div class="text-center py-10 text-gray-500" id="emptyState"> |
|
|
<i class="fas fa-box-open text-5xl mb-4 text-gray-300"></i> |
|
|
<h3 class="text-xl font-medium">No hay herramientas guardadas</h3> |
|
|
<p class="mt-2">Comienza agregando tu primera herramienta</p> |
|
|
</div> |
|
|
</div> |
|
|
</main> |
|
|
|
|
|
|
|
|
<div id="addToolModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 hidden"> |
|
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] overflow-y-auto"> |
|
|
<div class="p-6"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-2xl font-bold text-gray-800">Agregar Nueva Herramienta</h2> |
|
|
<button id="closeModalBtn" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times text-xl"></i> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<form id="toolForm" class="space-y-4"> |
|
|
<div> |
|
|
<label for="toolUrl" class="block text-sm font-medium text-gray-700 mb-1">URL de la herramienta*</label> |
|
|
<input type="url" id="toolUrl" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="https://ejemplo.com"> |
|
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
|
|
<div> |
|
|
<label for="toolTitle" class="block text-sm font-medium text-gray-700 mb-1">Título*</label> |
|
|
<input type="text" id="toolTitle" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="Nombre de la herramienta"> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label for="toolCategory" class="block text-sm font-medium text-gray-700 mb-1">Categoría*</label> |
|
|
<select id="toolCategory" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> |
|
|
<option value="">Seleccionar categoría</option> |
|
|
<option value="Desarrollo">Desarrollo</option> |
|
|
<option value="Diseño">Diseño</option> |
|
|
<option value="Productividad">Productividad</option> |
|
|
<option value="Marketing">Marketing</option> |
|
|
<option value="IA">IA</option> |
|
|
<option value="Otros">Otros</option> |
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label for="toolDescription" class="block text-sm font-medium text-gray-700 mb-1">Descripción</label> |
|
|
<textarea id="toolDescription" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="Breve descripción de la herramienta..."></textarea> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label for="toolTags" class="block text-sm font-medium text-gray-700 mb-1">Tags (separados por comas)</label> |
|
|
<input type="text" id="toolTags" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="web, diseño, ui"> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Calificación</label> |
|
|
<div class="rating-stars flex justify-center space-x-1"> |
|
|
<input type="radio" id="star5" name="rating" value="5"> |
|
|
<label for="star5"><i class="fas fa-star"></i></label> |
|
|
|
|
|
<input type="radio" id="star4" name="rating" value="4"> |
|
|
<label for="star4"><i class="fas fa-star"></i></label> |
|
|
|
|
|
<input type="radio" id="star3" name="rating" value="3"> |
|
|
<label for="star3"><i class="fas fa-star"></i></label> |
|
|
|
|
|
<input type="radio" id="star2" name="rating" value="2"> |
|
|
<label for="star2"><i class="fas fa-star"></i></label> |
|
|
|
|
|
<input type="radio" id="star1" name="rating" value="1"> |
|
|
<label for="star1"><i class="fas fa-star"></i></label> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">Screenshot</label> |
|
|
<div id="screenshotContainer" class="screenshot-container rounded-lg border border-gray-300 p-4 flex items-center justify-center h-48"> |
|
|
<p class="text-gray-500 text-center">El screenshot se generará automáticamente al guardar</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="flex justify-end space-x-3 pt-4"> |
|
|
<button type="button" id="cancelBtn" class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50">Cancelar</button> |
|
|
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 flex items-center"> |
|
|
<i class="fas fa-save mr-2"></i> Guardar |
|
|
</button> |
|
|
</div> |
|
|
</form> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
|
|
const addToolBtn = document.getElementById('addToolBtn'); |
|
|
const addToolModal = document.getElementById('addToolModal'); |
|
|
const closeModalBtn = document.getElementById('closeModalBtn'); |
|
|
const cancelBtn = document.getElementById('cancelBtn'); |
|
|
const toolForm = document.getElementById('toolForm'); |
|
|
const toolsGrid = document.getElementById('toolsGrid'); |
|
|
const emptyState = document.getElementById('emptyState'); |
|
|
const searchInput = document.getElementById('search'); |
|
|
const categoryFilter = document.getElementById('categoryFilter'); |
|
|
const tagFilter = document.getElementById('tagFilter'); |
|
|
|
|
|
|
|
|
let tools = JSON.parse(localStorage.getItem('tools')) || []; |
|
|
let allTags = new Set(); |
|
|
|
|
|
|
|
|
initApp(); |
|
|
|
|
|
|
|
|
addToolBtn.addEventListener('click', () => addToolModal.classList.remove('hidden')); |
|
|
closeModalBtn.addEventListener('click', () => addToolModal.classList.add('hidden')); |
|
|
cancelBtn.addEventListener('click', () => addToolModal.classList.add('hidden')); |
|
|
|
|
|
toolForm.addEventListener('submit', function(e) { |
|
|
e.preventDefault(); |
|
|
saveTool(); |
|
|
}); |
|
|
|
|
|
searchInput.addEventListener('input', filterTools); |
|
|
categoryFilter.addEventListener('change', filterTools); |
|
|
tagFilter.addEventListener('change', filterTools); |
|
|
|
|
|
|
|
|
function initApp() { |
|
|
if (tools.length > 0) { |
|
|
emptyState.classList.add('hidden'); |
|
|
renderTools(tools); |
|
|
updateTagFilter(); |
|
|
} |
|
|
} |
|
|
|
|
|
function saveTool() { |
|
|
const toolUrl = document.getElementById('toolUrl').value; |
|
|
const toolTitle = document.getElementById('toolTitle').value; |
|
|
const toolDescription = document.getElementById('toolDescription').value; |
|
|
const toolCategory = document.getElementById('toolCategory').value; |
|
|
const toolTags = document.getElementById('toolTags').value; |
|
|
const rating = document.querySelector('input[name="rating"]:checked')?.value || 0; |
|
|
|
|
|
|
|
|
const screenshot = `https://api.screenshotone.com/take?url=${encodeURIComponent(toolUrl)}&access_key=demo`; |
|
|
|
|
|
const newTool = { |
|
|
id: Date.now(), |
|
|
url: toolUrl, |
|
|
title: toolTitle, |
|
|
description: toolDescription, |
|
|
category: toolCategory, |
|
|
tags: toolTags.split(',').map(tag => tag.trim()).filter(tag => tag !== ''), |
|
|
rating: parseInt(rating), |
|
|
screenshot: screenshot, |
|
|
createdAt: new Date().toISOString() |
|
|
}; |
|
|
|
|
|
tools.unshift(newTool); |
|
|
localStorage.setItem('tools', JSON.stringify(tools)); |
|
|
|
|
|
|
|
|
if (emptyState.classList.contains('hidden')) { |
|
|
renderTools(tools); |
|
|
} else { |
|
|
emptyState.classList.add('hidden'); |
|
|
renderTools(tools); |
|
|
} |
|
|
|
|
|
|
|
|
updateTagFilter(); |
|
|
|
|
|
|
|
|
toolForm.reset(); |
|
|
addToolModal.classList.add('hidden'); |
|
|
} |
|
|
|
|
|
function renderTools(toolsToRender) { |
|
|
toolsGrid.innerHTML = ''; |
|
|
|
|
|
toolsToRender.forEach(tool => { |
|
|
const toolCard = document.createElement('div'); |
|
|
toolCard.className = 'bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow tool-card relative'; |
|
|
|
|
|
|
|
|
let stars = ''; |
|
|
for (let i = 0; i < 5; i++) { |
|
|
if (i < tool.rating) { |
|
|
stars += '<i class="fas fa-star text-yellow-400"></i>'; |
|
|
} else { |
|
|
stars += '<i class="far fa-star text-yellow-400"></i>'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let tagsHtml = ''; |
|
|
if (tool.tags && tool.tags.length > 0) { |
|
|
tool.tags.forEach(tag => { |
|
|
allTags.add(tag); |
|
|
tagsHtml += `<span class="tag inline-block bg-indigo-100 text-indigo-800 text-xs px-2 py-1 rounded-full mr-1 mb-1 transition-transform">${tag}</span>`; |
|
|
}); |
|
|
} |
|
|
|
|
|
toolCard.innerHTML = ` |
|
|
<div class="screenshot-container h-40 overflow-hidden relative"> |
|
|
<img src="${tool.screenshot}" alt="${tool.title}" class="w-full h-full object-cover"> |
|
|
<div class="absolute inset-0 bg-gradient-to-t from-black/30 to-transparent"></div> |
|
|
</div> |
|
|
<div class="p-4"> |
|
|
<div class="flex justify-between items-start mb-2"> |
|
|
<h3 class="font-bold text-lg text-gray-800 truncate">${tool.title}</h3> |
|
|
<span class="bg-indigo-100 text-indigo-800 text-xs px-2 py-1 rounded-full">${tool.category}</span> |
|
|
</div> |
|
|
<p class="text-gray-600 text-sm mb-3 line-clamp-2">${tool.description || 'Sin descripción'}</p> |
|
|
<div class="flex items-center mb-3"> |
|
|
${stars} |
|
|
<span class="text-gray-500 text-sm ml-2">${tool.rating}/5</span> |
|
|
</div> |
|
|
<div class="flex flex-wrap mb-2"> |
|
|
${tagsHtml} |
|
|
</div> |
|
|
<div class="flex justify-between items-center pt-2 border-t border-gray-100"> |
|
|
<a href="${tool.url}" target="_blank" class="text-indigo-600 hover:text-indigo-800 text-sm font-medium flex items-center"> |
|
|
<i class="fas fa-external-link-alt mr-1"></i> Visitar |
|
|
</a> |
|
|
<div class="tool-actions opacity-0 transition-opacity flex space-x-2"> |
|
|
<button class="text-gray-500 hover:text-indigo-600" onclick="editTool(${tool.id})"> |
|
|
<i class="fas fa-edit"></i> |
|
|
</button> |
|
|
<button class="text-gray-500 hover:text-red-600" onclick="deleteTool(${tool.id})"> |
|
|
<i class="fas fa-trash"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
toolsGrid.appendChild(toolCard); |
|
|
}); |
|
|
} |
|
|
|
|
|
function updateTagFilter() { |
|
|
tagFilter.innerHTML = '<option value="">Todos</option>'; |
|
|
allTags.forEach(tag => { |
|
|
const option = document.createElement('option'); |
|
|
option.value = tag; |
|
|
option.textContent = tag; |
|
|
tagFilter.appendChild(option); |
|
|
}); |
|
|
} |
|
|
|
|
|
function filterTools() { |
|
|
const searchTerm = searchInput.value.toLowerCase(); |
|
|
const selectedCategory = categoryFilter.value; |
|
|
const selectedTag = tagFilter.value; |
|
|
|
|
|
const filteredTools = tools.filter(tool => { |
|
|
const matchesSearch = tool.title.toLowerCase().includes(searchTerm) || |
|
|
tool.description.toLowerCase().includes(searchTerm); |
|
|
const matchesCategory = selectedCategory === '' || tool.category === selectedCategory; |
|
|
const matchesTag = selectedTag === '' || (tool.tags && tool.tags.includes(selectedTag)); |
|
|
|
|
|
return matchesSearch && matchesCategory && matchesTag; |
|
|
}); |
|
|
|
|
|
renderTools(filteredTools); |
|
|
|
|
|
if (filteredTools.length === 0) { |
|
|
emptyState.classList.remove('hidden'); |
|
|
} else { |
|
|
emptyState.classList.add('hidden'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
window.editTool = function(id) { |
|
|
const tool = tools.find(t => t.id === id); |
|
|
if (tool) { |
|
|
document.getElementById('toolUrl').value = tool.url; |
|
|
document.getElementById('toolTitle').value = tool.title; |
|
|
document.getElementById('toolDescription').value = tool.description || ''; |
|
|
document.getElementById('toolCategory').value = tool.category; |
|
|
document.getElementById('toolTags').value = tool.tags.join(', '); |
|
|
|
|
|
|
|
|
if (tool.rating > 0) { |
|
|
document.getElementById(`star${tool.rating}`).checked = true; |
|
|
} |
|
|
|
|
|
|
|
|
const screenshotContainer = document.getElementById('screenshotContainer'); |
|
|
screenshotContainer.innerHTML = `<img src="${tool.screenshot}" alt="${tool.title}" class="max-h-full max-w-full">`; |
|
|
|
|
|
|
|
|
toolForm.dataset.editingId = id; |
|
|
document.querySelector('#addToolModal h2').textContent = 'Editar Herramienta'; |
|
|
|
|
|
addToolModal.classList.remove('hidden'); |
|
|
} |
|
|
}; |
|
|
|
|
|
window.deleteTool = function(id) { |
|
|
if (confirm('¿Estás seguro de que quieres eliminar esta herramienta?')) { |
|
|
tools = tools.filter(tool => tool.id !== id); |
|
|
localStorage.setItem('tools', JSON.stringify(tools)); |
|
|
|
|
|
if (tools.length === 0) { |
|
|
emptyState.classList.remove('hidden'); |
|
|
toolsGrid.innerHTML = ''; |
|
|
} else { |
|
|
renderTools(tools); |
|
|
} |
|
|
|
|
|
updateTagFilter(); |
|
|
} |
|
|
}; |
|
|
}); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=ARX88/toolvault" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |