|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
|
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Tools Collection</title> |
|
|
<style> |
|
|
* { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
box-sizing: border-box; |
|
|
} |
|
|
|
|
|
body { |
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; |
|
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); |
|
|
min-height: 100vh; |
|
|
color: #e4e4e4; |
|
|
padding: 20px; |
|
|
} |
|
|
|
|
|
.container { |
|
|
max-width: 1200px; |
|
|
margin: 0 auto; |
|
|
} |
|
|
|
|
|
.header { |
|
|
text-align: center; |
|
|
margin-bottom: 50px; |
|
|
padding: 40px 20px; |
|
|
} |
|
|
|
|
|
.header h1 { |
|
|
font-size: 3.5rem; |
|
|
margin-bottom: 15px; |
|
|
background: linear-gradient(135deg, #e94560, #f39c12); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
background-clip: text; |
|
|
font-weight: 700; |
|
|
} |
|
|
|
|
|
.header p { |
|
|
font-size: 1.2rem; |
|
|
color: #8892b0; |
|
|
margin-bottom: 40px; |
|
|
} |
|
|
|
|
|
.search-container { |
|
|
max-width: 600px; |
|
|
margin: 0 auto; |
|
|
position: relative; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
.search-box { |
|
|
width: 100%; |
|
|
padding: 18px 50px 18px 25px; |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
border-radius: 50px; |
|
|
font-size: 1.1rem; |
|
|
background: rgba(255, 255, 255, 0.05); |
|
|
backdrop-filter: blur(10px); |
|
|
color: #e4e4e4; |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
.search-box::placeholder { |
|
|
color: #8892b0; |
|
|
} |
|
|
|
|
|
.search-box:focus { |
|
|
outline: none; |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
border-color: #e94560; |
|
|
box-shadow: 0 0 20px rgba(233, 69, 96, 0.2); |
|
|
} |
|
|
|
|
|
.search-icon { |
|
|
position: absolute; |
|
|
right: 25px; |
|
|
top: 50%; |
|
|
transform: translateY(-50%); |
|
|
font-size: 1.2rem; |
|
|
color: #8892b0; |
|
|
} |
|
|
|
|
|
.filter-container { |
|
|
max-width: 300px; |
|
|
margin: 0 auto; |
|
|
} |
|
|
|
|
|
.category-dropdown { |
|
|
width: 100%; |
|
|
padding: 12px 40px 12px 20px; |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
border-radius: 50px; |
|
|
font-size: 1rem; |
|
|
background: rgba(255, 255, 255, 0.05) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23e4e4e4' d='M6 8L1 3h10z'/%3E%3C/svg%3E") no-repeat right 20px center; |
|
|
backdrop-filter: blur(10px); |
|
|
color: #e4e4e4; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s ease; |
|
|
-webkit-appearance: none; |
|
|
-moz-appearance: none; |
|
|
appearance: none; |
|
|
} |
|
|
|
|
|
.category-dropdown:focus { |
|
|
outline: none; |
|
|
background-color: rgba(255, 255, 255, 0.1); |
|
|
border-color: #e94560; |
|
|
} |
|
|
|
|
|
.category-dropdown option { |
|
|
background: #16213e; |
|
|
color: #e4e4e4; |
|
|
} |
|
|
|
|
|
.features-grid { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); |
|
|
gap: 25px; |
|
|
padding: 0 20px; |
|
|
} |
|
|
|
|
|
.feature-card { |
|
|
background: rgba(255, 255, 255, 0.05); |
|
|
border-radius: 20px; |
|
|
padding: 30px; |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
transition: all 0.3s ease; |
|
|
cursor: pointer; |
|
|
text-decoration: none; |
|
|
color: inherit; |
|
|
display: block; |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
backdrop-filter: blur(5px); |
|
|
} |
|
|
|
|
|
.feature-card::before { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
height: 2px; |
|
|
background: linear-gradient(90deg, #e94560, #f39c12); |
|
|
transform: scaleX(0); |
|
|
transition: transform 0.3s ease; |
|
|
transform-origin: left; |
|
|
} |
|
|
|
|
|
.feature-card:hover { |
|
|
transform: translateY(-5px); |
|
|
background: rgba(255, 255, 255, 0.08); |
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); |
|
|
border-color: rgba(255, 255, 255, 0.2); |
|
|
} |
|
|
|
|
|
.feature-card:hover::before { |
|
|
transform: scaleX(1); |
|
|
} |
|
|
|
|
|
.feature-card.coming-soon { |
|
|
opacity: 0.6; |
|
|
cursor: not-allowed; |
|
|
} |
|
|
|
|
|
.feature-header { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 15px; |
|
|
margin-bottom: 15px; |
|
|
} |
|
|
|
|
|
.feature-icon { |
|
|
font-size: 2.5rem; |
|
|
width: 60px; |
|
|
height: 60px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
background: rgba(255, 255, 255, 0.05); |
|
|
border-radius: 15px; |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
} |
|
|
|
|
|
.feature-card h3 { |
|
|
color: #e4e4e4; |
|
|
font-size: 1.3rem; |
|
|
font-weight: 600; |
|
|
} |
|
|
|
|
|
.feature-path { |
|
|
color: #e94560; |
|
|
font-size: 0.8rem; |
|
|
font-weight: 500; |
|
|
margin-bottom: 10px; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 1px; |
|
|
} |
|
|
|
|
|
.feature-card p { |
|
|
color: #8892b0; |
|
|
margin-bottom: 20px; |
|
|
line-height: 1.6; |
|
|
font-size: 0.95rem; |
|
|
} |
|
|
|
|
|
.feature-status { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 10px; |
|
|
margin-bottom: 15px; |
|
|
font-size: 0.85rem; |
|
|
color: #8892b0; |
|
|
} |
|
|
|
|
|
.status-indicator { |
|
|
width: 8px; |
|
|
height: 8px; |
|
|
border-radius: 50%; |
|
|
background: #28a745; |
|
|
box-shadow: 0 0 10px rgba(40, 167, 69, 0.5); |
|
|
} |
|
|
|
|
|
.status-indicator.coming-soon { |
|
|
background: #6c757d; |
|
|
box-shadow: none; |
|
|
} |
|
|
|
|
|
.feature-tags { |
|
|
display: flex; |
|
|
flex-wrap: wrap; |
|
|
gap: 8px; |
|
|
margin-top: auto; |
|
|
} |
|
|
|
|
|
.tag { |
|
|
background: rgba(255, 255, 255, 0.05); |
|
|
color: #8892b0; |
|
|
padding: 4px 12px; |
|
|
border-radius: 20px; |
|
|
font-size: 0.75rem; |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
} |
|
|
|
|
|
.coming-soon-badge { |
|
|
position: absolute; |
|
|
top: 15px; |
|
|
right: 15px; |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
color: #e4e4e4; |
|
|
padding: 4px 12px; |
|
|
border-radius: 20px; |
|
|
font-size: 0.75rem; |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
} |
|
|
|
|
|
.external-link-icon { |
|
|
position: absolute; |
|
|
top: 15px; |
|
|
right: 15px; |
|
|
font-size: 1rem; |
|
|
opacity: 0.6; |
|
|
color: #e4e4e4; |
|
|
} |
|
|
|
|
|
.no-results { |
|
|
text-align: center; |
|
|
padding: 60px 20px; |
|
|
color: #8892b0; |
|
|
} |
|
|
|
|
|
.no-results .icon { |
|
|
font-size: 4rem; |
|
|
margin-bottom: 20px; |
|
|
opacity: 0.5; |
|
|
} |
|
|
|
|
|
@media (max-width: 768px) { |
|
|
.header h1 { |
|
|
font-size: 2.5rem; |
|
|
} |
|
|
|
|
|
.features-grid { |
|
|
grid-template-columns: 1fr; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
|
|
|
<body> |
|
|
<div class="container"> |
|
|
<div class="header"> |
|
|
<h1>🛠️ Tools Collection</h1> |
|
|
<p>Simple, fast, and reliable utility tools for your daily needs</p> |
|
|
|
|
|
<div class="search-container"> |
|
|
<input type="text" id="search-box" class="search-box" |
|
|
placeholder="Search tools... (e.g., image, pdf, convert)"> |
|
|
<span class="search-icon">🔍</span> |
|
|
</div> |
|
|
|
|
|
<div class="filter-container"> |
|
|
<select id="category-dropdown" class="category-dropdown"> |
|
|
<option value="">All Categories</option> |
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="content"> |
|
|
<div class="features-grid" id="features-grid"> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="no-results" id="no-results" style="display: none;"> |
|
|
<div class="icon">🔍</div> |
|
|
<h3>No tools found</h3> |
|
|
<p>Try searching with different keywords like "image", "pdf", or "convert"</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
let allFeatures = {}; |
|
|
let expandedFeatures = []; |
|
|
let currentCategory = ''; |
|
|
let currentSearch = ''; |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => { |
|
|
await loadFeatures(); |
|
|
setupSearch(); |
|
|
setupCategoryFilter(); |
|
|
}); |
|
|
|
|
|
async function loadFeatures() { |
|
|
try { |
|
|
const response = await fetch('/api/features'); |
|
|
if (response.ok) { |
|
|
const data = await response.json(); |
|
|
allFeatures = data.features; |
|
|
expandFeatures(); |
|
|
populateCategories(); |
|
|
displayFeatures(expandedFeatures); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Error loading features:', error); |
|
|
|
|
|
loadMockData(); |
|
|
} |
|
|
} |
|
|
|
|
|
function loadMockData() { |
|
|
allFeatures = { |
|
|
"image": { |
|
|
"name": "Image Tools", |
|
|
"description": "HEIC to PNG/JPG conversion and metadata removal", |
|
|
"icon": "🖼️", |
|
|
"features": ["convert", "remove_metadata"], |
|
|
"folder": "image", |
|
|
"tags": ["image", "heic", "png", "jpg", "convert", "metadata"] |
|
|
}, |
|
|
"pdf": { |
|
|
"name": "PDF Tools", |
|
|
"description": "Convert images to PDF, merge PDFs, and more", |
|
|
"icon": "📄", |
|
|
"features": ["images_to_pdf"], |
|
|
"folder": "pdf", |
|
|
"tags": ["pdf", "merge", "convert", "images", "document"], |
|
|
"coming_soon": true |
|
|
}, |
|
|
|
|
|
"video": { |
|
|
"name": "Video Tools", |
|
|
"description": "Video player with playlist management - add videos and convert via ⋮ menu", |
|
|
"icon": "🎬", |
|
|
"features": ["player"], |
|
|
"folder": "video", |
|
|
"tags": ["video", "player", "media", "watch", "convert", "playlist"] |
|
|
} |
|
|
}; |
|
|
expandFeatures(); |
|
|
populateCategories(); |
|
|
displayFeatures(expandedFeatures); |
|
|
} |
|
|
|
|
|
function expandFeatures() { |
|
|
expandedFeatures = []; |
|
|
|
|
|
Object.entries(allFeatures).forEach(([categoryKey, categoryData]) => { |
|
|
|
|
|
if (categoryData.features && categoryData.features.length > 0) { |
|
|
categoryData.features.forEach(feature => { |
|
|
expandedFeatures.push({ |
|
|
id: `${categoryKey}-${feature}`, |
|
|
path: `${categoryKey}/${feature}`, |
|
|
name: `${categoryData.name} - ${formatFeatureName(feature)}`, |
|
|
description: getFeatureDescription(categoryKey, feature), |
|
|
icon: categoryData.icon, |
|
|
tags: [...(categoryData.tags || []), feature], |
|
|
coming_soon: categoryData.coming_soon || false, |
|
|
category: categoryKey, |
|
|
type: 'feature' |
|
|
}); |
|
|
}); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
function formatFeatureName(feature) { |
|
|
return feature.split('_') |
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1)) |
|
|
.join(' '); |
|
|
} |
|
|
|
|
|
function getFeatureDescription(category, feature) { |
|
|
const descriptions = { |
|
|
'image': { |
|
|
'convert': 'Convert HEIC images to PNG/JPG format', |
|
|
'remove_metadata': 'Remove metadata from image files', |
|
|
'remove_background': 'Remove background from images' |
|
|
}, |
|
|
'pdf': { |
|
|
'images_to_pdf': 'Convert multiple images into a single PDF' |
|
|
}, |
|
|
|
|
|
'video': { |
|
|
'player': 'Playlist-based player with video conversion via ⋮ menu' |
|
|
} |
|
|
}; |
|
|
|
|
|
return descriptions[category]?.[feature] || `${formatFeatureName(feature)} functionality`; |
|
|
} |
|
|
|
|
|
function populateCategories() { |
|
|
const dropdown = document.getElementById('category-dropdown'); |
|
|
const categories = Object.keys(allFeatures); |
|
|
|
|
|
categories.forEach(category => { |
|
|
const option = document.createElement('option'); |
|
|
option.value = category; |
|
|
option.textContent = allFeatures[category].name; |
|
|
dropdown.appendChild(option); |
|
|
}); |
|
|
} |
|
|
|
|
|
function displayFeatures(features) { |
|
|
const grid = document.getElementById('features-grid'); |
|
|
const noResults = document.getElementById('no-results'); |
|
|
|
|
|
if (features.length === 0) { |
|
|
grid.style.display = 'none'; |
|
|
noResults.style.display = 'block'; |
|
|
return; |
|
|
} |
|
|
|
|
|
grid.style.display = 'grid'; |
|
|
noResults.style.display = 'none'; |
|
|
|
|
|
grid.innerHTML = features.map((feature) => { |
|
|
const isComingSoon = feature.coming_soon || false; |
|
|
const isExternal = feature.path === 'video/player'; |
|
|
const href = isComingSoon ? '#' : `/${feature.path}`; |
|
|
|
|
|
return ` |
|
|
<a href="${href}" class="feature-card ${isComingSoon ? 'coming-soon' : ''} ${isExternal ? 'external' : ''}" |
|
|
${isComingSoon ? 'onclick="return false;"' : ''}> |
|
|
${isComingSoon ? '<div class="coming-soon-badge">Coming Soon</div>' : ''} |
|
|
${isExternal ? '<div class="external-link-icon">🔗</div>' : ''} |
|
|
|
|
|
<div class="feature-path">${feature.path}</div> |
|
|
|
|
|
<div class="feature-header"> |
|
|
<div class="feature-icon">${feature.icon}</div> |
|
|
<h3>${feature.name}</h3> |
|
|
</div> |
|
|
|
|
|
<p>${feature.description}</p> |
|
|
|
|
|
<div class="feature-status"> |
|
|
<div class="status-indicator ${isComingSoon ? 'coming-soon' : ''}" |
|
|
id="${feature.id}-status"></div> |
|
|
<span id="${feature.id}-status-text">${isComingSoon ? 'Coming Soon' : 'Ready'}</span> |
|
|
</div> |
|
|
|
|
|
<div class="feature-tags"> |
|
|
${feature.tags ? feature.tags.map(tag => `<span class="tag">${tag}</span>`).join('') : ''} |
|
|
</div> |
|
|
</a> |
|
|
`; |
|
|
}).join(''); |
|
|
} |
|
|
|
|
|
function setupSearch() { |
|
|
const searchBox = document.getElementById('search-box'); |
|
|
let debounceTimer; |
|
|
|
|
|
searchBox.addEventListener('input', (e) => { |
|
|
clearTimeout(debounceTimer); |
|
|
debounceTimer = setTimeout(() => { |
|
|
currentSearch = e.target.value.toLowerCase(); |
|
|
filterFeatures(); |
|
|
}, 300); |
|
|
}); |
|
|
} |
|
|
|
|
|
function setupCategoryFilter() { |
|
|
const dropdown = document.getElementById('category-dropdown'); |
|
|
|
|
|
dropdown.addEventListener('change', (e) => { |
|
|
currentCategory = e.target.value; |
|
|
filterFeatures(); |
|
|
}); |
|
|
} |
|
|
|
|
|
function filterFeatures() { |
|
|
let filteredFeatures = expandedFeatures; |
|
|
|
|
|
|
|
|
if (currentCategory) { |
|
|
filteredFeatures = filteredFeatures.filter(feature => |
|
|
feature.category === currentCategory |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
if (currentSearch) { |
|
|
filteredFeatures = filteredFeatures.filter(feature => |
|
|
feature.name.toLowerCase().includes(currentSearch) || |
|
|
feature.description.toLowerCase().includes(currentSearch) || |
|
|
feature.path.toLowerCase().includes(currentSearch) || |
|
|
feature.tags.some(tag => tag.toLowerCase().includes(currentSearch)) |
|
|
); |
|
|
} |
|
|
|
|
|
displayFeatures(filteredFeatures); |
|
|
} |
|
|
</script> |
|
|
</body> |
|
|
|
|
|
</html> |