Spaces:
Running
Running
| <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> |