Spaces:
Running
Running
Add 3 files
Browse files- README.md +7 -5
- index.html +1195 -19
- prompts.txt +3 -0
README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
+
title: workout-archiver
|
| 3 |
+
emoji: 🐳
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: gray
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite
|
| 10 |
---
|
| 11 |
|
| 12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
index.html
CHANGED
|
@@ -1,19 +1,1195 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class="dark">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Workout Video Archiver</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 9 |
+
<style>
|
| 10 |
+
.video-upload-area {
|
| 11 |
+
border: 2px dashed #ccc;
|
| 12 |
+
transition: all 0.3s;
|
| 13 |
+
}
|
| 14 |
+
.video-upload-area:hover {
|
| 15 |
+
border-color: #4f46e5;
|
| 16 |
+
background-color: rgba(79, 70, 229, 0.05);
|
| 17 |
+
}
|
| 18 |
+
.video-upload-area.dragover {
|
| 19 |
+
border-color: #4f46e5;
|
| 20 |
+
background-color: rgba(79, 70, 229, 0.1);
|
| 21 |
+
}
|
| 22 |
+
.video-thumbnail {
|
| 23 |
+
aspect-ratio: 16/9;
|
| 24 |
+
background-color: #f3f4f6;
|
| 25 |
+
transition: transform 0.2s;
|
| 26 |
+
}
|
| 27 |
+
.video-thumbnail:hover {
|
| 28 |
+
transform: scale(1.02);
|
| 29 |
+
}
|
| 30 |
+
.tag {
|
| 31 |
+
transition: all 0.2s;
|
| 32 |
+
}
|
| 33 |
+
.tag:hover {
|
| 34 |
+
transform: translateY(-1px);
|
| 35 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 36 |
+
}
|
| 37 |
+
.dark .video-thumbnail {
|
| 38 |
+
background-color: #1f2937;
|
| 39 |
+
}
|
| 40 |
+
.dark .tag {
|
| 41 |
+
background-color: #374151;
|
| 42 |
+
}
|
| 43 |
+
.dark .video-upload-area {
|
| 44 |
+
border-color: #4b5563;
|
| 45 |
+
}
|
| 46 |
+
.dark .video-upload-area:hover {
|
| 47 |
+
border-color: #818cf8;
|
| 48 |
+
background-color: rgba(129, 140, 248, 0.05);
|
| 49 |
+
}
|
| 50 |
+
.dark .video-upload-area.dragover {
|
| 51 |
+
border-color: #818cf8;
|
| 52 |
+
background-color: rgba(129, 140, 248, 0.1);
|
| 53 |
+
}
|
| 54 |
+
/* Custom scrollbar */
|
| 55 |
+
::-webkit-scrollbar {
|
| 56 |
+
width: 8px;
|
| 57 |
+
height: 8px;
|
| 58 |
+
}
|
| 59 |
+
::-webkit-scrollbar-track {
|
| 60 |
+
background: #f1f1f1;
|
| 61 |
+
}
|
| 62 |
+
::-webkit-scrollbar-thumb {
|
| 63 |
+
background: #888;
|
| 64 |
+
border-radius: 4px;
|
| 65 |
+
}
|
| 66 |
+
::-webkit-scrollbar-thumb:hover {
|
| 67 |
+
background: #555;
|
| 68 |
+
}
|
| 69 |
+
.dark ::-webkit-scrollbar-track {
|
| 70 |
+
background: #1f2937;
|
| 71 |
+
}
|
| 72 |
+
.dark ::-webkit-scrollbar-thumb {
|
| 73 |
+
background: #4b5563;
|
| 74 |
+
}
|
| 75 |
+
.dark ::-webkit-scrollbar-thumb:hover {
|
| 76 |
+
background: #6b7280;
|
| 77 |
+
}
|
| 78 |
+
/* Loading spinner */
|
| 79 |
+
.spinner {
|
| 80 |
+
width: 24px;
|
| 81 |
+
height: 24px;
|
| 82 |
+
border: 3px solid rgba(255,255,255,0.3);
|
| 83 |
+
border-radius: 50%;
|
| 84 |
+
border-top-color: #fff;
|
| 85 |
+
animation: spin 1s ease-in-out infinite;
|
| 86 |
+
}
|
| 87 |
+
@keyframes spin {
|
| 88 |
+
to { transform: rotate(360deg); }
|
| 89 |
+
}
|
| 90 |
+
</style>
|
| 91 |
+
</head>
|
| 92 |
+
<body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200 transition-colors duration-200 min-h-screen">
|
| 93 |
+
<div class="container mx-auto px-4 py-6">
|
| 94 |
+
<!-- Header -->
|
| 95 |
+
<header class="flex justify-between items-center mb-8">
|
| 96 |
+
<div>
|
| 97 |
+
<h1 class="text-3xl font-bold text-indigo-600 dark:text-indigo-400">Workout Video Archiver</h1>
|
| 98 |
+
<p class="text-gray-600 dark:text-gray-400">Organize and share your training videos</p>
|
| 99 |
+
</div>
|
| 100 |
+
<div class="flex items-center space-x-4">
|
| 101 |
+
<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">
|
| 102 |
+
<i class="fas fa-moon dark:hidden"></i>
|
| 103 |
+
<i class="fas fa-sun hidden dark:inline"></i>
|
| 104 |
+
</button>
|
| 105 |
+
<div id="profile-section">
|
| 106 |
+
<div class="relative">
|
| 107 |
+
<img id="profile-pic" src="https://randomuser.me/api/portraits/men/32.jpg" alt="Profile" class="w-10 h-10 rounded-full cursor-pointer">
|
| 108 |
+
<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>
|
| 109 |
+
</div>
|
| 110 |
+
</div>
|
| 111 |
+
</div>
|
| 112 |
+
</header>
|
| 113 |
+
|
| 114 |
+
<!-- Main Content -->
|
| 115 |
+
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
| 116 |
+
<!-- Sidebar -->
|
| 117 |
+
<aside class="lg:col-span-1 space-y-6">
|
| 118 |
+
<!-- Navigation -->
|
| 119 |
+
<nav class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
| 120 |
+
<ul class="space-y-2">
|
| 121 |
+
<li>
|
| 122 |
+
<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">
|
| 123 |
+
<i class="fas fa-home w-5"></i>
|
| 124 |
+
<span>Dashboard</span>
|
| 125 |
+
</a>
|
| 126 |
+
</li>
|
| 127 |
+
<li>
|
| 128 |
+
<a href="#" class="flex items-center space-x-3 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
|
| 129 |
+
<i class="fas fa-video w-5"></i>
|
| 130 |
+
<span>My Videos</span>
|
| 131 |
+
</a>
|
| 132 |
+
</li>
|
| 133 |
+
<li>
|
| 134 |
+
<a href="#" class="flex items-center space-x-3 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
|
| 135 |
+
<i class="fas fa-tags w-5"></i>
|
| 136 |
+
<span>Tags</span>
|
| 137 |
+
</a>
|
| 138 |
+
</li>
|
| 139 |
+
<li>
|
| 140 |
+
<a href="#" class="flex items-center space-x-3 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
|
| 141 |
+
<i class="fas fa-chart-line w-5"></i>
|
| 142 |
+
<span>Analytics</span>
|
| 143 |
+
</a>
|
| 144 |
+
</li>
|
| 145 |
+
<li>
|
| 146 |
+
<a href="#" class="flex items-center space-x-3 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
|
| 147 |
+
<i class="fas fa-cog w-5"></i>
|
| 148 |
+
<span>Settings</span>
|
| 149 |
+
</a>
|
| 150 |
+
</li>
|
| 151 |
+
</ul>
|
| 152 |
+
</nav>
|
| 153 |
+
|
| 154 |
+
<!-- Quick Stats -->
|
| 155 |
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
| 156 |
+
<h3 class="font-medium mb-3">Quick Stats</h3>
|
| 157 |
+
<div class="space-y-3">
|
| 158 |
+
<div>
|
| 159 |
+
<p class="text-sm text-gray-500 dark:text-gray-400">Total Videos</p>
|
| 160 |
+
<p id="total-videos" class="text-xl font-semibold">0</p>
|
| 161 |
+
</div>
|
| 162 |
+
<div>
|
| 163 |
+
<p class="text-sm text-gray-500 dark:text-gray-400">Workouts</p>
|
| 164 |
+
<p id="total-workouts" class="text-xl font-semibold">0</p>
|
| 165 |
+
</div>
|
| 166 |
+
<div>
|
| 167 |
+
<p class="text-sm text-gray-500 dark:text-gray-400">Tags</p>
|
| 168 |
+
<p id="total-tags" class="text-xl font-semibold">0</p>
|
| 169 |
+
</div>
|
| 170 |
+
</div>
|
| 171 |
+
</div>
|
| 172 |
+
|
| 173 |
+
<!-- Recent Tags -->
|
| 174 |
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
| 175 |
+
<h3 class="font-medium mb-3">Recent Tags</h3>
|
| 176 |
+
<div id="recent-tags" class="flex flex-wrap gap-2">
|
| 177 |
+
<!-- Tags will be loaded here -->
|
| 178 |
+
</div>
|
| 179 |
+
</div>
|
| 180 |
+
</aside>
|
| 181 |
+
|
| 182 |
+
<!-- Main Area -->
|
| 183 |
+
<main class="lg:col-span-3 space-y-6">
|
| 184 |
+
<!-- Upload Section -->
|
| 185 |
+
<section class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
| 186 |
+
<div class="flex justify-between items-center mb-4">
|
| 187 |
+
<h2 class="text-xl font-semibold">Upload New Video</h2>
|
| 188 |
+
<div class="flex space-x-2">
|
| 189 |
+
<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">
|
| 190 |
+
<i class="fas fa-video"></i>
|
| 191 |
+
<span>Record</span>
|
| 192 |
+
</button>
|
| 193 |
+
<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">
|
| 194 |
+
<i class="fas fa-upload"></i>
|
| 195 |
+
<span>Upload</span>
|
| 196 |
+
</button>
|
| 197 |
+
</div>
|
| 198 |
+
</div>
|
| 199 |
+
|
| 200 |
+
<div id="upload-area" class="video-upload-area rounded-lg p-8 text-center cursor-pointer">
|
| 201 |
+
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"></i>
|
| 202 |
+
<p class="font-medium">Drag & drop your video here</p>
|
| 203 |
+
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">or click to browse files</p>
|
| 204 |
+
<input type="file" id="file-input" accept="video/*" class="hidden">
|
| 205 |
+
</div>
|
| 206 |
+
</section>
|
| 207 |
+
|
| 208 |
+
<!-- Filters -->
|
| 209 |
+
<section class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
| 210 |
+
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-4">
|
| 211 |
+
<h2 class="text-xl font-semibold">My Videos</h2>
|
| 212 |
+
<div class="flex flex-col sm:flex-row gap-3">
|
| 213 |
+
<div class="relative">
|
| 214 |
+
<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">
|
| 215 |
+
<option value="date">Sort by Date</option>
|
| 216 |
+
<option value="name">Sort by Name</option>
|
| 217 |
+
<option value="workout">Sort by Workout</option>
|
| 218 |
+
</select>
|
| 219 |
+
<i class="fas fa-chevron-down absolute right-3 top-3 text-gray-500 pointer-events-none"></i>
|
| 220 |
+
</div>
|
| 221 |
+
<div class="relative">
|
| 222 |
+
<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">
|
| 223 |
+
<option value="all">All Workouts</option>
|
| 224 |
+
<option value="strength">Strength Training</option>
|
| 225 |
+
<option value="hiit">HIIT</option>
|
| 226 |
+
<option value="yoga">Yoga</option>
|
| 227 |
+
<option value="cardio">Cardio</option>
|
| 228 |
+
</select>
|
| 229 |
+
<i class="fas fa-chevron-down absolute right-3 top-3 text-gray-500 pointer-events-none"></i>
|
| 230 |
+
</div>
|
| 231 |
+
<div class="relative">
|
| 232 |
+
<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">
|
| 233 |
+
<i class="fas fa-search absolute right-3 top-3 text-gray-500 pointer-events-none"></i>
|
| 234 |
+
</div>
|
| 235 |
+
</div>
|
| 236 |
+
</div>
|
| 237 |
+
|
| 238 |
+
<div id="active-filters" class="flex flex-wrap gap-3 mb-4">
|
| 239 |
+
<!-- Active filters will appear here -->
|
| 240 |
+
</div>
|
| 241 |
+
</section>
|
| 242 |
+
|
| 243 |
+
<!-- Video Grid -->
|
| 244 |
+
<section id="video-grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
| 245 |
+
<!-- Videos will be loaded here -->
|
| 246 |
+
<div id="loading-spinner" class="col-span-full flex justify-center py-8 hidden">
|
| 247 |
+
<div class="spinner"></div>
|
| 248 |
+
</div>
|
| 249 |
+
<div id="no-videos" class="col-span-full text-center py-8 text-gray-500 dark:text-gray-400">
|
| 250 |
+
<i class="fas fa-video-slash text-4xl mb-3"></i>
|
| 251 |
+
<p class="text-lg">No videos found</p>
|
| 252 |
+
<p class="text-sm">Upload your first workout video to get started</p>
|
| 253 |
+
</div>
|
| 254 |
+
</section>
|
| 255 |
+
</main>
|
| 256 |
+
</div>
|
| 257 |
+
</div>
|
| 258 |
+
|
| 259 |
+
<!-- Video Modal -->
|
| 260 |
+
<div id="video-modal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden">
|
| 261 |
+
<div class="bg-white dark:bg-gray-800 rounded-lg w-full max-w-4xl mx-4 relative">
|
| 262 |
+
<button id="close-modal" class="absolute -top-10 right-0 text-white hover:text-gray-300">
|
| 263 |
+
<i class="fas fa-times text-2xl"></i>
|
| 264 |
+
</button>
|
| 265 |
+
<div class="p-4">
|
| 266 |
+
<video id="modal-video" controls class="w-full rounded-lg">
|
| 267 |
+
Your browser does not support the video tag.
|
| 268 |
+
</video>
|
| 269 |
+
</div>
|
| 270 |
+
<div class="p-6">
|
| 271 |
+
<h3 id="modal-title" class="text-xl font-semibold mb-2"></h3>
|
| 272 |
+
<p id="modal-description" class="text-gray-600 dark:text-gray-400 mb-4"></p>
|
| 273 |
+
<div class="flex flex-wrap gap-2 mb-4" id="modal-tags"></div>
|
| 274 |
+
<div class="flex justify-between items-center">
|
| 275 |
+
<div class="text-sm text-gray-500 dark:text-gray-400">
|
| 276 |
+
<span id="modal-date"></span> • <span id="modal-duration"></span>
|
| 277 |
+
</div>
|
| 278 |
+
<div class="flex space-x-3">
|
| 279 |
+
<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">
|
| 280 |
+
<i class="fab fa-whatsapp"></i>
|
| 281 |
+
<span>WhatsApp</span>
|
| 282 |
+
</button>
|
| 283 |
+
<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">
|
| 284 |
+
<i class="fas fa-envelope"></i>
|
| 285 |
+
<span>Email</span>
|
| 286 |
+
</button>
|
| 287 |
+
<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">
|
| 288 |
+
<i class="fas fa-link"></i>
|
| 289 |
+
<span>Copy Link</span>
|
| 290 |
+
</button>
|
| 291 |
+
</div>
|
| 292 |
+
</div>
|
| 293 |
+
</div>
|
| 294 |
+
</div>
|
| 295 |
+
</div>
|
| 296 |
+
|
| 297 |
+
<!-- Upload Modal -->
|
| 298 |
+
<div id="upload-modal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden">
|
| 299 |
+
<div class="bg-white dark:bg-gray-800 rounded-lg w-full max-w-2xl mx-4 relative">
|
| 300 |
+
<button id="close-upload-modal" class="absolute -top-10 right-0 text-white hover:text-gray-300">
|
| 301 |
+
<i class="fas fa-times text-2xl"></i>
|
| 302 |
+
</button>
|
| 303 |
+
<div class="p-6">
|
| 304 |
+
<h3 class="text-xl font-semibold mb-4">Upload New Workout Video</h3>
|
| 305 |
+
|
| 306 |
+
<div class="mb-6">
|
| 307 |
+
<label class="block text-sm font-medium mb-2">Video File</label>
|
| 308 |
+
<div id="modal-upload-area" class="video-upload-area rounded-lg p-8 text-center cursor-pointer mb-2">
|
| 309 |
+
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"></i>
|
| 310 |
+
<p class="font-medium">Drag & drop your video here</p>
|
| 311 |
+
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">or click to browse files</p>
|
| 312 |
+
<input type="file" id="modal-file-input" accept="video/*" class="hidden">
|
| 313 |
+
</div>
|
| 314 |
+
<p class="text-xs text-gray-500 dark:text-gray-400">MP4, MOV or AVI. Max 500MB.</p>
|
| 315 |
+
</div>
|
| 316 |
+
|
| 317 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
| 318 |
+
<div>
|
| 319 |
+
<label for="video-title" class="block text-sm font-medium mb-2">Video Title</label>
|
| 320 |
+
<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">
|
| 321 |
+
</div>
|
| 322 |
+
<div>
|
| 323 |
+
<label for="workout-name" class="block text-sm font-medium mb-2">Workout Name</label>
|
| 324 |
+
<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">
|
| 325 |
+
</div>
|
| 326 |
+
<div>
|
| 327 |
+
<label for="training-name" class="block text-sm font-medium mb-2">Training Program</label>
|
| 328 |
+
<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">
|
| 329 |
+
</div>
|
| 330 |
+
<div>
|
| 331 |
+
<label for="video-date" class="block text-sm font-medium mb-2">Date</label>
|
| 332 |
+
<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">
|
| 333 |
+
</div>
|
| 334 |
+
</div>
|
| 335 |
+
|
| 336 |
+
<div class="mb-6">
|
| 337 |
+
<label for="video-description" class="block text-sm font-medium mb-2">Description</label>
|
| 338 |
+
<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>
|
| 339 |
+
</div>
|
| 340 |
+
|
| 341 |
+
<div class="mb-6">
|
| 342 |
+
<label class="block text-sm font-medium mb-2">Tags</label>
|
| 343 |
+
<div class="flex flex-wrap gap-2 mb-2" id="selected-tags">
|
| 344 |
+
<!-- Tags will be added here -->
|
| 345 |
+
</div>
|
| 346 |
+
<div class="flex">
|
| 347 |
+
<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">
|
| 348 |
+
<button id="add-tag-btn" class="px-4 bg-indigo-600 text-white rounded-r-lg hover:bg-indigo-700">Add</button>
|
| 349 |
+
</div>
|
| 350 |
+
<div class="flex flex-wrap gap-2 mt-2">
|
| 351 |
+
<span class="tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs cursor-pointer">strength</span>
|
| 352 |
+
<span class="tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs cursor-pointer">hiit</span>
|
| 353 |
+
<span class="tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs cursor-pointer">beginner</span>
|
| 354 |
+
<span class="tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs cursor-pointer">advanced</span>
|
| 355 |
+
<span class="tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs cursor-pointer">core</span>
|
| 356 |
+
<span class="tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs cursor-pointer">upper body</span>
|
| 357 |
+
</div>
|
| 358 |
+
</div>
|
| 359 |
+
|
| 360 |
+
<div class="flex justify-end space-x-3">
|
| 361 |
+
<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">
|
| 362 |
+
Cancel
|
| 363 |
+
</button>
|
| 364 |
+
<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">
|
| 365 |
+
<i class="fas fa-save"></i>
|
| 366 |
+
<span>Save Video</span>
|
| 367 |
+
</button>
|
| 368 |
+
</div>
|
| 369 |
+
</div>
|
| 370 |
+
</div>
|
| 371 |
+
</div>
|
| 372 |
+
|
| 373 |
+
<!-- Loading Modal -->
|
| 374 |
+
<div id="loading-modal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden">
|
| 375 |
+
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 text-center">
|
| 376 |
+
<div class="spinner mx-auto mb-4"></div>
|
| 377 |
+
<p id="loading-text" class="text-lg font-medium">Uploading video...</p>
|
| 378 |
+
</div>
|
| 379 |
+
</div>
|
| 380 |
+
|
| 381 |
+
<script>
|
| 382 |
+
// Global variables
|
| 383 |
+
let videos = [];
|
| 384 |
+
let selectedFile = null;
|
| 385 |
+
|
| 386 |
+
// DOM elements
|
| 387 |
+
const videoGrid = document.getElementById('video-grid');
|
| 388 |
+
const loadingSpinner = document.getElementById('loading-spinner');
|
| 389 |
+
const noVideos = document.getElementById('no-videos');
|
| 390 |
+
const totalVideos = document.getElementById('total-videos');
|
| 391 |
+
const totalWorkouts = document.getElementById('total-workouts');
|
| 392 |
+
const totalTags = document.getElementById('total-tags');
|
| 393 |
+
const recentTags = document.getElementById('recent-tags');
|
| 394 |
+
const activeFilters = document.getElementById('active-filters');
|
| 395 |
+
const sortSelect = document.getElementById('sort-select');
|
| 396 |
+
const workoutFilter = document.getElementById('workout-filter');
|
| 397 |
+
const searchInput = document.getElementById('search-input');
|
| 398 |
+
|
| 399 |
+
// Load videos from localStorage
|
| 400 |
+
function loadVideos() {
|
| 401 |
+
try {
|
| 402 |
+
loadingSpinner.classList.remove('hidden');
|
| 403 |
+
noVideos.classList.add('hidden');
|
| 404 |
+
|
| 405 |
+
// Load from localStorage
|
| 406 |
+
const savedVideos = localStorage.getItem('workoutVideos');
|
| 407 |
+
if (savedVideos) {
|
| 408 |
+
videos = JSON.parse(savedVideos);
|
| 409 |
+
} else {
|
| 410 |
+
// Default demo videos
|
| 411 |
+
videos = [
|
| 412 |
+
{
|
| 413 |
+
id: '1',
|
| 414 |
+
title: "Full Body Strength Workout - Week 1",
|
| 415 |
+
description: "Complete full body strength workout focusing on compound movements. Perfect for beginners starting their strength journey.",
|
| 416 |
+
tags: ["strength", "beginner", "full body"],
|
| 417 |
+
date: "2 days ago",
|
| 418 |
+
duration: "4:32",
|
| 419 |
+
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",
|
| 420 |
+
videoUrl: "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4",
|
| 421 |
+
workoutType: "strength",
|
| 422 |
+
uploadDate: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString()
|
| 423 |
+
},
|
| 424 |
+
{
|
| 425 |
+
id: '2',
|
| 426 |
+
title: "Advanced HIIT Circuit - Fat Burning",
|
| 427 |
+
description: "High intensity interval training circuit designed to maximize fat burning and improve cardiovascular endurance.",
|
| 428 |
+
tags: ["hiit", "advanced", "cardio", "fat loss"],
|
| 429 |
+
date: "1 week ago",
|
| 430 |
+
duration: "12:15",
|
| 431 |
+
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",
|
| 432 |
+
videoUrl: "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4",
|
| 433 |
+
workoutType: "hiit",
|
| 434 |
+
uploadDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()
|
| 435 |
+
},
|
| 436 |
+
{
|
| 437 |
+
id: '3',
|
| 438 |
+
title: "Morning Yoga Flow - Flexibility Routine",
|
| 439 |
+
description: "Gentle yoga flow perfect for mornings to improve flexibility, mobility and start your day with energy.",
|
| 440 |
+
tags: ["yoga", "flexibility", "morning"],
|
| 441 |
+
date: "2 weeks ago",
|
| 442 |
+
duration: "8:45",
|
| 443 |
+
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",
|
| 444 |
+
videoUrl: "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4",
|
| 445 |
+
workoutType: "yoga",
|
| 446 |
+
uploadDate: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString()
|
| 447 |
+
}
|
| 448 |
+
];
|
| 449 |
+
// Save to localStorage
|
| 450 |
+
localStorage.setItem('workoutVideos', JSON.stringify(videos));
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
updateStats();
|
| 454 |
+
renderVideos();
|
| 455 |
+
loadingSpinner.classList.add('hidden');
|
| 456 |
+
|
| 457 |
+
if (videos.length === 0) {
|
| 458 |
+
noVideos.classList.remove('hidden');
|
| 459 |
+
noVideos.innerHTML = `
|
| 460 |
+
<i class="fas fa-video-slash text-4xl mb-3"></i>
|
| 461 |
+
<p class="text-lg">No videos found</p>
|
| 462 |
+
<p class="text-sm">Upload your first workout video to get started</p>
|
| 463 |
+
`;
|
| 464 |
+
}
|
| 465 |
+
} catch (error) {
|
| 466 |
+
console.error('Error loading videos:', error);
|
| 467 |
+
loadingSpinner.classList.add('hidden');
|
| 468 |
+
alert('Failed to load videos. Please try again.');
|
| 469 |
+
}
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
// Save videos to localStorage
|
| 473 |
+
function saveVideosToStorage() {
|
| 474 |
+
localStorage.setItem('workoutVideos', JSON.stringify(videos));
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
// Update statistics
|
| 478 |
+
function updateStats() {
|
| 479 |
+
totalVideos.textContent = videos.length;
|
| 480 |
+
|
| 481 |
+
// Count unique workout types
|
| 482 |
+
const workoutTypes = new Set(videos.map(video => video.workoutType));
|
| 483 |
+
totalWorkouts.textContent = workoutTypes.size;
|
| 484 |
+
|
| 485 |
+
// Count unique tags
|
| 486 |
+
const allTags = videos.flatMap(video => video.tags);
|
| 487 |
+
const uniqueTags = new Set(allTags);
|
| 488 |
+
totalTags.textContent = uniqueTags.size;
|
| 489 |
+
|
| 490 |
+
// Update recent tags
|
| 491 |
+
recentTags.innerHTML = '';
|
| 492 |
+
const tagCounts = {};
|
| 493 |
+
allTags.forEach(tag => {
|
| 494 |
+
tagCounts[tag] = (tagCounts[tag] || 0) + 1;
|
| 495 |
+
});
|
| 496 |
+
|
| 497 |
+
const sortedTags = Object.keys(tagCounts).sort((a, b) => tagCounts[b] - tagCounts[a]);
|
| 498 |
+
sortedTags.slice(0, 6).forEach(tag => {
|
| 499 |
+
const tagElement = document.createElement('span');
|
| 500 |
+
tagElement.className = 'tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-sm';
|
| 501 |
+
tagElement.textContent = tag;
|
| 502 |
+
recentTags.appendChild(tagElement);
|
| 503 |
+
});
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
// Render videos to the grid
|
| 507 |
+
function renderVideos() {
|
| 508 |
+
videoGrid.innerHTML = '';
|
| 509 |
+
|
| 510 |
+
// Filter and sort videos
|
| 511 |
+
let filteredVideos = [...videos];
|
| 512 |
+
|
| 513 |
+
// Apply search filter
|
| 514 |
+
const searchTerm = searchInput.value.toLowerCase();
|
| 515 |
+
if (searchTerm) {
|
| 516 |
+
filteredVideos = filteredVideos.filter(video =>
|
| 517 |
+
video.title.toLowerCase().includes(searchTerm) ||
|
| 518 |
+
video.description.toLowerCase().includes(searchTerm) ||
|
| 519 |
+
video.tags.some(tag => tag.toLowerCase().includes(searchTerm))
|
| 520 |
+
);
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
// Apply workout type filter
|
| 524 |
+
const workoutType = workoutFilter.value;
|
| 525 |
+
if (workoutType !== 'all') {
|
| 526 |
+
filteredVideos = filteredVideos.filter(video => video.workoutType === workoutType);
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
// Apply sorting
|
| 530 |
+
const sortBy = sortSelect.value;
|
| 531 |
+
if (sortBy === 'date') {
|
| 532 |
+
filteredVideos.sort((a, b) => new Date(b.uploadDate) - new Date(a.uploadDate));
|
| 533 |
+
} else if (sortBy === 'name') {
|
| 534 |
+
filteredVideos.sort((a, b) => a.title.localeCompare(b.title));
|
| 535 |
+
} else if (sortBy === 'workout') {
|
| 536 |
+
filteredVideos.sort((a, b) => a.workoutType.localeCompare(b.workoutType));
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
// Update active filters
|
| 540 |
+
updateActiveFilters();
|
| 541 |
+
|
| 542 |
+
// Render videos
|
| 543 |
+
if (filteredVideos.length === 0) {
|
| 544 |
+
noVideos.classList.remove('hidden');
|
| 545 |
+
} else {
|
| 546 |
+
noVideos.classList.add('hidden');
|
| 547 |
+
filteredVideos.forEach((video, index) => {
|
| 548 |
+
const videoCard = createVideoCard(video, index);
|
| 549 |
+
videoGrid.appendChild(videoCard);
|
| 550 |
+
});
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
// Add "Add New Video" card
|
| 554 |
+
const addCard = document.createElement('div');
|
| 555 |
+
addCard.className = 'bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden hover:shadow-lg transition-shadow flex items-center justify-center';
|
| 556 |
+
addCard.innerHTML = `
|
| 557 |
+
<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">
|
| 558 |
+
<i class="fas fa-plus-circle text-4xl mb-3"></i>
|
| 559 |
+
<span class="font-medium">Add New Video</span>
|
| 560 |
+
</button>
|
| 561 |
+
`;
|
| 562 |
+
addCard.querySelector('button').addEventListener('click', () => {
|
| 563 |
+
document.getElementById('upload-modal').classList.remove('hidden');
|
| 564 |
+
document.body.style.overflow = 'hidden';
|
| 565 |
+
});
|
| 566 |
+
videoGrid.appendChild(addCard);
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
// Create a video card element
|
| 570 |
+
function createVideoCard(video, index) {
|
| 571 |
+
const card = document.createElement('div');
|
| 572 |
+
card.className = 'bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden hover:shadow-lg transition-shadow';
|
| 573 |
+
card.innerHTML = `
|
| 574 |
+
<div class="video-thumbnail relative">
|
| 575 |
+
<img src="${video.thumbnail}" alt="Workout Video" class="w-full h-full object-cover">
|
| 576 |
+
<div class="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity">
|
| 577 |
+
<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">
|
| 578 |
+
<i class="fas fa-play"></i>
|
| 579 |
+
</button>
|
| 580 |
+
</div>
|
| 581 |
+
<div class="absolute bottom-2 right-2 bg-black bg-opacity-70 text-white text-xs px-2 py-1 rounded">
|
| 582 |
+
${video.duration}
|
| 583 |
+
</div>
|
| 584 |
+
</div>
|
| 585 |
+
<div class="p-4">
|
| 586 |
+
<div class="flex justify-between items-start mb-2">
|
| 587 |
+
<h3 class="font-semibold line-clamp-1">${video.title}</h3>
|
| 588 |
+
<div class="dropdown relative">
|
| 589 |
+
<button class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
|
| 590 |
+
<i class="fas fa-ellipsis-v"></i>
|
| 591 |
+
</button>
|
| 592 |
+
<div class="dropdown-menu absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-md shadow-lg z-10 hidden">
|
| 593 |
+
<div class="py-1">
|
| 594 |
+
<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>
|
| 595 |
+
<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>
|
| 596 |
+
<a href="#" class="block px-4 py-2 text-sm text-red-600 hover:bg-gray-100 dark:hover:bg-gray-700">Delete</a>
|
| 597 |
+
</div>
|
| 598 |
+
</div>
|
| 599 |
+
</div>
|
| 600 |
+
</div>
|
| 601 |
+
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3 line-clamp-2">${video.description}</p>
|
| 602 |
+
<div class="flex flex-wrap gap-2 mb-3">
|
| 603 |
+
${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('')}
|
| 604 |
+
</div>
|
| 605 |
+
<div class="flex justify-between items-center text-xs text-gray-500 dark:text-gray-400">
|
| 606 |
+
<span>${video.date}</span>
|
| 607 |
+
<div class="flex space-x-2">
|
| 608 |
+
<button class="hover:text-indigo-600 dark:hover:text-indigo-400">
|
| 609 |
+
<i class="fas fa-share-alt"></i>
|
| 610 |
+
</button>
|
| 611 |
+
<button class="hover:text-indigo-600 dark:hover:text-indigo-400">
|
| 612 |
+
<i class="fas fa-download"></i>
|
| 613 |
+
</button>
|
| 614 |
+
</div>
|
| 615 |
+
</div>
|
| 616 |
+
</div>
|
| 617 |
+
`;
|
| 618 |
+
|
| 619 |
+
// Add click handler for play button
|
| 620 |
+
card.querySelector('.video-thumbnail button').addEventListener('click', () => {
|
| 621 |
+
openVideoModal(video);
|
| 622 |
+
});
|
| 623 |
+
|
| 624 |
+
// Add dropdown menu functionality
|
| 625 |
+
const dropdownBtn = card.querySelector('.dropdown button');
|
| 626 |
+
const dropdownMenu = card.querySelector('.dropdown-menu');
|
| 627 |
+
|
| 628 |
+
dropdownBtn.addEventListener('click', (e) => {
|
| 629 |
+
e.stopPropagation();
|
| 630 |
+
dropdownMenu.classList.toggle('hidden');
|
| 631 |
+
});
|
| 632 |
+
|
| 633 |
+
// Add click handler for edit button
|
| 634 |
+
const editBtn = card.querySelector('.dropdown-menu a:first-child');
|
| 635 |
+
editBtn.addEventListener('click', (e) => {
|
| 636 |
+
e.preventDefault();
|
| 637 |
+
openEditModal(video);
|
| 638 |
+
});
|
| 639 |
+
|
| 640 |
+
// Add click handler for delete button
|
| 641 |
+
const deleteBtn = card.querySelector('.dropdown-menu a:last-child');
|
| 642 |
+
deleteBtn.addEventListener('click', (e) => {
|
| 643 |
+
e.preventDefault();
|
| 644 |
+
if (confirm('Are you sure you want to delete this video?')) {
|
| 645 |
+
deleteVideo(video.id);
|
| 646 |
+
}
|
| 647 |
+
});
|
| 648 |
+
|
| 649 |
+
return card;
|
| 650 |
+
}
|
| 651 |
+
|
| 652 |
+
// Update active filters display
|
| 653 |
+
function updateActiveFilters() {
|
| 654 |
+
activeFilters.innerHTML = '';
|
| 655 |
+
|
| 656 |
+
// Add search filter if applicable
|
| 657 |
+
const searchTerm = searchInput.value.trim();
|
| 658 |
+
if (searchTerm) {
|
| 659 |
+
const filter = document.createElement('span');
|
| 660 |
+
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';
|
| 661 |
+
filter.innerHTML = `
|
| 662 |
+
<span>Search: "${searchTerm}"</span>
|
| 663 |
+
<button class="ml-2 text-indigo-500 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300">
|
| 664 |
+
<i class="fas fa-times"></i>
|
| 665 |
+
</button>
|
| 666 |
+
`;
|
| 667 |
+
filter.querySelector('button').addEventListener('click', () => {
|
| 668 |
+
searchInput.value = '';
|
| 669 |
+
renderVideos();
|
| 670 |
+
});
|
| 671 |
+
activeFilters.appendChild(filter);
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
// Add workout type filter if applicable
|
| 675 |
+
const workoutType = workoutFilter.value;
|
| 676 |
+
if (workoutType !== 'all') {
|
| 677 |
+
const filter = document.createElement('span');
|
| 678 |
+
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';
|
| 679 |
+
filter.innerHTML = `
|
| 680 |
+
<span>Workout: ${workoutType.charAt(0).toUpperCase() + workoutType.slice(1)}</span>
|
| 681 |
+
<button class="ml-2 text-indigo-500 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300">
|
| 682 |
+
<i class="fas fa-times"></i>
|
| 683 |
+
</button>
|
| 684 |
+
`;
|
| 685 |
+
filter.querySelector('button').addEventListener('click', () => {
|
| 686 |
+
workoutFilter.value = 'all';
|
| 687 |
+
renderVideos();
|
| 688 |
+
});
|
| 689 |
+
activeFilters.appendChild(filter);
|
| 690 |
+
}
|
| 691 |
+
|
| 692 |
+
// Add sort filter if applicable
|
| 693 |
+
const sortBy = sortSelect.value;
|
| 694 |
+
if (sortBy !== 'date') {
|
| 695 |
+
const filter = document.createElement('span');
|
| 696 |
+
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';
|
| 697 |
+
filter.innerHTML = `
|
| 698 |
+
<span>Sorted by: ${sortBy.charAt(0).toUpperCase() + sortBy.slice(1)}</span>
|
| 699 |
+
<button class="ml-2 text-indigo-500 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300">
|
| 700 |
+
<i class="fas fa-times"></i>
|
| 701 |
+
</button>
|
| 702 |
+
`;
|
| 703 |
+
filter.querySelector('button').addEventListener('click', () => {
|
| 704 |
+
sortSelect.value = 'date';
|
| 705 |
+
renderVideos();
|
| 706 |
+
});
|
| 707 |
+
activeFilters.appendChild(filter);
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
// Add "Clear All" button if there are active filters
|
| 711 |
+
if (activeFilters.children.length > 0) {
|
| 712 |
+
const clearAll = document.createElement('button');
|
| 713 |
+
clearAll.className = 'text-sm text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300 flex items-center';
|
| 714 |
+
clearAll.innerHTML = `
|
| 715 |
+
<i class="fas fa-times mr-1"></i>
|
| 716 |
+
<span>Clear All</span>
|
| 717 |
+
`;
|
| 718 |
+
clearAll.addEventListener('click', () => {
|
| 719 |
+
searchInput.value = '';
|
| 720 |
+
workoutFilter.value = 'all';
|
| 721 |
+
sortSelect.value = 'date';
|
| 722 |
+
renderVideos();
|
| 723 |
+
});
|
| 724 |
+
activeFilters.appendChild(clearAll);
|
| 725 |
+
}
|
| 726 |
+
}
|
| 727 |
+
|
| 728 |
+
// Open video modal
|
| 729 |
+
function openVideoModal(video) {
|
| 730 |
+
const videoModal = document.getElementById('video-modal');
|
| 731 |
+
const modalVideo = document.getElementById('modal-video');
|
| 732 |
+
const modalTitle = document.getElementById('modal-title');
|
| 733 |
+
const modalDescription = document.getElementById('modal-description');
|
| 734 |
+
const modalTags = document.getElementById('modal-tags');
|
| 735 |
+
const modalDate = document.getElementById('modal-date');
|
| 736 |
+
const modalDuration = document.getElementById('modal-duration');
|
| 737 |
+
|
| 738 |
+
modalVideo.src = video.videoUrl;
|
| 739 |
+
modalTitle.textContent = video.title;
|
| 740 |
+
modalDescription.textContent = video.description;
|
| 741 |
+
modalDate.textContent = video.date;
|
| 742 |
+
modalDuration.textContent = video.duration;
|
| 743 |
+
|
| 744 |
+
// Clear previous tags
|
| 745 |
+
modalTags.innerHTML = '';
|
| 746 |
+
|
| 747 |
+
// Add new tags
|
| 748 |
+
video.tags.forEach(tag => {
|
| 749 |
+
const tagElement = document.createElement('span');
|
| 750 |
+
tagElement.className = 'tag px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs';
|
| 751 |
+
tagElement.textContent = tag;
|
| 752 |
+
modalTags.appendChild(tagElement);
|
| 753 |
+
});
|
| 754 |
+
|
| 755 |
+
// Add share button handlers
|
| 756 |
+
document.getElementById('whatsapp-share').addEventListener('click', () => {
|
| 757 |
+
shareVideo(video, 'whatsapp');
|
| 758 |
+
});
|
| 759 |
+
|
| 760 |
+
document.getElementById('email-share').addEventListener('click', () => {
|
| 761 |
+
shareVideo(video, 'email');
|
| 762 |
+
});
|
| 763 |
+
|
| 764 |
+
document.getElementById('copy-link').addEventListener('click', () => {
|
| 765 |
+
shareVideo(video, 'link');
|
| 766 |
+
});
|
| 767 |
+
|
| 768 |
+
videoModal.classList.remove('hidden');
|
| 769 |
+
document.body.style.overflow = 'hidden';
|
| 770 |
+
}
|
| 771 |
+
|
| 772 |
+
// Share video
|
| 773 |
+
function shareVideo(video, method) {
|
| 774 |
+
let message = `Check out my workout video: ${video.title}\n\n${video.description}\n\nTags: ${video.tags.join(', ')}`;
|
| 775 |
+
|
| 776 |
+
switch (method) {
|
| 777 |
+
case 'whatsapp':
|
| 778 |
+
window.open(`https://wa.me/?text=${encodeURIComponent(message)}`);
|
| 779 |
+
break;
|
| 780 |
+
case 'email':
|
| 781 |
+
window.open(`mailto:?subject=${encodeURIComponent(video.title)}&body=${encodeURIComponent(message)}`);
|
| 782 |
+
break;
|
| 783 |
+
case 'link':
|
| 784 |
+
navigator.clipboard.writeText(video.videoUrl).then(() => {
|
| 785 |
+
alert('Video link copied to clipboard!');
|
| 786 |
+
});
|
| 787 |
+
break;
|
| 788 |
+
}
|
| 789 |
+
}
|
| 790 |
+
|
| 791 |
+
// Open edit modal
|
| 792 |
+
function openEditModal(video) {
|
| 793 |
+
const uploadModal = document.getElementById('upload-modal');
|
| 794 |
+
const modalTitle = uploadModal.querySelector('h3');
|
| 795 |
+
const videoTitle = document.getElementById('video-title');
|
| 796 |
+
const workoutName = document.getElementById('workout-name');
|
| 797 |
+
const trainingName = document.getElementById('training-name');
|
| 798 |
+
const videoDate = document.getElementById('video-date');
|
| 799 |
+
const videoDescription = document.getElementById('video-description');
|
| 800 |
+
const selectedTags = document.getElementById('selected-tags');
|
| 801 |
+
const modalUploadArea = document.getElementById('modal-upload-area');
|
| 802 |
+
|
| 803 |
+
modalTitle.textContent = 'Edit Video';
|
| 804 |
+
videoTitle.value = video.title;
|
| 805 |
+
workoutName.value = video.workoutType;
|
| 806 |
+
trainingName.value = '';
|
| 807 |
+
videoDate.value = '';
|
| 808 |
+
videoDescription.value = video.description;
|
| 809 |
+
|
| 810 |
+
// Clear previous tags
|
| 811 |
+
selectedTags.innerHTML = '';
|
| 812 |
+
|
| 813 |
+
// Add current tags
|
| 814 |
+
video.tags.forEach(tag => {
|
| 815 |
+
addTagToForm(tag);
|
| 816 |
+
});
|
| 817 |
+
|
| 818 |
+
// Update upload area to show current video
|
| 819 |
+
modalUploadArea.innerHTML = `
|
| 820 |
+
<i class="fas fa-check-circle text-4xl text-green-500 mb-3"></i>
|
| 821 |
+
<p class="font-medium">Current Video: ${video.title}</p>
|
| 822 |
+
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Click to change file</p>
|
| 823 |
+
`;
|
| 824 |
+
|
| 825 |
+
// Store the video being edited
|
| 826 |
+
selectedFile = { name: video.title };
|
| 827 |
+
|
| 828 |
+
uploadModal.classList.remove('hidden');
|
| 829 |
+
document.body.style.overflow = 'hidden';
|
| 830 |
+
}
|
| 831 |
+
|
| 832 |
+
// Delete video
|
| 833 |
+
function deleteVideo(videoId) {
|
| 834 |
+
videos = videos.filter(video => video.id !== videoId);
|
| 835 |
+
saveVideosToStorage();
|
| 836 |
+
updateStats();
|
| 837 |
+
renderVideos();
|
| 838 |
+
}
|
| 839 |
+
|
| 840 |
+
// Upload video (save to localStorage)
|
| 841 |
+
async function uploadVideo(file, metadata) {
|
| 842 |
+
try {
|
| 843 |
+
// Show loading modal
|
| 844 |
+
const loadingModal = document.getElementById('loading-modal');
|
| 845 |
+
const loadingText = document.getElementById('loading-text');
|
| 846 |
+
loadingText.textContent = 'Uploading video...';
|
| 847 |
+
loadingModal.classList.remove('hidden');
|
| 848 |
+
|
| 849 |
+
// Simulate upload delay
|
| 850 |
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
| 851 |
+
|
| 852 |
+
// Create a new video object
|
| 853 |
+
const newVideo = {
|
| 854 |
+
id: Date.now().toString(),
|
| 855 |
+
title: metadata.title,
|
| 856 |
+
description: metadata.description,
|
| 857 |
+
tags: metadata.tags,
|
| 858 |
+
date: "Just now",
|
| 859 |
+
duration: "5:00", // In a real app, you would get this from the video
|
| 860 |
+
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",
|
| 861 |
+
videoUrl: URL.createObjectURL(file),
|
| 862 |
+
workoutType: metadata.workoutType,
|
| 863 |
+
uploadDate: new Date().toISOString()
|
| 864 |
+
};
|
| 865 |
+
|
| 866 |
+
// Add to videos array
|
| 867 |
+
videos.unshift(newVideo);
|
| 868 |
+
|
| 869 |
+
// Save to localStorage
|
| 870 |
+
saveVideosToStorage();
|
| 871 |
+
|
| 872 |
+
// Update UI
|
| 873 |
+
updateStats();
|
| 874 |
+
renderVideos();
|
| 875 |
+
|
| 876 |
+
// Hide loading modal
|
| 877 |
+
loadingModal.classList.add('hidden');
|
| 878 |
+
|
| 879 |
+
// Show success message
|
| 880 |
+
alert('Video uploaded successfully!');
|
| 881 |
+
|
| 882 |
+
// Close upload modal
|
| 883 |
+
document.getElementById('upload-modal').classList.add('hidden');
|
| 884 |
+
document.body.style.overflow = 'auto';
|
| 885 |
+
|
| 886 |
+
// Reset form
|
| 887 |
+
resetUploadForm();
|
| 888 |
+
} catch (error) {
|
| 889 |
+
console.error('Error uploading video:', error);
|
| 890 |
+
loadingModal.classList.add('hidden');
|
| 891 |
+
alert('Failed to upload video. Please try again.');
|
| 892 |
+
}
|
| 893 |
+
}
|
| 894 |
+
|
| 895 |
+
// Reset upload form
|
| 896 |
+
function resetUploadForm() {
|
| 897 |
+
document.getElementById('video-title').value = '';
|
| 898 |
+
document.getElementById('workout-name').value = '';
|
| 899 |
+
document.getElementById('training-name').value = '';
|
| 900 |
+
document.getElementById('video-date').value = '';
|
| 901 |
+
document.getElementById('video-description').value = '';
|
| 902 |
+
document.getElementById('selected-tags').innerHTML = '';
|
| 903 |
+
document.getElementById('modal-upload-area').innerHTML = `
|
| 904 |
+
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"></i>
|
| 905 |
+
<p class="font-medium">Drag & drop your video here</p>
|
| 906 |
+
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">or click to browse files</p>
|
| 907 |
+
`;
|
| 908 |
+
selectedFile = null;
|
| 909 |
+
}
|
| 910 |
+
|
| 911 |
+
// Add tag to form
|
| 912 |
+
function addTagToForm(tagText) {
|
| 913 |
+
const tagElement = document.createElement('span');
|
| 914 |
+
tagElement.className = 'tag px-3 py-1 bg-gray-100 dark:bg-gray-700 rounded-full text-xs flex items-center';
|
| 915 |
+
tagElement.innerHTML = `
|
| 916 |
+
<span>${tagText}</span>
|
| 917 |
+
<button class="ml-2 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300">
|
| 918 |
+
<i class="fas fa-times text-xs"></i>
|
| 919 |
+
</button>
|
| 920 |
+
`;
|
| 921 |
+
|
| 922 |
+
const removeBtn = tagElement.querySelector('button');
|
| 923 |
+
removeBtn.addEventListener('click', () => {
|
| 924 |
+
tagElement.remove();
|
| 925 |
+
});
|
| 926 |
+
|
| 927 |
+
document.getElementById('selected-tags').appendChild(tagElement);
|
| 928 |
+
}
|
| 929 |
+
|
| 930 |
+
// Initialize event listeners
|
| 931 |
+
function initEventListeners() {
|
| 932 |
+
// Theme toggle
|
| 933 |
+
const themeToggle = document.getElementById('theme-toggle');
|
| 934 |
+
themeToggle.addEventListener('click', () => {
|
| 935 |
+
document.documentElement.classList.toggle('dark');
|
| 936 |
+
localStorage.setItem('darkMode', document.documentElement.classList.contains('dark'));
|
| 937 |
+
});
|
| 938 |
+
|
| 939 |
+
// Check for saved theme preference
|
| 940 |
+
if (localStorage.getItem('darkMode') === 'true') {
|
| 941 |
+
document.documentElement.classList.add('dark');
|
| 942 |
+
} else if (localStorage.getItem('darkMode') === 'false') {
|
| 943 |
+
document.documentElement.classList.remove('dark');
|
| 944 |
+
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
| 945 |
+
document.documentElement.classList.add('dark');
|
| 946 |
+
}
|
| 947 |
+
|
| 948 |
+
// Video modal functionality
|
| 949 |
+
const videoModal = document.getElementById('video-modal');
|
| 950 |
+
const closeModal = document.getElementById('close-modal');
|
| 951 |
+
|
| 952 |
+
closeModal.addEventListener('click', () => {
|
| 953 |
+
videoModal.classList.add('hidden');
|
| 954 |
+
document.getElementById('modal-video').pause();
|
| 955 |
+
document.body.style.overflow = 'auto';
|
| 956 |
+
});
|
| 957 |
+
|
| 958 |
+
// Close modal when clicking outside
|
| 959 |
+
videoModal.addEventListener('click', (e) => {
|
| 960 |
+
if (e.target === videoModal) {
|
| 961 |
+
videoModal.classList.add('hidden');
|
| 962 |
+
document.getElementById('modal-video').pause();
|
| 963 |
+
document.body.style.overflow = 'auto';
|
| 964 |
+
}
|
| 965 |
+
});
|
| 966 |
+
|
| 967 |
+
// Upload modal functionality
|
| 968 |
+
const uploadModal = document.getElementById('upload-modal');
|
| 969 |
+
const closeUploadModal = document.getElementById('close-upload-modal');
|
| 970 |
+
const cancelUpload = document.getElementById('cancel-upload');
|
| 971 |
+
const uploadBtn = document.getElementById('upload-btn');
|
| 972 |
+
const recordBtn = document.getElementById('record-btn');
|
| 973 |
+
const uploadArea = document.getElementById('upload-area');
|
| 974 |
+
const fileInput = document.getElementById('file-input');
|
| 975 |
+
const modalFileInput = document.getElementById('modal-file-input');
|
| 976 |
+
const modalUploadArea = document.getElementById('modal-upload-area');
|
| 977 |
+
const selectedTags = document.getElementById('selected-tags');
|
| 978 |
+
const tagInput = document.getElementById('tag-input');
|
| 979 |
+
const addTagBtn = document.getElementById('add-tag-btn');
|
| 980 |
+
const saveVideoBtn = document.getElementById('save-video');
|
| 981 |
+
|
| 982 |
+
// Open upload modal
|
| 983 |
+
uploadBtn.addEventListener('click', () => {
|
| 984 |
+
uploadModal.classList.remove('hidden');
|
| 985 |
+
document.body.style.overflow = 'hidden';
|
| 986 |
+
});
|
| 987 |
+
|
| 988 |
+
// Open upload modal from record button
|
| 989 |
+
recordBtn.addEventListener('click', () => {
|
| 990 |
+
// In a real app, this would open camera recording
|
| 991 |
+
uploadModal.classList.remove('hidden');
|
| 992 |
+
document.body.style.overflow = 'hidden';
|
| 993 |
+
});
|
| 994 |
+
|
| 995 |
+
// Close upload modal
|
| 996 |
+
closeUploadModal.addEventListener('click', () => {
|
| 997 |
+
uploadModal.classList.add('hidden');
|
| 998 |
+
document.body.style.overflow = 'auto';
|
| 999 |
+
resetUploadForm();
|
| 1000 |
+
});
|
| 1001 |
+
|
| 1002 |
+
cancelUpload.addEventListener('click', () => {
|
| 1003 |
+
uploadModal.classList.add('hidden');
|
| 1004 |
+
document.body.style.overflow = 'auto';
|
| 1005 |
+
resetUploadForm();
|
| 1006 |
+
});
|
| 1007 |
+
|
| 1008 |
+
// Close modal when clicking outside
|
| 1009 |
+
uploadModal.addEventListener('click', (e) => {
|
| 1010 |
+
if (e.target === uploadModal) {
|
| 1011 |
+
uploadModal.classList.add('hidden');
|
| 1012 |
+
document.body.style.overflow = 'auto';
|
| 1013 |
+
resetUploadForm();
|
| 1014 |
+
}
|
| 1015 |
+
});
|
| 1016 |
+
|
| 1017 |
+
// File upload handling
|
| 1018 |
+
uploadArea.addEventListener('click', () => {
|
| 1019 |
+
fileInput.click();
|
| 1020 |
+
});
|
| 1021 |
+
|
| 1022 |
+
modalUploadArea.addEventListener('click', () => {
|
| 1023 |
+
modalFileInput.click();
|
| 1024 |
+
});
|
| 1025 |
+
|
| 1026 |
+
modalFileInput.addEventListener('change', (e) => {
|
| 1027 |
+
if (e.target.files.length > 0) {
|
| 1028 |
+
selectedFile = e.target.files[0];
|
| 1029 |
+
modalUploadArea.innerHTML = `
|
| 1030 |
+
<i class="fas fa-check-circle text-4xl text-green-500 mb-3"></i>
|
| 1031 |
+
<p class="font-medium">${selectedFile.name}</p>
|
| 1032 |
+
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Click to change file</p>
|
| 1033 |
+
`;
|
| 1034 |
+
}
|
| 1035 |
+
});
|
| 1036 |
+
|
| 1037 |
+
// Drag and drop functionality
|
| 1038 |
+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
| 1039 |
+
[uploadArea, modalUploadArea].forEach(area => {
|
| 1040 |
+
area.addEventListener(eventName, preventDefaults, false);
|
| 1041 |
+
});
|
| 1042 |
+
});
|
| 1043 |
+
|
| 1044 |
+
function preventDefaults(e) {
|
| 1045 |
+
e.preventDefault();
|
| 1046 |
+
e.stopPropagation();
|
| 1047 |
+
}
|
| 1048 |
+
|
| 1049 |
+
['dragenter', 'dragover'].forEach(eventName => {
|
| 1050 |
+
[uploadArea, modalUploadArea].forEach(area => {
|
| 1051 |
+
area.addEventListener(eventName, highlight, false);
|
| 1052 |
+
});
|
| 1053 |
+
});
|
| 1054 |
+
|
| 1055 |
+
['dragleave', 'drop'].forEach(eventName => {
|
| 1056 |
+
[uploadArea, modalUploadArea].forEach(area => {
|
| 1057 |
+
area.addEventListener(eventName, unhighlight, false);
|
| 1058 |
+
});
|
| 1059 |
+
});
|
| 1060 |
+
|
| 1061 |
+
function highlight() {
|
| 1062 |
+
this.classList.add('dragover');
|
| 1063 |
+
}
|
| 1064 |
+
|
| 1065 |
+
function unhighlight() {
|
| 1066 |
+
this.classList.remove('dragover');
|
| 1067 |
+
}
|
| 1068 |
+
|
| 1069 |
+
uploadArea.addEventListener('drop', handleDrop, false);
|
| 1070 |
+
modalUploadArea.addEventListener('drop', handleModalDrop, false);
|
| 1071 |
+
|
| 1072 |
+
function handleDrop(e) {
|
| 1073 |
+
const dt = e.dataTransfer;
|
| 1074 |
+
const files = dt.files;
|
| 1075 |
+
fileInput.files = files;
|
| 1076 |
+
|
| 1077 |
+
if (files.length > 0) {
|
| 1078 |
+
const fileName = files[0].name;
|
| 1079 |
+
uploadArea.innerHTML = `
|
| 1080 |
+
<i class="fas fa-check-circle text-4xl text-green-500 mb-3"></i>
|
| 1081 |
+
<p class="font-medium">${fileName}</p>
|
| 1082 |
+
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Click to change file</p>
|
| 1083 |
+
`;
|
| 1084 |
+
}
|
| 1085 |
+
}
|
| 1086 |
+
|
| 1087 |
+
function handleModalDrop(e) {
|
| 1088 |
+
const dt = e.dataTransfer;
|
| 1089 |
+
const files = dt.files;
|
| 1090 |
+
modalFileInput.files = files;
|
| 1091 |
+
|
| 1092 |
+
if (files.length > 0) {
|
| 1093 |
+
selectedFile = files[0];
|
| 1094 |
+
modalUploadArea.innerHTML = `
|
| 1095 |
+
<i class="fas fa-check-circle text-4xl text-green-500 mb-3"></i>
|
| 1096 |
+
<p class="font-medium">${selectedFile.name}</p>
|
| 1097 |
+
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Click to change file</p>
|
| 1098 |
+
`;
|
| 1099 |
+
}
|
| 1100 |
+
}
|
| 1101 |
+
|
| 1102 |
+
// Tag functionality
|
| 1103 |
+
addTagBtn.addEventListener('click', () => {
|
| 1104 |
+
const tagText = tagInput.value.trim();
|
| 1105 |
+
if (tagText) {
|
| 1106 |
+
addTagToForm(tagText);
|
| 1107 |
+
tagInput.value = '';
|
| 1108 |
+
}
|
| 1109 |
+
});
|
| 1110 |
+
|
| 1111 |
+
tagInput.addEventListener('keypress', (e) => {
|
| 1112 |
+
if (e.key === 'Enter') {
|
| 1113 |
+
const tagText = tagInput.value.trim();
|
| 1114 |
+
if (tagText) {
|
| 1115 |
+
addTagToForm(tagText);
|
| 1116 |
+
tagInput.value = '';
|
| 1117 |
+
}
|
| 1118 |
+
}
|
| 1119 |
+
});
|
| 1120 |
+
|
| 1121 |
+
// Add click handler for suggested tags
|
| 1122 |
+
document.querySelectorAll('#upload-modal .tag').forEach(tag => {
|
| 1123 |
+
tag.addEventListener('click', () => {
|
| 1124 |
+
const tagText = tag.textContent;
|
| 1125 |
+
addTagToForm(tagText);
|
| 1126 |
+
});
|
| 1127 |
+
});
|
| 1128 |
+
|
| 1129 |
+
// Save video
|
| 1130 |
+
saveVideoBtn.addEventListener('click', () => {
|
| 1131 |
+
if (!selectedFile) {
|
| 1132 |
+
alert('Please select a video file to upload.');
|
| 1133 |
+
return;
|
| 1134 |
+
}
|
| 1135 |
+
|
| 1136 |
+
const title = document.getElementById('video-title').value.trim();
|
| 1137 |
+
if (!title) {
|
| 1138 |
+
alert('Please enter a title for your video.');
|
| 1139 |
+
return;
|
| 1140 |
+
}
|
| 1141 |
+
|
| 1142 |
+
const workoutType = document.getElementById('workout-name').value.trim();
|
| 1143 |
+
const description = document.getElementById('video-description').value.trim();
|
| 1144 |
+
|
| 1145 |
+
// Get selected tags
|
| 1146 |
+
const tags = [];
|
| 1147 |
+
document.querySelectorAll('#selected-tags span span:first-child').forEach(tag => {
|
| 1148 |
+
tags.push(tag.textContent);
|
| 1149 |
+
});
|
| 1150 |
+
|
| 1151 |
+
// Create metadata object
|
| 1152 |
+
const metadata = {
|
| 1153 |
+
title,
|
| 1154 |
+
workoutType,
|
| 1155 |
+
description,
|
| 1156 |
+
tags
|
| 1157 |
+
};
|
| 1158 |
+
|
| 1159 |
+
// Upload the video
|
| 1160 |
+
uploadVideo(selectedFile, metadata);
|
| 1161 |
+
});
|
| 1162 |
+
|
| 1163 |
+
// Dropdown menus
|
| 1164 |
+
document.querySelectorAll('.dropdown button').forEach(button => {
|
| 1165 |
+
button.addEventListener('click', (e) => {
|
| 1166 |
+
e.stopPropagation();
|
| 1167 |
+
const menu = button.nextElementSibling;
|
| 1168 |
+
menu.classList.toggle('hidden');
|
| 1169 |
+
});
|
| 1170 |
+
});
|
| 1171 |
+
|
| 1172 |
+
// Close dropdown when clicking outside
|
| 1173 |
+
document.addEventListener('click', () => {
|
| 1174 |
+
document.querySelectorAll('.dropdown-menu').forEach(menu => {
|
| 1175 |
+
menu.classList.add('hidden');
|
| 1176 |
+
});
|
| 1177 |
+
});
|
| 1178 |
+
|
| 1179 |
+
// Filter and search functionality
|
| 1180 |
+
sortSelect.addEventListener('change', renderVideos);
|
| 1181 |
+
workoutFilter.addEventListener('change', renderVideos);
|
| 1182 |
+
searchInput.addEventListener('input', renderVideos);
|
| 1183 |
+
}
|
| 1184 |
+
|
| 1185 |
+
// Initialize the app
|
| 1186 |
+
function init() {
|
| 1187 |
+
initEventListeners();
|
| 1188 |
+
loadVideos();
|
| 1189 |
+
}
|
| 1190 |
+
|
| 1191 |
+
// Initialize when DOM is loaded
|
| 1192 |
+
document.addEventListener('DOMContentLoaded', init);
|
| 1193 |
+
</script>
|
| 1194 |
+
<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>
|
| 1195 |
+
</html>
|
prompts.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
can you connect it to database or maybe even google drive
|
| 2 |
+
itaybarak01@gmail.com You can't sign in to this app because it doesn't comply with Google's OAuth 2.0 policy for keeping apps secure. You can let the app developer know that this app doesn't comply with one or more Google validation rules. Learn more about this error If you are a developer of this app, see error details. Error 400: invalid_request
|
| 3 |
+
cess blocked: Authorization Error itaybarak01@gmail.com You can't sign in to this app because it doesn't comply with Google's OAuth 2.0 policy for keeping apps secure. You can let the app developer know that this app doesn't comply with one or more Google validation rules. Learn more about this error If you are a developer of this app, see error details. Error 400: invalid_request
|