plugin-library / index.html
Boobs00's picture
Add 3 files
c844a46 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hermit Dev Tools Pro - Ultimate Plugin Ecosystem</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
'hermit-blue': '#2563eb',
'hermit-purple': '#7c3aed',
'hermit-pink': '#ec4899',
'hermit-dark': '#1e293b',
'hermit-light': '#f8fafc',
'hermit-teal': '#0d9488',
'hermit-orange': '#ea580c',
},
fontFamily: {
'sans': ['Inter', 'ui-sans-serif', 'system-ui'],
'mono': ['Fira Code', 'ui-monospace', 'SFMono-Regular'],
},
animation: {
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'bounce-slow': 'bounce 2s infinite',
'spin-slow': 'spin 3s linear infinite',
}
}
}
}
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Fira+Code:wght@400;500&display=swap">
<style>
:root {
--hermit-accent: #2563eb;
--hermit-secondary: #7c3aed;
--hermit-danger: #dc2626;
--hermit-success: #16a34a;
--hermit-warning: #d97706;
}
body {
font-family: 'Inter', sans-serif;
background-color: #f8fafc;
color: #1e293b;
transition: all 0.3s ease;
}
.dark-mode {
background-color: #0f172a;
color: #f8fafc;
}
.code-font {
font-family: 'Fira Code', monospace;
font-feature-settings: 'calt' 1;
}
/* Plugin Card Enhancements */
.plugin-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid #e2e8f0;
position: relative;
overflow: hidden;
background: linear-gradient(145deg, #ffffff, #f8fafc);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
}
.dark-mode .plugin-card {
background: linear-gradient(145deg, #1e293b, #0f172a);
border-color: #334155;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
}
.plugin-card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
border-color: var(--hermit-accent);
}
.plugin-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 0;
background: linear-gradient(to bottom, var(--hermit-accent), var(--hermit-purple));
transition: height 0.3s ease;
}
.plugin-card:hover::before {
height: 100%;
}
/* Star Rating */
.star-rating {
color: #fbbf24;
}
.star-rating .empty {
color: #e2e8f0;
}
.dark-mode .star-rating .empty {
color: #334155;
}
/* Tabs */
.tab-active {
border-bottom: 3px solid var(--hermit-accent);
color: var(--hermit-accent);
font-weight: 600;
}
/* Badges */
.plugin-badge {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 10;
}
/* Dropdown */
.dropdown-content {
display: none;
position: absolute;
right: 0;
min-width: 200px;
z-index: 50;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
border-radius: 0.5rem;
background-color: white;
}
.dark-mode .dropdown-content {
background-color: #1e293b;
border: 1px solid #334155;
}
.dropdown:hover .dropdown-content {
display: block;
animation: fadeIn 0.2s ease-out;
}
/* Progress Bar */
.progress-bar {
height: 6px;
border-radius: 3px;
background-color: #e5e7eb;
}
.dark-mode .progress-bar {
background-color: #334155;
}
.progress-fill {
height: 100%;
border-radius: 3px;
background: linear-gradient(to right, var(--hermit-accent), var(--hermit-purple));
transition: width 0.3s ease;
}
/* Loading Skeletons */
.skeleton {
animation: pulse 2s infinite;
background-color: #e5e7eb;
border-radius: 4px;
}
.dark-mode .skeleton {
background-color: #334155;
}
/* Tags */
.tag {
transition: all 0.2s ease;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
}
.tag:hover {
transform: scale(1.05);
}
/* Modals */
.modal-overlay {
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
}
.modal-content {
animation: modalFadeIn 0.3s ease-out;
max-height: 90vh;
overflow-y: auto;
}
/* Plugin Icons */
.plugin-icon {
transition: all 0.3s ease;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12px;
background: linear-gradient(135deg, rgba(37, 99, 235, 0.1), rgba(124, 58, 237, 0.1));
}
.dark-mode .plugin-icon {
background: linear-gradient(135deg, rgba(37, 99, 235, 0.2), rgba(124, 58, 237, 0.2));
}
.plugin-card:hover .plugin-icon {
transform: rotate(10deg) scale(1.1);
}
/* Search Input */
.search-input {
transition: all 0.3s ease;
background-color: white;
}
.dark-mode .search-input {
background-color: #1e293b;
border-color: #334155;
color: #f8fafc;
}
.search-input:focus {
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
}
/* Buttons */
.install-btn {
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.install-btn::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 5px;
height: 5px;
background: rgba(255, 255, 255, 0.5);
opacity: 0;
border-radius: 100%;
transform: scale(1, 1) translate(-50%, -50%);
transform-origin: 50% 50%;
}
.install-btn:focus:not(:active)::after {
animation: ripple 1s ease-out;
}
/* Notifications */
.notification-badge {
animation: ping 2s cubic-bezier(0, 0, 0.2, 1) infinite;
}
/* Sidebar */
.sidebar {
transition: all 0.3s ease;
}
.sidebar-item {
transition: all 0.2s ease;
border-radius: 0.375rem;
}
.sidebar-item:hover {
background-color: rgba(37, 99, 235, 0.1);
}
.sidebar-item.active {
background-color: rgba(37, 99, 235, 0.1);
border-left: 3px solid var(--hermit-accent);
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes modalFadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes ripple {
0% {
transform: scale(0, 0);
opacity: 0.5;
}
100% {
transform: scale(20, 20);
opacity: 0;
}
}
@keyframes ping {
75%, 100% {
transform: scale(1.2);
opacity: 0;
}
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
.dark-mode ::-webkit-scrollbar-track {
background: #1e293b;
}
.dark-mode ::-webkit-scrollbar-thumb {
background: #475569;
}
/* Tooltips */
.tooltip {
position: relative;
}
.tooltip .tooltip-text {
visibility: hidden;
width: 120px;
background-color: #1e293b;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -60px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip .tooltip-text::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #1e293b transparent transparent transparent;
}
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
/* Code Block Styling */
.code-block {
background-color: #f8fafc;
border-radius: 0.5rem;
padding: 1rem;
font-family: 'Fira Code', monospace;
font-size: 0.875rem;
overflow-x: auto;
}
.dark-mode .code-block {
background-color: #1e293b;
border: 1px solid #334155;
}
/* Toast Notifications */
.toast {
position: fixed;
bottom: 20px;
right: 20px;
padding: 1rem;
border-radius: 0.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
z-index: 100;
display: flex;
align-items: center;
transform: translateX(150%);
transition: transform 0.3s ease;
}
.toast.show {
transform: translateX(0);
}
.toast.success {
background-color: #16a34a;
color: white;
}
.toast.error {
background-color: #dc2626;
color: white;
}
.toast.warning {
background-color: #d97706;
color: white;
}
.toast.info {
background-color: #2563eb;
color: white;
}
/* Responsive Grid */
.plugin-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
/* Loading Spinner */
.spinner {
animation: spin-slow 2s linear infinite;
}
/* Error Boundary */
.error-boundary {
border: 1px solid var(--hermit-danger);
border-radius: 0.5rem;
padding: 1rem;
background-color: rgba(220, 38, 38, 0.1);
}
.dark-mode .error-boundary {
background-color: rgba(220, 38, 38, 0.2);
}
/* Empty State */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
text-align: center;
}
/* Context Menu */
.context-menu {
position: absolute;
z-index: 100;
background-color: white;
border-radius: 0.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
padding: 0.5rem 0;
min-width: 200px;
display: none;
}
.dark-mode .context-menu {
background-color: #1e293b;
border: 1px solid #334155;
}
.context-menu-item {
padding: 0.5rem 1rem;
cursor: pointer;
transition: all 0.2s ease;
}
.context-menu-item:hover {
background-color: rgba(37, 99, 235, 0.1);
}
.dark-mode .context-menu-item:hover {
background-color: rgba(37, 99, 235, 0.2);
}
/* Tour Highlight */
.tour-highlight {
position: relative;
z-index: 999;
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
border-radius: 0.5rem;
animation: pulse-slow 2s infinite;
}
/* Plugin Health Indicator */
.health-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
margin-right: 0.5rem;
}
.health-excellent {
background-color: #16a34a;
box-shadow: 0 0 5px #16a34a;
}
.health-good {
background-color: #65a30d;
box-shadow: 0 0 5px #65a30d;
}
.health-fair {
background-color: #d97706;
box-shadow: 0 0 5px #d97706;
}
.health-poor {
background-color: #dc2626;
box-shadow: 0 0 5px #dc2626;
}
/* Version Badge */
.version-badge {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
background-color: rgba(37, 99, 235, 0.1);
color: var(--hermit-accent);
}
.dark-mode .version-badge {
background-color: rgba(37, 99, 235, 0.2);
}
/* Dependency Tree */
.dependency-tree {
border-left: 2px solid #e2e8f0;
padding-left: 1rem;
margin-left: 1rem;
}
.dark-mode .dependency-tree {
border-left-color: #334155;
}
.dependency-item {
position: relative;
padding-left: 1.5rem;
margin-bottom: 0.5rem;
}
.dependency-item::before {
content: '';
position: absolute;
left: 0;
top: 50%;
width: 1rem;
height: 2px;
background-color: #e2e8f0;
}
.dark-mode .dependency-item::before {
background-color: #334155;
}
/* Plugin Comparison */
.comparison-table {
width: 100%;
border-collapse: collapse;
}
.comparison-table th, .comparison-table td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}
.dark-mode .comparison-table th, .dark-mode .comparison-table td {
border-bottom-color: #334155;
}
.comparison-table tr:last-child td {
border-bottom: none;
}
/* Plugin Stats */
.stats-card {
background-color: white;
border-radius: 0.5rem;
padding: 1rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.dark-mode .stats-card {
background-color: #1e293b;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
/* Responsive Adjustments */
@media (max-width: 768px) {
.plugin-grid {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.sidebar {
transform: translateX(-100%);
position: fixed;
top: 0;
left: 0;
height: 100vh;
z-index: 40;
background-color: white;
}
.dark-mode .sidebar {
background-color: #1e293b;
}
.sidebar.open {
transform: translateX(0);
}
.mobile-menu-button {
display: block;
}
}
</style>
</head>
<body class="bg-gray-50 dark:bg-gray-900 transition-colors duration-300">
<!-- Toast Notification Container -->
<div id="toast-container"></div>
<!-- Mobile Menu Button (hidden on desktop) -->
<button id="mobile-menu-button" class="md:hidden fixed top-4 left-4 z-50 p-2 rounded-full bg-white dark:bg-gray-800 shadow-lg">
<i class="fas fa-bars text-gray-800 dark:text-gray-200"></i>
</button>
<!-- Main Container -->
<div class="flex min-h-screen">
<!-- Sidebar Navigation -->
<aside id="sidebar" class="sidebar w-64 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 p-4 hidden md:block">
<div class="flex items-center justify-between mb-8">
<div class="flex items-center space-x-2">
<div class="w-8 h-8 rounded-full bg-hermit-blue flex items-center justify-center">
<i class="fas fa-code text-white"></i>
</div>
<span class="text-xl font-bold text-gray-800 dark:text-gray-200">Hermit Pro</span>
</div>
<button id="dark-mode-toggle" class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">
<i class="fas fa-moon text-gray-600 dark:text-yellow-300"></i>
</button>
</div>
<div class="mb-6">
<div class="relative">
<input type="text" placeholder="Search plugins..." class="search-input w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-hermit-blue focus:border-transparent bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200">
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
</div>
<nav>
<ul class="space-y-1">
<li>
<a href="#" class="sidebar-item active flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-800 dark:text-gray-200">
<i class="fas fa-box-open mr-3 text-gray-500 dark:text-gray-400"></i>
Plugin Library
</a>
</li>
<li>
<a href="#" class="sidebar-item flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200">
<i class="fas fa-cube mr-3 text-gray-500 dark:text-gray-400"></i>
My Plugins
</a>
</li>
<li>
<a href="#" class="sidebar-item flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200">
<i class="fas fa-cloud-download-alt mr-3 text-gray-500 dark:text-gray-400"></i>
Updates
<span class="ml-auto px-2 py-0.5 rounded-full text-xs font-medium bg-hermit-blue text-white">3</span>
</a>
</li>
<li>
<a href="#" class="sidebar-item flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200">
<i class="fas fa-star mr-3 text-gray-500 dark:text-gray-400"></i>
Favorites
</a>
</li>
<li>
<a href="#" class="sidebar-item flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200">
<i class="fas fa-chart-line mr-3 text-gray-500 dark:text-gray-400"></i>
Analytics
</a>
</li>
<li>
<a href="#" class="sidebar-item flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200">
<i class="fas fa-cog mr-3 text-gray-500 dark:text-gray-400"></i>
Settings
</a>
</li>
</ul>
</nav>
<div class="mt-8 pt-4 border-t border-gray-200 dark:border-gray-700">
<h3 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">Categories</h3>
<ul class="space-y-1">
<li>
<a href="#" class="flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200">
<span class="w-2 h-2 mr-3 rounded-full bg-hermit-blue"></span>
All Plugins
</a>
</li>
<li>
<a href="#" class="flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200">
<span class="w-2 h-2 mr-3 rounded-full bg-hermit-purple"></span>
UI Components
</a>
</li>
<li>
<a href="#" class="flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200">
<span class="w-2 h-2 mr-3 rounded-full bg-hermit-pink"></span>
Utilities
</a>
</li>
<li>
<a href="#" class="flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200">
<span class="w-2 h-2 mr-3 rounded-full bg-hermit-teal"></span>
Integrations
</a>
</li>
<li>
<a href="#" class="flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-200">
<span class="w-2 h-2 mr-3 rounded-full bg-hermit-orange"></span>
Developer Tools
</a>
</li>
</ul>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 p-6">
<!-- Header -->
<header class="mb-8">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800 dark:text-gray-200">Plugin Library</h1>
<div class="flex space-x-4">
<button id="new-plugin-btn" class="px-4 py-2 bg-hermit-blue text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center">
<i class="fas fa-plus mr-2"></i> New Plugin
</button>
<div class="dropdown relative">
<button class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors flex items-center">
<i class="fas fa-sliders-h mr-2"></i> Filters
</button>
<div class="dropdown-content mt-2 p-2">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Most Popular</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Recently Added</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Highest Rated</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">Verified Only</a>
</div>
</div>
</div>
</div>
<div class="flex border-b border-gray-200 dark:border-gray-700">
<button class="tab-active px-4 py-2 text-sm font-medium">All</button>
<button class="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200">Installed</button>
<button class="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200">Updates</button>
<button class="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200">Beta</button>
</div>
</header>
<!-- Stats Overview -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<div class="stats-card">
<div class="flex items-center">
<div class="p-3 rounded-full bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 mr-4">
<i class="fas fa-box-open"></i>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Total Plugins</p>
<p class="text-2xl font-bold text-gray-800 dark:text-gray-200">1,248</p>
</div>
</div>
</div>
<div class="stats-card">
<div class="flex items-center">
<div class="p-3 rounded-full bg-green-100 dark:bg-green-900 text-green-600 dark:text-green-300 mr-4">
<i class="fas fa-download"></i>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Installed</p>
<p class="text-2xl font-bold text-gray-800 dark:text-gray-200">24</p>
</div>
</div>
</div>
<div class="stats-card">
<div class="flex items-center">
<div class="p-3 rounded-full bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-300 mr-4">
<i class="fas fa-sync-alt"></i>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Updates</p>
<p class="text-2xl font-bold text-gray-800 dark:text-gray-200">3</p>
</div>
</div>
</div>
<div class="stats-card">
<div class="flex items-center">
<div class="p-3 rounded-full bg-yellow-100 dark:bg-yellow-900 text-yellow-600 dark:text-yellow-300 mr-4">
<i class="fas fa-star"></i>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Favorites</p>
<p class="text-2xl font-bold text-gray-800 dark:text-gray-200">8</p>
</div>
</div>
</div>
</div>
<!-- Plugin Grid -->
<div class="plugin-grid" id="plugin-container">
<!-- Plugin cards will be dynamically loaded here -->
</div>
<!-- Loading State -->
<div id="loading-state" class="flex flex-col items-center justify-center py-12">
<div class="spinner w-12 h-12 border-4 border-hermit-blue border-t-transparent rounded-full mb-4"></div>
<p class="text-gray-600 dark:text-gray-400">Loading plugins...</p>
</div>
<!-- Empty State -->
<div id="empty-state" class="empty-state hidden">
<div class="w-24 h-24 bg-gray-100 dark:bg-gray-700 rounded-full flex items-center justify-center mb-4">
<i class="fas fa-box-open text-3xl text-gray-400"></i>
</div>
<h3 class="text-xl font-medium text-gray-800 dark:text-gray-200 mb-2">No plugins found</h3>
<p class="text-gray-600 dark:text-gray-400 mb-6">Try adjusting your search or filters</p>
<button class="px-4 py-2 bg-hermit-blue text-white rounded-lg hover:bg-blue-700 transition-colors">
Reset Filters
</button>
</div>
<!-- Pagination -->
<div class="flex justify-between items-center mt-8">
<button class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50" disabled>
Previous
</button>
<div class="flex space-x-1">
<button class="w-10 h-10 rounded-lg bg-hermit-blue text-white">1</button>
<button class="w-10 h-10 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">2</button>
<button class="w-10 h-10 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">3</button>
<span class="flex items-center px-2">...</span>
<button class="w-10 h-10 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">8</button>
</div>
<button class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
Next
</button>
</div>
</main>
</div>
<!-- Plugin Details Modal -->
<div id="plugin-modal" class="fixed inset-0 z-50 hidden">
<div class="modal-overlay absolute inset-0"></div>
<div class="flex items-center justify-center min-h-screen">
<div class="modal-content bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-4xl mx-4">
<!-- Modal content will be dynamically loaded here -->
</div>
</div>
</div>
<!-- New Plugin Modal -->
<div id="new-plugin-modal" class="fixed inset-0 z-50 hidden">
<div class="modal-overlay absolute inset-0"></div>
<div class="flex items-center justify-center min-h-screen">
<div class="modal-content bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-2xl mx-4">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-gray-800 dark:text-gray-200">Create New Plugin</h3>
<button id="close-new-plugin-modal" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-4">
<div>
<label for="plugin-name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Plugin Name</label>
<input type="text" id="plugin-name" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-hermit-blue focus:border-transparent bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200">
</div>
<div>
<label for="plugin-description" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Description</label>
<textarea id="plugin-description" rows="3" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-hermit-blue focus:border-transparent bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200"></textarea>
</div>
<div>
<label for="plugin-category" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Category</label>
<select id="plugin-category" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-hermit-blue focus:border-transparent bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200">
<option value="ui">UI Components</option>
<option value="utility">Utilities</option>
<option value="integration">Integrations</option>
<option value="dev">Developer Tools</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Visibility</label>
<div class="flex space-x-4">
<label class="inline-flex items-center">
<input type="radio" name="visibility" value="public" checked class="h-4 w-4 text-hermit-blue focus:ring-hermit-blue border-gray-300 dark:border-gray-600">
<span class="ml-2 text-gray-700 dark:text-gray-300">Public</span>
</label>
<label class="inline-flex items-center">
<input type="radio" name="visibility" value="private" class="h-4 w-4 text-hermit-blue focus:ring-hermit-blue border-gray-300 dark:border-gray-600">
<span class="ml-2 text-gray-700 dark:text-gray-300">Private</span>
</label>
</div>
</div>
<div class="pt-4 border-t border-gray-200 dark:border-gray-700">
<div class="flex justify-end space-x-3">
<button id="cancel-new-plugin" class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
Cancel
</button>
<button id="create-plugin" class="px-4 py-2 bg-hermit-blue text-white rounded-lg hover:bg-blue-700 transition-colors">
Create Plugin
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Error Boundary (hidden by default) -->
<div id="error-boundary" class="error-boundary fixed bottom-4 left-4 max-w-md p-4 hidden">
<div class="flex items-start">
<div class="flex-shrink-0">
<i class="fas fa-exclamation-triangle text-hermit-danger"></i>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-gray-800 dark:text-gray-200">Something went wrong</h3>
<div class="mt-2 text-sm text-gray-700 dark:text-gray-300">
<p id="error-message">Failed to load plugins. Please try again later.</p>
</div>
<div class="mt-4">
<button id="retry-button" class="text-sm font-medium text-hermit-blue hover:text-blue-700 dark:hover:text-blue-500">
Retry
</button>
</div>
</div>
</div>
</div>
<script>
// Global state
const state = {
darkMode: localStorage.getItem('darkMode') === 'true',
plugins: [],
filteredPlugins: [],
currentPage: 1,
pluginsPerPage: 12,
selectedPlugin: null,
isLoading: true,
error: null
};
// DOM Elements
const elements = {
body: document.body,
darkModeToggle: document.getElementById('dark-mode-toggle'),
mobileMenuButton: document.getElementById('mobile-menu-button'),
sidebar: document.getElementById('sidebar'),
pluginContainer: document.getElementById('plugin-container'),
loadingState: document.getElementById('loading-state'),
emptyState: document.getElementById('empty-state'),
pluginModal: document.getElementById('plugin-modal'),
newPluginBtn: document.getElementById('new-plugin-btn'),
newPluginModal: document.getElementById('new-plugin-modal'),
closeNewPluginModal: document.getElementById('close-new-plugin-modal'),
cancelNewPlugin: document.getElementById('cancel-new-plugin'),
createPlugin: document.getElementById('create-plugin'),
errorBoundary: document.getElementById('error-boundary'),
errorMessage: document.getElementById('error-message'),
retryButton: document.getElementById('retry-button'),
toastContainer: document.getElementById('toast-container')
};
// Initialize the app
function init() {
setupEventListeners();
applyDarkMode();
fetchPlugins();
}
// Set up event listeners
function setupEventListeners() {
// Dark mode toggle
elements.darkModeToggle.addEventListener('click', toggleDarkMode);
// Mobile menu toggle
elements.mobileMenuButton.addEventListener('click', toggleMobileMenu);
// New plugin modal
elements.newPluginBtn.addEventListener('click', () => {
elements.newPluginModal.classList.remove('hidden');
});
elements.closeNewPluginModal.addEventListener('click', () => {
elements.newPluginModal.classList.add('hidden');
});
elements.cancelNewPlugin.addEventListener('click', () => {
elements.newPluginModal.classList.add('hidden');
});
// Create plugin
elements.createPlugin.addEventListener('click', createNewPlugin);
// Error retry
elements.retryButton.addEventListener('click', fetchPlugins);
// Close modals when clicking outside
document.addEventListener('click', (e) => {
if (e.target === elements.pluginModal) {
elements.pluginModal.classList.add('hidden');
}
if (e.target === elements.newPluginModal) {
elements.newPluginModal.classList.add('hidden');
}
});
}
// Toggle dark mode
function toggleDarkMode() {
state.darkMode = !state.darkMode;
localStorage.setItem('darkMode', state.darkMode);
applyDarkMode();
}
// Apply dark mode styles
function applyDarkMode() {
if (state.darkMode) {
elements.body.classList.add('dark-mode');
elements.darkModeToggle.innerHTML = '<i class="fas fa-sun text-yellow-300"></i>';
} else {
elements.body.classList.remove('dark-mode');
elements.darkModeToggle.innerHTML = '<i class="fas fa-moon text-gray-600"></i>';
}
}
// Toggle mobile menu
function toggleMobileMenu() {
elements.sidebar.classList.toggle('open');
}
// Fetch plugins from API
function fetchPlugins() {
state.isLoading = true;
state.error = null;
// Show loading state
elements.loadingState.classList.remove('hidden');
elements.emptyState.classList.add('hidden');
elements.errorBoundary.classList.add('hidden');
// Simulate API call with timeout
setTimeout(() => {
try {
// In a real app, this would be an actual API call
// axios.get('/api/plugins')
// .then(response => {
// state.plugins = response.data;
// renderPlugins();
// })
// .catch(error => {
// handleError(error);
// });
// Mock data for demonstration
state.plugins = generateMockPlugins();
renderPlugins();
} catch (error) {
handleError(error);
}
}, 1000);
}
// Render plugins to the DOM
function renderPlugins() {
state.isLoading = false;
elements.loadingState.classList.add('hidden');
if (state.plugins.length === 0) {
elements.emptyState.classList.remove('hidden');
return;
}
elements.pluginContainer.innerHTML = '';
// Calculate pagination
const startIndex = (state.currentPage - 1) * state.pluginsPerPage;
const endIndex = startIndex + state.pluginsPerPage;
const paginatedPlugins = state.plugins.slice(startIndex, endIndex);
paginatedPlugins.forEach(plugin => {
const pluginCard = createPluginCard(plugin);
elements.pluginContainer.appendChild(pluginCard);
});
}
// Create a plugin card element
function createPluginCard(plugin) {
const card = document.createElement('div');
card.className = 'plugin-card bg-white dark:bg-gray-800 rounded-lg p-5 hover:shadow-lg transition-all duration-300';
card.dataset.id = plugin.id;
// Add click event to open modal
card.addEventListener('click', () => {
openPluginModal(plugin);
});
// Badge for featured plugins
const badge = plugin.featured ?
`<div class="plugin-badge bg-hermit-pink text-white">
<i class="fas fa-star"></i>
</div>` : '';
// Rating stars
const stars = Array(5).fill('')
.map((_, i) =>
i < Math.floor(plugin.rating) ?
`<i class="fas fa-star"></i>` :
(i < plugin.rating ? `<i class="fas fa-star-half-alt"></i>` : `<i class="far fa-star empty"></i>`)
).join('');
// Tags
const tags = plugin.tags.map(tag =>
`<span class="tag inline-block bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 mr-2 mb-2 px-2 py-1 rounded-full text-xs">
${tag}
</span>`
).join('');
card.innerHTML = `
${badge}
<div class="flex items-start mb-4">
<div class="plugin-icon mr-4">
<i class="${plugin.icon} text-hermit-blue text-xl"></i>
</div>
<div class="flex-1">
<div class="flex justify-between items-start">
<h3 class="font-bold text-lg text-gray-800 dark:text-gray-200">${plugin.name}</h3>
<span class="version-badge">v${plugin.version}</span>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">${plugin.author}</p>
</div>
</div>
<p class="text-gray-700 dark:text-gray-300 text-sm mb-4">${plugin.description}</p>
<div class="flex items-center justify-between mb-4">
<div class="star-rating text-sm">
${stars}
<span class="ml-1 text-gray-600 dark:text-gray-400">(${plugin.rating.toFixed(1)})</span>
</div>
<span class="text-xs text-gray-500 dark:text-gray-400">${plugin.downloads.toLocaleString()} installs</span>
</div>
<div class="mb-4">
${tags}
</div>
<button class="install-btn w-full py-2 bg-hermit-blue text-white rounded-lg hover:bg-blue-700 transition-colors">
${plugin.installed ? 'Update' : 'Install'}
</button>
`;
return card;
}
// Open plugin modal
function openPluginModal(plugin) {
state.selectedPlugin = plugin;
// In a real app, we might fetch more detailed plugin info here
const modalContent = `
<div class="p-6">
<div class="flex justify-between items-start mb-6">
<div class="flex items-start">
<div class="plugin-icon mr-4">
<i class="${plugin.icon} text-hermit-blue text-2xl"></i>
</div>
<div>
<h3 class="text-xl font-bold text-gray-800 dark:text-gray-200">${plugin.name}</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">by ${plugin.author}</p>
</div>
</div>
<button id="close-plugin-modal" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<div class="md:col-span-2">
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4 mb-4">
<h4 class="font-medium text-gray-800 dark:text-gray-200 mb-2">Description</h4>
<p class="text-gray-700 dark:text-gray-300">${plugin.description}</p>
</div>
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4 mb-4">
<h4 class="font-medium text-gray-800 dark:text-gray-200 mb-2">Features</h4>
<ul class="list-disc pl-5 text-gray-700 dark:text-gray-300 space-y-1">
${plugin.features.map(feature => `<li>${feature}</li>`).join('')}
</ul>
</div>
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
<h4 class="font-medium text-gray-800 dark:text-gray-200 mb-2">Installation</h4>
<div class="code-block mb-2">
<code>npm install ${plugin.packageName}</code>
</div>
<div class="code-block">
<code>yarn add ${plugin.packageName}</code>
</div>
</div>
</div>
<div>
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4 mb-4">
<div class="flex items-center justify-between mb-2">
<h4 class="font-medium text-gray-800 dark:text-gray-200">Details</h4>
<span class="version-badge">v${plugin.version}</span>
</div>
<div class="space-y-3">
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Category</p>
<p class="text-sm text-gray-700 dark:text-gray-300">${plugin.category}</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Last Updated</p>
<p class="text-sm text-gray-700 dark:text-gray-300">${plugin.lastUpdated}</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">License</p>
<p class="text-sm text-gray-700 dark:text-gray-300">${plugin.license}</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Compatibility</p>
<p class="text-sm text-gray-700 dark:text-gray-300">${plugin.compatibility}</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Dependencies</p>
<div class="dependency-tree">
${plugin.dependencies.map(dep => `
<div class="dependency-item">
<span class="text-sm text-gray-700 dark:text-gray-300">${dep}</span>
</div>
`).join('')}
</div>
</div>
</div>
</div>
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4 mb-4">
<h4 class="font-medium text-gray-800 dark:text-gray-200 mb-3">Stats</h4>
<div class="space-y-3">
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-700 dark:text-gray-300">Downloads</span>
<span class="text-gray-600 dark:text-gray-400">${plugin.downloads.toLocaleString()}</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${Math.min(100, plugin.downloads / 10000 * 100)}%"></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-700 dark:text-gray-300">Rating</span>
<span class="text-gray-600 dark:text-gray-400">${plugin.rating.toFixed(1)}/5</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${plugin.rating / 5 * 100}%"></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span class="text-gray-700 dark:text-gray-300">Health</span>
<span class="flex items-center">
<span class="health-indicator health-${plugin.health} mr-1"></span>
<span class="text-gray-600 dark:text-gray-400 capitalize">${plugin.health}</span>
</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${plugin.health === 'excellent' ? 100 : plugin.health === 'good' ? 75 : plugin.health === 'fair' ? 50 : 25}%"></div>
</div>
</div>
</div>
</div>
<button class="w-full py-2 bg-hermit-blue text-white rounded-lg hover:bg-blue-700 transition-colors mb-3">
${plugin.installed ? 'Update Plugin' : 'Install Plugin'}
</button>
<button class="w-full py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors flex items-center justify-center">
<i class="far fa-star mr-2"></i> Add to Favorites
</button>
</div>
</div>
<div class="border-t border-gray-200 dark:border-gray-700 pt-4">
<h4 class="font-medium text-gray-800 dark:text-gray-200 mb-3">Reviews</h4>
<div class="space-y-4">
${plugin.reviews.slice(0, 3).map(review => `
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
<div class="flex justify-between mb-2">
<div class="flex items-center">
<div class="star-rating text-sm mr-2">
${Array(5).fill('')
.map((_, i) =>
i < review.rating ?
`<i class="fas fa-star"></i>` :
`<i class="far fa-star empty"></i>`
).join('')}
</div>
<span class="text-sm font-medium text-gray-800 dark:text-gray-200">${review.user}</span>
</div>
<span class="text-xs text-gray-500 dark:text-gray-400">${review.date}</span>
</div>
<p class="text-sm text-gray-700 dark:text-gray-300">${review.comment}</p>
</div>
`).join('')}
<button class="text-sm text-hermit-blue hover:text-blue-700 dark:hover:text-blue-500">
View all ${plugin.reviews.length} reviews
</button>
</div>
</div>
</div>
`;
elements.pluginModal.querySelector('.modal-content').innerHTML = modalContent;
elements.pluginModal.classList.remove('hidden');
// Add event listener to close button
document.getElementById('close-plugin-modal').addEventListener('click', () => {
elements.pluginModal.classList.add('hidden');
});
}
// Create new plugin
function createNewPlugin() {
const name = document.getElementById('plugin-name').value.trim();
const description = document.getElementById('plugin-description').value.trim();
const category = document.getElementById('plugin-category').value;
const visibility = document.querySelector('input[name="visibility"]:checked').value;
if (!name) {
showToast('Plugin name is required', 'error');
return;
}
// Simulate API call
setTimeout(() => {
showToast('Plugin created successfully!', 'success');
elements.newPluginModal.classList.add('hidden');
// Reset form
document.getElementById('plugin-name').value = '';
document.getElementById('plugin-description').value = '';
// In a real app, we would add the new plugin to the list
// and refresh the view
}, 1000);
}
// Handle errors
function handleError(error) {
state.isLoading = false;
state.error = error;
elements.loadingState.classList.add('hidden');
elements.errorBoundary.classList.remove('hidden');
elements.errorMessage.textContent = error.message || 'Failed to load plugins. Please try again later.';
console.error('Error:', error);
}
// Show toast notification
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerHTML = `
<i class="fas ${type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-exclamation-circle' : type === 'warning' ? 'fa-exclamation-triangle' : 'fa-info-circle'} mr-2"></i>
${message}
`;
elements.toastContainer.appendChild(toast);
// Show toast
setTimeout(() => {
toast.classList.add('show');
}, 10);
// Hide after delay
setTimeout(() => {
toast.classList.remove('show');
// Remove after animation
setTimeout(() => {
toast.remove();
}, 300);
}, 3000);
}
// Generate mock plugins for demonstration
function generateMockPlugins() {
const categories = ['UI Components', 'Utilities', 'Integrations', 'Developer Tools'];
const icons = [
'fas fa-palette',
'fas fa-cube',
'fas fa-plug',
'fas fa-code',
'fas fa-chart-bar',
'fas fa-database',
'fas fa-server',
'fas fa-mobile-alt'
];
const plugins = [];
for (let i = 1; i <= 24; i++) {
const category = categories[Math.floor(Math.random() * categories.length)];
const icon = icons[Math.floor(Math.random() * icons.length)];
const rating = (Math.random() * 2 + 3).toFixed(1);
const downloads = Math.floor(Math.random() * 100000);
const version = `${Math.floor(Math.random() * 5)}.${Math.floor(Math.random() * 10)}.${Math.floor(Math.random() * 10)}`;
const featured = Math.random() > 0.7;
const installed = Math.random() > 0.8;
const healthLevels = ['excellent', 'good', 'fair', 'poor'];
const health = healthLevels[Math.floor(Math.random() * healthLevels.length)];
const tags = [];
const tagCount = Math.floor(Math.random() * 3) + 1;
const possibleTags = ['React', 'Vue', 'Angular', 'JavaScript', 'TypeScript', 'CSS', 'HTML', 'Node.js', 'Frontend', 'Backend'];
for (let j = 0; j < tagCount; j++) {
const randomTag = possibleTags[Math.floor(Math.random() * possibleTags.length)];
if (!tags.includes(randomTag)) {
tags.push(randomTag);
}
}
const features = [];
const featureCount = Math.floor(Math.random() * 3) + 2;
const possibleFeatures = [
'Easy to integrate',
'Fully customizable',
'Responsive design',
'Lightweight',
'High performance',
'TypeScript support',
'Well documented',
'Accessible',
'SEO friendly'
];
for (let j = 0; j < featureCount; j++) {
const randomFeature = possibleFeatures[Math.floor(Math.random() * possibleFeatures.length)];
if (!features.includes(randomFeature)) {
features.push(randomFeature);
}
}
const dependencies = [];
const depCount = Math.floor(Math.random() * 3);
const possibleDeps = ['react', 'vue', 'lodash', 'axios', 'moment', 'date-fns', 'tailwindcss'];
for (let j = 0; j < depCount; j++) {
const randomDep = possibleDeps[Math.floor(Math.random() * possibleDeps.length)];
if (!dependencies.includes(randomDep)) {
dependencies.push(randomDep);
}
}
const reviews = [];
const reviewCount = Math.floor(Math.random() * 5) + 1;
const possibleUsers = ['devUser123', 'codeMaster', 'webWizard', 'jsNinja', 'reactPro'];
const possibleComments = [
'Great plugin, saved me hours of work!',
'Easy to use and well documented.',
'Had some issues with compatibility.',
'Works perfectly for my needs.',
'Could use more customization options.',
'The best plugin for this purpose!'
];
for (let j = 0; j < reviewCount; j++) {
reviews.push({
user: possibleUsers[Math.floor(Math.random() * possibleUsers.length)],
rating: Math.floor(Math.random() * 5) + 1,
comment: possibleComments[Math.floor(Math.random() * possibleComments.length)],
date: `${Math.floor(Math.random() * 12) + 1} months ago`
});
}
plugins.push({
id: i,
name: `${category.split(' ')[0]} Plugin ${i}`,
description: `A powerful ${category.toLowerCase()} plugin that helps you ${['build better interfaces', 'write cleaner code', 'integrate with third-party services', 'optimize your workflow'][categories.indexOf(category)]}.`,
author: `Plugin Author ${i}`,
version: version,
rating: parseFloat(rating),
downloads: downloads,
category: category,
icon: icon,
tags: tags,
featured: featured,
installed: installed,
packageName: `hermit-${category.toLowerCase().replace(' ', '-')}-plugin-${i}`,
lastUpdated: `${Math.floor(Math.random() * 12) + 1} days ago`,
license: ['MIT', 'Apache 2.0', 'GPL 3.0'][Math.floor(Math.random() * 3)],
compatibility: ['React 16+', 'Vue 2/3', 'Angular 9+', 'All modern browsers'][Math.floor(Math.random() * 4)],
health: health,
features: features,
dependencies: dependencies,
reviews: reviews
});
}
return plugins;
}
// Initialize the app when DOM is loaded
document.addEventListener('DOMContentLoaded', init);
</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=Boobs00/plugin-library" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>