Spaces:
Running
Running
| <html lang="en" class="dark"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Workout Video Archiver</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .video-upload-area { | |
| border: 2px dashed #ccc; | |
| transition: all 0.3s; | |
| } | |
| .video-upload-area:hover { | |
| border-color: #4f46e5; | |
| background-color: rgba(79, 70, 229, 0.05); | |
| } | |
| .video-upload-area.dragover { | |
| border-color: #4f46e5; | |
| background-color: rgba(79, 70, 229, 0.1); | |
| } | |
| .video-thumbnail { | |
| aspect-ratio: 16/9; | |
| background-color: #f3f4f6; | |
| transition: transform 0.2s; | |
| } | |
| .video-thumbnail:hover { | |
| transform: scale(1.02); | |
| } | |
| .tag { | |
| transition: all 0.2s; | |
| } | |
| .tag:hover { | |
| transform: translateY(-1px); | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| .dark .video-thumbnail { | |
| background-color: #1f2937; | |
| } | |
| .dark .tag { | |
| background-color: #374151; | |
| } | |
| .dark .video-upload-area { | |
| border-color: #4b5563; | |
| } | |
| .dark .video-upload-area:hover { | |
| border-color: #818cf8; | |
| background-color: rgba(129, 140, 248, 0.05); | |
| } | |
| .dark .video-upload-area.dragover { | |
| border-color: #818cf8; | |
| background-color: rgba(129, 140, 248, 0.1); | |
| } | |
| /* Custom scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #888; | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #555; | |
| } | |
| .dark ::-webkit-scrollbar-track { | |
| background: #1f2937; | |
| } | |
| .dark ::-webkit-scrollbar-thumb { | |
| background: #4b5563; | |
| } | |
| .dark ::-webkit-scrollbar-thumb:hover { | |
| background: #6b7280; | |
| } | |
| /* Loading spinner */ | |
| .spinner { | |
| width: 24px; | |
| height: 24px; | |
| border: 3px solid rgba(255,255,255,0.3); | |
| border-radius: 50%; | |
| border-top-color: #fff; | |
| animation: spin 1s ease-in-out infinite; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200 transition-colors duration-200 min-h-screen"> | |
| <div class="container mx-auto px-4 py-6"> | |
| <!-- Header --> | |
| <header class="flex justify-between items-center mb-8"> | |
| <div> | |
| <h1 class="text-3xl font-bold text-indigo-600 dark:text-indigo-400">Workout Video Archiver</h1> | |
| <p class="text-gray-600 dark:text-gray-400">Organize and share your training videos</p> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <button id="theme-toggle" class="p-2 rounded-full bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600"> | |
| <i class="fas fa-moon dark:hidden"></i> | |
| <i class="fas fa-sun hidden dark:inline"></i> | |
| </button> | |
| <div id="profile-section"> | |
| <div class="relative"> | |
| <img id="profile-pic" src="https://randomuser.me/api/portraits/men/32.jpg" alt="Profile" class="w-10 h-10 rounded-full cursor-pointer"> | |
| <div class="absolute right-0 bottom-0 w-3 h-3 bg-green-500 rounded-full border-2 border-white dark:border-gray-900"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <div class="grid grid-cols-1 lg:grid-cols-4 gap-6"> | |
| <!-- Sidebar --> | |
| <aside class="lg:col-span-1 space-y-6"> | |
| <!-- Navigation --> | |
| <nav class="bg-white dark:bg-gray-800 rounded-lg shadow p-4"> | |
| <ul class="space-y-2"> | |
| <li> | |
| <a href="#" class="flex items-center space-x-3 p-2 rounded-lg bg-indigo-50 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300"> | |
| <i class="fas fa-home w-5"></i> | |
| <span>Dashboard</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center space-x-3 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"> | |
| <i class="fas fa-video w-5"></i> | |
| <span>My Videos</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center space-x-3 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"> | |
| <i class="fas fa-tags w-5"></i> | |
| <span>Tags</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center space-x-3 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"> | |
| <i class="fas fa-chart-line w-5"></i> | |
| <span>Analytics</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center space-x-3 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"> | |
| <i class="fas fa-cog w-5"></i> | |
| <span>Settings</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </nav> | |
| <!-- Quick Stats --> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4"> | |
| <h3 class="font-medium mb-3">Quick Stats</h3> | |
| <div class="space-y-3"> | |
| <div> | |
| <p class="text-sm text-gray-500 dark:text-gray-400">Total Videos</p> | |
| <p id="total-videos" class="text-xl font-semibold">0</p> | |
| </div> | |
| <div> | |
| <p class="text-sm text-gray-500 dark:text-gray-400">Workouts</p> | |
| <p id="total-workouts" class="text-xl font-semibold">0</p> | |
| </div> | |
| <div> | |
| <p class="text-sm text-gray-500 dark:text-gray-400">Tags</p> | |
| <p id="total-tags" class="text-xl font-semibold">0</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Recent Tags --> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4"> | |
| <h3 class="font-medium mb-3">Recent Tags</h3> | |
| <div id="recent-tags" class="flex flex-wrap gap-2"> | |
| <!-- Tags will be loaded here --> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- Main Area --> | |
| <main class="lg:col-span-3 space-y-6"> | |
| <!-- Upload Section --> | |
| <section class="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold">Upload New Video</h2> | |
| <div class="flex space-x-2"> | |
| <button id="record-btn" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg flex items-center space-x-2"> | |
| <i class="fas fa-video"></i> | |
| <span>Record</span> | |
| </button> | |
| <button id="upload-btn" class="px-4 py-2 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-lg flex items-center space-x-2"> | |
| <i class="fas fa-upload"></i> | |
| <span>Upload</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div id="upload-area" class="video-upload-area rounded-lg p-8 text-center cursor-pointer"> | |
| <i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"></i> | |
| <p class="font-medium">Drag & drop your video here</p> | |
| <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">or click to browse files</p> | |
| <input type="file" id="file-input" accept="video/*" class="hidden"> | |
| </div> | |
| </section> | |
| <!-- Filters --> | |
| <section class="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> | |
| <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-4"> | |
| <h2 class="text-xl font-semibold">My Videos</h2> | |
| <div class="flex flex-col sm:flex-row gap-3"> | |
| <div class="relative"> | |
| <select id="sort-select" class="appearance-none bg-gray-100 dark:bg-gray-700 border-0 rounded-lg pl-4 pr-8 py-2 focus:ring-2 focus:ring-indigo-500 focus:outline-none"> | |
| <option value="date">Sort by Date</option> | |
| <option value="name">Sort by Name</option> | |
| <option value="workout">Sort by Workout</option> | |
| </select> | |
| <i class="fas fa-chevron-down absolute right-3 top-3 text-gray-500 pointer-events-none"></i> | |
| </div> | |
| <div class="relative"> | |
| <select id="workout-filter" class="appearance-none bg-gray-100 dark:bg-gray-700 border-0 rounded-lg pl-4 pr-8 py-2 focus:ring-2 focus:ring-indigo-500 focus:outline-none"> | |
| <option value="all">All Workouts</option> | |
| <option value="strength">Strength Training</option> | |
| <option value="hiit">HIIT</option> | |
| <option value="yoga">Yoga</option> | |
| <option value="cardio">Cardio</option> | |
| </select> | |
| <i class="fas fa-chevron-down absolute right-3 top-3 text-gray-500 pointer-events-none"></i> | |
| </div> | |
| <div class="relative"> | |
| <input id="search-input" type="text" placeholder="Search videos..." class="bg-gray-100 dark:bg-gray-700 border-0 rounded-lg pl-4 pr-10 py-2 focus:ring-2 focus:ring-indigo-500 focus:outline-none"> | |
| <i class="fas fa-search absolute right-3 top-3 text-gray-500 pointer-events-none"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="active-filters" class="flex flex-wrap gap-3 mb-4"> | |
| <!-- Active filters will appear here --> | |
| </div> | |
| </section> | |
| <!-- Video Grid --> | |
| <section id="video-grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| <!-- Videos will be loaded here --> | |
| <div id="loading-spinner" class="col-span-full flex justify-center py-8 hidden"> | |
| <div class="spinner"></div> | |
| </div> | |
| <div id="no-videos" class="col-span-full text-center py-8 text-gray-500 dark:text-gray-400"> | |
| <i class="fas fa-video-slash text-4xl mb-3"></i> | |
| <p class="text-lg">No videos found</p> | |
| <p class="text-sm">Upload your first workout video to get started</p> | |
| </div> | |
| </section> | |
| </main> | |
| </div> | |
| </div> | |
| <!-- Video Modal --> | |
| <div id="video-modal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg w-full max-w-4xl mx-4 relative"> | |
| <button id="close-modal" class="absolute -top-10 right-0 text-white hover:text-gray-300"> | |
| <i class="fas fa-times text-2xl"></i> | |
| </button> | |
| <div class="p-4"> | |
| <video id="modal-video" controls class="w-full rounded-lg"> | |
| Your browser does not support the video tag. | |
| </video> | |
| </div> | |
| <div class="p-6"> | |
| <h3 id="modal-title" class="text-xl font-semibold mb-2"></h3> | |
| <p id="modal-description" class="text-gray-600 dark:text-gray-400 mb-4"></p> | |
| <div class="flex flex-wrap gap-2 mb-4" id="modal-tags"></div> | |
| <div class="flex justify-between items-center"> | |
| <div class="text-sm text-gray-500 dark:text-gray-400"> | |
| <span id="modal-date"></span> • <span id="modal-duration"></span> | |
| </div> | |
| <div class="flex space-x-3"> | |
| <button id="whatsapp-share" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg flex items-center space-x-2"> | |
| <i class="fab fa-whatsapp"></i> | |
| <span>WhatsApp</span> | |
| </button> | |
| <button id="email-share" class="px-4 py-2 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-lg flex items-center space-x-2"> | |
| <i class="fas fa-envelope"></i> | |
| <span>Email</span> | |
| </button> | |
| <button id="copy-link" class="px-4 py-2 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-lg flex items-center space-x-2"> | |
| <i class="fas fa-link"></i> | |
| <span>Copy Link</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Upload Modal --> | |
| <div id="upload-modal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg w-full max-w-2xl mx-4 relative"> | |
| <button id="close-upload-modal" class="absolute -top-10 right-0 text-white hover:text-gray-300"> | |
| <i class="fas fa-times text-2xl"></i> | |
| </button> | |
| <div class="p-6"> | |
| <h3 class="text-xl font-semibold mb-4">Upload New Workout Video</h3> | |
| <div class="mb-6"> | |
| <label class="block text-sm font-medium mb-2">Video File</label> | |
| <div id="modal-upload-area" class="video-upload-area rounded-lg p-8 text-center cursor-pointer mb-2"> | |
| <i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"></i> | |
| <p class="font-medium">Drag & drop your video here</p> | |
| <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">or click to browse files</p> | |
| <input type="file" id="modal-file-input" accept="video/*" class="hidden"> | |
| </div> | |
| <p class="text-xs text-gray-500 dark:text-gray-400">MP4, MOV or AVI. Max 500MB.</p> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> | |
| <div> | |
| <label for="video-title" class="block text-sm font-medium mb-2">Video Title</label> | |
| <input type="text" id="video-title" class="w-full bg-gray-100 dark:bg-gray-700 border-0 rounded-lg px-4 py-2 focus:ring-2 focus:ring-indigo-500 focus:outline-none"> | |
| </div> | |
| <div> | |
| <label for="workout-name" class="block text-sm font-medium mb-2">Workout Name</label> | |
| <input type="text" id="workout-name" class="w-full bg-gray-100 dark:bg-gray-700 border-0 rounded-lg px-4 py-2 focus:ring-2 focus:ring-indigo-500 focus:outline-none"> | |
| </div> | |
| <div> | |
| <label for="training-name" class="block text-sm font-medium mb-2">Training Program</label> | |
| <input type="text" id="training-name" class="w-full bg-gray-100 dark:bg-gray-700 border-0 rounded-lg px-4 py-2 focus:ring-2 focus:ring-indigo-500 focus:outline-none"> | |
| </div> | |
| <div> | |
| <label for="video-date" class="block text-sm font-medium mb-2">Date</label> | |
| <input type="date" id="video-date" class="w-full bg-gray-100 dark:bg-gray-700 border-0 rounded-lg px-4 py-2 focus:ring-2 focus:ring-indigo-500 focus:outline-none"> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <label for="video-description" class="block text-sm font-medium mb-2">Description</label> | |
| <textarea id="video-description" rows="3" class="w-full bg-gray-100 dark:bg-gray-700 border-0 rounded-lg px-4 py-2 focus:ring-2 focus:ring-indigo-500 focus:outline-none"></textarea> | |
| </div> | |
| <div class="mb-6"> | |
| <label class="block text-sm font-medium mb-2">Tags</label> | |
| <div class="flex flex-wrap gap-2 mb-2" id="selected-tags"> | |
| <!-- Tags will be added here --> | |
| </div> | |
| <div class="flex"> | |
| <input type="text" id="tag-input" placeholder="Add a tag" class="flex-1 bg-gray-100 dark:bg-gray-700 border-0 rounded-l-lg px-4 py-2 focus:ring-2 focus:ring-indigo-500 focus:outline-none"> | |
| <button id="add-tag-btn" class="px-4 bg-indigo-600 text-white rounded-r-lg hover:bg-indigo-700">Add</button> | |
| </div> | |
| <div class="flex flex-wrap gap-2 mt-2"> | |
| <span class="tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs cursor-pointer">strength</span> | |
| <span class="tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs cursor-pointer">hiit</span> | |
| <span class="tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs cursor-pointer">beginner</span> | |
| <span class="tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs cursor-pointer">advanced</span> | |
| <span class="tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs cursor-pointer">core</span> | |
| <span class="tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs cursor-pointer">upper body</span> | |
| </div> | |
| </div> | |
| <div class="flex justify-end space-x-3"> | |
| <button id="cancel-upload" class="px-4 py-2 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-lg"> | |
| Cancel | |
| </button> | |
| <button id="save-video" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg flex items-center space-x-2"> | |
| <i class="fas fa-save"></i> | |
| <span>Save Video</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Loading Modal --> | |
| <div id="loading-modal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg p-8 text-center"> | |
| <div class="spinner mx-auto mb-4"></div> | |
| <p id="loading-text" class="text-lg font-medium">Uploading video...</p> | |
| </div> | |
| </div> | |
| <script> | |
| // Global variables | |
| let videos = []; | |
| let selectedFile = null; | |
| // DOM elements | |
| const videoGrid = document.getElementById('video-grid'); | |
| const loadingSpinner = document.getElementById('loading-spinner'); | |
| const noVideos = document.getElementById('no-videos'); | |
| const totalVideos = document.getElementById('total-videos'); | |
| const totalWorkouts = document.getElementById('total-workouts'); | |
| const totalTags = document.getElementById('total-tags'); | |
| const recentTags = document.getElementById('recent-tags'); | |
| const activeFilters = document.getElementById('active-filters'); | |
| const sortSelect = document.getElementById('sort-select'); | |
| const workoutFilter = document.getElementById('workout-filter'); | |
| const searchInput = document.getElementById('search-input'); | |
| // Load videos from localStorage | |
| function loadVideos() { | |
| try { | |
| loadingSpinner.classList.remove('hidden'); | |
| noVideos.classList.add('hidden'); | |
| // Load from localStorage | |
| const savedVideos = localStorage.getItem('workoutVideos'); | |
| if (savedVideos) { | |
| videos = JSON.parse(savedVideos); | |
| } else { | |
| // Default demo videos | |
| videos = [ | |
| { | |
| id: '1', | |
| title: "Full Body Strength Workout - Week 1", | |
| description: "Complete full body strength workout focusing on compound movements. Perfect for beginners starting their strength journey.", | |
| tags: ["strength", "beginner", "full body"], | |
| date: "2 days ago", | |
| duration: "4:32", | |
| thumbnail: "https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=800&q=80", | |
| videoUrl: "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4", | |
| workoutType: "strength", | |
| uploadDate: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString() | |
| }, | |
| { | |
| id: '2', | |
| title: "Advanced HIIT Circuit - Fat Burning", | |
| description: "High intensity interval training circuit designed to maximize fat burning and improve cardiovascular endurance.", | |
| tags: ["hiit", "advanced", "cardio", "fat loss"], | |
| date: "1 week ago", | |
| duration: "12:15", | |
| thumbnail: "https://images.unsplash.com/photo-1571019614242-c5c5a73d6587?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=800&q=80", | |
| videoUrl: "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4", | |
| workoutType: "hiit", | |
| uploadDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString() | |
| }, | |
| { | |
| id: '3', | |
| title: "Morning Yoga Flow - Flexibility Routine", | |
| description: "Gentle yoga flow perfect for mornings to improve flexibility, mobility and start your day with energy.", | |
| tags: ["yoga", "flexibility", "morning"], | |
| date: "2 weeks ago", | |
| duration: "8:45", | |
| thumbnail: "https://images.unsplash.com/photo-1545205597-3d9d02c29597?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=800&q=80", | |
| videoUrl: "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4", | |
| workoutType: "yoga", | |
| uploadDate: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString() | |
| } | |
| ]; | |
| // Save to localStorage | |
| localStorage.setItem('workoutVideos', JSON.stringify(videos)); | |
| } | |
| updateStats(); | |
| renderVideos(); | |
| loadingSpinner.classList.add('hidden'); | |
| if (videos.length === 0) { | |
| noVideos.classList.remove('hidden'); | |
| noVideos.innerHTML = ` | |
| <i class="fas fa-video-slash text-4xl mb-3"></i> | |
| <p class="text-lg">No videos found</p> | |
| <p class="text-sm">Upload your first workout video to get started</p> | |
| `; | |
| } | |
| } catch (error) { | |
| console.error('Error loading videos:', error); | |
| loadingSpinner.classList.add('hidden'); | |
| alert('Failed to load videos. Please try again.'); | |
| } | |
| } | |
| // Save videos to localStorage | |
| function saveVideosToStorage() { | |
| localStorage.setItem('workoutVideos', JSON.stringify(videos)); | |
| } | |
| // Update statistics | |
| function updateStats() { | |
| totalVideos.textContent = videos.length; | |
| // Count unique workout types | |
| const workoutTypes = new Set(videos.map(video => video.workoutType)); | |
| totalWorkouts.textContent = workoutTypes.size; | |
| // Count unique tags | |
| const allTags = videos.flatMap(video => video.tags); | |
| const uniqueTags = new Set(allTags); | |
| totalTags.textContent = uniqueTags.size; | |
| // Update recent tags | |
| recentTags.innerHTML = ''; | |
| const tagCounts = {}; | |
| allTags.forEach(tag => { | |
| tagCounts[tag] = (tagCounts[tag] || 0) + 1; | |
| }); | |
| const sortedTags = Object.keys(tagCounts).sort((a, b) => tagCounts[b] - tagCounts[a]); | |
| sortedTags.slice(0, 6).forEach(tag => { | |
| const tagElement = document.createElement('span'); | |
| tagElement.className = 'tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-sm'; | |
| tagElement.textContent = tag; | |
| recentTags.appendChild(tagElement); | |
| }); | |
| } | |
| // Render videos to the grid | |
| function renderVideos() { | |
| videoGrid.innerHTML = ''; | |
| // Filter and sort videos | |
| let filteredVideos = [...videos]; | |
| // Apply search filter | |
| const searchTerm = searchInput.value.toLowerCase(); | |
| if (searchTerm) { | |
| filteredVideos = filteredVideos.filter(video => | |
| video.title.toLowerCase().includes(searchTerm) || | |
| video.description.toLowerCase().includes(searchTerm) || | |
| video.tags.some(tag => tag.toLowerCase().includes(searchTerm)) | |
| ); | |
| } | |
| // Apply workout type filter | |
| const workoutType = workoutFilter.value; | |
| if (workoutType !== 'all') { | |
| filteredVideos = filteredVideos.filter(video => video.workoutType === workoutType); | |
| } | |
| // Apply sorting | |
| const sortBy = sortSelect.value; | |
| if (sortBy === 'date') { | |
| filteredVideos.sort((a, b) => new Date(b.uploadDate) - new Date(a.uploadDate)); | |
| } else if (sortBy === 'name') { | |
| filteredVideos.sort((a, b) => a.title.localeCompare(b.title)); | |
| } else if (sortBy === 'workout') { | |
| filteredVideos.sort((a, b) => a.workoutType.localeCompare(b.workoutType)); | |
| } | |
| // Update active filters | |
| updateActiveFilters(); | |
| // Render videos | |
| if (filteredVideos.length === 0) { | |
| noVideos.classList.remove('hidden'); | |
| } else { | |
| noVideos.classList.add('hidden'); | |
| filteredVideos.forEach((video, index) => { | |
| const videoCard = createVideoCard(video, index); | |
| videoGrid.appendChild(videoCard); | |
| }); | |
| } | |
| // Add "Add New Video" card | |
| const addCard = document.createElement('div'); | |
| addCard.className = 'bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden hover:shadow-lg transition-shadow flex items-center justify-center'; | |
| addCard.innerHTML = ` | |
| <button class="w-full h-full p-8 flex flex-col items-center justify-center text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300"> | |
| <i class="fas fa-plus-circle text-4xl mb-3"></i> | |
| <span class="font-medium">Add New Video</span> | |
| </button> | |
| `; | |
| addCard.querySelector('button').addEventListener('click', () => { | |
| document.getElementById('upload-modal').classList.remove('hidden'); | |
| document.body.style.overflow = 'hidden'; | |
| }); | |
| videoGrid.appendChild(addCard); | |
| } | |
| // Create a video card element | |
| function createVideoCard(video, index) { | |
| const card = document.createElement('div'); | |
| card.className = 'bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden hover:shadow-lg transition-shadow'; | |
| card.innerHTML = ` | |
| <div class="video-thumbnail relative"> | |
| <img src="${video.thumbnail}" alt="Workout Video" class="w-full h-full object-cover"> | |
| <div class="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity"> | |
| <button class="w-12 h-12 bg-white bg-opacity-80 rounded-full flex items-center justify-center text-indigo-600 hover:bg-opacity-100"> | |
| <i class="fas fa-play"></i> | |
| </button> | |
| </div> | |
| <div class="absolute bottom-2 right-2 bg-black bg-opacity-70 text-white text-xs px-2 py-1 rounded"> | |
| ${video.duration} | |
| </div> | |
| </div> | |
| <div class="p-4"> | |
| <div class="flex justify-between items-start mb-2"> | |
| <h3 class="font-semibold line-clamp-1">${video.title}</h3> | |
| <div class="dropdown relative"> | |
| <button class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"> | |
| <i class="fas fa-ellipsis-v"></i> | |
| </button> | |
| <div class="dropdown-menu absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-md shadow-lg z-10 hidden"> | |
| <div class="py-1"> | |
| <a href="#" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">Edit</a> | |
| <a href="#" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">Share</a> | |
| <a href="#" class="block px-4 py-2 text-sm text-red-600 hover:bg-gray-100 dark:hover:bg-gray-700">Delete</a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <p class="text-sm text-gray-600 dark:text-gray-400 mb-3 line-clamp-2">${video.description}</p> | |
| <div class="flex flex-wrap gap-2 mb-3"> | |
| ${video.tags.map(tag => `<span class="tag px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs">${tag}</span>`).join('')} | |
| </div> | |
| <div class="flex justify-between items-center text-xs text-gray-500 dark:text-gray-400"> | |
| <span>${video.date}</span> | |
| <div class="flex space-x-2"> | |
| <button class="hover:text-indigo-600 dark:hover:text-indigo-400"> | |
| <i class="fas fa-share-alt"></i> | |
| </button> | |
| <button class="hover:text-indigo-600 dark:hover:text-indigo-400"> | |
| <i class="fas fa-download"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| // Add click handler for play button | |
| card.querySelector('.video-thumbnail button').addEventListener('click', () => { | |
| openVideoModal(video); | |
| }); | |
| // Add dropdown menu functionality | |
| const dropdownBtn = card.querySelector('.dropdown button'); | |
| const dropdownMenu = card.querySelector('.dropdown-menu'); | |
| dropdownBtn.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| dropdownMenu.classList.toggle('hidden'); | |
| }); | |
| // Add click handler for edit button | |
| const editBtn = card.querySelector('.dropdown-menu a:first-child'); | |
| editBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| openEditModal(video); | |
| }); | |
| // Add click handler for delete button | |
| const deleteBtn = card.querySelector('.dropdown-menu a:last-child'); | |
| deleteBtn.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| if (confirm('Are you sure you want to delete this video?')) { | |
| deleteVideo(video.id); | |
| } | |
| }); | |
| return card; | |
| } | |
| // Update active filters display | |
| function updateActiveFilters() { | |
| activeFilters.innerHTML = ''; | |
| // Add search filter if applicable | |
| const searchTerm = searchInput.value.trim(); | |
| if (searchTerm) { | |
| const filter = document.createElement('span'); | |
| filter.className = 'tag px-3 py-1 bg-indigo-100 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300 rounded-full text-sm flex items-center'; | |
| filter.innerHTML = ` | |
| <span>Search: "${searchTerm}"</span> | |
| <button class="ml-2 text-indigo-500 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| `; | |
| filter.querySelector('button').addEventListener('click', () => { | |
| searchInput.value = ''; | |
| renderVideos(); | |
| }); | |
| activeFilters.appendChild(filter); | |
| } | |
| // Add workout type filter if applicable | |
| const workoutType = workoutFilter.value; | |
| if (workoutType !== 'all') { | |
| const filter = document.createElement('span'); | |
| filter.className = 'tag px-3 py-1 bg-indigo-100 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300 rounded-full text-sm flex items-center'; | |
| filter.innerHTML = ` | |
| <span>Workout: ${workoutType.charAt(0).toUpperCase() + workoutType.slice(1)}</span> | |
| <button class="ml-2 text-indigo-500 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| `; | |
| filter.querySelector('button').addEventListener('click', () => { | |
| workoutFilter.value = 'all'; | |
| renderVideos(); | |
| }); | |
| activeFilters.appendChild(filter); | |
| } | |
| // Add sort filter if applicable | |
| const sortBy = sortSelect.value; | |
| if (sortBy !== 'date') { | |
| const filter = document.createElement('span'); | |
| filter.className = 'tag px-3 py-1 bg-indigo-100 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300 rounded-full text-sm flex items-center'; | |
| filter.innerHTML = ` | |
| <span>Sorted by: ${sortBy.charAt(0).toUpperCase() + sortBy.slice(1)}</span> | |
| <button class="ml-2 text-indigo-500 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| `; | |
| filter.querySelector('button').addEventListener('click', () => { | |
| sortSelect.value = 'date'; | |
| renderVideos(); | |
| }); | |
| activeFilters.appendChild(filter); | |
| } | |
| // Add "Clear All" button if there are active filters | |
| if (activeFilters.children.length > 0) { | |
| const clearAll = document.createElement('button'); | |
| clearAll.className = 'text-sm text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300 flex items-center'; | |
| clearAll.innerHTML = ` | |
| <i class="fas fa-times mr-1"></i> | |
| <span>Clear All</span> | |
| `; | |
| clearAll.addEventListener('click', () => { | |
| searchInput.value = ''; | |
| workoutFilter.value = 'all'; | |
| sortSelect.value = 'date'; | |
| renderVideos(); | |
| }); | |
| activeFilters.appendChild(clearAll); | |
| } | |
| } | |
| // Open video modal | |
| function openVideoModal(video) { | |
| const videoModal = document.getElementById('video-modal'); | |
| const modalVideo = document.getElementById('modal-video'); | |
| const modalTitle = document.getElementById('modal-title'); | |
| const modalDescription = document.getElementById('modal-description'); | |
| const modalTags = document.getElementById('modal-tags'); | |
| const modalDate = document.getElementById('modal-date'); | |
| const modalDuration = document.getElementById('modal-duration'); | |
| modalVideo.src = video.videoUrl; | |
| modalTitle.textContent = video.title; | |
| modalDescription.textContent = video.description; | |
| modalDate.textContent = video.date; | |
| modalDuration.textContent = video.duration; | |
| // Clear previous tags | |
| modalTags.innerHTML = ''; | |
| // Add new tags | |
| video.tags.forEach(tag => { | |
| const tagElement = document.createElement('span'); | |
| tagElement.className = 'tag px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs'; | |
| tagElement.textContent = tag; | |
| modalTags.appendChild(tagElement); | |
| }); | |
| // Add share button handlers | |
| document.getElementById('whatsapp-share').addEventListener('click', () => { | |
| shareVideo(video, 'whatsapp'); | |
| }); | |
| document.getElementById('email-share').addEventListener('click', () => { | |
| shareVideo(video, 'email'); | |
| }); | |
| document.getElementById('copy-link').addEventListener('click', () => { | |
| shareVideo(video, 'link'); | |
| }); | |
| videoModal.classList.remove('hidden'); | |
| document.body.style.overflow = 'hidden'; | |
| } | |
| // Share video | |
| function shareVideo(video, method) { | |
| let message = `Check out my workout video: ${video.title}\n\n${video.description}\n\nTags: ${video.tags.join(', ')}`; | |
| switch (method) { | |
| case 'whatsapp': | |
| window.open(`https://wa.me/?text=${encodeURIComponent(message)}`); | |
| break; | |
| case 'email': | |
| window.open(`mailto:?subject=${encodeURIComponent(video.title)}&body=${encodeURIComponent(message)}`); | |
| break; | |
| case 'link': | |
| navigator.clipboard.writeText(video.videoUrl).then(() => { | |
| alert('Video link copied to clipboard!'); | |
| }); | |
| break; | |
| } | |
| } | |
| // Open edit modal | |
| function openEditModal(video) { | |
| const uploadModal = document.getElementById('upload-modal'); | |
| const modalTitle = uploadModal.querySelector('h3'); | |
| const videoTitle = document.getElementById('video-title'); | |
| const workoutName = document.getElementById('workout-name'); | |
| const trainingName = document.getElementById('training-name'); | |
| const videoDate = document.getElementById('video-date'); | |
| const videoDescription = document.getElementById('video-description'); | |
| const selectedTags = document.getElementById('selected-tags'); | |
| const modalUploadArea = document.getElementById('modal-upload-area'); | |
| modalTitle.textContent = 'Edit Video'; | |
| videoTitle.value = video.title; | |
| workoutName.value = video.workoutType; | |
| trainingName.value = ''; | |
| videoDate.value = ''; | |
| videoDescription.value = video.description; | |
| // Clear previous tags | |
| selectedTags.innerHTML = ''; | |
| // Add current tags | |
| video.tags.forEach(tag => { | |
| addTagToForm(tag); | |
| }); | |
| // Update upload area to show current video | |
| modalUploadArea.innerHTML = ` | |
| <i class="fas fa-check-circle text-4xl text-green-500 mb-3"></i> | |
| <p class="font-medium">Current Video: ${video.title}</p> | |
| <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Click to change file</p> | |
| `; | |
| // Store the video being edited | |
| selectedFile = { name: video.title }; | |
| uploadModal.classList.remove('hidden'); | |
| document.body.style.overflow = 'hidden'; | |
| } | |
| // Delete video | |
| function deleteVideo(videoId) { | |
| videos = videos.filter(video => video.id !== videoId); | |
| saveVideosToStorage(); | |
| updateStats(); | |
| renderVideos(); | |
| } | |
| // Upload video (save to localStorage) | |
| async function uploadVideo(file, metadata) { | |
| try { | |
| // Show loading modal | |
| const loadingModal = document.getElementById('loading-modal'); | |
| const loadingText = document.getElementById('loading-text'); | |
| loadingText.textContent = 'Uploading video...'; | |
| loadingModal.classList.remove('hidden'); | |
| // Simulate upload delay | |
| await new Promise(resolve => setTimeout(resolve, 1000)); | |
| // Create a new video object | |
| const newVideo = { | |
| id: Date.now().toString(), | |
| title: metadata.title, | |
| description: metadata.description, | |
| tags: metadata.tags, | |
| date: "Just now", | |
| duration: "5:00", // In a real app, you would get this from the video | |
| thumbnail: "https://images.unsplash.com/photo-1571019613454-1cb2f99b2d8b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=800&q=80", | |
| videoUrl: URL.createObjectURL(file), | |
| workoutType: metadata.workoutType, | |
| uploadDate: new Date().toISOString() | |
| }; | |
| // Add to videos array | |
| videos.unshift(newVideo); | |
| // Save to localStorage | |
| saveVideosToStorage(); | |
| // Update UI | |
| updateStats(); | |
| renderVideos(); | |
| // Hide loading modal | |
| loadingModal.classList.add('hidden'); | |
| // Show success message | |
| alert('Video uploaded successfully!'); | |
| // Close upload modal | |
| document.getElementById('upload-modal').classList.add('hidden'); | |
| document.body.style.overflow = 'auto'; | |
| // Reset form | |
| resetUploadForm(); | |
| } catch (error) { | |
| console.error('Error uploading video:', error); | |
| loadingModal.classList.add('hidden'); | |
| alert('Failed to upload video. Please try again.'); | |
| } | |
| } | |
| // Reset upload form | |
| function resetUploadForm() { | |
| document.getElementById('video-title').value = ''; | |
| document.getElementById('workout-name').value = ''; | |
| document.getElementById('training-name').value = ''; | |
| document.getElementById('video-date').value = ''; | |
| document.getElementById('video-description').value = ''; | |
| document.getElementById('selected-tags').innerHTML = ''; | |
| document.getElementById('modal-upload-area').innerHTML = ` | |
| <i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"></i> | |
| <p class="font-medium">Drag & drop your video here</p> | |
| <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">or click to browse files</p> | |
| `; | |
| selectedFile = null; | |
| } | |
| // Add tag to form | |
| function addTagToForm(tagText) { | |
| const tagElement = document.createElement('span'); | |
| tagElement.className = 'tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs flex items-center'; | |
| tagElement.innerHTML = ` | |
| <span>${tagText}</span> | |
| <button class="ml-2 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"> | |
| <i class="fas fa-times text-xs"></i> | |
| </button> | |
| `; | |
| const removeBtn = tagElement.querySelector('button'); | |
| removeBtn.addEventListener('click', () => { | |
| tagElement.remove(); | |
| }); | |
| document.getElementById('selected-tags').appendChild(tagElement); | |
| } | |
| // Initialize event listeners | |
| function initEventListeners() { | |
| // Theme toggle | |
| const themeToggle = document.getElementById('theme-toggle'); | |
| themeToggle.addEventListener('click', () => { | |
| document.documentElement.classList.toggle('dark'); | |
| localStorage.setItem('darkMode', document.documentElement.classList.contains('dark')); | |
| }); | |
| // Check for saved theme preference | |
| if (localStorage.getItem('darkMode') === 'true') { | |
| document.documentElement.classList.add('dark'); | |
| } else if (localStorage.getItem('darkMode') === 'false') { | |
| document.documentElement.classList.remove('dark'); | |
| } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { | |
| document.documentElement.classList.add('dark'); | |
| } | |
| // Video modal functionality | |
| const videoModal = document.getElementById('video-modal'); | |
| const closeModal = document.getElementById('close-modal'); | |
| closeModal.addEventListener('click', () => { | |
| videoModal.classList.add('hidden'); | |
| document.getElementById('modal-video').pause(); | |
| document.body.style.overflow = 'auto'; | |
| }); | |
| // Close modal when clicking outside | |
| videoModal.addEventListener('click', (e) => { | |
| if (e.target === videoModal) { | |
| videoModal.classList.add('hidden'); | |
| document.getElementById('modal-video').pause(); | |
| document.body.style.overflow = 'auto'; | |
| } | |
| }); | |
| // Upload modal functionality | |
| const uploadModal = document.getElementById('upload-modal'); | |
| const closeUploadModal = document.getElementById('close-upload-modal'); | |
| const cancelUpload = document.getElementById('cancel-upload'); | |
| const uploadBtn = document.getElementById('upload-btn'); | |
| const recordBtn = document.getElementById('record-btn'); | |
| const uploadArea = document.getElementById('upload-area'); | |
| const fileInput = document.getElementById('file-input'); | |
| const modalFileInput = document.getElementById('modal-file-input'); | |
| const modalUploadArea = document.getElementById('modal-upload-area'); | |
| const selectedTags = document.getElementById('selected-tags'); | |
| const tagInput = document.getElementById('tag-input'); | |
| const addTagBtn = document.getElementById('add-tag-btn'); | |
| const saveVideoBtn = document.getElementById('save-video'); | |
| // Open upload modal | |
| uploadBtn.addEventListener('click', () => { | |
| uploadModal.classList.remove('hidden'); | |
| document.body.style.overflow = 'hidden'; | |
| }); | |
| // Open upload modal from record button | |
| recordBtn.addEventListener('click', () => { | |
| // In a real app, this would open camera recording | |
| uploadModal.classList.remove('hidden'); | |
| document.body.style.overflow = 'hidden'; | |
| }); | |
| // Close upload modal | |
| closeUploadModal.addEventListener('click', () => { | |
| uploadModal.classList.add('hidden'); | |
| document.body.style.overflow = 'auto'; | |
| resetUploadForm(); | |
| }); | |
| cancelUpload.addEventListener('click', () => { | |
| uploadModal.classList.add('hidden'); | |
| document.body.style.overflow = 'auto'; | |
| resetUploadForm(); | |
| }); | |
| // Close modal when clicking outside | |
| uploadModal.addEventListener('click', (e) => { | |
| if (e.target === uploadModal) { | |
| uploadModal.classList.add('hidden'); | |
| document.body.style.overflow = 'auto'; | |
| resetUploadForm(); | |
| } | |
| }); | |
| // File upload handling | |
| uploadArea.addEventListener('click', () => { | |
| fileInput.click(); | |
| }); | |
| modalUploadArea.addEventListener('click', () => { | |
| modalFileInput.click(); | |
| }); | |
| modalFileInput.addEventListener('change', (e) => { | |
| if (e.target.files.length > 0) { | |
| selectedFile = e.target.files[0]; | |
| modalUploadArea.innerHTML = ` | |
| <i class="fas fa-check-circle text-4xl text-green-500 mb-3"></i> | |
| <p class="font-medium">${selectedFile.name}</p> | |
| <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Click to change file</p> | |
| `; | |
| } | |
| }); | |
| // Drag and drop functionality | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| [uploadArea, modalUploadArea].forEach(area => { | |
| area.addEventListener(eventName, preventDefaults, false); | |
| }); | |
| }); | |
| function preventDefaults(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| [uploadArea, modalUploadArea].forEach(area => { | |
| area.addEventListener(eventName, highlight, false); | |
| }); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| [uploadArea, modalUploadArea].forEach(area => { | |
| area.addEventListener(eventName, unhighlight, false); | |
| }); | |
| }); | |
| function highlight() { | |
| this.classList.add('dragover'); | |
| } | |
| function unhighlight() { | |
| this.classList.remove('dragover'); | |
| } | |
| uploadArea.addEventListener('drop', handleDrop, false); | |
| modalUploadArea.addEventListener('drop', handleModalDrop, false); | |
| function handleDrop(e) { | |
| const dt = e.dataTransfer; | |
| const files = dt.files; | |
| fileInput.files = files; | |
| if (files.length > 0) { | |
| const fileName = files[0].name; | |
| uploadArea.innerHTML = ` | |
| <i class="fas fa-check-circle text-4xl text-green-500 mb-3"></i> | |
| <p class="font-medium">${fileName}</p> | |
| <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Click to change file</p> | |
| `; | |
| } | |
| } | |
| function handleModalDrop(e) { | |
| const dt = e.dataTransfer; | |
| const files = dt.files; | |
| modalFileInput.files = files; | |
| if (files.length > 0) { | |
| selectedFile = files[0]; | |
| modalUploadArea.innerHTML = ` | |
| <i class="fas fa-check-circle text-4xl text-green-500 mb-3"></i> | |
| <p class="font-medium">${selectedFile.name}</p> | |
| <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Click to change file</p> | |
| `; | |
| } | |
| } | |
| // Tag functionality | |
| addTagBtn.addEventListener('click', () => { | |
| const tagText = tagInput.value.trim(); | |
| if (tagText) { | |
| addTagToForm(tagText); | |
| tagInput.value = ''; | |
| } | |
| }); | |
| tagInput.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| const tagText = tagInput.value.trim(); | |
| if (tagText) { | |
| addTagToForm(tagText); | |
| tagInput.value = ''; | |
| } | |
| } | |
| }); | |
| // Add click handler for suggested tags | |
| document.querySelectorAll('#upload-modal .tag').forEach(tag => { | |
| tag.addEventListener('click', () => { | |
| const tagText = tag.textContent; | |
| addTagToForm(tagText); | |
| }); | |
| }); | |
| // Save video | |
| saveVideoBtn.addEventListener('click', () => { | |
| if (!selectedFile) { | |
| alert('Please select a video file to upload.'); | |
| return; | |
| } | |
| const title = document.getElementById('video-title').value.trim(); | |
| if (!title) { | |
| alert('Please enter a title for your video.'); | |
| return; | |
| } | |
| const workoutType = document.getElementById('workout-name').value.trim(); | |
| const description = document.getElementById('video-description').value.trim(); | |
| // Get selected tags | |
| const tags = []; | |
| document.querySelectorAll('#selected-tags span span:first-child').forEach(tag => { | |
| tags.push(tag.textContent); | |
| }); | |
| // Create metadata object | |
| const metadata = { | |
| title, | |
| workoutType, | |
| description, | |
| tags | |
| }; | |
| // Upload the video | |
| uploadVideo(selectedFile, metadata); | |
| }); | |
| // Dropdown menus | |
| document.querySelectorAll('.dropdown button').forEach(button => { | |
| button.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| const menu = button.nextElementSibling; | |
| menu.classList.toggle('hidden'); | |
| }); | |
| }); | |
| // Close dropdown when clicking outside | |
| document.addEventListener('click', () => { | |
| document.querySelectorAll('.dropdown-menu').forEach(menu => { | |
| menu.classList.add('hidden'); | |
| }); | |
| }); | |
| // Filter and search functionality | |
| sortSelect.addEventListener('change', renderVideos); | |
| workoutFilter.addEventListener('change', renderVideos); | |
| searchInput.addEventListener('input', renderVideos); | |
| } | |
| // Initialize the app | |
| function init() { | |
| initEventListeners(); | |
| loadVideos(); | |
| } | |
| // Initialize 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=frucht/workout-archiver" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |