Tools / index.html
jebin2's picture
remove status
5522d82
<!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: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40px 30px;
text-align: center;
}
.header h1 {
font-size: 3rem;
margin-bottom: 15px;
font-weight: 300;
}
.header p {
font-size: 1.2rem;
opacity: 0.9;
margin-bottom: 30px;
}
.search-container {
max-width: 500px;
margin: 0 auto;
position: relative;
margin-bottom: 20px;
}
.search-box {
width: 100%;
padding: 15px 50px 15px 20px;
border: none;
border-radius: 50px;
font-size: 1.1rem;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.search-box:focus {
outline: none;
background: white;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
}
.search-icon {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
font-size: 1.2rem;
color: #667eea;
}
.filter-container {
max-width: 300px;
margin: 0 auto;
}
.category-dropdown {
width: 100%;
padding: 12px 20px;
border: none;
border-radius: 50px;
font-size: 1rem;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
color: #495057;
cursor: pointer;
transition: all 0.3s ease;
}
.category-dropdown:focus {
outline: none;
background: white;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
}
.content {
padding: 40px;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 25px;
margin-top: 30px;
}
.feature-card {
background: white;
border-radius: 15px;
padding: 30px;
border: 1px solid #e9ecef;
transition: all 0.3s ease;
cursor: pointer;
text-decoration: none;
color: inherit;
display: block;
position: relative;
overflow: hidden;
}
.feature-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
transform: scaleX(0);
transition: transform 0.3s ease;
}
.feature-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 40px rgba(0,0,0,0.1);
}
.feature-card:hover::before {
transform: scaleX(1);
}
.feature-card.coming-soon {
opacity: 0.6;
cursor: not-allowed;
}
.feature-card.coming-soon:hover {
transform: none;
box-shadow: none;
}
.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: linear-gradient(135deg, #667eea15, #764ba215);
border-radius: 12px;
}
.feature-card h3 {
color: #495057;
font-size: 1.4rem;
font-weight: 600;
}
.feature-card p {
color: #6c757d;
margin-bottom: 20px;
line-height: 1.6;
}
.feature-status {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background: #28a745;
}
.status-indicator.busy {
background: #ffc107;
animation: pulse 2s infinite;
}
.status-indicator.coming-soon {
background: #6c757d;
}
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.7; }
100% { transform: scale(1); opacity: 1; }
}
.feature-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 15px;
}
.tag {
background: #f8f9fa;
color: #495057;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8rem;
border: 1px solid #e9ecef;
}
.coming-soon-badge {
position: absolute;
top: 15px;
right: 15px;
background: #6c757d;
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 500;
}
.stat-item h4 {
color: #667eea;
font-size: 1.5rem;
margin-bottom: 5px;
}
.stat-item p {
color: #6c757d;
font-size: 0.9rem;
}
.no-results {
text-align: center;
padding: 60px 20px;
color: #6c757d;
}
.no-results .icon {
font-size: 4rem;
margin-bottom: 20px;
opacity: 0.5;
}
.feature-path {
color: #667eea;
font-size: 0.9rem;
font-weight: 500;
margin-bottom: 5px;
}
@media (max-width: 768px) {
.header h1 {
font-size: 2rem;
}
.header p {
font-size: 1rem;
}
.content {
padding: 20px;
}
.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">
<!-- Features will be populated by JavaScript -->
</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 = '';
// Load features on page load
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);
// Fallback to mock data for demo
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
},
"audio": {
"name": "Audio Tools",
"description": "Convert audio formats and compress audio files",
"icon": "🎵",
"features": ["convert_audio", "compress_audio"],
"folder": "audio",
"tags": ["audio", "music", "convert", "compress", "mp3", "wav"],
"coming_soon": true
},
"video": {
"name": "Video Tools",
"description": "Basic video editing and format conversion",
"icon": "🎬",
"features": ["convert_video", "compress_video"],
"folder": "video",
"tags": ["video", "convert", "compress", "mp4", "avi", "editing"],
"coming_soon": true
}
};
expandFeatures();
populateCategories();
displayFeatures(expandedFeatures);
}
function expandFeatures() {
expandedFeatures = [];
Object.entries(allFeatures).forEach(([categoryKey, categoryData]) => {
// Add the main category
// expandedFeatures.push({
// id: categoryKey,
// path: categoryKey,
// name: categoryData.name,
// description: categoryData.description,
// icon: categoryData.icon,
// tags: categoryData.tags || [],
// coming_soon: categoryData.coming_soon || false,
// category: categoryKey,
// type: 'category'
// });
// Add individual features
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'
},
'pdf': {
'images_to_pdf': 'Convert multiple images into a single PDF'
},
'audio': {
'convert_audio': 'Convert between different audio formats',
'compress_audio': 'Reduce audio file size while maintaining quality'
},
'video': {
'convert_video': 'Convert between different video formats',
'compress_video': 'Reduce video file size while maintaining quality'
}
};
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 href = isComingSoon ? '#' : `/${feature.path}`;
return `
<a href="${href}" class="feature-card ${isComingSoon ? 'coming-soon' : ''}"
${isComingSoon ? 'onclick="return false;"' : ''}>
${isComingSoon ? '<div class="coming-soon-badge">Coming Soon</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;
// Filter by category
if (currentCategory) {
filteredFeatures = filteredFeatures.filter(feature =>
feature.category === currentCategory
);
}
// Filter by search term
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>