Sami commited on
Commit
cf9c856
·
1 Parent(s): a7515cf

Setup Docker-based Hugging Face Space with project directory

Browse files
.gitignore ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Node modules
2
+ node_modules/
3
+
4
+ # Build files
5
+ dist/
6
+ build/
7
+
8
+ # Environment variables
9
+ .env
10
+ .env.local
11
+ .env.development.local
12
+ .env.test.local
13
+ .env.production.local
14
+
15
+ # Log files
16
+ npm-debug.log*
17
+ yarn-debug.log*
18
+ yarn-error.log*
19
+ logs/
20
+ *.log
21
+
22
+ # Editor directories and files
23
+ .idea/
24
+ .vscode/
25
+ *.swp
26
+ *.swo
27
+
28
+ # OS files
29
+ .DS_Store
30
+ Thumbs.db
31
+
32
+ # Docker
33
+ .dockerignore
Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM nginx:alpine
2
+
3
+ # Install Python for health check
4
+ RUN apk add --no-cache python3
5
+
6
+ # Copy all project files to the nginx html directory
7
+ COPY . /usr/share/nginx/html
8
+
9
+ # Remove the .git directory from the final image
10
+ RUN rm -rf /usr/share/nginx/html/.git
11
+
12
+ # Configure nginx
13
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
14
+
15
+ # Make health check script executable
16
+ RUN chmod +x /usr/share/nginx/html/healthcheck.py
17
+
18
+ # Expose port 7860 (default for Hugging Face Spaces)
19
+ EXPOSE 7860
20
+
21
+ # Health check
22
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
23
+ CMD python3 /usr/share/nginx/html/healthcheck.py
24
+
25
+ # Start nginx
26
+ CMD ["nginx", "-g", "daemon off;"]
README.md CHANGED
@@ -7,4 +7,36 @@ sdk: docker
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  pinned: false
8
  ---
9
 
10
+ # Wordlist Video Projects
11
+
12
+ A collection of video editing and generation tools hosted on Hugging Face Spaces.
13
+
14
+ ## Available Projects
15
+
16
+ ### React-based Video Editors
17
+ - **Enhanced VideoGen Pro**: A React-based video generation tool with advanced features
18
+ - **VideoGen Pro V2**: Version 2 with additional features
19
+ - **VideoGen Pro V3**: Version 3 with hand movement capabilities
20
+
21
+ ### HTML-based Video Editors
22
+ - **HTML Video Editor**: A simple HTML-based video editor
23
+ - **Advanced HTML Video Animation**: Enhanced HTML video animation tool
24
+
25
+ ## Technical Details
26
+
27
+ This Hugging Face Space uses a simple Docker setup with Nginx to serve the various projects. The main page provides a directory to navigate between the different tools.
28
+
29
+ ## Usage
30
+
31
+ Simply visit the Space URL to access the main directory page, then click on any project to explore its features.
32
+
33
+ ## Development
34
+
35
+ To modify or add new projects:
36
+ 1. Add your project files to the appropriate directory
37
+ 2. Update the main index.html to include links to your new project
38
+ 3. Commit and push your changes to update the Space
39
+
40
+ ## License
41
+
42
+ This project is open source and available under the MIT License.
favicon.ico ADDED
healthcheck.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import http.client
3
+ import time
4
+ import sys
5
+
6
+ def check_server_health():
7
+ """Check if the server is running and responding to requests."""
8
+ conn = http.client.HTTPConnection("localhost", 7860)
9
+
10
+ try:
11
+ conn.request("GET", "/")
12
+ response = conn.getresponse()
13
+ return response.status == 200
14
+ except Exception as e:
15
+ print(f"Error checking server health: {e}")
16
+ return False
17
+ finally:
18
+ conn.close()
19
+
20
+ # Wait for the server to start (max 30 seconds)
21
+ max_attempts = 30
22
+ for attempt in range(max_attempts):
23
+ if check_server_health():
24
+ print("Server is healthy!")
25
+ sys.exit(0)
26
+
27
+ print(f"Waiting for server to start... ({attempt+1}/{max_attempts})")
28
+ time.sleep(1)
29
+
30
+ print("Server failed to start within the expected time.")
31
+ sys.exit(1)
index.html ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Wordlist Video Projects</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
+ line-height: 1.6;
11
+ color: #333;
12
+ max-width: 1200px;
13
+ margin: 0 auto;
14
+ padding: 20px;
15
+ background-color: #f9f9f9;
16
+ }
17
+ header {
18
+ background: linear-gradient(to right, #FFD700, #CCCCCC);
19
+ color: white;
20
+ text-align: center;
21
+ padding: 2rem;
22
+ border-radius: 8px;
23
+ margin-bottom: 2rem;
24
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
25
+ }
26
+ h1 {
27
+ margin: 0;
28
+ font-size: 2.5rem;
29
+ text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
30
+ }
31
+ .projects-container {
32
+ display: grid;
33
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
34
+ gap: 2rem;
35
+ }
36
+ .project-card {
37
+ background-color: white;
38
+ border-radius: 8px;
39
+ overflow: hidden;
40
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
41
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
42
+ }
43
+ .project-card:hover {
44
+ transform: translateY(-5px);
45
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
46
+ }
47
+ .project-image {
48
+ height: 200px;
49
+ background-color: #eee;
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: center;
53
+ font-size: 3rem;
54
+ color: #666;
55
+ }
56
+ .project-content {
57
+ padding: 1.5rem;
58
+ }
59
+ .project-title {
60
+ margin-top: 0;
61
+ margin-bottom: 0.5rem;
62
+ font-size: 1.5rem;
63
+ }
64
+ .project-description {
65
+ color: #666;
66
+ margin-bottom: 1.5rem;
67
+ }
68
+ .project-link {
69
+ display: inline-block;
70
+ background-color: #FFD700;
71
+ color: white;
72
+ text-decoration: none;
73
+ padding: 0.5rem 1rem;
74
+ border-radius: 4px;
75
+ font-weight: bold;
76
+ transition: background-color 0.3s ease;
77
+ }
78
+ .project-link:hover {
79
+ background-color: #E6C200;
80
+ }
81
+ footer {
82
+ text-align: center;
83
+ margin-top: 3rem;
84
+ padding-top: 1rem;
85
+ border-top: 1px solid #eee;
86
+ color: #666;
87
+ }
88
+ </style>
89
+ </head>
90
+ <body>
91
+ <header>
92
+ <h1>Wordlist Video Projects</h1>
93
+ <p>A collection of video editing and generation tools</p>
94
+ </header>
95
+
96
+ <main>
97
+ <div class="projects-container">
98
+ <!-- React Video Editor Projects -->
99
+ <div class="project-card">
100
+ <div class="project-image">🎬</div>
101
+ <div class="project-content">
102
+ <h2 class="project-title">Enhanced VideoGen Pro</h2>
103
+ <p class="project-description">A React-based video generation tool with advanced features.</p>
104
+ <a href="react/enhanced-videogen-pro.tsx" class="project-link">View Project</a>
105
+ </div>
106
+ </div>
107
+
108
+ <div class="project-card">
109
+ <div class="project-image">🎬</div>
110
+ <div class="project-content">
111
+ <h2 class="project-title">VideoGen Pro V2</h2>
112
+ <p class="project-description">Version 2 of the React video generation tool with additional features.</p>
113
+ <a href="react/enhanced-videogen-pro-v2.tsx" class="project-link">View Project</a>
114
+ </div>
115
+ </div>
116
+
117
+ <div class="project-card">
118
+ <div class="project-image">🎬</div>
119
+ <div class="project-content">
120
+ <h2 class="project-title">VideoGen Pro V3</h2>
121
+ <p class="project-description">Version 3 with hand movement capabilities for more interactive editing.</p>
122
+ <a href="react/enhanced-videogen-pro-v3-permite-mover-con-mano.tsx" class="project-link">View Project</a>
123
+ </div>
124
+ </div>
125
+
126
+ <!-- HTML Video Editor -->
127
+ <div class="project-card">
128
+ <div class="project-image">🎥</div>
129
+ <div class="project-content">
130
+ <h2 class="project-title">HTML Video Editor</h2>
131
+ <p class="project-description">A simple HTML-based video editor with basic functionality.</p>
132
+ <a href="video-editor-html/index.html" class="project-link">View Project</a>
133
+ </div>
134
+ </div>
135
+
136
+ <div class="project-card">
137
+ <div class="project-image">🎥</div>
138
+ <div class="project-content">
139
+ <h2 class="project-title">Advanced HTML Video Animation</h2>
140
+ <p class="project-description">Enhanced HTML video animation tool with additional features.</p>
141
+ <a href="video-editor-html/video-animation copy BUENO BUENO BUENO copy.html" class="project-link">View Project</a>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </main>
146
+
147
+ <footer>
148
+ <p>© 2023 Wordlist Video Projects. Hosted on Hugging Face Spaces.</p>
149
+ </footer>
150
+ </body>
151
+ </html>
nginx.conf ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ server {
2
+ listen 7860;
3
+ server_name localhost;
4
+ root /usr/share/nginx/html;
5
+ index index.html;
6
+
7
+ location / {
8
+ try_files $uri $uri/ =404;
9
+ }
10
+
11
+ # Enable gzip compression
12
+ gzip on;
13
+ gzip_types text/plain text/css application/javascript application/json;
14
+ gzip_min_length 1000;
15
+ }
react/enhanced-videogen-pro-v2.tsx ADDED
@@ -0,0 +1,1267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import {
3
+ Plus, Trash2, Edit3, Download, Play, Pause, ChevronDown,
4
+ Settings, Save, X, FileText, UploadCloud, RefreshCw,
5
+ Volume2, Clock, FileVideo, Check, AlertTriangle, Zap,
6
+ Layers, Grid, ArrowLeft, ArrowRight, Move, Eye
7
+ } from 'lucide-react';
8
+
9
+ // Shadcn UI components
10
+ import { Alert, AlertDescription } from '@/components/ui/alert';
11
+ import { Button } from '@/components/ui/button';
12
+ import { Input } from '@/components/ui/input';
13
+ import { Label } from '@/components/ui/label';
14
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
15
+ import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
16
+ import { Badge } from '@/components/ui/badge';
17
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
18
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
19
+ import { Switch } from '@/components/ui/switch';
20
+ import { Progress } from '@/components/ui/progress';
21
+ import { Checkbox } from '@/components/ui/checkbox';
22
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
23
+ import { Textarea } from '@/components/ui/textarea';
24
+
25
+ const VideoGeneratorApp = () => {
26
+ // State management
27
+ const [activeTab, setActiveTab] = useState("blocks");
28
+ const [currentBlock, setCurrentBlock] = useState(null);
29
+ const [isPlaying, setIsPlaying] = useState(false);
30
+ const [progress, setProgress] = useState(0);
31
+ const [showEditor, setShowEditor] = useState(false);
32
+ const [targetLanguage, setTargetLanguage] = useState("es-ES");
33
+ const [nativeLanguage, setNativeLanguage] = useState("en-US");
34
+ const [currentTime, setCurrentTime] = useState(0);
35
+ const [duration, setDuration] = useState(15);
36
+ const [editingElement, setEditingElement] = useState(null);
37
+ const timelineRef = useRef(null);
38
+ const previewRef = useRef(null);
39
+
40
+ // Project data
41
+ const [projectName, setProjectName] = useState("Vocabulary Project");
42
+ const [showNotification, setShowNotification] = useState(false);
43
+ const [notificationMessage, setNotificationMessage] = useState("");
44
+ const [generatingStatus, setGeneratingStatus] = useState(null);
45
+ const [renderQueue, setRenderQueue] = useState([]);
46
+ const [selectedAnimationStyle, setSelectedAnimationStyle] = useState("fadeIn");
47
+ const [audioSyncEnabled, setAudioSyncEnabled] = useState(true);
48
+
49
+ // Content data
50
+ const [content, setContent] = useState([
51
+ {
52
+ id: 1,
53
+ targetWord: "médico",
54
+ nativeWord: "doctor",
55
+ targetPhrase1: "quiere ser médico",
56
+ nativePhrase1: "he wants to be a doctor",
57
+ targetPhrase2: "su médico es bueno",
58
+ nativePhrase2: "his doctor is good",
59
+ },
60
+ {
61
+ id: 2,
62
+ targetWord: "abogado",
63
+ nativeWord: "lawyer",
64
+ targetPhrase1: "estudia para abogado",
65
+ nativePhrase1: "he studies to be a lawyer",
66
+ targetPhrase2: "necesita un abogado",
67
+ nativePhrase2: "he needs a lawyer",
68
+ }
69
+ ]);
70
+
71
+ const [currentContentIndex, setCurrentContentIndex] = useState(0);
72
+ const [newRow, setNewRow] = useState({
73
+ targetWord: "",
74
+ nativeWord: "",
75
+ targetPhrase1: "",
76
+ nativePhrase1: "",
77
+ targetPhrase2: "",
78
+ nativePhrase2: ""
79
+ });
80
+
81
+ // Blocks
82
+ const [blocks, setBlocks] = useState([
83
+ {
84
+ id: "vocab-block",
85
+ name: "Vocabulary Block",
86
+ description: "Standard vocabulary block with word and phrases",
87
+ duration: 15,
88
+ background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)"
89
+ },
90
+ {
91
+ id: "grammar-block",
92
+ name: "Grammar Block",
93
+ description: "Block for grammar explanations with examples",
94
+ duration: 20,
95
+ background: "linear-gradient(135deg, #16213e 0%, #0f3460 100%)"
96
+ },
97
+ {
98
+ id: "conversation-block",
99
+ name: "Conversation Block",
100
+ description: "Dialog-based block for conversation practice",
101
+ duration: 30,
102
+ background: "linear-gradient(135deg, #0f3460 0%, #950740 100%)"
103
+ }
104
+ ]);
105
+
106
+ // Timeline elements
107
+ const [timelineElements, setTimelineElements] = useState([
108
+ {
109
+ id: "el-1",
110
+ type: "text",
111
+ content: "Vocabulary Word",
112
+ start: 0,
113
+ duration: 3,
114
+ position: {x: 50, y: 20},
115
+ style: {color: "#ffffff", fontSize: 36, fontWeight: "bold"},
116
+ animation: "fadeIn",
117
+ contentKey: "targetWord"
118
+ },
119
+ {
120
+ id: "el-2",
121
+ type: "block",
122
+ content: "Color Block",
123
+ start: 0.5,
124
+ duration: 12,
125
+ position: {x: 30, y: 35},
126
+ style: {width: 200, height: 200, borderRadius: "8px", background: "linear-gradient(45deg, #3490dc, #6574cd)"},
127
+ animation: "zoomIn"
128
+ },
129
+ {
130
+ id: "el-3",
131
+ type: "text",
132
+ content: "Example Phrase 1",
133
+ start: 4,
134
+ duration: 3,
135
+ position: {x: 50, y: 65},
136
+ style: {color: "#ffffff", fontSize: 24},
137
+ animation: "slideIn",
138
+ contentKey: "targetPhrase1"
139
+ },
140
+ {
141
+ id: "el-4",
142
+ type: "text",
143
+ content: "Example Phrase 2",
144
+ start: 8,
145
+ duration: 3,
146
+ position: {x: 50, y: 80},
147
+ style: {color: "#ffffff", fontSize: 24},
148
+ animation: "slideIn",
149
+ contentKey: "targetPhrase2"
150
+ },
151
+ {
152
+ id: "el-5",
153
+ type: "audio",
154
+ content: "narration.mp3",
155
+ start: 0,
156
+ duration: 15
157
+ },
158
+ ]);
159
+
160
+ // Notification helper
161
+ const showNotify = (message) => {
162
+ setNotificationMessage(message);
163
+ setShowNotification(true);
164
+ setTimeout(() => setShowNotification(false), 3000);
165
+ };
166
+
167
+ // Block selection
168
+ const selectBlock = (blockId) => {
169
+ setCurrentBlock(blocks.find(b => b.id === blockId));
170
+ showNotify("Block selected: " + blocks.find(b => b.id === blockId).name);
171
+ setActiveTab("content");
172
+ };
173
+
174
+ // Add new content row
175
+ const addContentRow = () => {
176
+ if (!newRow.targetWord || !newRow.nativeWord) {
177
+ showNotify("Target and native words are required");
178
+ return;
179
+ }
180
+
181
+ setContent([...content, {
182
+ id: content.length + 1,
183
+ ...newRow
184
+ }]);
185
+
186
+ setNewRow({
187
+ targetWord: "",
188
+ nativeWord: "",
189
+ targetPhrase1: "",
190
+ nativePhrase1: "",
191
+ targetPhrase2: "",
192
+ nativePhrase2: ""
193
+ });
194
+
195
+ showNotify("New content row added");
196
+ };
197
+
198
+ // Delete content row
199
+ const deleteContentRow = (id) => {
200
+ setContent(content.filter(row => row.id !== id));
201
+ showNotify("Content row deleted");
202
+ };
203
+
204
+ // Toggle play/pause
205
+ const togglePlayback = () => {
206
+ setIsPlaying(!isPlaying);
207
+ };
208
+
209
+ // Animation preview
210
+ useEffect(() => {
211
+ let interval;
212
+ if (isPlaying) {
213
+ interval = setInterval(() => {
214
+ setCurrentTime(time => {
215
+ const newTime = time + 0.1;
216
+ if (newTime >= duration) {
217
+ setIsPlaying(false);
218
+ return 0;
219
+ }
220
+ return newTime;
221
+ });
222
+
223
+ setProgress(prog => {
224
+ const newProg = prog + (100 / (duration * 10));
225
+ return newProg > 100 ? 0 : newProg;
226
+ });
227
+ }, 100);
228
+ }
229
+
230
+ return () => clearInterval(interval);
231
+ }, [isPlaying, duration]);
232
+
233
+ // Handle timeline scrubbing
234
+ const handleTimelineClick = (e) => {
235
+ if (!timelineRef.current) return;
236
+
237
+ const rect = timelineRef.current.getBoundingClientRect();
238
+ const clickPosition = e.clientX - rect.left;
239
+ const percentClicked = clickPosition / rect.width;
240
+ const newTime = percentClicked * duration;
241
+
242
+ setCurrentTime(Math.max(0, Math.min(newTime, duration)));
243
+ setProgress(percentClicked * 100);
244
+ };
245
+
246
+ // Open element editor
247
+ const openElementEditor = (element) => {
248
+ setEditingElement(element);
249
+ setShowEditor(true);
250
+ };
251
+
252
+ // Update element
253
+ const updateElement = (updatedElement) => {
254
+ setTimelineElements(elements =>
255
+ elements.map(el => el.id === updatedElement.id ? updatedElement : el)
256
+ );
257
+ setShowEditor(false);
258
+ showNotify("Element updated");
259
+ };
260
+
261
+ // Add new element
262
+ const addTimelineElement = (type) => {
263
+ const newElement = {
264
+ id: `el-${Date.now()}`,
265
+ type,
266
+ content: type === 'text' ? 'New Text' : type === 'block' ? 'Color Block' : 'audio.mp3',
267
+ start: currentTime,
268
+ duration: type === 'audio' ? 5 : 3,
269
+ position: type !== 'audio' ? {x: 50, y: 50} : undefined,
270
+ style: type === 'text' ? {color: "#ffffff", fontSize: 24} :
271
+ type === 'block' ? {
272
+ width: 150,
273
+ height: 150,
274
+ borderRadius: "8px",
275
+ background: "linear-gradient(45deg, #3490dc, #6574cd)"
276
+ } : undefined,
277
+ animation: type !== 'audio' ? selectedAnimationStyle : undefined
278
+ };
279
+
280
+ setTimelineElements([...timelineElements, newElement]);
281
+ setEditingElement(newElement);
282
+ setShowEditor(true);
283
+ showNotify(`New ${type} element added`);
284
+ };
285
+
286
+ // Generate audio
287
+ const generateAudio = () => {
288
+ showNotify("Generating audio for content...");
289
+
290
+ // Simulate audio generation
291
+ setTimeout(() => {
292
+ showNotify("Audio generation complete!");
293
+ }, 1500);
294
+ };
295
+
296
+ // Generate videos for all content
297
+ const generateAllVideos = () => {
298
+ if (!currentBlock) {
299
+ showNotify("Please select a block first");
300
+ return;
301
+ }
302
+
303
+ if (content.length === 0) {
304
+ showNotify("No content to generate");
305
+ return;
306
+ }
307
+
308
+ // Create render queue
309
+ const queue = content.map(item => ({
310
+ id: `render-${item.id}`,
311
+ content: item,
312
+ block: currentBlock,
313
+ status: "pending",
314
+ progress: 0
315
+ }));
316
+
317
+ setRenderQueue(queue);
318
+ setGeneratingStatus("preparing");
319
+
320
+ // Simulate processing
321
+ setTimeout(() => {
322
+ setGeneratingStatus("processing");
323
+
324
+ let currentIndex = 0;
325
+ const processNext = () => {
326
+ if (currentIndex >= queue.length) {
327
+ setGeneratingStatus("complete");
328
+ return;
329
+ }
330
+
331
+ const updatedQueue = [...queue];
332
+ updatedQueue[currentIndex].status = "processing";
333
+ setRenderQueue(updatedQueue);
334
+
335
+ // Simulate processing time
336
+ let progress = 0;
337
+ const interval = setInterval(() => {
338
+ progress += 10;
339
+
340
+ const newQueue = [...updatedQueue];
341
+ newQueue[currentIndex].progress = progress;
342
+ setRenderQueue(newQueue);
343
+
344
+ if (progress >= 100) {
345
+ clearInterval(interval);
346
+ newQueue[currentIndex].status = "complete";
347
+ setRenderQueue(newQueue);
348
+ currentIndex++;
349
+ setTimeout(processNext, 500);
350
+ }
351
+ }, 300);
352
+ };
353
+
354
+ processNext();
355
+ }, 1500);
356
+ };
357
+
358
+ // Reset progress for new render
359
+ const startNewProject = () => {
360
+ setCurrentBlock(null);
361
+ setActiveTab("blocks");
362
+ setProjectName("New Project");
363
+ setContent([]);
364
+ setRenderQueue([]);
365
+ setGeneratingStatus(null);
366
+ showNotify("Started new project");
367
+ };
368
+
369
+ // Get element animation state
370
+ const getElementAnimationState = (element) => {
371
+ if (currentTime < element.start) {
372
+ return "before";
373
+ } else if (currentTime >= element.start && currentTime < element.start + element.duration) {
374
+ return "active";
375
+ } else {
376
+ return "after";
377
+ }
378
+ };
379
+
380
+ // Get real content based on timeline element
381
+ const getElementContent = (element) => {
382
+ if (!element.contentKey || currentContentIndex >= content.length) {
383
+ return element.content;
384
+ }
385
+
386
+ const contentItem = content[currentContentIndex];
387
+ return contentItem[element.contentKey] || element.content;
388
+ };
389
+
390
+ return (
391
+ <div className="flex flex-col h-screen bg-slate-50 overflow-hidden">
392
+ {/* Header */}
393
+ <header className="border-b bg-white shadow-sm z-10">
394
+ <div className="container mx-auto px-4 py-3 flex items-center justify-between">
395
+ <div className="flex items-center space-x-2">
396
+ <FileVideo className="h-6 w-6 text-blue-600" />
397
+ <h1 className="text-xl font-bold tracking-tight">VideoGen Pro</h1>
398
+ </div>
399
+
400
+ <div className="flex space-x-2">
401
+ <Input
402
+ className="w-60"
403
+ value={projectName}
404
+ onChange={(e) => setProjectName(e.target.value)}
405
+ placeholder="Project Name"
406
+ />
407
+
408
+ <DropdownMenu>
409
+ <DropdownMenuTrigger asChild>
410
+ <Button variant="outline" size="sm">
411
+ <FileText className="h-4 w-4 mr-1" /> Project
412
+ </Button>
413
+ </DropdownMenuTrigger>
414
+ <DropdownMenuContent>
415
+ <DropdownMenuItem onClick={startNewProject}>
416
+ New Project
417
+ </DropdownMenuItem>
418
+ <DropdownMenuItem>
419
+ Load Project
420
+ </DropdownMenuItem>
421
+ <DropdownMenuItem>
422
+ Save Project
423
+ </DropdownMenuItem>
424
+ </DropdownMenuContent>
425
+ </DropdownMenu>
426
+
427
+ <Button size="sm">
428
+ <Download className="h-4 w-4 mr-1" /> Export
429
+ </Button>
430
+
431
+ <Button variant="ghost" size="icon">
432
+ <Settings className="h-5 w-5" />
433
+ </Button>
434
+ </div>
435
+ </div>
436
+ </header>
437
+
438
+ {/* Main Content */}
439
+ <div className="flex-1 flex overflow-hidden">
440
+ {/* Left Sidebar */}
441
+ <div className="w-56 border-r bg-white">
442
+ <div className="p-4">
443
+ <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
444
+ <TabsList className="grid grid-cols-2 mb-4">
445
+ <TabsTrigger value="blocks">Blocks</TabsTrigger>
446
+ <TabsTrigger value="content">Content</TabsTrigger>
447
+ </TabsList>
448
+ </Tabs>
449
+
450
+ <div className="space-y-2">
451
+ <Button
452
+ variant={activeTab === "blocks" ? "default" : "outline"}
453
+ className="w-full justify-start"
454
+ onClick={() => setActiveTab("blocks")}
455
+ >
456
+ <Layers className="h-4 w-4 mr-2" />
457
+ Blocks
458
+ </Button>
459
+
460
+ <Button
461
+ variant={activeTab === "content" ? "default" : "outline"}
462
+ className="w-full justify-start"
463
+ onClick={() => setActiveTab("content")}
464
+ >
465
+ <FileText className="h-4 w-4 mr-2" />
466
+ Content
467
+ </Button>
468
+
469
+ <Button
470
+ variant={activeTab === "editor" ? "default" : "outline"}
471
+ className="w-full justify-start"
472
+ onClick={() => setActiveTab("editor")}
473
+ >
474
+ <Edit3 className="h-4 w-4 mr-2" />
475
+ Editor
476
+ </Button>
477
+
478
+ <Button
479
+ variant={activeTab === "generate" ? "default" : "outline"}
480
+ className="w-full justify-start"
481
+ onClick={() => setActiveTab("generate")}
482
+ >
483
+ <Zap className="h-4 w-4 mr-2" />
484
+ Generate
485
+ </Button>
486
+ </div>
487
+
488
+ {activeTab === "editor" && (
489
+ <div className="mt-6 space-y-2 border-t pt-4">
490
+ <h3 className="text-sm font-medium text-slate-500">Add Elements</h3>
491
+ <div className="grid grid-cols-3 gap-2">
492
+ <Button variant="outline" size="sm" className="w-full p-1" onClick={() => addTimelineElement("text")}>
493
+ <FileText className="h-4 w-4" />
494
+ </Button>
495
+ <Button variant="outline" size="sm" className="w-full p-1" onClick={() => addTimelineElement("block")}>
496
+ <Layers className="h-4 w-4" />
497
+ </Button>
498
+ <Button variant="outline" size="sm" className="w-full p-1" onClick={() => addTimelineElement("audio")}>
499
+ <Volume2 className="h-4 w-4" />
500
+ </Button>
501
+ </div>
502
+
503
+ <h3 className="text-sm font-medium text-slate-500 mt-4">Animation Style</h3>
504
+ <Select value={selectedAnimationStyle} onValueChange={setSelectedAnimationStyle}>
505
+ <SelectTrigger className="w-full">
506
+ <SelectValue placeholder="Animation Style" />
507
+ </SelectTrigger>
508
+ <SelectContent>
509
+ <SelectItem value="fadeIn">Fade In</SelectItem>
510
+ <SelectItem value="slideIn">Slide In</SelectItem>
511
+ <SelectItem value="zoomIn">Zoom In</SelectItem>
512
+ <SelectItem value="bounceIn">Bounce In</SelectItem>
513
+ <SelectItem value="flipIn">Flip In</SelectItem>
514
+ </SelectContent>
515
+ </Select>
516
+
517
+ <div className="flex items-center justify-between mt-4">
518
+ <Label htmlFor="audio-sync" className="text-sm">
519
+ Auto-sync to Audio
520
+ </Label>
521
+ <Switch
522
+ id="audio-sync"
523
+ checked={audioSyncEnabled}
524
+ onCheckedChange={setAudioSyncEnabled}
525
+ />
526
+ </div>
527
+ </div>
528
+ )}
529
+ </div>
530
+ </div>
531
+
532
+ {/* Main Panel */}
533
+ <div className="flex-1 overflow-auto">
534
+ <div className="container mx-auto p-6">
535
+ {activeTab === "blocks" && (
536
+ <div className="space-y-6">
537
+ <div className="flex justify-between items-center">
538
+ <h2 className="text-2xl font-bold">Video Blocks</h2>
539
+ <Button size="sm">
540
+ <Plus className="h-4 w-4 mr-1" /> Import Block
541
+ </Button>
542
+ </div>
543
+
544
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
545
+ {blocks.map(block => (
546
+ <Card key={block.id} className={`overflow-hidden transition-all ${currentBlock?.id === block.id ? 'ring-2 ring-blue-500' : ''}`}>
547
+ <div className="aspect-video relative overflow-hidden"
548
+ style={{ background: block.background }}
549
+ >
550
+ <div className="absolute inset-0 flex items-center justify-center">
551
+ <div className="w-24 h-24 rounded"
552
+ style={{ background: "rgba(255,255,255,0.1)" }}
553
+ />
554
+ </div>
555
+ <div
556
+ className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent flex flex-col justify-end p-4"
557
+ >
558
+ <h3 className="text-lg font-medium text-white">{block.name}</h3>
559
+ <p className="text-sm text-white/80">{block.description}</p>
560
+ </div>
561
+ <Badge className="absolute top-2 right-2">
562
+ {block.duration}s
563
+ </Badge>
564
+ </div>
565
+ <CardFooter className="bg-white p-3">
566
+ <Button
567
+ className="w-full"
568
+ onClick={() => selectBlock(block.id)}
569
+ >
570
+ {currentBlock?.id === block.id ? (
571
+ <>
572
+ <Check className="h-4 w-4 mr-1" />
573
+ Selected
574
+ </>
575
+ ) : "Select Block"}
576
+ </Button>
577
+ </CardFooter>
578
+ </Card>
579
+ ))}
580
+ </div>
581
+ </div>
582
+ )}
583
+
584
+ {activeTab === "content" && (
585
+ <div className="space-y-6">
586
+ <div className="flex justify-between items-center">
587
+ <h2 className="text-2xl font-bold">Content</h2>
588
+ <div className="flex space-x-2">
589
+ <Select value={targetLanguage} onValueChange={setTargetLanguage}>
590
+ <SelectTrigger className="w-40">
591
+ <SelectValue placeholder="Target Language" />
592
+ </SelectTrigger>
593
+ <SelectContent>
594
+ <SelectItem value="es-ES">Español</SelectItem>
595
+ <SelectItem value="fr-FR">Français</SelectItem>
596
+ <SelectItem value="de-DE">Deutsch</SelectItem>
597
+ <SelectItem value="it-IT">Italiano</SelectItem>
598
+ <SelectItem value="pt-PT">Português</SelectItem>
599
+ </SelectContent>
600
+ </Select>
601
+
602
+ <Select value={nativeLanguage} onValueChange={setNativeLanguage}>
603
+ <SelectTrigger className="w-40">
604
+ <SelectValue placeholder="Native Language" />
605
+ </SelectTrigger>
606
+ <SelectContent>
607
+ <SelectItem value="en-US">English</SelectItem>
608
+ <SelectItem value="es-ES">Español</SelectItem>
609
+ <SelectItem value="fr-FR">Français</SelectItem>
610
+ <SelectItem value="de-DE">Deutsch</SelectItem>
611
+ <SelectItem value="it-IT">Italiano</SelectItem>
612
+ </SelectContent>
613
+ </Select>
614
+ </div>
615
+ </div>
616
+
617
+ <div className="bg-white rounded-md shadow overflow-hidden">
618
+ <div className="border-b px-4 py-2 bg-slate-50 grid grid-cols-7 gap-4 font-medium text-sm">
619
+ <div className="col-span-1">Target Word</div>
620
+ <div className="col-span-1">Native Word</div>
621
+ <div className="col-span-2">Target Phrase 1</div>
622
+ <div className="col-span-2">Native Phrase 1</div>
623
+ <div className="col-span-1">Actions</div>
624
+ </div>
625
+
626
+ {content.map(row => (
627
+ <div key={row.id} className="px-4 py-3 border-b grid grid-cols-7 gap-4 items-center">
628
+ <div className="col-span-1">{row.targetWord}</div>
629
+ <div className="col-span-1">{row.nativeWord}</div>
630
+ <div className="col-span-2">{row.targetPhrase1}</div>
631
+ <div className="col-span-2">{row.nativePhrase1}</div>
632
+ <div className="col-span-1 flex space-x-1">
633
+ <Button variant="ghost" size="icon" onClick={() => setCurrentContentIndex(content.indexOf(row))}>
634
+ <Eye className="h-4 w-4" />
635
+ </Button>
636
+ <Button variant="ghost" size="icon" onClick={() => deleteContentRow(row.id)}>
637
+ <Trash2 className="h-4 w-4" />
638
+ </Button>
639
+ </div>
640
+ </div>
641
+ ))}
642
+
643
+ <div className="px-4 py-3 grid grid-cols-7 gap-4 items-center bg-slate-50">
644
+ <div className="col-span-1">
645
+ <Input
646
+ placeholder="Target Word"
647
+ value={newRow.targetWord}
648
+ onChange={(e) => setNewRow({...newRow, targetWord: e.target.value})}
649
+ />
650
+ </div>
651
+ <div className="col-span-1">
652
+ <Input
653
+ placeholder="Native Word"
654
+ value={newRow.nativeWord}
655
+ onChange={(e) => setNewRow({...newRow, nativeWord: e.target.value})}
656
+ />
657
+ </div>
658
+ <div className="col-span-2">
659
+ <Input
660
+ placeholder="Target Phrase 1"
661
+ value={newRow.targetPhrase1}
662
+ onChange={(e) => setNewRow({...newRow, targetPhrase1: e.target.value})}
663
+ />
664
+ </div>
665
+ <div className="col-span-2">
666
+ <Input
667
+ placeholder="Native Phrase 1"
668
+ value={newRow.nativePhrase1}
669
+ onChange={(e) => setNewRow({...newRow, nativePhrase1: e.target.value})}
670
+ />
671
+ </div>
672
+ <div className="col-span-1">
673
+ <Button onClick={addContentRow}>
674
+ <Plus className="h-4 w-4 mr-1" /> Add
675
+ </Button>
676
+ </div>
677
+ </div>
678
+ </div>
679
+
680
+ <div className="flex justify-between">
681
+ <Button variant="outline" onClick={generateAudio}>
682
+ <Volume2 className="h-4 w-4 mr-1" /> Generate Audio
683
+ </Button>
684
+
685
+ <Button onClick={() => setActiveTab("editor")}>
686
+ Continue to Editor <ChevronDown className="h-4 w-4 ml-1 rotate-270" />
687
+ </Button>
688
+ </div>
689
+ </div>
690
+ )}
691
+
692
+ {(activeTab === "editor" || activeTab === "generate") && (
693
+ <div className="space-y-6">
694
+ <div className="flex justify-between items-center">
695
+ <h2 className="text-2xl font-bold">
696
+ {activeTab === "editor" ? "Block Editor" : "Generate Videos"}
697
+ </h2>
698
+ {activeTab === "generate" && (
699
+ <div className="flex space-x-2">
700
+ <Select defaultValue="1080p">
701
+ <SelectTrigger className="w-32">
702
+ <SelectValue placeholder="Resolution" />
703
+ </SelectTrigger>
704
+ <SelectContent>
705
+ <SelectItem value="720p">720p</SelectItem>
706
+ <SelectItem value="1080p">1080p</SelectItem>
707
+ <SelectItem value="4k">4K</SelectItem>
708
+ </SelectContent>
709
+ </Select>
710
+
711
+ <Select defaultValue="mp4">
712
+ <SelectTrigger className="w-32">
713
+ <SelectValue placeholder="Format" />
714
+ </SelectTrigger>
715
+ <SelectContent>
716
+ <SelectItem value="mp4">MP4 (H.264)</SelectItem>
717
+ <SelectItem value="webm">WebM</SelectItem>
718
+ <SelectItem value="gif">GIF</SelectItem>
719
+ </SelectContent>
720
+ </Select>
721
+ </div>
722
+ )}
723
+ </div>
724
+
725
+ {/* Preview and Timeline Area */}
726
+ <div className="space-y-4">
727
+ {/* Preview Area */}
728
+ <div
729
+ ref={previewRef}
730
+ className="aspect-video rounded-lg relative overflow-hidden"
731
+ style={{
732
+ background: currentBlock?.background || "linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)"
733
+ }}
734
+ >
735
+ {/* Preview elements */}
736
+ {timelineElements.map(element => {
737
+ if (element.type === 'audio') return null; // Audio doesn't render visually
738
+
739
+ const animationState = getElementAnimationState(element);
740
+ if (animationState === "before") return null;
741
+
742
+ // Calculate opacity based on time
743
+ const opacity = animationState === "active" ? 1 : 0;
744
+
745
+ return (
746
+ <div
747
+ key={element.id}
748
+ className="absolute"
749
+ style={{
750
+ left: `${element.position?.x || 0}%`,
751
+ top: `${element.position?.y || 0}%`,
752
+ opacity,
753
+ transition: 'opacity 0.3s',
754
+ cursor: 'move',
755
+ transform: 'translate(-50%, -50%)'
756
+ }}
757
+ onClick={() => openElementEditor(element)}
758
+ >
759
+ {element.type === 'text' ? (
760
+ <div
761
+ style={{
762
+ color: element.style?.color || 'white',
763
+ fontSize: `${element.style?.fontSize || 24}px`,
764
+ fontWeight: element.style?.fontWeight || 'normal',
765
+ textShadow: '0 1px 2px rgba(0,0,0,0.5)',
766
+ whiteSpace: 'nowrap'
767
+ }}
768
+ >
769
+ {getElementContent(element)}
770
+ </div>
771
+ ) : element.type === 'block' ? (
772
+ <div
773
+ style={{
774
+ width: `${element.style?.width || 100}px`,
775
+ height: `${element.style?.height || 100}px`,
776
+ borderRadius: element.style?.borderRadius || '0px',
777
+ background: element.style?.background || 'rgba(255,255,255,0.2)'
778
+ }}
779
+ />
780
+ ) : null}
781
+ </div>
782
+ );
783
+ })}
784
+
785
+ {/* Controls */}
786
+ <div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex space-x-2 bg-black/70 rounded-full p-2">
787
+ <Button variant="ghost" size="icon" className="text-white">
788
+ <ArrowLeft className="h-4 w-4" />
789
+ </Button>
790
+ <Button variant="ghost" size="icon" className="text-white" onClick={togglePlayback}>
791
+ {isPlaying ? <Pause className="h-5 w-5" /> : <Play className="h-5 w-5" />}
792
+ </Button>
793
+ <Button variant="ghost" size="icon" className="text-white">
794
+ <ArrowRight className="h-4 w-4" />
795
+ </Button>
796
+ </div>
797
+
798
+ {/* Timeline progress */}
799
+ <div className="absolute bottom-0 left-0 right-0 h-1 bg-gray-700">
800
+ <div
801
+ className="h-full bg-blue-500 transition-all"
802
+ style={{ width: `${progress}%` }}
803
+ ></div>
804
+ </div>
805
+ </div>
806
+
807
+ {/* Timeline */}
808
+ <div
809
+ ref={timelineRef}
810
+ className="h-16 bg-gray-800 rounded relative overflow-hidden cursor-pointer"
811
+ onClick={handleTimelineClick}
812
+ >
813
+ {/* Time markers */}
814
+ <div className="absolute top-0 left-0 right-0 h-4 flex text-xs text-gray-400">
815
+ {Array.from({ length: Math.ceil(duration) + 1 }).map((_, i) => (
816
+ <div
817
+ key={i}
818
+ className="border-l border-gray-700 h-full flex items-center px-1"
819
+ style={{
820
+ position: 'absolute',
821
+ left: `${(i / duration) * 100}%`
822
+ }}
823
+ >
824
+ {i}s
825
+ </div>
826
+ ))}
827
+ </div>
828
+
829
+ {/* Elements timeline */}
830
+ <div className="absolute top-4 left-0 right-0 bottom-0 px-2">
831
+ {timelineElements.map(element => (
832
+ <div
833
+ key={element.id}
834
+ className={`absolute h-5 rounded cursor-pointer ${
835
+ element.type === 'text' ? 'bg-blue-600/70' :
836
+ element.type === 'block' ? 'bg-green-600/70' :
837
+ 'bg-purple-600/70'
838
+ } ${currentTime >= element.start && currentTime < element.start + element.duration ? 'ring-1 ring-white' : ''}`}
839
+ style={{
840
+ left: `${(element.start / duration) * 100}%`,
841
+ width: `${(element.duration / duration) * 100}%`,
842
+ top: element.type === 'audio' ? '7px' : '0px'
843
+ }}
844
+ onClick={(e) => {
845
+ e.stopPropagation();
846
+ openElementEditor(element);
847
+ }}
848
+ >
849
+ <div className="truncate text-xs text-white px-1 py-0.5">
850
+ {element.type === 'text' ? getElementContent(element).substring(0, 10) : element.type}
851
+ {element.type === 'text' && getElementContent(element).length > 10 ? '...' : ''}
852
+ </div>
853
+ </div>
854
+ ))}
855
+ </div>
856
+
857
+ {/* Current time indicator */}
858
+ <div className="absolute top-0 bottom-0 w-px bg-red-500 z-10" style={{ left: `${(currentTime / duration) * 100}%` }}>
859
+ <div className="w-2 h-2 bg-red-500 rounded-full -ml-1"></div>
860
+ </div>
861
+ </div>
862
+
863
+ <div className="flex justify-between">
864
+ <div className="flex space-x-2">
865
+ <Button
866
+ variant="outline"
867
+ size="sm"
868
+ onClick={() => setCurrentContentIndex(prev => (prev > 0 ? prev - 1 : 0))}
869
+ disabled={currentContentIndex <= 0}
870
+ >
871
+ <ArrowLeft className="h-4 w-4 mr-1" />
872
+ Previous
873
+ </Button>
874
+ <Button
875
+ variant="outline"
876
+ size="sm"
877
+ onClick={() => setCurrentContentIndex(prev => (prev < content.length - 1 ? prev + 1 : prev))}
878
+ disabled={currentContentIndex >= content.length - 1}
879
+ >
880
+ Next
881
+ <ArrowRight className="h-4 w-4 ml-1" />
882
+ </Button>
883
+ <div className="bg-slate-100 rounded px-2 py-1 text-sm flex items-center">
884
+ Item {currentContentIndex + 1} of {content.length}
885
+ </div>
886
+ </div>
887
+
888
+ {activeTab === "editor" && (
889
+ <Button onClick={() => setActiveTab("generate")}>
890
+ Continue to Generate <ChevronDown className="h-4 w-4 ml-1 rotate-270" />
891
+ </Button>
892
+ )}
893
+
894
+ {activeTab === "generate" && (
895
+ <Button onClick={generateAllVideos} disabled={generatingStatus === "processing" || generatingStatus === "preparing"}>
896
+ {generatingStatus === "complete" ?
897
+ <><RefreshCw className="h-4 w-4 mr-1" /> Generate More</> :
898
+ generatingStatus === "processing" || generatingStatus === "preparing" ?
899
+ <><RefreshCw className="h-4 w-4 mr-1 animate-spin" /> Processing...</> :
900
+ <><Zap className="h-4 w-4 mr-1" /> Generate All Videos</>}
901
+ </Button>
902
+ )}
903
+ </div>
904
+ </div>
905
+
906
+ {activeTab === "generate" && (
907
+ <div className="mt-6">
908
+ <Alert className={generatingStatus === "complete" ? "bg-green-50 border-green-200" : "bg-blue-50 border-blue-200"}>
909
+ <AlertDescription className={generatingStatus === "complete" ? "text-green-700" : "text-blue-700"}>
910
+ {generatingStatus === "complete" ?
911
+ `Successfully generated ${renderQueue.length} videos.` :
912
+ generatingStatus === "processing" ?
913
+ `Processing ${renderQueue.filter(item => item.status === "complete").length} of ${renderQueue.length} videos...` :
914
+ generatingStatus === "preparing" ?
915
+ "Setting up render queue..." :
916
+ "Click the button below to generate videos for all content."}
917
+ </AlertDescription>
918
+ </Alert>
919
+
920
+ <Card className="mt-4">
921
+ <CardHeader>
922
+ <CardTitle>Generation Queue</CardTitle>
923
+ <CardDescription>
924
+ {content.length} videos to generate using {currentBlock?.name || "selected block"}
925
+ </CardDescription>
926
+ </CardHeader>
927
+ <CardContent className="space-y-4">
928
+ {renderQueue.length > 0 ? (
929
+ renderQueue.map(item => (
930
+ <div key={item.id} className="flex items-center space-x-4">
931
+ <div className="w-8 h-8 rounded-full bg-slate-100 flex items-center justify-center">
932
+ {item.status === "complete" ?
933
+ <Check className="h-4 w-4 text-green-500" /> :
934
+ item.status === "processing" ?
935
+ <RefreshCw className="h-4 w-4 text-blue-500 animate-spin" /> :
936
+ <Clock className="h-4 w-4 text-slate-400" />}
937
+ </div>
938
+ <div className="flex-1">
939
+ <div className="font-medium">{item.content.targetWord} - {item.content.nativeWord}</div>
940
+ <Progress
941
+ value={item.progress}
942
+ className="h-2 mt-1"
943
+ />
944
+ </div>
945
+ <div className="text-sm text-slate-500">
946
+ {item.status === "complete" ?
947
+ "Complete" :
948
+ item.status === "processing" ?
949
+ `${item.progress}%` :
950
+ "Pending"}
951
+ </div>
952
+ <Button
953
+ variant="ghost"
954
+ size="icon"
955
+ disabled={item.status !== "complete"}
956
+ >
957
+ <Download className="h-4 w-4" />
958
+ </Button>
959
+ </div>
960
+ ))
961
+ ) : (
962
+ <div className="text-center py-8 text-slate-500">
963
+ <Grid className="h-8 w-8 mx-auto mb-2" />
964
+ <p>No videos in queue yet</p>
965
+ <p className="text-sm">Click the "Generate All Videos" button to start generation</p>
966
+ </div>
967
+ )}
968
+ </CardContent>
969
+ <CardFooter className="flex justify-between">
970
+ <div>
971
+ <div className="flex items-center space-x-2">
972
+ <Checkbox id="audio-checkbox" checked />
973
+ <Label htmlFor="audio-checkbox">Include audio</Label>
974
+ </div>
975
+ </div>
976
+ </CardFooter>
977
+ </Card>
978
+ </div>
979
+ )}
980
+ </div>
981
+ )}
982
+ </div>
983
+ </div>
984
+ </div>
985
+
986
+ {/* Element Editor Dialog */}
987
+ <Dialog open={showEditor} onOpenChange={setShowEditor}>
988
+ <DialogContent className="sm:max-w-[500px]">
989
+ <DialogHeader>
990
+ <DialogTitle>Edit Element</DialogTitle>
991
+ <DialogDescription>
992
+ Adjust properties for this timeline element
993
+ </DialogDescription>
994
+ </DialogHeader>
995
+
996
+ {editingElement && (
997
+ <div className="space-y-4 pt-2">
998
+ <div className="grid grid-cols-2 gap-4">
999
+ <div className="space-y-2">
1000
+ <Label>Start Time (seconds)</Label>
1001
+ <Input
1002
+ type="number"
1003
+ min="0"
1004
+ step="0.1"
1005
+ value={editingElement.start}
1006
+ onChange={(e) => setEditingElement({...editingElement, start: parseFloat(e.target.value)})}
1007
+ />
1008
+ </div>
1009
+
1010
+ <div className="space-y-2">
1011
+ <Label>Duration (seconds)</Label>
1012
+ <Input
1013
+ type="number"
1014
+ min="0.1"
1015
+ step="0.1"
1016
+ value={editingElement.duration}
1017
+ onChange={(e) => setEditingElement({...editingElement, duration: parseFloat(e.target.value)})}
1018
+ />
1019
+ </div>
1020
+ </div>
1021
+
1022
+ {editingElement.type === "text" && (
1023
+ <>
1024
+ <div className="space-y-2">
1025
+ <Label>Text Content</Label>
1026
+ <Textarea
1027
+ value={editingElement.content}
1028
+ onChange={(e) => setEditingElement({...editingElement, content: e.target.value})}
1029
+ />
1030
+ </div>
1031
+
1032
+ <div className="grid grid-cols-2 gap-4">
1033
+ <div className="space-y-2">
1034
+ <Label>Font Size</Label>
1035
+ <Input
1036
+ type="number"
1037
+ value={editingElement.style?.fontSize || 24}
1038
+ onChange={(e) => setEditingElement({
1039
+ ...editingElement,
1040
+ style: {...editingElement.style, fontSize: parseInt(e.target.value)}
1041
+ })}
1042
+ />
1043
+ </div>
1044
+
1045
+ <div className="space-y-2">
1046
+ <Label>Text Color</Label>
1047
+ <Input
1048
+ type="color"
1049
+ value={editingElement.style?.color || "#ffffff"}
1050
+ onChange={(e) => setEditingElement({
1051
+ ...editingElement,
1052
+ style: {...editingElement.style, color: e.target.value}
1053
+ })}
1054
+ />
1055
+ </div>
1056
+ </div>
1057
+
1058
+ <div className="space-y-2">
1059
+ <Label>Content Link</Label>
1060
+ <Select
1061
+ value={editingElement.contentKey || "none"}
1062
+ onValueChange={(val) => setEditingElement({
1063
+ ...editingElement,
1064
+ contentKey: val === "none" ? undefined : val
1065
+ })}
1066
+ >
1067
+ <SelectTrigger>
1068
+ <SelectValue placeholder="Link to content field" />
1069
+ </SelectTrigger>
1070
+ <SelectContent>
1071
+ <SelectItem value="none">No link (use fixed text)</SelectItem>
1072
+ <SelectItem value="targetWord">Target Word</SelectItem>
1073
+ <SelectItem value="nativeWord">Native Word</SelectItem>
1074
+ <SelectItem value="targetPhrase1">Target Phrase 1</SelectItem>
1075
+ <SelectItem value="nativePhrase1">Native Phrase 1</SelectItem>
1076
+ <SelectItem value="targetPhrase2">Target Phrase 2</SelectItem>
1077
+ <SelectItem value="nativePhrase2">Native Phrase 2</SelectItem>
1078
+ </SelectContent>
1079
+ </Select>
1080
+ </div>
1081
+ </>
1082
+ )}
1083
+
1084
+ {editingElement.type === "block" && (
1085
+ <>
1086
+ <div className="grid grid-cols-2 gap-4">
1087
+ <div className="space-y-2">
1088
+ <Label>Width (px)</Label>
1089
+ <Input
1090
+ type="number"
1091
+ value={editingElement.style?.width || 100}
1092
+ onChange={(e) => setEditingElement({
1093
+ ...editingElement,
1094
+ style: {...editingElement.style, width: parseInt(e.target.value)}
1095
+ })}
1096
+ />
1097
+ </div>
1098
+
1099
+ <div className="space-y-2">
1100
+ <Label>Height (px)</Label>
1101
+ <Input
1102
+ type="number"
1103
+ value={editingElement.style?.height || 100}
1104
+ onChange={(e) => setEditingElement({
1105
+ ...editingElement,
1106
+ style: {...editingElement.style, height: parseInt(e.target.value)}
1107
+ })}
1108
+ />
1109
+ </div>
1110
+ </div>
1111
+
1112
+ <div className="space-y-2">
1113
+ <Label>Border Radius (px)</Label>
1114
+ <Input
1115
+ type="number"
1116
+ value={parseInt(editingElement.style?.borderRadius) || 0}
1117
+ onChange={(e) => setEditingElement({
1118
+ ...editingElement,
1119
+ style: {...editingElement.style, borderRadius: `${e.target.value}px`}
1120
+ })}
1121
+ />
1122
+ </div>
1123
+
1124
+ <div className="space-y-2">
1125
+ <Label>Background</Label>
1126
+ <Select
1127
+ value={editingElement.style?.background || "gradient1"}
1128
+ onValueChange={(val) => {
1129
+ let bgValue = val;
1130
+ if (val === "gradient1") {
1131
+ bgValue = "linear-gradient(45deg, #3490dc, #6574cd)";
1132
+ } else if (val === "gradient2") {
1133
+ bgValue = "linear-gradient(45deg, #38c172, #4dc0b5)";
1134
+ } else if (val === "gradient3") {
1135
+ bgValue = "linear-gradient(45deg, #e3342f, #f6993f)";
1136
+ } else if (val === "solid1") {
1137
+ bgValue = "#3490dc";
1138
+ } else if (val === "solid2") {
1139
+ bgValue = "#38c172";
1140
+ } else if (val === "solid3") {
1141
+ bgValue = "#e3342f";
1142
+ }
1143
+
1144
+ setEditingElement({
1145
+ ...editingElement,
1146
+ style: {...editingElement.style, background: bgValue}
1147
+ });
1148
+ }}
1149
+ >
1150
+ <SelectTrigger>
1151
+ <SelectValue placeholder="Choose background" />
1152
+ </SelectTrigger>
1153
+ <SelectContent>
1154
+ <SelectItem value="gradient1">Blue Gradient</SelectItem>
1155
+ <SelectItem value="gradient2">Green Gradient</SelectItem>
1156
+ <SelectItem value="gradient3">Red Gradient</SelectItem>
1157
+ <SelectItem value="solid1">Solid Blue</SelectItem>
1158
+ <SelectItem value="solid2">Solid Green</SelectItem>
1159
+ <SelectItem value="solid3">Solid Red</SelectItem>
1160
+ </SelectContent>
1161
+ </Select>
1162
+ </div>
1163
+ </>
1164
+ )}
1165
+
1166
+ {editingElement.type === "audio" && (
1167
+ <div className="space-y-2">
1168
+ <Label>Audio Source</Label>
1169
+ <div className="flex space-x-2">
1170
+ <Input
1171
+ value={editingElement.content}
1172
+ onChange={(e) => setEditingElement({...editingElement, content: e.target.value})}
1173
+ />
1174
+ <Button variant="outline" size="icon">
1175
+ <UploadCloud className="h-4 w-4" />
1176
+ </Button>
1177
+ </div>
1178
+ </div>
1179
+ )}
1180
+
1181
+ {editingElement.type !== "audio" && (
1182
+ <div className="space-y-2">
1183
+ <Label>Position</Label>
1184
+ <div className="grid grid-cols-2 gap-4">
1185
+ <div className="flex items-center space-x-2">
1186
+ <Label className="w-8">X %</Label>
1187
+ <Input
1188
+ type="number"
1189
+ min="0"
1190
+ max="100"
1191
+ value={editingElement.position?.x || 50}
1192
+ onChange={(e) => setEditingElement({
1193
+ ...editingElement,
1194
+ position: {...editingElement.position, x: parseFloat(e.target.value)}
1195
+ })}
1196
+ />
1197
+ </div>
1198
+ <div className="flex items-center space-x-2">
1199
+ <Label className="w-8">Y %</Label>
1200
+ <Input
1201
+ type="number"
1202
+ min="0"
1203
+ max="100"
1204
+ value={editingElement.position?.y || 50}
1205
+ onChange={(e) => setEditingElement({
1206
+ ...editingElement,
1207
+ position: {...editingElement.position, y: parseFloat(e.target.value)}
1208
+ })}
1209
+ />
1210
+ </div>
1211
+ </div>
1212
+ </div>
1213
+ )}
1214
+
1215
+ {editingElement.type !== "audio" && (
1216
+ <div className="space-y-2">
1217
+ <Label>Animation</Label>
1218
+ <Select
1219
+ value={editingElement.animation || "fadeIn"}
1220
+ onValueChange={(val) => setEditingElement({
1221
+ ...editingElement,
1222
+ animation: val
1223
+ })}
1224
+ >
1225
+ <SelectTrigger>
1226
+ <SelectValue placeholder="Choose animation" />
1227
+ </SelectTrigger>
1228
+ <SelectContent>
1229
+ <SelectItem value="fadeIn">Fade In</SelectItem>
1230
+ <SelectItem value="slideIn">Slide In</SelectItem>
1231
+ <SelectItem value="zoomIn">Zoom In</SelectItem>
1232
+ <SelectItem value="bounceIn">Bounce In</SelectItem>
1233
+ <SelectItem value="flipIn">Flip In</SelectItem>
1234
+ </SelectContent>
1235
+ </Select>
1236
+ </div>
1237
+ )}
1238
+
1239
+ <div className="flex items-center mt-4 space-x-2">
1240
+ <Label htmlFor="element-sync" className="text-sm">
1241
+ Sync with audio
1242
+ </Label>
1243
+ <Switch
1244
+ id="element-sync"
1245
+ checked={true}
1246
+ />
1247
+ </div>
1248
+ </div>
1249
+ )}
1250
+
1251
+ <DialogFooter>
1252
+ <Button variant="outline" onClick={() => setShowEditor(false)}>Cancel</Button>
1253
+ <Button onClick={() => updateElement(editingElement)}>Save Changes</Button>
1254
+ </DialogFooter>
1255
+ </DialogContent>
1256
+ </Dialog>
1257
+
1258
+ {/* Notification Toast */}
1259
+ <div className={`fixed bottom-4 right-4 bg-slate-800 text-white px-4 py-2 rounded-md shadow-lg transition-opacity duration-300 flex items-center ${showNotification ? 'opacity-100' : 'opacity-0'}`}>
1260
+ <Check className="h-4 w-4 mr-2 text-green-400" />
1261
+ {notificationMessage}
1262
+ </div>
1263
+ </div>
1264
+ );
1265
+ };
1266
+
1267
+ export default VideoGeneratorApp;
react/enhanced-videogen-pro-v3-permite-mover-con-mano.tsx ADDED
@@ -0,0 +1,1812 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { Camera, Plus, Trash2, Edit3, Download, Play, Pause, ChevronDown,
3
+ Settings, Save, X, FileText, Upload, RefreshCw, Globe, Volume2, Image,
4
+ Clock, Copy, FileVideo, Check, AlertTriangle, Zap, Layers, UploadCloud,
5
+ Grid, CheckCircle, Move, ArrowLeft, ArrowRight, SkipBack, SkipForward,
6
+ Music, Mic, Eye, EyeOff, Maximize, Minimize, Monitor, Wind } from 'lucide-react';
7
+ import { motion, AnimatePresence } from 'framer-motion';
8
+ import * as d3 from 'd3';
9
+
10
+ // Mock imports for shadcn UI components
11
+ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
12
+ import { Button } from '@/components/ui/button';
13
+ import { Input } from '@/components/ui/input';
14
+ import { Label } from '@/components/ui/label';
15
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
16
+ import { Slider } from '@/components/ui/slider';
17
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
18
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
19
+ import { Badge } from '@/components/ui/badge';
20
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from '@/components/ui/dialog';
21
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
22
+ import { Switch } from '@/components/ui/switch';
23
+ import { Progress } from '@/components/ui/progress';
24
+ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
25
+ import { Checkbox } from '@/components/ui/checkbox';
26
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
27
+ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
28
+ import { ScrollArea } from '@/components/ui/scroll-area';
29
+ import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
30
+ import { Textarea } from '@/components/ui/textarea';
31
+
32
+ // Animation presets for elements
33
+ const animationPresets = {
34
+ fadeIn: {
35
+ initial: { opacity: 0 },
36
+ animate: { opacity: 1 },
37
+ exit: { opacity: 0 },
38
+ transition: { duration: 0.5 }
39
+ },
40
+ slideIn: {
41
+ initial: { x: -100, opacity: 0 },
42
+ animate: { x: 0, opacity: 1 },
43
+ exit: { x: 100, opacity: 0 },
44
+ transition: { duration: 0.5 }
45
+ },
46
+ zoomIn: {
47
+ initial: { scale: 0, opacity: 0 },
48
+ animate: { scale: 1, opacity: 1 },
49
+ exit: { scale: 0, opacity: 0 },
50
+ transition: { duration: 0.5 }
51
+ },
52
+ bounceIn: {
53
+ initial: { scale: 0, opacity: 0 },
54
+ animate: { scale: 1, opacity: 1 },
55
+ exit: { scale: 0, opacity: 0 },
56
+ transition: { type: "spring", stiffness: 200, damping: 10 }
57
+ },
58
+ flipIn: {
59
+ initial: { rotateY: 90, opacity: 0 },
60
+ animate: { rotateY: 0, opacity: 1 },
61
+ exit: { rotateY: -90, opacity: 0 },
62
+ transition: { duration: 0.5 }
63
+ }
64
+ };
65
+
66
+ // Helper for getting element animation based on time
67
+ const getElementAnimationState = (element, currentTime) => {
68
+ if (currentTime < element.start) {
69
+ return "before";
70
+ } else if (currentTime >= element.start && currentTime < element.start + element.duration) {
71
+ return "active";
72
+ } else {
73
+ return "after";
74
+ }
75
+ };
76
+
77
+ const VideoGeneratorApp = () => {
78
+ // References
79
+ const canvasRef = useRef(null);
80
+ const videoRef = useRef(null);
81
+ const timelineRef = useRef(null);
82
+ const previewContainerRef = useRef(null);
83
+
84
+ // State management
85
+ const [activeTab, setActiveTab] = useState("templates");
86
+ const [currentTemplate, setCurrentTemplate] = useState(null);
87
+ const [isPlaying, setIsPlaying] = useState(false);
88
+ const [progress, setProgress] = useState(0);
89
+ const [showEditor, setShowEditor] = useState(false);
90
+ const [targetLanguage, setTargetLanguage] = useState("es-ES");
91
+ const [nativeLanguage, setNativeLanguage] = useState("en-US");
92
+ const [currentTime, setCurrentTime] = useState(0);
93
+ const [duration, setDuration] = useState(15);
94
+ const [isDraggingTimeline, setIsDraggingTimeline] = useState(false);
95
+ const [showWaveform, setShowWaveform] = useState(true);
96
+ const [fullscreenPreview, setFullscreenPreview] = useState(false);
97
+ const [exportProgress, setExportProgress] = useState(0);
98
+ const [isExporting, setIsExporting] = useState(false);
99
+ const [exportFormat, setExportFormat] = useState("mp4");
100
+ const [exportResolution, setExportResolution] = useState("1080p");
101
+ const [savedProjects, setSavedProjects] = useState([]);
102
+ const [showLoadProjectDialog, setShowLoadProjectDialog] = useState(false);
103
+ const [previewQuality, setPreviewQuality] = useState("medium");
104
+ const [draggedElement, setDraggedElement] = useState(null);
105
+
106
+ const [content, setContent] = useState([
107
+ {
108
+ id: 1,
109
+ image: "medico.jpg",
110
+ targetWord: "médico",
111
+ nativeWord: "doctor",
112
+ targetPhrase1: "quiere ser médico",
113
+ nativePhrase1: "he wants to be a doctor",
114
+ targetPhrase2: "su médico es bueno",
115
+ nativePhrase2: "his doctor is good",
116
+ audio: "medico-audio.mp3"
117
+ },
118
+ {
119
+ id: 2,
120
+ image: "abogado.jpg",
121
+ targetWord: "abogado",
122
+ nativeWord: "lawyer",
123
+ targetPhrase1: "estudia para abogado",
124
+ nativePhrase1: "he studies to be a lawyer",
125
+ targetPhrase2: "necesita un abogado",
126
+ nativePhrase2: "he needs a lawyer",
127
+ audio: "abogado-audio.mp3"
128
+ }
129
+ ]);
130
+
131
+ const [currentContentIndex, setCurrentContentIndex] = useState(0);
132
+ const [newRow, setNewRow] = useState({
133
+ image: "",
134
+ targetWord: "",
135
+ nativeWord: "",
136
+ targetPhrase1: "",
137
+ nativePhrase1: "",
138
+ targetPhrase2: "",
139
+ nativePhrase2: ""
140
+ });
141
+
142
+ const [selectedAnimationStyle, setSelectedAnimationStyle] = useState("fadeIn");
143
+ const [audioSyncEnabled, setAudioSyncEnabled] = useState(true);
144
+ const [generatingStatus, setGeneratingStatus] = useState(null);
145
+ const [renderQueue, setRenderQueue] = useState([]);
146
+ const [projectName, setProjectName] = useState("Vocabulary Project");
147
+ const [showNotification, setShowNotification] = useState(false);
148
+ const [notificationMessage, setNotificationMessage] = useState("");
149
+ const [notificationType, setNotificationType] = useState("success");
150
+ const [editingElement, setEditingElement] = useState(null);
151
+ const [audioData, setAudioData] = useState(null);
152
+ const [audioWaveform, setAudioWaveform] = useState([]);
153
+ const [templates, setTemplates] = useState([
154
+ {
155
+ id: "vocab-template",
156
+ name: "Vocabulary Template",
157
+ description: "Standard vocabulary template with word and phrases",
158
+ thumbnail: "template1.jpg",
159
+ duration: 15,
160
+ background: "#1a1a2e"
161
+ },
162
+ {
163
+ id: "grammar-template",
164
+ name: "Grammar Template",
165
+ description: "Template for grammar explanations with examples",
166
+ thumbnail: "template2.jpg",
167
+ duration: 20,
168
+ background: "#16213e"
169
+ },
170
+ {
171
+ id: "conversation-template",
172
+ name: "Conversation Template",
173
+ description: "Dialog-based template for conversation practice",
174
+ thumbnail: "template3.jpg",
175
+ duration: 30,
176
+ background: "#0f3460"
177
+ }
178
+ ]);
179
+
180
+ const [timelineElements, setTimelineElements] = useState([
181
+ {
182
+ id: "el-1",
183
+ type: "text",
184
+ content: "Vocabulary Word",
185
+ start: 0,
186
+ duration: 3,
187
+ position: {x: 400, y: 150},
188
+ style: {color: "#ffffff", fontSize: 36, fontWeight: "bold"},
189
+ animation: "fadeIn",
190
+ contentKey: "targetWord"
191
+ },
192
+ {
193
+ id: "el-2",
194
+ type: "image",
195
+ content: "medico.jpg",
196
+ start: 0.5,
197
+ duration: 12,
198
+ position: {x: 200, y: 250},
199
+ style: {width: 300, height: 300, borderRadius: "8px"},
200
+ animation: "zoomIn",
201
+ contentKey: "image"
202
+ },
203
+ {
204
+ id: "el-3",
205
+ type: "text",
206
+ content: "Example Phrase 1",
207
+ start: 4,
208
+ duration: 3,
209
+ position: {x: 400, y: 350},
210
+ style: {color: "#ffffff", fontSize: 24},
211
+ animation: "slideIn",
212
+ contentKey: "targetPhrase1"
213
+ },
214
+ {
215
+ id: "el-4",
216
+ type: "text",
217
+ content: "Example Phrase 2",
218
+ start: 8,
219
+ duration: 3,
220
+ position: {x: 400, y: 450},
221
+ style: {color: "#ffffff", fontSize: 24},
222
+ animation: "slideIn",
223
+ contentKey: "targetPhrase2"
224
+ },
225
+ {
226
+ id: "el-5",
227
+ type: "audio",
228
+ content: "narration.mp3",
229
+ start: 0,
230
+ duration: 15,
231
+ contentKey: "audio"
232
+ },
233
+ ]);
234
+
235
+ // Initialize local storage and load saved projects
236
+ useEffect(() => {
237
+ const loadSavedProjects = () => {
238
+ try {
239
+ const projectsJson = localStorage.getItem('videogen-projects');
240
+ if (projectsJson) {
241
+ const projects = JSON.parse(projectsJson);
242
+ setSavedProjects(projects);
243
+ }
244
+ } catch (error) {
245
+ console.error("Error loading saved projects:", error);
246
+ showNotify("Error loading saved projects", "error");
247
+ }
248
+ };
249
+
250
+ loadSavedProjects();
251
+
252
+ // Setup auto-save
253
+ const autoSaveInterval = setInterval(() => {
254
+ if (currentTemplate && content.length > 0) {
255
+ saveCurrentProject(true);
256
+ }
257
+ }, 60000); // Auto-save every minute
258
+
259
+ return () => clearInterval(autoSaveInterval);
260
+ }, []);
261
+
262
+ // Save current project
263
+ const saveCurrentProject = (isAutoSave = false) => {
264
+ try {
265
+ const project = {
266
+ id: Date.now().toString(),
267
+ name: projectName,
268
+ template: currentTemplate,
269
+ content: content,
270
+ timelineElements: timelineElements,
271
+ lastSaved: new Date().toISOString()
272
+ };
273
+
274
+ let projects = [];
275
+ const projectsJson = localStorage.getItem('videogen-projects');
276
+
277
+ if (projectsJson) {
278
+ projects = JSON.parse(projectsJson);
279
+ // Check if project with same name exists
280
+ const existingProjectIndex = projects.findIndex(p => p.name === projectName);
281
+ if (existingProjectIndex >= 0) {
282
+ projects[existingProjectIndex] = project;
283
+ } else {
284
+ projects.push(project);
285
+ }
286
+ } else {
287
+ projects = [project];
288
+ }
289
+
290
+ localStorage.setItem('videogen-projects', JSON.stringify(projects));
291
+ setSavedProjects(projects);
292
+
293
+ if (!isAutoSave) {
294
+ showNotify("Project saved successfully");
295
+ }
296
+ } catch (error) {
297
+ console.error("Error saving project:", error);
298
+ showNotify("Error saving project", "error");
299
+ }
300
+ };
301
+
302
+ // Load a saved project
303
+ const loadProject = (project) => {
304
+ setProjectName(project.name);
305
+ setCurrentTemplate(project.template);
306
+ setContent(project.content);
307
+ setTimelineElements(project.timelineElements);
308
+ setActiveTab("content");
309
+ setShowLoadProjectDialog(false);
310
+ showNotify(`Project "${project.name}" loaded successfully`);
311
+ };
312
+
313
+ // Template selection
314
+ const selectTemplate = (templateId) => {
315
+ setCurrentTemplate(templates.find(t => t.id === templateId));
316
+ showNotify("Template selected: " + templates.find(t => t.id === templateId).name);
317
+ setActiveTab("content");
318
+ };
319
+
320
+ // Notification helper
321
+ const showNotify = (message, type = "success") => {
322
+ setNotificationMessage(message);
323
+ setNotificationType(type);
324
+ setShowNotification(true);
325
+ setTimeout(() => setShowNotification(false), 3000);
326
+ };
327
+
328
+ // Add new content row
329
+ const addContentRow = () => {
330
+ if (!newRow.targetWord || !newRow.nativeWord) {
331
+ showNotify("Target and native words are required", "error");
332
+ return;
333
+ }
334
+
335
+ setContent([...content, {
336
+ id: content.length + 1,
337
+ ...newRow,
338
+ audio: `audio-${content.length + 1}.mp3` // Placeholder for generated audio
339
+ }]);
340
+
341
+ setNewRow({
342
+ image: "",
343
+ targetWord: "",
344
+ nativeWord: "",
345
+ targetPhrase1: "",
346
+ nativePhrase1: "",
347
+ targetPhrase2: "",
348
+ nativePhrase2: ""
349
+ });
350
+
351
+ showNotify("New content row added");
352
+ };
353
+
354
+ // Delete content row
355
+ const deleteContentRow = (id) => {
356
+ setContent(content.filter(row => row.id !== id));
357
+ showNotify("Content row deleted");
358
+ };
359
+
360
+ // Toggle play/pause
361
+ const togglePlayback = () => {
362
+ setIsPlaying(!isPlaying);
363
+ };
364
+
365
+ // Add new element to timeline
366
+ const addTimelineElement = (type) => {
367
+ const newElement = {
368
+ id: `el-${Date.now()}`,
369
+ type,
370
+ content: type === 'text' ? 'New Text' : type === 'image' ? 'image.jpg' : 'audio.mp3',
371
+ start: currentTime,
372
+ duration: type === 'audio' ? 5 : 3,
373
+ position: type !== 'audio' ? {x: 400, y: 300} : undefined,
374
+ style: type === 'text' ? {color: "#ffffff", fontSize: 24} :
375
+ type === 'image' ? {width: 200, height: 200, borderRadius: "8px"} : undefined,
376
+ animation: type !== 'audio' ? selectedAnimationStyle : undefined
377
+ };
378
+
379
+ setTimelineElements([...timelineElements, newElement]);
380
+ setEditingElement(newElement);
381
+ setShowEditor(true);
382
+ showNotify(`New ${type} element added`);
383
+ };
384
+
385
+ // Animation preview
386
+ useEffect(() => {
387
+ let interval;
388
+ if (isPlaying) {
389
+ interval = setInterval(() => {
390
+ setCurrentTime(time => {
391
+ const newTime = time + 0.1;
392
+ if (newTime >= duration) {
393
+ setIsPlaying(false);
394
+ return 0;
395
+ }
396
+ return newTime;
397
+ });
398
+
399
+ setProgress(prog => {
400
+ const newProg = prog + (100 / (duration * 10));
401
+ return newProg > 100 ? 0 : newProg;
402
+ });
403
+ }, 100);
404
+ }
405
+
406
+ return () => clearInterval(interval);
407
+ }, [isPlaying, duration]);
408
+
409
+ // Handle timeline scrubbing
410
+ const handleTimelineClick = (e) => {
411
+ if (!timelineRef.current) return;
412
+
413
+ const rect = timelineRef.current.getBoundingClientRect();
414
+ const clickPosition = e.clientX - rect.left;
415
+ const percentClicked = clickPosition / rect.width;
416
+ const newTime = percentClicked * duration;
417
+
418
+ setCurrentTime(Math.max(0, Math.min(newTime, duration)));
419
+ setProgress(percentClicked * 100);
420
+ };
421
+
422
+ // Handle timeline drag
423
+ const handleTimelineDragStart = () => {
424
+ setIsDraggingTimeline(true);
425
+ setIsPlaying(false);
426
+ };
427
+
428
+ const handleTimelineDragMove = (e) => {
429
+ if (!isDraggingTimeline || !timelineRef.current) return;
430
+
431
+ const rect = timelineRef.current.getBoundingClientRect();
432
+ const dragPosition = e.clientX - rect.left;
433
+ const percentDragged = Math.max(0, Math.min(dragPosition / rect.width, 1));
434
+ const newTime = percentDragged * duration;
435
+
436
+ setCurrentTime(newTime);
437
+ setProgress(percentDragged * 100);
438
+ };
439
+
440
+ const handleTimelineDragEnd = () => {
441
+ setIsDraggingTimeline(false);
442
+ };
443
+
444
+ // Effect for handling window mouse events during timeline dragging
445
+ useEffect(() => {
446
+ const handleMouseMove = (e) => handleTimelineDragMove(e);
447
+ const handleMouseUp = () => handleTimelineDragEnd();
448
+
449
+ if (isDraggingTimeline) {
450
+ window.addEventListener('mousemove', handleMouseMove);
451
+ window.addEventListener('mouseup', handleMouseUp);
452
+ }
453
+
454
+ return () => {
455
+ window.removeEventListener('mousemove', handleMouseMove);
456
+ window.removeEventListener('mouseup', handleMouseUp);
457
+ };
458
+ }, [isDraggingTimeline]);
459
+
460
+ // Generate videos for all content
461
+ const generateAllVideos = () => {
462
+ if (!currentTemplate) {
463
+ showNotify("Please select a template first", "error");
464
+ return;
465
+ }
466
+
467
+ if (content.length === 0) {
468
+ showNotify("No content to generate", "error");
469
+ return;
470
+ }
471
+
472
+ // Create render queue
473
+ const queue = content.map(item => ({
474
+ id: `render-${item.id}`,
475
+ content: item,
476
+ template: currentTemplate,
477
+ status: "pending",
478
+ progress: 0
479
+ }));
480
+
481
+ setRenderQueue(queue);
482
+ setGeneratingStatus("preparing");
483
+
484
+ // Simulate processing
485
+ setTimeout(() => {
486
+ setGeneratingStatus("processing");
487
+
488
+ let currentIndex = 0;
489
+ const processNext = () => {
490
+ if (currentIndex >= queue.length) {
491
+ setGeneratingStatus("complete");
492
+ showNotify(`Successfully generated ${queue.length} videos`);
493
+ return;
494
+ }
495
+
496
+ const updatedQueue = [...queue];
497
+ updatedQueue[currentIndex].status = "processing";
498
+ setRenderQueue(updatedQueue);
499
+
500
+ // Simulate processing time (in real app, this would be actual rendering)
501
+ let progress = 0;
502
+ const interval = setInterval(() => {
503
+ progress += 5;
504
+
505
+ const newQueue = [...updatedQueue];
506
+ newQueue[currentIndex].progress = progress;
507
+ setRenderQueue(newQueue);
508
+
509
+ if (progress >= 100) {
510
+ clearInterval(interval);
511
+ newQueue[currentIndex].status = "complete";
512
+ setRenderQueue(newQueue);
513
+ currentIndex++;
514
+ setTimeout(processNext, 500);
515
+ }
516
+ }, 300);
517
+ };
518
+
519
+ processNext();
520
+ }, 1500);
521
+ };
522
+
523
+ // Open element editor
524
+ const openElementEditor = (element) => {
525
+ setEditingElement(element);
526
+ setShowEditor(true);
527
+ };
528
+
529
+ // Update element
530
+ const updateElement = (updatedElement) => {
531
+ setTimelineElements(elements =>
532
+ elements.map(el => el.id === updatedElement.id ? updatedElement : el)
533
+ );
534
+ setShowEditor(false);
535
+ showNotify("Element updated");
536
+ };
537
+
538
+ // Handle element drag in preview
539
+ const startElementDrag = (element) => {
540
+ setDraggedElement(element);
541
+ };
542
+
543
+ const handleElementDrag = (e, element) => {
544
+ if (!previewContainerRef.current) return;
545
+
546
+ const rect = previewContainerRef.current.getBoundingClientRect();
547
+ const newX = e.clientX - rect.left;
548
+ const newY = e.clientY - rect.top;
549
+
550
+ setTimelineElements(elements =>
551
+ elements.map(el => {
552
+ if (el.id === element.id && el.position) {
553
+ return {
554
+ ...el,
555
+ position: {
556
+ x: Math.max(0, Math.min(newX, rect.width)),
557
+ y: Math.max(0, Math.min(newY, rect.height))
558
+ }
559
+ };
560
+ }
561
+ return el;
562
+ })
563
+ );
564
+ };
565
+
566
+ const endElementDrag = () => {
567
+ setDraggedElement(null);
568
+ };
569
+
570
+ // Effect for mouse events during element dragging
571
+ useEffect(() => {
572
+ const handleMouseMove = (e) => {
573
+ if (draggedElement) {
574
+ handleElementDrag(e, draggedElement);
575
+ }
576
+ };
577
+
578
+ const handleMouseUp = () => {
579
+ if (draggedElement) {
580
+ endElementDrag();
581
+ }
582
+ };
583
+
584
+ if (draggedElement) {
585
+ window.addEventListener('mousemove', handleMouseMove);
586
+ window.addEventListener('mouseup', handleMouseUp);
587
+ }
588
+
589
+ return () => {
590
+ window.removeEventListener('mousemove', handleMouseMove);
591
+ window.removeEventListener('mouseup', handleMouseUp);
592
+ };
593
+ }, [draggedElement]);
594
+
595
+ // Generate audio for content
596
+ const generateAudio = () => {
597
+ showNotify("Generating audio for content...");
598
+
599
+ // Simulate audio generation
600
+ let generatedCount = 0;
601
+
602
+ const processNext = () => {
603
+ if (generatedCount >= content.length) {
604
+ showNotify("Audio generation complete!");
605
+ // Generate mock waveform data
606
+ generateMockWaveform();
607
+ return;
608
+ }
609
+
610
+ setTimeout(() => {
611
+ generatedCount++;
612
+ processNext();
613
+ }, 500);
614
+ };
615
+
616
+ processNext();
617
+ };
618
+
619
+ // Generate mock waveform for audio visualization
620
+ const generateMockWaveform = () => {
621
+ const waveformPoints = [];
622
+ const numPoints = 100;
623
+
624
+ for (let i = 0; i < numPoints; i++) {
625
+ // Create a semi-random waveform height, with more activity in the middle
626
+ const x = i / numPoints;
627
+ let height = 0.2 + 0.6 * Math.sin(x * Math.PI) * Math.random();
628
+ waveformPoints.push(height);
629
+ }
630
+
631
+ setAudioWaveform(waveformPoints);
632
+ };
633
+
634
+ // Export current video
635
+ const exportCurrentVideo = () => {
636
+ if (currentContentIndex >= content.length) {
637
+ showNotify("No content selected for export", "error");
638
+ return;
639
+ }
640
+
641
+ setIsExporting(true);
642
+ setExportProgress(0);
643
+
644
+ // Simulate export process
645
+ const interval = setInterval(() => {
646
+ setExportProgress(prev => {
647
+ const next = prev + 5;
648
+ if (next >= 100) {
649
+ clearInterval(interval);
650
+ setTimeout(() => {
651
+ setIsExporting(false);
652
+ showNotify(`Video exported successfully as ${projectName}-${currentContentIndex + 1}.${exportFormat}`);
653
+ }, 500);
654
+ return 100;
655
+ }
656
+ return next;
657
+ });
658
+ }, 200);
659
+ };
660
+
661
+ // Reset progress for new render
662
+ const startNewProject = () => {
663
+ setCurrentTemplate(null);
664
+ setActiveTab("templates");
665
+ setProjectName("New Project");
666
+ setContent([]);
667
+ setRenderQueue([]);
668
+ setGeneratingStatus(null);
669
+ setCurrentTime(0);
670
+ setProgress(0);
671
+ showNotify("Started new project");
672
+ };
673
+
674
+ // Define placeholder thumbnails and assets
675
+ const getImageUrl = (name) => {
676
+ if (name === "medico.jpg") {
677
+ return "/api/placeholder/300/300";
678
+ } else if (name === "abogado.jpg") {
679
+ return "/api/placeholder/300/300";
680
+ } else if (name === "template1.jpg" || name === "template2.jpg" || name === "template3.jpg") {
681
+ return "/api/placeholder/400/220";
682
+ }
683
+ return "/api/placeholder/200/200";
684
+ };
685
+
686
+ // Get real content based on timeline element
687
+ const getElementContent = (element) => {
688
+ if (!element.contentKey || currentContentIndex >= content.length) {
689
+ return element.content;
690
+ }
691
+
692
+ const contentItem = content[currentContentIndex];
693
+ return contentItem[element.contentKey] || element.content;
694
+ };
695
+
696
+ // Timeline rendering
697
+ const renderTimeline = () => {
698
+ // Find the time range to display
699
+ const visibleTimeStart = Math.max(0, currentTime - 5);
700
+ const visibleTimeEnd = Math.min(duration, currentTime + 10);
701
+
702
+ return (
703
+ <div className="relative h-24 bg-slate-800 rounded overflow-hidden">
704
+ {/* Time markers */}
705
+ <div className="absolute top-0 left-0 right-0 h-6 flex text-xs text-slate-400">
706
+ {Array.from({ length: Math.ceil(duration) + 1 }).map((_, i) => (
707
+ <div
708
+ key={i}
709
+ className="border-l border-slate-700 h-full flex items-center px-1"
710
+ style={{
711
+ position: 'absolute',
712
+ left: `${(i / duration) * 100}%`,
713
+ opacity: i % 5 === 0 ? 1 : 0.5
714
+ }}
715
+ >
716
+ {i}s
717
+ </div>
718
+ ))}
719
+ </div>
720
+
721
+ {/* Waveform */}
722
+ {showWaveform && audioWaveform.length > 0 && (
723
+ <div className="absolute top-6 left-0 right-0 h-10 px-2">
724
+ <svg width="100%" height="100%" className="opacity-50">
725
+ <defs>
726
+ <linearGradient id="waveformGradient" x1="0%" y1="0%" x2="0%" y2="100%">
727
+ <stop offset="0%" stopColor="#4F46E5" />
728
+ <stop offset="100%" stopColor="#818CF8" />
729
+ </linearGradient>
730
+ </defs>
731
+ <path
732
+ d={`M0,${10} ${audioWaveform.map((h, i) => `L${(i / audioWaveform.length) * 100}%,${(1-h) * 10} `).join('')}`}
733
+ stroke="url(#waveformGradient)"
734
+ strokeWidth="1.5"
735
+ fill="none"
736
+ />
737
+ </svg>
738
+ </div>
739
+ )}
740
+
741
+ {/* Elements timeline */}
742
+ <div className="absolute top-16 left-0 right-0 bottom-0 px-2">
743
+ {timelineElements.map(element => (
744
+ <div
745
+ key={element.id}
746
+ className={`absolute h-6 rounded cursor-pointer ${
747
+ element.type === 'text' ? 'bg-blue-600/70' :
748
+ element.type === 'image' ? 'bg-green-600/70' :
749
+ 'bg-purple-600/70'
750
+ } ${currentTime >= element.start && currentTime < element.start + element.duration ? 'ring-2 ring-white' : ''}`}
751
+ style={{
752
+ left: `${(element.start / duration) * 100}%`,
753
+ width: `${(element.duration / duration) * 100}%`,
754
+ top: element.type === 'audio' ? 'auto' :
755
+ element.type === 'text' ? '0px' : '8px',
756
+ bottom: element.type === 'audio' ? '0px' : 'auto'
757
+ }}
758
+ onClick={() => openElementEditor(element)}
759
+ >
760
+ <div className="truncate text-xs text-white px-2 py-1">
761
+ {element.type === 'text' ? getElementContent(element).substring(0, 15) : element.type}
762
+ {element.type === 'text' && getElementContent(element).length > 15 ? '...' : ''}
763
+ </div>
764
+ </div>
765
+ ))}
766
+ </div>
767
+
768
+ {/* Current time indicator */}
769
+ <div className="absolute top-0 bottom-0 w-px bg-red-500 z-10" style={{ left: `${(currentTime / duration) * 100}%` }}>
770
+ <div className="w-3 h-3 bg-red-500 rounded-full -ml-1.5 -mt-1"></div>
771
+ </div>
772
+
773
+ {/* Click area */}
774
+ <div
775
+ ref={timelineRef}
776
+ className="absolute inset-0 cursor-pointer"
777
+ onClick={handleTimelineClick}
778
+ onMouseDown={handleTimelineDragStart}
779
+ ></div>
780
+ </div>
781
+ );
782
+ };
783
+
784
+ // Render preview with elements
785
+ const renderPreview = () => {
786
+ const currentContent = content[currentContentIndex];
787
+ const backgroundColor = currentTemplate?.background || "#1a1a2e";
788
+
789
+ return (
790
+ <div
791
+ ref={previewContainerRef}
792
+ className={`aspect-video bg-slate-800 rounded-lg relative overflow-hidden ${fullscreenPreview ? 'fixed inset-0 z-50 rounded-none' : ''}`}
793
+ style={{ backgroundColor }}
794
+ >
795
+ {/* Background elements */}
796
+ {currentTemplate && (
797
+ <div className="absolute inset-0 opacity-20">
798
+ <svg width="100%" height="100%">
799
+ <defs>
800
+ <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
801
+ <path d="M 40 0 L 0 0 0 40" fill="none" stroke="white" strokeWidth="0.5" />
802
+ </pattern>
803
+ </defs>
804
+ <rect width="100%" height="100%" fill="url(#grid)" />
805
+ </svg>
806
+ </div>
807
+ )}
808
+
809
+ {/* Display timeline elements */}
810
+ {timelineElements.map(element => {
811
+ if (element.type === 'audio') return null; // Don't visually render audio
812
+
813
+ const animationState = getElementAnimationState(element, currentTime);
814
+ if (animationState === "before") return null;
815
+
816
+ const content = getElementContent(element);
817
+ const animationPreset = animationPresets[element.animation || "fadeIn"];
818
+
819
+ // Determine animation based on time
820
+ const animationProgress = Math.min(1, (currentTime - element.start) / 0.5); // 0.5s animation duration
821
+ const exitProgress = Math.min(1, (currentTime - (element.start + element.duration - 0.5)) / 0.5);
822
+
823
+ const shouldExit = animationState === "after" || exitProgress > 0;
824
+ const shouldAnimate = animationProgress < 1 && !shouldExit;
825
+
826
+ // Calculate final animation values
827
+ const opacity = shouldExit ? 1 - exitProgress : shouldAnimate ? animationProgress : 1;
828
+
829
+ // Determine transform based on animation type
830
+ let transform = '';
831
+ if (element.animation === 'slideIn') {
832
+ const xOffset = shouldExit ? (exitProgress * 100) : shouldAnimate ? ((1 - animationProgress) * -100) : 0;
833
+ transform = `translateX(${xOffset}px)`;
834
+ } else if (element.animation === 'zoomIn') {
835
+ const scale = shouldExit ? (1 - exitProgress * 0.5) : shouldAnimate ? animationProgress : 1;
836
+ transform = `scale(${scale})`;
837
+ } else if (element.animation === 'bounceIn') {
838
+ const scale = shouldExit ? (1 - exitProgress * 0.5) : shouldAnimate ? (animationProgress < 0.5 ? 2 * animationProgress : 1 + (1 - animationProgress)) : 1;
839
+ transform = `scale(${scale})`;
840
+ } else if (element.animation === 'flipIn') {
841
+ const rotateY = shouldExit ? (exitProgress * 90) : shouldAnimate ? ((1 - animationProgress) * 90) : 0;
842
+ transform = `perspective(800px) rotateY(${rotateY}deg)`;
843
+ }
844
+
845
+ return (
846
+ <div
847
+ key={element.id}
848
+ className="absolute p-1"
849
+ style={{
850
+ left: element.position?.x || 0,
851
+ top: element.position?.y || 0,
852
+ opacity,
853
+ transform,
854
+ transition: 'opacity 0.1s, transform 0.1s',
855
+ cursor: activeTab === 'editor' ? 'move' : 'default',
856
+ transformOrigin: 'center'
857
+ }}
858
+ onMouseDown={activeTab === 'editor' ? () => startElementDrag(element) : undefined}
859
+ >
860
+ {element.type === 'text' ? (
861
+ <div
862
+ style={{
863
+ color: element.style?.color || 'white',
864
+ fontSize: `${element.style?.fontSize || 24}px`,
865
+ fontWeight: element.style?.fontWeight || 'normal',
866
+ fontFamily: element.style?.fontFamily || 'Inter, sans-serif',
867
+ textShadow: '0 1px 3px rgba(0,0,0,0.5)'
868
+ }}
869
+ >
870
+ {content}
871
+ </div>
872
+ ) : element.type === 'image' ? (
873
+ <img
874
+ src={getImageUrl(content)}
875
+ alt={content}
876
+ style={{
877
+ width: `${element.style?.width || 200}px`,
878
+ height: `${element.style?.height || 200}px`,
879
+ borderRadius: element.style?.borderRadius || '0px',
880
+ objectFit: 'cover',
881
+ boxShadow: '0 4px 6px rgba(0,0,0,0.1)'
882
+ }}
883
+ />
884
+ ) : null}
885
+ </div>
886
+ );
887
+ })}
888
+
889
+ {/* Playback controls */}
890
+ <div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex space-x-2 bg-black/70 rounded-full p-2 backdrop-blur-sm">
891
+ <Button variant="ghost" size="icon" className="text-white" onClick={() => setCurrentTime(Math.max(0, currentTime - 1))}>
892
+ <SkipBack className="h-4 w-4" />
893
+ </Button>
894
+ <Button variant="ghost" size="icon" className="text-white" onClick={togglePlayback}>
895
+ {isPlaying ? <Pause className="h-5 w-5" /> : <Play className="h-5 w-5" />}
896
+ </Button>
897
+ <Button variant="ghost" size="icon" className="text-white" onClick={() => setCurrentTime(Math.min(duration, currentTime + 1))}>
898
+ <SkipForward className="h-4 w-4" />
899
+ </Button>
900
+ </div>
901
+
902
+ {/* Additional controls */}
903
+ <div className="absolute top-2 right-2 flex space-x-1">
904
+ <Button
905
+ variant="ghost"
906
+ size="icon"
907
+ className="text-white/70 hover:text-white bg-black/20 hover:bg-black/40"
908
+ onClick={() => setShowWaveform(!showWaveform)}
909
+ >
910
+ {showWaveform ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
911
+ </Button>
912
+ <Button
913
+ variant="ghost"
914
+ size="icon"
915
+ className="text-white/70 hover:text-white bg-black/20 hover:bg-black/40"
916
+ onClick={() => setFullscreenPreview(!fullscreenPreview)}
917
+ >
918
+ {fullscreenPreview ? <Minimize className="h-4 w-4" /> : <Maximize className="h-4 w-4" />}
919
+ </Button>
920
+ </div>
921
+
922
+ {/* Export progress overlay */}
923
+ {isExporting && (
924
+ <div className="absolute inset-0 bg-black/80 flex flex-col items-center justify-center">
925
+ <div className="w-64 mb-4">
926
+ <Progress value={exportProgress} className="h-2" />
927
+ </div>
928
+ <div className="text-white font-medium">Exporting video... {exportProgress}%</div>
929
+ </div>
930
+ )}
931
+
932
+ {/* Timeline progress */}
933
+ <div className="absolute bottom-0 left-0 right-0 h-1 bg-gray-700 z-10">
934
+ <div
935
+ className="h-full bg-blue-500 transition-all"
936
+ style={{ width: `${progress}%` }}
937
+ ></div>
938
+ </div>
939
+ </div>
940
+ );
941
+ };
942
+
943
+ return (
944
+ <div className="flex flex-col h-screen bg-slate-50 overflow-hidden">
945
+ {/* Header */}
946
+ <header className="border-b bg-white shadow-sm">
947
+ <div className="container mx-auto px-4 py-3 flex items-center justify-between">
948
+ <div className="flex items-center space-x-2">
949
+ <FileVideo className="h-6 w-6 text-blue-600" />
950
+ <h1 className="text-xl font-bold tracking-tight">VideoGen Pro</h1>
951
+ </div>
952
+
953
+ <div className="flex space-x-2">
954
+ <TooltipProvider>
955
+ <Tooltip>
956
+ <TooltipTrigger asChild>
957
+ <Input
958
+ className="w-60"
959
+ value={projectName}
960
+ onChange={(e) => setProjectName(e.target.value)}
961
+ />
962
+ </TooltipTrigger>
963
+ <TooltipContent>
964
+ <p>Project Name</p>
965
+ </TooltipContent>
966
+ </Tooltip>
967
+ </TooltipProvider>
968
+
969
+ <DropdownMenu>
970
+ <DropdownMenuTrigger asChild>
971
+ <Button variant="outline" size="sm">
972
+ <FileText className="h-4 w-4 mr-1" /> Project
973
+ </Button>
974
+ </DropdownMenuTrigger>
975
+ <DropdownMenuContent>
976
+ <DropdownMenuItem onClick={startNewProject}>
977
+ <FileText className="h-4 w-4 mr-2" />
978
+ New Project
979
+ </DropdownMenuItem>
980
+ <DropdownMenuItem onClick={() => setShowLoadProjectDialog(true)}>
981
+ <Upload className="h-4 w-4 mr-2" />
982
+ Load Project
983
+ </DropdownMenuItem>
984
+ <DropdownMenuItem onClick={() => saveCurrentProject()}>
985
+ <Save className="h-4 w-4 mr-2" />
986
+ Save Project
987
+ </DropdownMenuItem>
988
+ </DropdownMenuContent>
989
+ </DropdownMenu>
990
+
991
+ <Button size="sm" onClick={() => exportCurrentVideo()}>
992
+ <Download className="h-4 w-4 mr-1" /> Export
993
+ </Button>
994
+
995
+ <DropdownMenu>
996
+ <DropdownMenuTrigger asChild>
997
+ <Button variant="ghost" size="icon">
998
+ <Settings className="h-5 w-5" />
999
+ </Button>
1000
+ </DropdownMenuTrigger>
1001
+ <DropdownMenuContent align="end">
1002
+ <DropdownMenuLabel>Settings</DropdownMenuLabel>
1003
+ <DropdownMenuSeparator />
1004
+ <DropdownMenuItem onClick={() => setPreviewQuality("low")}>
1005
+ <div className="flex items-center">
1006
+ <span className="mr-2">Low Quality Preview</span>
1007
+ {previewQuality === "low" && <Check className="h-4 w-4" />}
1008
+ </div>
1009
+ </DropdownMenuItem>
1010
+ <DropdownMenuItem onClick={() => setPreviewQuality("medium")}>
1011
+ <div className="flex items-center">
1012
+ <span className="mr-2">Medium Quality Preview</span>
1013
+ {previewQuality === "medium" && <Check className="h-4 w-4" />}
1014
+ </div>
1015
+ </DropdownMenuItem>
1016
+ <DropdownMenuItem onClick={() => setPreviewQuality("high")}>
1017
+ <div className="flex items-center">
1018
+ <span className="mr-2">High Quality Preview</span>
1019
+ {previewQuality === "high" && <Check className="h-4 w-4" />}
1020
+ </div>
1021
+ </DropdownMenuItem>
1022
+ <DropdownMenuSeparator />
1023
+ <DropdownMenuItem>Project Settings</DropdownMenuItem>
1024
+ <DropdownMenuItem>Export Settings</DropdownMenuItem>
1025
+ <DropdownMenuItem>User Preferences</DropdownMenuItem>
1026
+ </DropdownMenuContent>
1027
+ </DropdownMenu>
1028
+ </div>
1029
+ </div>
1030
+ </header>
1031
+
1032
+ {/* Main Content */}
1033
+ <div className="flex-1 flex overflow-hidden">
1034
+ {/* Left Sidebar */}
1035
+ <div className="w-56 border-r bg-white">
1036
+ <div className="p-4">
1037
+ <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
1038
+ <TabsList className="grid grid-cols-2 mb-4">
1039
+ <TabsTrigger value="templates">Templates</TabsTrigger>
1040
+ <TabsTrigger value="content">Content</TabsTrigger>
1041
+ </TabsList>
1042
+ </Tabs>
1043
+
1044
+ <div className="space-y-2">
1045
+ <Button
1046
+ variant={activeTab === "templates" ? "default" : "outline"}
1047
+ className="w-full justify-start"
1048
+ onClick={() => setActiveTab("templates")}
1049
+ >
1050
+ <Layers className="h-4 w-4 mr-2" />
1051
+ Templates
1052
+ </Button>
1053
+
1054
+ <Button
1055
+ variant={activeTab === "content" ? "default" : "outline"}
1056
+ className="w-full justify-start"
1057
+ onClick={() => setActiveTab("content")}
1058
+ >
1059
+ <FileText className="h-4 w-4 mr-2" />
1060
+ Content
1061
+ </Button>
1062
+
1063
+ <Button
1064
+ variant={activeTab === "editor" ? "default" : "outline"}
1065
+ className="w-full justify-start"
1066
+ onClick={() => setActiveTab("editor")}
1067
+ >
1068
+ <Edit3 className="h-4 w-4 mr-2" />
1069
+ Editor
1070
+ </Button>
1071
+
1072
+ <Button
1073
+ variant={activeTab === "generate" ? "default" : "outline"}
1074
+ className="w-full justify-start"
1075
+ onClick={() => setActiveTab("generate")}
1076
+ >
1077
+ <Zap className="h-4 w-4 mr-2" />
1078
+ Generate
1079
+ </Button>
1080
+ </div>
1081
+
1082
+ {activeTab === "editor" && (
1083
+ <div className="mt-6 space-y-2 border-t pt-4">
1084
+ <h3 className="text-sm font-medium text-slate-500">Add Elements</h3>
1085
+ <div className="grid grid-cols-3 gap-2">
1086
+ <Button variant="outline" size="sm" className="w-full p-1" onClick={() => addTimelineElement("text")}>
1087
+ <FileText className="h-4 w-4" />
1088
+ </Button>
1089
+ <Button variant="outline" size="sm" className="w-full p-1" onClick={() => addTimelineElement("image")}>
1090
+ <Image className="h-4 w-4" />
1091
+ </Button>
1092
+ <Button variant="outline" size="sm" className="w-full p-1" onClick={() => addTimelineElement("audio")}>
1093
+ <Volume2 className="h-4 w-4" />
1094
+ </Button>
1095
+ </div>
1096
+
1097
+ <h3 className="text-sm font-medium text-slate-500 mt-4">Animation Style</h3>
1098
+ <Select value={selectedAnimationStyle} onValueChange={setSelectedAnimationStyle}>
1099
+ <SelectTrigger className="w-full">
1100
+ <SelectValue placeholder="Animation Style" />
1101
+ </SelectTrigger>
1102
+ <SelectContent>
1103
+ <SelectItem value="fadeIn">Fade In</SelectItem>
1104
+ <SelectItem value="slideIn">Slide In</SelectItem>
1105
+ <SelectItem value="zoomIn">Zoom In</SelectItem>
1106
+ <SelectItem value="bounceIn">Bounce In</SelectItem>
1107
+ <SelectItem value="flipIn">Flip In</SelectItem>
1108
+ </SelectContent>
1109
+ </Select>
1110
+
1111
+ <div className="flex items-center justify-between mt-4">
1112
+ <Label htmlFor="audio-sync" className="text-sm">
1113
+ Auto-sync to Audio
1114
+ </Label>
1115
+ <Switch
1116
+ id="audio-sync"
1117
+ checked={audioSyncEnabled}
1118
+ onCheckedChange={setAudioSyncEnabled}
1119
+ />
1120
+ </div>
1121
+ </div>
1122
+ )}
1123
+ </div>
1124
+ </div>
1125
+
1126
+ {/* Main Panel */}
1127
+ <div className="flex-1 overflow-auto">
1128
+ <div className="container mx-auto p-6">
1129
+ {activeTab === "templates" && (
1130
+ <div className="space-y-6">
1131
+ <div className="flex justify-between items-center">
1132
+ <h2 className="text-2xl font-bold">Video Templates</h2>
1133
+ <Button size="sm">
1134
+ <Plus className="h-4 w-4 mr-1" /> Import Template
1135
+ </Button>
1136
+ </div>
1137
+
1138
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
1139
+ {templates.map(template => (
1140
+ <Card key={template.id} className={`overflow-hidden transition-all ${currentTemplate?.id === template.id ? 'ring-2 ring-blue-500' : ''}`}>
1141
+ <div className="aspect-video bg-slate-100 relative overflow-hidden">
1142
+ <img
1143
+ src={getImageUrl(template.thumbnail)}
1144
+ alt={template.name}
1145
+ className="w-full h-full object-cover"
1146
+ />
1147
+ <div
1148
+ className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent flex flex-col justify-end p-4"
1149
+ >
1150
+ <h3 className="text-lg font-medium text-white">{template.name}</h3>
1151
+ <p className="text-sm text-white/80">{template.description}</p>
1152
+ </div>
1153
+ <Badge className="absolute top-2 right-2">
1154
+ {template.duration}s
1155
+ </Badge>
1156
+ </div>
1157
+ <CardFooter className="bg-white p-3">
1158
+ <Button
1159
+ className="w-full"
1160
+ onClick={() => selectTemplate(template.id)}
1161
+ >
1162
+ {currentTemplate?.id === template.id ? (
1163
+ <>
1164
+ <CheckCircle className="h-4 w-4 mr-1" />
1165
+ Selected
1166
+ </>
1167
+ ) : "Select Template"}
1168
+ </Button>
1169
+ </CardFooter>
1170
+ </Card>
1171
+ ))}
1172
+ </div>
1173
+ </div>
1174
+ )}
1175
+
1176
+ {activeTab === "content" && (
1177
+ <div className="space-y-6">
1178
+ <div className="flex justify-between items-center">
1179
+ <h2 className="text-2xl font-bold">Content</h2>
1180
+ <div className="flex space-x-2">
1181
+ <Select value={targetLanguage} onValueChange={setTargetLanguage}>
1182
+ <SelectTrigger className="w-40">
1183
+ <SelectValue placeholder="Target Language" />
1184
+ </SelectTrigger>
1185
+ <SelectContent>
1186
+ <SelectItem value="es-ES">Español</SelectItem>
1187
+ <SelectItem value="fr-FR">Français</SelectItem>
1188
+ <SelectItem value="de-DE">Deutsch</SelectItem>
1189
+ <SelectItem value="it-IT">Italiano</SelectItem>
1190
+ <SelectItem value="pt-PT">Português</SelectItem>
1191
+ </SelectContent>
1192
+ </Select>
1193
+
1194
+ <Select value={nativeLanguage} onValueChange={setNativeLanguage}>
1195
+ <SelectTrigger className="w-40">
1196
+ <SelectValue placeholder="Native Language" />
1197
+ </SelectTrigger>
1198
+ <SelectContent>
1199
+ <SelectItem value="en-US">English</SelectItem>
1200
+ <SelectItem value="es-ES">Español</SelectItem>
1201
+ <SelectItem value="fr-FR">Français</SelectItem>
1202
+ <SelectItem value="de-DE">Deutsch</SelectItem>
1203
+ <SelectItem value="it-IT">Italiano</SelectItem>
1204
+ </SelectContent>
1205
+ </Select>
1206
+ </div>
1207
+ </div>
1208
+
1209
+ <div className="bg-white rounded-md shadow overflow-hidden">
1210
+ <div className="border-b px-4 py-2 bg-slate-50 grid grid-cols-9 gap-4 font-medium text-sm">
1211
+ <div className="col-span-1">Image</div>
1212
+ <div className="col-span-1">Target Word</div>
1213
+ <div className="col-span-1">Native Word</div>
1214
+ <div className="col-span-2">Target Phrase 1</div>
1215
+ <div className="col-span-2">Native Phrase 1</div>
1216
+ <div className="col-span-1">Target Phrase 2</div>
1217
+ <div className="col-span-1">Actions</div>
1218
+ </div>
1219
+
1220
+ {content.map(row => (
1221
+ <div key={row.id} className="px-4 py-3 border-b grid grid-cols-9 gap-4 items-center">
1222
+ <div className="col-span-1">
1223
+ <div className="w-10 h-10 bg-slate-100 rounded overflow-hidden">
1224
+ <img
1225
+ src={getImageUrl(row.image)}
1226
+ alt={row.targetWord}
1227
+ className="w-full h-full object-cover"
1228
+ />
1229
+ </div>
1230
+ </div>
1231
+ <div className="col-span-1">{row.targetWord}</div>
1232
+ <div className="col-span-1">{row.nativeWord}</div>
1233
+ <div className="col-span-2">{row.targetPhrase1}</div>
1234
+ <div className="col-span-2">{row.nativePhrase1}</div>
1235
+ <div className="col-span-1">{row.targetPhrase2}</div>
1236
+ <div className="col-span-1 flex space-x-1">
1237
+ <Button variant="ghost" size="icon" onClick={() => setCurrentContentIndex(content.indexOf(row))}>
1238
+ <Eye className="h-4 w-4" />
1239
+ </Button>
1240
+ <Button variant="ghost" size="icon" onClick={() => deleteContentRow(row.id)}>
1241
+ <Trash2 className="h-4 w-4" />
1242
+ </Button>
1243
+ </div>
1244
+ </div>
1245
+ ))}
1246
+
1247
+ <div className="px-4 py-3 grid grid-cols-9 gap-4 items-center bg-slate-50">
1248
+ <div className="col-span-1">
1249
+ <Button variant="outline" size="sm" className="w-10 h-10 p-0">
1250
+ <Upload className="h-4 w-4" />
1251
+ </Button>
1252
+ </div>
1253
+ <div className="col-span-1">
1254
+ <Input
1255
+ placeholder="Word"
1256
+ value={newRow.targetWord}
1257
+ onChange={(e) => setNewRow({...newRow, targetWord: e.target.value})}
1258
+ />
1259
+ </div>
1260
+ <div className="col-span-1">
1261
+ <Input
1262
+ placeholder="Word"
1263
+ value={newRow.nativeWord}
1264
+ onChange={(e) => setNewRow({...newRow, nativeWord: e.target.value})}
1265
+ />
1266
+ </div>
1267
+ <div className="col-span-2">
1268
+ <Input
1269
+ placeholder="Phrase"
1270
+ value={newRow.targetPhrase1}
1271
+ onChange={(e) => setNewRow({...newRow, targetPhrase1: e.target.value})}
1272
+ />
1273
+ </div>
1274
+ <div className="col-span-2">
1275
+ <Input
1276
+ placeholder="Phrase"
1277
+ value={newRow.nativePhrase1}
1278
+ onChange={(e) => setNewRow({...newRow, nativePhrase1: e.target.value})}
1279
+ />
1280
+ </div>
1281
+ <div className="col-span-1">
1282
+ <Input
1283
+ placeholder="Phrase"
1284
+ value={newRow.targetPhrase2}
1285
+ onChange={(e) => setNewRow({...newRow, targetPhrase2: e.target.value})}
1286
+ />
1287
+ </div>
1288
+ <div className="col-span-1">
1289
+ <Button onClick={addContentRow}>
1290
+ <Plus className="h-4 w-4 mr-1" /> Add
1291
+ </Button>
1292
+ </div>
1293
+ </div>
1294
+ </div>
1295
+
1296
+ <div className="flex justify-between">
1297
+ <Button variant="outline" onClick={generateAudio}>
1298
+ <Volume2 className="h-4 w-4 mr-1" /> Generate Audio
1299
+ </Button>
1300
+
1301
+ <Button onClick={() => setActiveTab("editor")}>
1302
+ Continue to Editor <ChevronDown className="h-4 w-4 ml-1 rotate-270" />
1303
+ </Button>
1304
+ </div>
1305
+ </div>
1306
+ )}
1307
+
1308
+ {(activeTab === "editor" || activeTab === "generate") && (
1309
+ <div className="space-y-6">
1310
+ <div className="flex justify-between items-center">
1311
+ <h2 className="text-2xl font-bold">
1312
+ {activeTab === "editor" ? "Template Editor" : "Generate Videos"}
1313
+ </h2>
1314
+ {activeTab === "generate" && (
1315
+ <div className="flex space-x-2">
1316
+ <Select value={exportResolution} onValueChange={setExportResolution}>
1317
+ <SelectTrigger className="w-32">
1318
+ <SelectValue placeholder="Resolution" />
1319
+ </SelectTrigger>
1320
+ <SelectContent>
1321
+ <SelectItem value="720p">720p</SelectItem>
1322
+ <SelectItem value="1080p">1080p</SelectItem>
1323
+ <SelectItem value="4k">4K</SelectItem>
1324
+ </SelectContent>
1325
+ </Select>
1326
+
1327
+ <Select value={exportFormat} onValueChange={setExportFormat}>
1328
+ <SelectTrigger className="w-32">
1329
+ <SelectValue placeholder="Format" />
1330
+ </SelectTrigger>
1331
+ <SelectContent>
1332
+ <SelectItem value="mp4">MP4 (H.264)</SelectItem>
1333
+ <SelectItem value="webm">WebM</SelectItem>
1334
+ <SelectItem value="gif">GIF</SelectItem>
1335
+ </SelectContent>
1336
+ </Select>
1337
+ </div>
1338
+ )}
1339
+ </div>
1340
+
1341
+ {/* Preview and Timeline Area */}
1342
+ <div className="space-y-4">
1343
+ {/* Preview Area */}
1344
+ {renderPreview()}
1345
+
1346
+ {/* Timeline */}
1347
+ {renderTimeline()}
1348
+
1349
+ <div className="flex justify-between">
1350
+ <div className="flex space-x-2">
1351
+ <Button
1352
+ variant="outline"
1353
+ size="sm"
1354
+ onClick={() => setCurrentContentIndex(prev => (prev > 0 ? prev - 1 : 0))}
1355
+ disabled={currentContentIndex <= 0}
1356
+ >
1357
+ <ArrowLeft className="h-4 w-4 mr-1" />
1358
+ Previous
1359
+ </Button>
1360
+ <Button
1361
+ variant="outline"
1362
+ size="sm"
1363
+ onClick={() => setCurrentContentIndex(prev => (prev < content.length - 1 ? prev + 1 : prev))}
1364
+ disabled={currentContentIndex >= content.length - 1}
1365
+ >
1366
+ Next
1367
+ <ArrowRight className="h-4 w-4 ml-1" />
1368
+ </Button>
1369
+ <div className="bg-slate-100 rounded px-2 py-1 text-sm flex items-center">
1370
+ Item {currentContentIndex + 1} of {content.length}
1371
+ </div>
1372
+ </div>
1373
+
1374
+ {activeTab === "editor" && (
1375
+ <Button onClick={() => setActiveTab("generate")}>
1376
+ Continue to Generate <ChevronDown className="h-4 w-4 ml-1 rotate-270" />
1377
+ </Button>
1378
+ )}
1379
+
1380
+ {activeTab === "generate" && (
1381
+ <Button onClick={generateAllVideos} disabled={generatingStatus === "processing" || generatingStatus === "preparing"}>
1382
+ {generatingStatus === "complete" ?
1383
+ <><RefreshCw className="h-4 w-4 mr-1" /> Generate More</> :
1384
+ generatingStatus === "processing" || generatingStatus === "preparing" ?
1385
+ <><RefreshCw className="h-4 w-4 mr-1 animate-spin" /> Processing...</> :
1386
+ <><Zap className="h-4 w-4 mr-1" /> Generate All Videos</>}
1387
+ </Button>
1388
+ )}
1389
+ </div>
1390
+ </div>
1391
+
1392
+ {activeTab === "generate" && (
1393
+ <div className="mt-6">
1394
+ <Alert variant={generatingStatus === "complete" ? "success" : "default"}>
1395
+ <CheckCircle className="h-4 w-4" />
1396
+ <AlertTitle>
1397
+ {generatingStatus === "complete" ?
1398
+ "Generation Complete!" :
1399
+ generatingStatus === "processing" ?
1400
+ "Processing Videos..." :
1401
+ generatingStatus === "preparing" ?
1402
+ "Preparing..." :
1403
+ "Ready to Generate"}
1404
+ </AlertTitle>
1405
+ <AlertDescription>
1406
+ {generatingStatus === "complete" ?
1407
+ `Successfully generated ${renderQueue.length} videos.` :
1408
+ generatingStatus === "processing" ?
1409
+ `Processing ${renderQueue.filter(item => item.status === "complete").length} of ${renderQueue.length} videos...` :
1410
+ generatingStatus === "preparing" ?
1411
+ "Setting up render queue..." :
1412
+ "Click the button below to generate videos for all content."}
1413
+ </AlertDescription>
1414
+ </Alert>
1415
+
1416
+ <Card className="mt-4">
1417
+ <CardHeader>
1418
+ <CardTitle>Generation Queue</CardTitle>
1419
+ <CardDescription>
1420
+ {content.length} videos to generate using {currentTemplate?.name || "selected template"}
1421
+ </CardDescription>
1422
+ </CardHeader>
1423
+ <CardContent className="space-y-4">
1424
+ {renderQueue.length > 0 ? (
1425
+ renderQueue.map(item => (
1426
+ <div key={item.id} className="flex items-center space-x-4">
1427
+ <div className="w-8 h-8 rounded-full bg-slate-100 flex items-center justify-center">
1428
+ {item.status === "complete" ?
1429
+ <Check className="h-4 w-4 text-green-500" /> :
1430
+ item.status === "processing" ?
1431
+ <RefreshCw className="h-4 w-4 text-blue-500 animate-spin" /> :
1432
+ <Clock className="h-4 w-4 text-slate-400" />}
1433
+ </div>
1434
+ <div className="flex-1">
1435
+ <div className="font-medium">{item.content.targetWord} - {item.content.nativeWord}</div>
1436
+ <Progress
1437
+ value={item.progress}
1438
+ className="h-2 mt-1"
1439
+ />
1440
+ </div>
1441
+ <div className="text-sm text-slate-500">
1442
+ {item.status === "complete" ?
1443
+ "Complete" :
1444
+ item.status === "processing" ?
1445
+ `${item.progress}%` :
1446
+ "Pending"}
1447
+ </div>
1448
+ <Button
1449
+ variant="ghost"
1450
+ size="icon"
1451
+ disabled={item.status !== "complete"}
1452
+ >
1453
+ <Download className="h-4 w-4" />
1454
+ </Button>
1455
+ </div>
1456
+ ))
1457
+ ) : (
1458
+ <div className="text-center py-8 text-slate-500">
1459
+ <Grid className="h-8 w-8 mx-auto mb-2" />
1460
+ <p>No videos in queue yet</p>
1461
+ <p className="text-sm">Click the "Generate All Videos" button to start generation</p>
1462
+ </div>
1463
+ )}
1464
+ </CardContent>
1465
+ <CardFooter className="flex justify-between">
1466
+ <div>
1467
+ <div className="flex items-center space-x-2">
1468
+ <Checkbox id="audio-checkbox" checked />
1469
+ <Label htmlFor="audio-checkbox">Include audio</Label>
1470
+ </div>
1471
+ </div>
1472
+ </CardFooter>
1473
+ </Card>
1474
+ </div>
1475
+ )}
1476
+ </div>
1477
+ )}
1478
+ </div>
1479
+ </div>
1480
+ </div>
1481
+
1482
+ {/* Element Editor Dialog */}
1483
+ <Dialog open={showEditor} onOpenChange={setShowEditor}>
1484
+ <DialogContent className="sm:max-w-[500px]">
1485
+ <DialogHeader>
1486
+ <DialogTitle>Edit Element</DialogTitle>
1487
+ <DialogDescription>
1488
+ Adjust properties for this timeline element
1489
+ </DialogDescription>
1490
+ </DialogHeader>
1491
+
1492
+ {editingElement && (
1493
+ <div className="space-y-4 pt-2">
1494
+ <div className="grid grid-cols-2 gap-4">
1495
+ <div className="space-y-2">
1496
+ <Label>Start Time (seconds)</Label>
1497
+ <Input
1498
+ type="number"
1499
+ min="0"
1500
+ step="0.1"
1501
+ value={editingElement.start}
1502
+ onChange={(e) => setEditingElement({...editingElement, start: parseFloat(e.target.value)})}
1503
+ />
1504
+ </div>
1505
+
1506
+ <div className="space-y-2">
1507
+ <Label>Duration (seconds)</Label>
1508
+ <Input
1509
+ type="number"
1510
+ min="0.1"
1511
+ step="0.1"
1512
+ value={editingElement.duration}
1513
+ onChange={(e) => setEditingElement({...editingElement, duration: parseFloat(e.target.value)})}
1514
+ />
1515
+ </div>
1516
+ </div>
1517
+
1518
+ {editingElement.type === "text" && (
1519
+ <>
1520
+ <div className="space-y-2">
1521
+ <Label>Text Content</Label>
1522
+ <Textarea
1523
+ value={editingElement.content}
1524
+ onChange={(e) => setEditingElement({...editingElement, content: e.target.value})}
1525
+ />
1526
+ </div>
1527
+
1528
+ <div className="grid grid-cols-2 gap-4">
1529
+ <div className="space-y-2">
1530
+ <Label>Font Size</Label>
1531
+ <Input
1532
+ type="number"
1533
+ value={editingElement.style?.fontSize || 24}
1534
+ onChange={(e) => setEditingElement({
1535
+ ...editingElement,
1536
+ style: {...editingElement.style, fontSize: parseInt(e.target.value)}
1537
+ })}
1538
+ />
1539
+ </div>
1540
+
1541
+ <div className="space-y-2">
1542
+ <Label>Text Color</Label>
1543
+ <Input
1544
+ type="color"
1545
+ value={editingElement.style?.color || "#ffffff"}
1546
+ onChange={(e) => setEditingElement({
1547
+ ...editingElement,
1548
+ style: {...editingElement.style, color: e.target.value}
1549
+ })}
1550
+ />
1551
+ </div>
1552
+ </div>
1553
+
1554
+ <div className="space-y-2">
1555
+ <Label>Content Link</Label>
1556
+ <Select
1557
+ value={editingElement.contentKey || "none"}
1558
+ onValueChange={(val) => setEditingElement({
1559
+ ...editingElement,
1560
+ contentKey: val === "none" ? undefined : val
1561
+ })}
1562
+ >
1563
+ <SelectTrigger>
1564
+ <SelectValue placeholder="Link to content field" />
1565
+ </SelectTrigger>
1566
+ <SelectContent>
1567
+ <SelectItem value="none">No link (use fixed text)</SelectItem>
1568
+ <SelectItem value="targetWord">Target Word</SelectItem>
1569
+ <SelectItem value="nativeWord">Native Word</SelectItem>
1570
+ <SelectItem value="targetPhrase1">Target Phrase 1</SelectItem>
1571
+ <SelectItem value="nativePhrase1">Native Phrase 1</SelectItem>
1572
+ <SelectItem value="targetPhrase2">Target Phrase 2</SelectItem>
1573
+ <SelectItem value="nativePhrase2">Native Phrase 2</SelectItem>
1574
+ </SelectContent>
1575
+ </Select>
1576
+ </div>
1577
+
1578
+ <div className="space-y-2">
1579
+ <Label>Animation</Label>
1580
+ <Select
1581
+ value={editingElement.animation || "fadeIn"}
1582
+ onValueChange={(val) => setEditingElement({
1583
+ ...editingElement,
1584
+ animation: val
1585
+ })}
1586
+ >
1587
+ <SelectTrigger>
1588
+ <SelectValue placeholder="Choose animation" />
1589
+ </SelectTrigger>
1590
+ <SelectContent>
1591
+ <SelectItem value="fadeIn">Fade In</SelectItem>
1592
+ <SelectItem value="slideIn">Slide In</SelectItem>
1593
+ <SelectItem value="zoomIn">Zoom In</SelectItem>
1594
+ <SelectItem value="bounceIn">Bounce In</SelectItem>
1595
+ <SelectItem value="flipIn">Flip In</SelectItem>
1596
+ </SelectContent>
1597
+ </Select>
1598
+ </div>
1599
+ </>
1600
+ )}
1601
+
1602
+ {editingElement.type === "image" && (
1603
+ <>
1604
+ <div className="space-y-2">
1605
+ <Label>Image Source</Label>
1606
+ <div className="flex space-x-2">
1607
+ <Input
1608
+ value={editingElement.content}
1609
+ onChange={(e) => setEditingElement({...editingElement, content: e.target.value})}
1610
+ />
1611
+ <Button variant="outline" size="icon">
1612
+ <Upload className="h-4 w-4" />
1613
+ </Button>
1614
+ </div>
1615
+ </div>
1616
+
1617
+ <div className="grid grid-cols-2 gap-4">
1618
+ <div className="space-y-2">
1619
+ <Label>Width (px)</Label>
1620
+ <Input
1621
+ type="number"
1622
+ value={editingElement.style?.width || 200}
1623
+ onChange={(e) => setEditingElement({
1624
+ ...editingElement,
1625
+ style: {...editingElement.style, width: parseInt(e.target.value)}
1626
+ })}
1627
+ />
1628
+ </div>
1629
+
1630
+ <div className="space-y-2">
1631
+ <Label>Height (px)</Label>
1632
+ <Input
1633
+ type="number"
1634
+ value={editingElement.style?.height || 200}
1635
+ onChange={(e) => setEditingElement({
1636
+ ...editingElement,
1637
+ style: {...editingElement.style, height: parseInt(e.target.value)}
1638
+ })}
1639
+ />
1640
+ </div>
1641
+ </div>
1642
+
1643
+ <div className="space-y-2">
1644
+ <Label>Border Radius (px)</Label>
1645
+ <Slider
1646
+ value={[parseInt(editingElement.style?.borderRadius) || 0]}
1647
+ max={50}
1648
+ step={1}
1649
+ onValueChange={(val) => setEditingElement({
1650
+ ...editingElement,
1651
+ style: {...editingElement.style, borderRadius: `${val[0]}px`}
1652
+ })}
1653
+ />
1654
+ </div>
1655
+
1656
+ <div className="space-y-2">
1657
+ <Label>Content Link</Label>
1658
+ <Select
1659
+ value={editingElement.contentKey || "none"}
1660
+ onValueChange={(val) => setEditingElement({
1661
+ ...editingElement,
1662
+ contentKey: val === "none" ? undefined : val
1663
+ })}
1664
+ >
1665
+ <SelectTrigger>
1666
+ <SelectValue placeholder="Link to content field" />
1667
+ </SelectTrigger>
1668
+ <SelectContent>
1669
+ <SelectItem value="none">No link (use fixed image)</SelectItem>
1670
+ <SelectItem value="image">Content Image</SelectItem>
1671
+ </SelectContent>
1672
+ </Select>
1673
+ </div>
1674
+
1675
+ <div className="space-y-2">
1676
+ <Label>Animation</Label>
1677
+ <Select
1678
+ value={editingElement.animation || "fadeIn"}
1679
+ onValueChange={(val) => setEditingElement({
1680
+ ...editingElement,
1681
+ animation: val
1682
+ })}
1683
+ >
1684
+ <SelectTrigger>
1685
+ <SelectValue placeholder="Choose animation" />
1686
+ </SelectTrigger>
1687
+ <SelectContent>
1688
+ <SelectItem value="fadeIn">Fade In</SelectItem>
1689
+ <SelectItem value="slideIn">Slide In</SelectItem>
1690
+ <SelectItem value="zoomIn">Zoom In</SelectItem>
1691
+ <SelectItem value="bounceIn">Bounce In</SelectItem>
1692
+ <SelectItem value="flipIn">Flip In</SelectItem>
1693
+ </SelectContent>
1694
+ </Select>
1695
+ </div>
1696
+ </>
1697
+ )}
1698
+
1699
+ {editingElement.type === "audio" && (
1700
+ <>
1701
+ <div className="space-y-2">
1702
+ <Label>Audio Source</Label>
1703
+ <div className="flex space-x-2">
1704
+ <Input
1705
+ value={editingElement.content}
1706
+ onChange={(e) => setEditingElement({...editingElement, content: e.target.value})}
1707
+ />
1708
+ <Button variant="outline" size="icon">
1709
+ <Upload className="h-4 w-4" />
1710
+ </Button>
1711
+ </div>
1712
+ </div>
1713
+
1714
+ <div className="space-y-2">
1715
+ <Label>Content Link</Label>
1716
+ <Select
1717
+ value={editingElement.contentKey || "none"}
1718
+ onValueChange={(val) => setEditingElement({
1719
+ ...editingElement,
1720
+ contentKey: val === "none" ? undefined : val
1721
+ })}
1722
+ >
1723
+ <SelectTrigger>
1724
+ <SelectValue placeholder="Link to content field" />
1725
+ </SelectTrigger>
1726
+ <SelectContent>
1727
+ <SelectItem value="none">No link (use fixed audio)</SelectItem>
1728
+ <SelectItem value="audio">Content Audio</SelectItem>
1729
+ </SelectContent>
1730
+ </Select>
1731
+ </div>
1732
+ </>
1733
+ )}
1734
+
1735
+ <Alert className="bg-blue-50 border-blue-200">
1736
+ <AlertTriangle className="h-4 w-4 text-blue-500" />
1737
+ <AlertDescription className="text-blue-700 text-sm">
1738
+ {audioSyncEnabled ?
1739
+ "Audio synchronization is enabled. Element timing will adapt based on audio duration." :
1740
+ "Audio synchronization is disabled. Element timing will use fixed values."}
1741
+ </AlertDescription>
1742
+ </Alert>
1743
+ </div>
1744
+ )}
1745
+
1746
+ <DialogFooter>
1747
+ <Button variant="outline" onClick={() => setShowEditor(false)}>Cancel</Button>
1748
+ <Button onClick={() => updateElement(editingElement)}>Save Changes</Button>
1749
+ </DialogFooter>
1750
+ </DialogContent>
1751
+ </Dialog>
1752
+
1753
+ {/* Load Project Dialog */}
1754
+ <Dialog open={showLoadProjectDialog} onOpenChange={setShowLoadProjectDialog}>
1755
+ <DialogContent className="sm:max-w-[500px]">
1756
+ <DialogHeader>
1757
+ <DialogTitle>Load Project</DialogTitle>
1758
+ <DialogDescription>
1759
+ Select a project to load
1760
+ </DialogDescription>
1761
+ </DialogHeader>
1762
+
1763
+ <div className="space-y-4 pt-2">
1764
+ {savedProjects.length > 0 ? (
1765
+ <div className="space-y-2">
1766
+ {savedProjects.map(project => (
1767
+ <div
1768
+ key={project.id}
1769
+ className="flex items-center justify-between p-3 border rounded-md hover:bg-gray-50 cursor-pointer"
1770
+ onClick={() => loadProject(project)}
1771
+ >
1772
+ <div>
1773
+ <h3 className="font-medium">{project.name}</h3>
1774
+ <p className="text-sm text-gray-500">
1775
+ Last saved: {new Date(project.lastSaved).toLocaleString()}
1776
+ </p>
1777
+ </div>
1778
+ <Button variant="ghost" size="sm">
1779
+ <Upload className="h-4 w-4 mr-1" />
1780
+ Load
1781
+ </Button>
1782
+ </div>
1783
+ ))}
1784
+ </div>
1785
+ ) : (
1786
+ <div className="text-center py-8 text-slate-500">
1787
+ <FileText className="h-8 w-8 mx-auto mb-2" />
1788
+ <p>No saved projects found</p>
1789
+ </div>
1790
+ )}
1791
+ </div>
1792
+
1793
+ <DialogFooter>
1794
+ <Button variant="outline" onClick={() => setShowLoadProjectDialog(false)}>Cancel</Button>
1795
+ </DialogFooter>
1796
+ </DialogContent>
1797
+ </Dialog>
1798
+
1799
+ {/* Notification Toast */}
1800
+ <div className={`fixed bottom-4 right-4 ${notificationType === 'error' ? 'bg-red-600' : 'bg-slate-800'} text-white px-4 py-2 rounded-md shadow-lg transition-opacity duration-300 flex items-center ${showNotification ? 'opacity-100' : 'opacity-0'}`}>
1801
+ {notificationType === 'error' ? (
1802
+ <AlertTriangle className="h-4 w-4 mr-2 text-white" />
1803
+ ) : (
1804
+ <Check className="h-4 w-4 mr-2 text-green-400" />
1805
+ )}
1806
+ {notificationMessage}
1807
+ </div>
1808
+ </div>
1809
+ );
1810
+ };
1811
+
1812
+ export default VideoGeneratorApp;
react/enhanced-videogen-pro.tsx ADDED
@@ -0,0 +1,916 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import {
3
+ Play, Pause, Plus, Trash2, ChevronRight, ChevronLeft,
4
+ Save, Download, Settings, Clock, Volume2, Edit3, Copy,
5
+ Scissors, Music, Image, FileText, RotateCcw, RotateCw,
6
+ CheckCircle, UploadCloud, Maximize, Layers, ZoomIn, ZoomOut
7
+ } from 'lucide-react';
8
+
9
+ // Shadcn UI components
10
+ import { Button } from '@/components/ui/button';
11
+ import { Slider } from '@/components/ui/slider';
12
+ import { Input } from '@/components/ui/input';
13
+ import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
14
+ import { Card, CardContent } from '@/components/ui/card';
15
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
16
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
17
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
18
+ import { ScrollArea } from '@/components/ui/scroll-area';
19
+ import { Textarea } from '@/components/ui/textarea';
20
+ import { Label } from '@/components/ui/label';
21
+ import { Switch } from '@/components/ui/switch';
22
+ import { Progress } from '@/components/ui/progress';
23
+ import { Badge } from '@/components/ui/badge';
24
+
25
+ const VideoEditor = () => {
26
+ // Editor state
27
+ const [projectName, setProjectName] = useState("My Video Project");
28
+ const [isPlaying, setIsPlaying] = useState(false);
29
+ const [currentTime, setCurrentTime] = useState(0);
30
+ const [duration, setDuration] = useState(60); // Total duration in seconds
31
+ const [zoom, setZoom] = useState(1);
32
+ const [selectedSceneIndex, setSelectedSceneIndex] = useState(0);
33
+ const [showSceneEditor, setShowSceneEditor] = useState(false);
34
+ const [notification, setNotification] = useState(null);
35
+ const [showExportDialog, setShowExportDialog] = useState(false);
36
+ const [exportProgress, setExportProgress] = useState(0);
37
+ const [isExporting, setIsExporting] = useState(false);
38
+
39
+ // References
40
+ const timelineRef = useRef(null);
41
+ const previewRef = useRef(null);
42
+
43
+ // Scenes data structure
44
+ const [scenes, setScenes] = useState([
45
+ {
46
+ id: "scene-1",
47
+ name: "Introduction",
48
+ duration: 5,
49
+ background: "linear-gradient(120deg, #1e3c72, #2a5298)",
50
+ type: "title",
51
+ content: {
52
+ title: "Learning Spanish",
53
+ subtitle: "Basic Vocabulary"
54
+ }
55
+ },
56
+ {
57
+ id: "scene-2",
58
+ name: "Word 1",
59
+ duration: 8,
60
+ background: "linear-gradient(120deg, #2a5298, #4776E6)",
61
+ type: "word",
62
+ content: {
63
+ word: "médico",
64
+ translation: "doctor",
65
+ example: "Quiero ser médico"
66
+ }
67
+ },
68
+ {
69
+ id: "scene-3",
70
+ name: "Word 2",
71
+ duration: 8,
72
+ background: "linear-gradient(120deg, #4776E6, #8E54E9)",
73
+ type: "word",
74
+ content: {
75
+ word: "abogado",
76
+ translation: "lawyer",
77
+ example: "Necesito un abogado"
78
+ }
79
+ },
80
+ {
81
+ id: "scene-4",
82
+ name: "Conclusion",
83
+ duration: 5,
84
+ background: "linear-gradient(120deg, #8E54E9, #1e3c72)",
85
+ type: "title",
86
+ content: {
87
+ title: "Keep Practicing!",
88
+ subtitle: "Learn a new word every day"
89
+ }
90
+ }
91
+ ]);
92
+
93
+ // Scene templates
94
+ const sceneTemplates = [
95
+ {
96
+ id: "template-title",
97
+ name: "Title Scene",
98
+ type: "title",
99
+ thumbnail: "linear-gradient(120deg, #1e3c72, #2a5298)",
100
+ duration: 5
101
+ },
102
+ {
103
+ id: "template-word",
104
+ name: "Vocabulary Word",
105
+ type: "word",
106
+ thumbnail: "linear-gradient(120deg, #4776E6, #8E54E9)",
107
+ duration: 8
108
+ },
109
+ {
110
+ id: "template-split",
111
+ name: "Split Screen",
112
+ type: "split",
113
+ thumbnail: "linear-gradient(120deg, #11998e, #38ef7d)",
114
+ duration: 6
115
+ },
116
+ {
117
+ id: "template-blank",
118
+ name: "Blank Scene",
119
+ type: "blank",
120
+ thumbnail: "linear-gradient(120deg, #333, #666)",
121
+ duration: 5
122
+ }
123
+ ];
124
+
125
+ // Get current scene
126
+ const currentScene = scenes[selectedSceneIndex] || scenes[0];
127
+
128
+ // Update the total duration when scenes change
129
+ useEffect(() => {
130
+ const total = scenes.reduce((sum, scene) => sum + scene.duration, 0);
131
+ setDuration(total);
132
+ }, [scenes]);
133
+
134
+ // When scene selection changes, update current time
135
+ useEffect(() => {
136
+ if (selectedSceneIndex >= 0 && selectedSceneIndex < scenes.length) {
137
+ let startTime = 0;
138
+ for (let i = 0; i < selectedSceneIndex; i++) {
139
+ startTime += scenes[i].duration;
140
+ }
141
+ setCurrentTime(startTime);
142
+ }
143
+ }, [selectedSceneIndex]);
144
+
145
+ // Animation playback
146
+ useEffect(() => {
147
+ let interval;
148
+ if (isPlaying) {
149
+ interval = setInterval(() => {
150
+ setCurrentTime(time => {
151
+ const newTime = time + 0.1;
152
+ if (newTime >= duration) {
153
+ setIsPlaying(false);
154
+ return 0;
155
+ }
156
+
157
+ // Update selected scene based on current time
158
+ let timeSum = 0;
159
+ for (let i = 0; i < scenes.length; i++) {
160
+ timeSum += scenes[i].duration;
161
+ if (newTime < timeSum) {
162
+ if (selectedSceneIndex !== i) {
163
+ setSelectedSceneIndex(i);
164
+ }
165
+ break;
166
+ }
167
+ }
168
+
169
+ return newTime;
170
+ });
171
+ }, 100);
172
+ }
173
+
174
+ return () => clearInterval(interval);
175
+ }, [isPlaying, duration, scenes, selectedSceneIndex]);
176
+
177
+ // Handle timeline scrubbing
178
+ const handleTimelineClick = (e) => {
179
+ if (!timelineRef.current) return;
180
+
181
+ const rect = timelineRef.current.getBoundingClientRect();
182
+ const clickPosition = e.clientX - rect.left;
183
+ const percentClicked = clickPosition / rect.width;
184
+ const newTime = percentClicked * duration;
185
+
186
+ setCurrentTime(Math.max(0, Math.min(newTime, duration)));
187
+
188
+ // Update selected scene
189
+ let timeSum = 0;
190
+ for (let i = 0; i < scenes.length; i++) {
191
+ timeSum += scenes[i].duration;
192
+ if (newTime < timeSum) {
193
+ setSelectedSceneIndex(i);
194
+ break;
195
+ }
196
+ }
197
+ };
198
+
199
+ // Add a new scene
200
+ const addScene = (templateId) => {
201
+ const template = sceneTemplates.find(t => t.id === templateId);
202
+ if (!template) return;
203
+
204
+ const newScene = {
205
+ id: `scene-${Date.now()}`,
206
+ name: `New ${template.name}`,
207
+ duration: template.duration,
208
+ background: template.thumbnail,
209
+ type: template.type,
210
+ content: template.type === 'title'
211
+ ? { title: "New Title", subtitle: "Add a subtitle" }
212
+ : template.type === 'word'
213
+ ? { word: "palabra", translation: "word", example: "Una palabra nueva" }
214
+ : template.type === 'split'
215
+ ? { leftText: "Left side", rightText: "Right side" }
216
+ : {}
217
+ };
218
+
219
+ // Insert after currently selected scene
220
+ const newScenes = [...scenes];
221
+ newScenes.splice(selectedSceneIndex + 1, 0, newScene);
222
+
223
+ setScenes(newScenes);
224
+ setSelectedSceneIndex(selectedSceneIndex + 1);
225
+ showNotification("Scene added");
226
+ };
227
+
228
+ // Delete scene
229
+ const deleteScene = (index) => {
230
+ if (scenes.length <= 1) {
231
+ showNotification("Cannot delete the only scene", "error");
232
+ return;
233
+ }
234
+
235
+ const newScenes = [...scenes];
236
+ newScenes.splice(index, 1);
237
+
238
+ setScenes(newScenes);
239
+ if (selectedSceneIndex >= newScenes.length) {
240
+ setSelectedSceneIndex(newScenes.length - 1);
241
+ }
242
+ showNotification("Scene deleted");
243
+ };
244
+
245
+ // Duplicate scene
246
+ const duplicateScene = (index) => {
247
+ const sceneToClone = scenes[index];
248
+ const clonedScene = {
249
+ ...sceneToClone,
250
+ id: `scene-${Date.now()}`,
251
+ name: `${sceneToClone.name} (Copy)`
252
+ };
253
+
254
+ const newScenes = [...scenes];
255
+ newScenes.splice(index + 1, 0, clonedScene);
256
+
257
+ setScenes(newScenes);
258
+ setSelectedSceneIndex(index + 1);
259
+ showNotification("Scene duplicated");
260
+ };
261
+
262
+ // Update scene
263
+ const updateScene = (sceneData) => {
264
+ const newScenes = [...scenes];
265
+ newScenes[selectedSceneIndex] = {
266
+ ...newScenes[selectedSceneIndex],
267
+ ...sceneData
268
+ };
269
+
270
+ setScenes(newScenes);
271
+ setShowSceneEditor(false);
272
+ showNotification("Scene updated");
273
+ };
274
+
275
+ // Show notification
276
+ const showNotification = (message, type = "success") => {
277
+ setNotification({ message, type });
278
+ setTimeout(() => setNotification(null), 3000);
279
+ };
280
+
281
+ // Export video
282
+ const exportVideo = (settings) => {
283
+ setIsExporting(true);
284
+ setExportProgress(0);
285
+
286
+ // Simulate export process
287
+ const interval = setInterval(() => {
288
+ setExportProgress(prev => {
289
+ const newValue = prev + 5;
290
+ if (newValue >= 100) {
291
+ clearInterval(interval);
292
+ setTimeout(() => {
293
+ setIsExporting(false);
294
+ setShowExportDialog(false);
295
+ showNotification("Video exported successfully!");
296
+ }, 500);
297
+ return 100;
298
+ }
299
+ return newValue;
300
+ });
301
+ }, 200);
302
+ };
303
+
304
+ // Get time at scene start
305
+ const getSceneStartTime = (index) => {
306
+ let startTime = 0;
307
+ for (let i = 0; i < index; i++) {
308
+ startTime += scenes[i].duration;
309
+ }
310
+ return startTime;
311
+ };
312
+
313
+ // Format time as MM:SS
314
+ const formatTime = (seconds) => {
315
+ const mins = Math.floor(seconds / 60);
316
+ const secs = Math.floor(seconds % 60);
317
+ return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
318
+ };
319
+
320
+ // Jump to previous scene
321
+ const goToPreviousScene = () => {
322
+ if (selectedSceneIndex > 0) {
323
+ setSelectedSceneIndex(selectedSceneIndex - 1);
324
+ }
325
+ };
326
+
327
+ // Jump to next scene
328
+ const goToNextScene = () => {
329
+ if (selectedSceneIndex < scenes.length - 1) {
330
+ setSelectedSceneIndex(selectedSceneIndex + 1);
331
+ }
332
+ };
333
+
334
+ // Render scene preview based on type
335
+ const renderScenePreview = (scene) => {
336
+ switch (scene.type) {
337
+ case 'title':
338
+ return (
339
+ <div className="absolute inset-0 flex flex-col items-center justify-center text-white">
340
+ <h1 className="text-4xl font-bold mb-4 text-center">{scene.content.title}</h1>
341
+ <h2 className="text-xl text-center">{scene.content.subtitle}</h2>
342
+ </div>
343
+ );
344
+ case 'word':
345
+ return (
346
+ <div className="absolute inset-0 flex flex-col items-center justify-center text-white">
347
+ <h2 className="text-xl mb-1">New Word</h2>
348
+ <h1 className="text-5xl font-bold mb-2">{scene.content.word}</h1>
349
+ <p className="text-2xl mb-6 opacity-80">{scene.content.translation}</p>
350
+ <div className="bg-white/20 p-4 rounded-lg text-xl">
351
+ "{scene.content.example}"
352
+ </div>
353
+ </div>
354
+ );
355
+ case 'split':
356
+ return (
357
+ <div className="absolute inset-0 flex text-white">
358
+ <div className="w-1/2 border-r border-white/20 flex items-center justify-center p-6">
359
+ <p className="text-2xl text-center">{scene.content.leftText}</p>
360
+ </div>
361
+ <div className="w-1/2 flex items-center justify-center p-6">
362
+ <p className="text-2xl text-center">{scene.content.rightText}</p>
363
+ </div>
364
+ </div>
365
+ );
366
+ default:
367
+ return (
368
+ <div className="absolute inset-0 flex items-center justify-center text-white">
369
+ <p className="text-xl">Empty Scene</p>
370
+ </div>
371
+ );
372
+ }
373
+ };
374
+
375
+ return (
376
+ <div className="flex flex-col h-screen bg-gray-900 text-white overflow-hidden">
377
+ {/* Top toolbar */}
378
+ <header className="border-b border-gray-700 bg-gray-800 p-3 flex items-center justify-between">
379
+ <div className="flex items-center space-x-4">
380
+ <div className="flex items-center space-x-2">
381
+ <DropdownMenu>
382
+ <DropdownMenuTrigger asChild>
383
+ <Button variant="ghost" size="sm" className="text-white">Project</Button>
384
+ </DropdownMenuTrigger>
385
+ <DropdownMenuContent className="bg-gray-800 border-gray-700 text-white">
386
+ <DropdownMenuItem className="hover:bg-gray-700">New Project</DropdownMenuItem>
387
+ <DropdownMenuItem className="hover:bg-gray-700">Open Project</DropdownMenuItem>
388
+ <DropdownMenuItem className="hover:bg-gray-700">Save Project</DropdownMenuItem>
389
+ </DropdownMenuContent>
390
+ </DropdownMenu>
391
+
392
+ <Input
393
+ className="w-40 bg-gray-800 border-gray-600 text-white"
394
+ value={projectName}
395
+ onChange={(e) => setProjectName(e.target.value)}
396
+ />
397
+ </div>
398
+
399
+ <div className="flex items-center space-x-2">
400
+ <Button
401
+ variant="ghost"
402
+ size="sm"
403
+ className="text-white"
404
+ onClick={() => setShowSceneEditor(true)}
405
+ >
406
+ <Edit3 className="h-4 w-4 mr-1" /> Edit Scene
407
+ </Button>
408
+
409
+ <Button
410
+ variant="ghost"
411
+ size="sm"
412
+ className="text-white"
413
+ onClick={() => duplicateScene(selectedSceneIndex)}
414
+ >
415
+ <Copy className="h-4 w-4 mr-1" /> Duplicate
416
+ </Button>
417
+
418
+ <Button
419
+ variant="ghost"
420
+ size="sm"
421
+ className="text-white"
422
+ onClick={() => deleteScene(selectedSceneIndex)}
423
+ >
424
+ <Trash2 className="h-4 w-4 mr-1" /> Delete
425
+ </Button>
426
+ </div>
427
+ </div>
428
+
429
+ <div className="flex items-center space-x-2">
430
+ <Button
431
+ variant="ghost"
432
+ size="sm"
433
+ className="text-white"
434
+ onClick={() => setShowExportDialog(true)}
435
+ >
436
+ <Download className="h-4 w-4 mr-1" /> Export
437
+ </Button>
438
+
439
+ <Button variant="ghost" size="icon" className="text-white">
440
+ <Settings className="h-5 w-5" />
441
+ </Button>
442
+ </div>
443
+ </header>
444
+
445
+ {/* Main Content Area */}
446
+ <div className="flex flex-1 overflow-hidden">
447
+ {/* Left panel - Templates */}
448
+ <div className="w-64 border-r border-gray-700 bg-gray-800 overflow-y-auto">
449
+ <div className="p-3 border-b border-gray-700">
450
+ <h2 className="font-medium">Scene Templates</h2>
451
+ </div>
452
+
453
+ <div className="p-3 space-y-3">
454
+ {sceneTemplates.map(template => (
455
+ <Card
456
+ key={template.id}
457
+ className="bg-gray-700 border-gray-600 cursor-pointer hover:bg-gray-600 transition-colors overflow-hidden"
458
+ onClick={() => addScene(template.id)}
459
+ >
460
+ <div className="aspect-video relative" style={{ background: template.thumbnail }}>
461
+ <div className="absolute inset-0 flex items-center justify-center">
462
+ <Plus className="h-8 w-8 text-white opacity-50" />
463
+ </div>
464
+ </div>
465
+ <CardContent className="p-2">
466
+ <div className="text-sm">{template.name}</div>
467
+ <div className="text-xs text-gray-400">{template.duration}s</div>
468
+ </CardContent>
469
+ </Card>
470
+ ))}
471
+ </div>
472
+ </div>
473
+
474
+ {/* Middle Panel - Preview */}
475
+ <div className="flex-1 flex flex-col overflow-hidden">
476
+ {/* Scene Preview */}
477
+ <div className="flex-1 bg-gray-950 p-4 flex items-center justify-center">
478
+ <div
479
+ ref={previewRef}
480
+ className="aspect-video w-full max-h-full relative rounded overflow-hidden"
481
+ style={{ background: currentScene.background }}
482
+ >
483
+ {renderScenePreview(currentScene)}
484
+
485
+ {/* Scene info overlay */}
486
+ <div className="absolute top-2 left-2 bg-black/50 text-white text-xs px-2 py-1 rounded">
487
+ {currentScene.name} ({formatTime(getSceneStartTime(selectedSceneIndex))} - {formatTime(getSceneStartTime(selectedSceneIndex) + currentScene.duration)})
488
+ </div>
489
+ </div>
490
+ </div>
491
+
492
+ {/* Playback Controls */}
493
+ <div className="h-12 border-t border-gray-700 bg-gray-800 flex items-center px-4 space-x-4">
494
+ <div className="flex items-center">
495
+ <Button
496
+ variant="ghost"
497
+ size="icon"
498
+ className="text-white"
499
+ onClick={goToPreviousScene}
500
+ disabled={selectedSceneIndex === 0}
501
+ >
502
+ <ChevronLeft className="h-5 w-5" />
503
+ </Button>
504
+
505
+ <Button
506
+ variant="ghost"
507
+ size="icon"
508
+ className="text-white"
509
+ onClick={() => setIsPlaying(!isPlaying)}
510
+ >
511
+ {isPlaying ? <Pause className="h-5 w-5" /> : <Play className="h-5 w-5" />}
512
+ </Button>
513
+
514
+ <Button
515
+ variant="ghost"
516
+ size="icon"
517
+ className="text-white"
518
+ onClick={goToNextScene}
519
+ disabled={selectedSceneIndex === scenes.length - 1}
520
+ >
521
+ <ChevronRight className="h-5 w-5" />
522
+ </Button>
523
+ </div>
524
+
525
+ <div className="text-sm flex items-center space-x-2 text-gray-300">
526
+ <span>{formatTime(currentTime)}</span>
527
+ <span>/</span>
528
+ <span>{formatTime(duration)}</span>
529
+ </div>
530
+
531
+ <div className="flex-1">
532
+ <Slider
533
+ value={[currentTime]}
534
+ max={duration}
535
+ step={0.1}
536
+ onValueChange={(val) => setCurrentTime(val[0])}
537
+ className="mt-0.5"
538
+ />
539
+ </div>
540
+
541
+ <div className="flex items-center space-x-2">
542
+ <Button
543
+ variant="ghost"
544
+ size="sm"
545
+ className="text-gray-300"
546
+ onClick={() => setZoom(Math.max(0.5, zoom - 0.25))}
547
+ >
548
+ <ZoomOut className="h-4 w-4" />
549
+ </Button>
550
+
551
+ <span className="text-xs">{Math.round(zoom * 100)}%</span>
552
+
553
+ <Button
554
+ variant="ghost"
555
+ size="sm"
556
+ className="text-gray-300"
557
+ onClick={() => setZoom(Math.min(2, zoom + 0.25))}
558
+ >
559
+ <ZoomIn className="h-4 w-4" />
560
+ </Button>
561
+ </div>
562
+ </div>
563
+
564
+ {/* Timeline */}
565
+ <div
566
+ ref={timelineRef}
567
+ className="h-24 border-t border-gray-700 bg-gray-800 overflow-x-auto relative"
568
+ onClick={handleTimelineClick}
569
+ >
570
+ <div
571
+ className="absolute top-0 left-0 h-full flex"
572
+ style={{ width: `${100 * zoom}%` }}
573
+ >
574
+ {/* Time markers */}
575
+ <div className="absolute top-0 left-0 right-0 h-5 flex">
576
+ {Array.from({ length: Math.ceil(duration) + 1 }).map((_, i) => (
577
+ <div
578
+ key={i}
579
+ className="border-l border-gray-600 h-full flex items-center px-1 text-xs text-gray-500"
580
+ style={{ position: 'absolute', left: `${(i / duration) * 100}%` }}
581
+ >
582
+ {formatTime(i)}
583
+ </div>
584
+ ))}
585
+ </div>
586
+
587
+ {/* Scene blocks */}
588
+ <div className="absolute top-5 left-0 right-0 bottom-0">
589
+ {scenes.map((scene, index) => {
590
+ const startPercent = (getSceneStartTime(index) / duration) * 100;
591
+ const widthPercent = (scene.duration / duration) * 100;
592
+
593
+ return (
594
+ <div
595
+ key={scene.id}
596
+ className={`
597
+ absolute h-16 cursor-pointer
598
+ ${selectedSceneIndex === index ? 'ring-2 ring-blue-500' : ''}
599
+ hover:ring-1 hover:ring-blue-400
600
+ `}
601
+ style={{
602
+ left: `${startPercent}%`,
603
+ width: `${widthPercent}%`,
604
+ background: scene.background,
605
+ borderRight: '1px solid rgba(0,0,0,0.3)',
606
+ borderRadius: '2px'
607
+ }}
608
+ onClick={(e) => {
609
+ e.stopPropagation();
610
+ setSelectedSceneIndex(index);
611
+ }}
612
+ >
613
+ <div className="p-1 text-xs text-white truncate overflow-hidden">
614
+ {scene.name}
615
+ </div>
616
+ <div className="absolute bottom-1 right-1 text-xs bg-black/50 px-1 rounded">
617
+ {scene.duration}s
618
+ </div>
619
+ </div>
620
+ );
621
+ })}
622
+ </div>
623
+
624
+ {/* Playhead */}
625
+ <div
626
+ className="absolute top-0 bottom-0 w-px bg-red-500 z-10"
627
+ style={{ left: `${(currentTime / duration) * 100}%` }}
628
+ >
629
+ <div className="w-3 h-3 bg-red-500 -ml-1.5 -mt-0.5 rounded-full" />
630
+ </div>
631
+ </div>
632
+ </div>
633
+ </div>
634
+ </div>
635
+
636
+ {/* Scene Editor Dialog */}
637
+ <Dialog open={showSceneEditor} onOpenChange={setShowSceneEditor}>
638
+ <DialogContent className="bg-gray-800 text-white border-gray-700">
639
+ <DialogHeader>
640
+ <DialogTitle>Edit Scene</DialogTitle>
641
+ </DialogHeader>
642
+
643
+ {currentScene && (
644
+ <div className="space-y-4">
645
+ <div className="space-y-2">
646
+ <Label className="text-white">Scene Name</Label>
647
+ <Input
648
+ value={currentScene.name}
649
+ onChange={(e) => updateScene({ name: e.target.value })}
650
+ className="bg-gray-700 border-gray-600 text-white"
651
+ />
652
+ </div>
653
+
654
+ <div className="space-y-2">
655
+ <Label className="text-white">Duration (seconds)</Label>
656
+ <div className="flex space-x-2">
657
+ <Input
658
+ type="number"
659
+ value={currentScene.duration}
660
+ onChange={(e) => updateScene({ duration: parseFloat(e.target.value) })}
661
+ className="bg-gray-700 border-gray-600 text-white"
662
+ min="1"
663
+ step="0.5"
664
+ />
665
+ <Button
666
+ variant="outline"
667
+ size="icon"
668
+ className="border-gray-600 text-white"
669
+ onClick={() => updateScene({ duration: currentScene.duration - 0.5 })}
670
+ disabled={currentScene.duration <= 1}
671
+ >
672
+ <ChevronLeft className="h-4 w-4" />
673
+ </Button>
674
+ <Button
675
+ variant="outline"
676
+ size="icon"
677
+ className="border-gray-600 text-white"
678
+ onClick={() => updateScene({ duration: currentScene.duration + 0.5 })}
679
+ >
680
+ <ChevronRight className="h-4 w-4" />
681
+ </Button>
682
+ </div>
683
+ </div>
684
+
685
+ <div className="space-y-2">
686
+ <Label className="text-white">Background</Label>
687
+ <Select
688
+ value="custom"
689
+ onValueChange={(val) => {
690
+ if (val === "blue") {
691
+ updateScene({ background: "linear-gradient(120deg, #1e3c72, #2a5298)" });
692
+ } else if (val === "purple") {
693
+ updateScene({ background: "linear-gradient(120deg, #8E54E9, #4776E6)" });
694
+ } else if (val === "green") {
695
+ updateScene({ background: "linear-gradient(120deg, #11998e, #38ef7d)" });
696
+ } else if (val === "red") {
697
+ updateScene({ background: "linear-gradient(120deg, #c31432, #240b36)" });
698
+ }
699
+ }}
700
+ >
701
+ <SelectTrigger className="bg-gray-700 border-gray-600 text-white">
702
+ <SelectValue placeholder="Choose background" />
703
+ </SelectTrigger>
704
+ <SelectContent className="bg-gray-800 border-gray-700 text-white">
705
+ <SelectItem value="blue">Blue Gradient</SelectItem>
706
+ <SelectItem value="purple">Purple Gradient</SelectItem>
707
+ <SelectItem value="green">Green Gradient</SelectItem>
708
+ <SelectItem value="red">Red Gradient</SelectItem>
709
+ <SelectItem value="custom">Custom</SelectItem>
710
+ </SelectContent>
711
+ </Select>
712
+ </div>
713
+
714
+ {currentScene.type === 'title' && (
715
+ <>
716
+ <div className="space-y-2">
717
+ <Label className="text-white">Title</Label>
718
+ <Input
719
+ value={currentScene.content.title}
720
+ onChange={(e) => updateScene({
721
+ content: { ...currentScene.content, title: e.target.value }
722
+ })}
723
+ className="bg-gray-700 border-gray-600 text-white"
724
+ />
725
+ </div>
726
+
727
+ <div className="space-y-2">
728
+ <Label className="text-white">Subtitle</Label>
729
+ <Input
730
+ value={currentScene.content.subtitle}
731
+ onChange={(e) => updateScene({
732
+ content: { ...currentScene.content, subtitle: e.target.value }
733
+ })}
734
+ className="bg-gray-700 border-gray-600 text-white"
735
+ />
736
+ </div>
737
+ </>
738
+ )}
739
+
740
+ {currentScene.type === 'word' && (
741
+ <>
742
+ <div className="space-y-2">
743
+ <Label className="text-white">Word</Label>
744
+ <Input
745
+ value={currentScene.content.word}
746
+ onChange={(e) => updateScene({
747
+ content: { ...currentScene.content, word: e.target.value }
748
+ })}
749
+ className="bg-gray-700 border-gray-600 text-white"
750
+ />
751
+ </div>
752
+
753
+ <div className="space-y-2">
754
+ <Label className="text-white">Translation</Label>
755
+ <Input
756
+ value={currentScene.content.translation}
757
+ onChange={(e) => updateScene({
758
+ content: { ...currentScene.content, translation: e.target.value }
759
+ })}
760
+ className="bg-gray-700 border-gray-600 text-white"
761
+ />
762
+ </div>
763
+
764
+ <div className="space-y-2">
765
+ <Label className="text-white">Example</Label>
766
+ <Input
767
+ value={currentScene.content.example}
768
+ onChange={(e) => updateScene({
769
+ content: { ...currentScene.content, example: e.target.value }
770
+ })}
771
+ className="bg-gray-700 border-gray-600 text-white"
772
+ />
773
+ </div>
774
+ </>
775
+ )}
776
+
777
+ {currentScene.type === 'split' && (
778
+ <>
779
+ <div className="space-y-2">
780
+ <Label className="text-white">Left Side Text</Label>
781
+ <Textarea
782
+ value={currentScene.content.leftText}
783
+ onChange={(e) => updateScene({
784
+ content: { ...currentScene.content, leftText: e.target.value }
785
+ })}
786
+ className="bg-gray-700 border-gray-600 text-white"
787
+ />
788
+ </div>
789
+
790
+ <div className="space-y-2">
791
+ <Label className="text-white">Right Side Text</Label>
792
+ <Textarea
793
+ value={currentScene.content.rightText}
794
+ onChange={(e) => updateScene({
795
+ content: { ...currentScene.content, rightText: e.target.value }
796
+ })}
797
+ className="bg-gray-700 border-gray-600 text-white"
798
+ />
799
+ </div>
800
+ </>
801
+ )}
802
+ </div>
803
+ )}
804
+
805
+ <DialogFooter>
806
+ <Button
807
+ variant="outline"
808
+ onClick={() => setShowSceneEditor(false)}
809
+ className="border-gray-600 text-white hover:bg-gray-700"
810
+ >
811
+ Cancel
812
+ </Button>
813
+ <Button
814
+ onClick={() => setShowSceneEditor(false)}
815
+ className="bg-blue-600 text-white hover:bg-blue-700"
816
+ >
817
+ Save Changes
818
+ </Button>
819
+ </DialogFooter>
820
+ </DialogContent>
821
+ </Dialog>
822
+
823
+ {/* Export Dialog */}
824
+ <Dialog open={showExportDialog} onOpenChange={setShowExportDialog}>
825
+ <DialogContent className="bg-gray-800 text-white border-gray-700">
826
+ <DialogHeader>
827
+ <DialogTitle>Export Video</DialogTitle>
828
+ </DialogHeader>
829
+
830
+ <div className="space-y-4">
831
+ <div className="space-y-2">
832
+ <Label className="text-white">Format</Label>
833
+ <Select defaultValue="mp4">
834
+ <SelectTrigger className="bg-gray-700 border-gray-600 text-white">
835
+ <SelectValue placeholder="Select format" />
836
+ </SelectTrigger>
837
+ <SelectContent className="bg-gray-800 border-gray-700 text-white">
838
+ <SelectItem value="mp4">MP4 (H.264)</SelectItem>
839
+ <SelectItem value="webm">WebM</SelectItem>
840
+ <SelectItem value="gif">GIF</SelectItem>
841
+ </SelectContent>
842
+ </Select>
843
+ </div>
844
+
845
+ <div className="space-y-2">
846
+ <Label className="text-white">Resolution</Label>
847
+ <Select defaultValue="1080p">
848
+ <SelectTrigger className="bg-gray-700 border-gray-600 text-white">
849
+ <SelectValue placeholder="Select resolution" />
850
+ </SelectTrigger>
851
+ <SelectContent className="bg-gray-800 border-gray-700 text-white">
852
+ <SelectItem value="720p">720p</SelectItem>
853
+ <SelectItem value="1080p">1080p (Full HD)</SelectItem>
854
+ <SelectItem value="4k">4K (UHD)</SelectItem>
855
+ </SelectContent>
856
+ </Select>
857
+ </div>
858
+
859
+ <div className="space-y-2">
860
+ <div className="flex justify-between">
861
+ <Label className="text-white">Include Audio</Label>
862
+ <Switch defaultChecked />
863
+ </div>
864
+ </div>
865
+
866
+ {isExporting && (
867
+ <div className="space-y-2">
868
+ <Label className="text-white">Export Progress</Label>
869
+ <Progress value={exportProgress} className="h-2" />
870
+ <div className="text-sm text-center text-gray-400">
871
+ {exportProgress}% complete
872
+ </div>
873
+ </div>
874
+ )}
875
+ </div>
876
+
877
+ <DialogFooter>
878
+ <Button
879
+ variant="outline"
880
+ onClick={() => setShowExportDialog(false)}
881
+ className="border-gray-600 text-white hover:bg-gray-700"
882
+ disabled={isExporting}
883
+ >
884
+ Cancel
885
+ </Button>
886
+ <Button
887
+ onClick={exportVideo}
888
+ className="bg-blue-600 text-white hover:bg-blue-700"
889
+ disabled={isExporting}
890
+ >
891
+ {isExporting ? "Exporting..." : "Export Video"}
892
+ </Button>
893
+ </DialogFooter>
894
+ </DialogContent>
895
+ </Dialog>
896
+
897
+ {/* Notification */}
898
+ {notification && (
899
+ <div className={`
900
+ fixed bottom-4 right-4 px-4 py-2 rounded-md shadow-lg
901
+ transition-opacity duration-300 flex items-center
902
+ ${notification.type === 'error' ? 'bg-red-800' : 'bg-gray-800'} text-white
903
+ `}>
904
+ {notification.type === 'error' ? (
905
+ <AlertTriangle className="h-4 w-4 mr-2 text-red-400" />
906
+ ) : (
907
+ <CheckCircle className="h-4 w-4 mr-2 text-green-400" />
908
+ )}
909
+ {notification.message}
910
+ </div>
911
+ )}
912
+ </div>
913
+ );
914
+ };
915
+
916
+ export default VideoEditor;
robots.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ User-agent: *
2
+ Allow: /
3
+ Disallow: /.git/
run_local.sh ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Build the Docker image
4
+ docker build -t wordlist-video .
5
+
6
+ # Run the Docker container
7
+ docker run -p 7860:7860 wordlist-video
8
+
9
+ echo "Server running at http://localhost:7860"
video-editor-html/SplashScreen 3.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":140,"w":1080,"h":1920,"nm":"SplashScreen 3","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 8","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,1920,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 19","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,1920,0],"ix":2},"a":{"a":0,"k":[6,-30,0],"ix":1},"s":{"a":0,"k":[102,198,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.3,0.3],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":99,"s":[0,2020]},{"t":149,"s":[1204,2020]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.87450986376,0.003921568627,0.345098039216,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6,-30],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 3","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.3,0.3],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":89,"s":[0,2020]},{"t":139,"s":[1204,2020]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.87450986376,0.003921568627,0.345098039216,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6,-30],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":60,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.3,0.3],"y":[1,1]},"o":{"x":[0.7,0.7],"y":[0,0]},"t":79,"s":[0,2020]},{"t":129,"s":[1204,2020]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.87450986376,0.003921568627,0.345098039216,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6,-30],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":60,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":5,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 18","tt":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[539,1898,0],"ix":2},"a":{"a":0,"k":[-1,566,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.33,0.33,0.33],"y":[1,1,1]},"o":{"x":[0.67,0.67,0.67],"y":[0,0,0]},"t":0,"s":[100,0,100]},{"i":{"x":[0.23,0.826,0.33],"y":[1,1,1]},"o":{"x":[0.468,0.385,0.67],"y":[0,0,0]},"t":20,"s":[100,100,100]},{"t":34,"s":[100,203,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.675,0.675],"y":[1,0.337]},"o":{"x":[0.342,0.342],"y":[0,0]},"t":32,"s":[1295,136]},{"i":{"x":[0.3,0.3],"y":[1,1]},"o":{"x":[0.676,0.676],"y":[0,-26.239]},"t":33,"s":[3358,138.365]},{"i":{"x":[0.675,0.675],"y":[1,0.337]},"o":{"x":[0.342,0.342],"y":[0,0]},"t":52,"s":[1295,136]},{"i":{"x":[0.288,0.288],"y":[1,1]},"o":{"x":[0.676,0.676],"y":[0,0.053]},"t":53,"s":[3358,138.365]},{"t":102,"s":[3358,3141]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":124,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.87450986376,0.003921568627,0.345098039216,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-1,566],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":52,"s":[0]},{"t":102,"s":[90]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 3","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.675,0.675],"y":[1,0.337]},"o":{"x":[0.342,0.342],"y":[0,0]},"t":32,"s":[1295,136]},{"i":{"x":[0.3,0.3],"y":[1,1]},"o":{"x":[0.676,0.676],"y":[0,-12.429]},"t":33,"s":[3358,138.365]},{"i":{"x":[0.675,0.675],"y":[1,0.337]},"o":{"x":[0.342,0.342],"y":[0,0]},"t":42,"s":[1295,136]},{"i":{"x":[0.288,0.288],"y":[1,1]},"o":{"x":[0.676,0.676],"y":[0,0.053]},"t":43,"s":[3358,138.365]},{"t":92,"s":[3358,3141]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":124,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.87450986376,0.003921568627,0.345098039216,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-1,566],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":42,"s":[0]},{"t":92,"s":[90]}],"ix":6},"o":{"a":0,"k":60,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.675,0.675],"y":[1,0.337]},"o":{"x":[0.342,0.342],"y":[0,0]},"t":32,"s":[1295,136]},{"i":{"x":[0.288,0.288],"y":[1,1]},"o":{"x":[0.676,0.676],"y":[0,0.053]},"t":33,"s":[3358,138.365]},{"t":82,"s":[3358,3141]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":124,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.87450986376,0.003921568627,0.345098039216,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-1,566],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":32,"s":[0]},{"t":82,"s":[90]}],"ix":6},"o":{"a":0,"k":60,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":0,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":5,"bm":0}]},{"id":"comp_1","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[960,1920,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[60,60,100],"ix":6}},"ao":0,"ip":0,"op":100,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"L","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-49.635,-49.351,0],"ix":2},"a":{"a":0,"k":[910.365,910.649,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.77,"y":0},"t":0,"s":[{"i":[[0,-3.542],[0,0],[-8.692,0],[0,0],[0,8.692],[0,0],[6.897,1.833],[0,0],[0,7.137],[0,0],[9.664,-0.166],[0,0],[5.389,-6.82]],"o":[[0,0],[0,8.692],[0,0],[8.692,0],[0,0],[0,-7.132],[0,0],[-6.893,-1.833],[0,0],[0,-12.033],[-9.664,0.166],[-13.72,0.379],[-2.197,2.778]],"v":[[-188.595,-201.413],[-188.292,413.666],[-172.556,429.403],[172.553,429.403],[188.292,413.666],[188.292,296.01],[176.596,280.8],[-18.011,276.984],[-29.703,261.773],[-30.57,-201.432],[-45.481,-219.923],[-163.174,-219.511],[-185.204,-211.172]],"c":true}]},{"t":96,"s":[{"i":[[0,-3.542],[0,0],[-8.692,0],[0,0],[0,8.692],[0,0],[6.897,1.833],[0,0],[0,7.137],[0,0],[9.439,7.461],[0,0],[5.389,-6.82]],"o":[[0,0],[0,8.692],[0,0],[8.692,0],[0,0],[0,-7.132],[0,0],[-6.893,-1.833],[0,0],[0,-12.033],[0,0],[-6.82,-5.39],[-2.197,2.778]],"v":[[-188.292,-413.663],[-188.292,413.666],[-172.556,429.403],[172.553,429.403],[188.292,413.666],[188.292,296.01],[176.596,280.8],[-18.255,228.984],[-29.948,213.773],[-29.948,-301.932],[-44.893,-332.798],[-162.794,-426.011],[-184.901,-423.422]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.25,"y":1},"o":{"x":0.75,"y":0},"t":34,"s":[1314.145,2287.632],"to":[0,0],"ti":[0,0]},{"t":64,"s":[1314.145,954.632]}],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":26,"op":100,"st":26,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"ribbon","parent":1,"tt":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":330,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.036],"y":[1]},"o":{"x":[0.056],"y":[0.253]},"t":26,"s":[-1129]},{"t":52,"s":[0]}],"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-7.081],[0,0],[-17.321,0],[-5.275,3.695],[0,0],[-13.439,-8.941],[0,0],[-9.595,14.419],[0,6.183],[0,0],[28.066,22.31],[0,0],[10.778,-13.563]],"o":[[0,0],[0,17.323],[6.44,0],[0,0],[13.223,-9.263],[0,0],[14.418,9.593],[3.418,-5.148],[0,0],[0,-35.858],[0,0],[-13.558,-10.78],[-4.41,5.542]],"v":[[-309.14,-625.943],[-309.14,625.94],[-277.779,657.305],[-259.78,651.627],[3.686,528.117],[47.895,527.582],[260.405,607.87],[303.89,599.129],[309.14,578.98],[309.14,-142.69],[264.715,-234.755],[-258.262,-650.493],[-302.329,-645.451]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":26,"op":100,"st":26,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"matte ribbon izq","parent":1,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-705,26,0],"ix":2},"a":{"a":0,"k":[-653,-4,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[88,-750],[-1218,166],[-1360.667,3831.334],[89.333,3831.334]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.573681999655,0.70613600787,0.890196018593,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":100,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"ribbbon izq","parent":1,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-180,"ix":10},"p":{"s":true,"x":{"a":0,"k":-336,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.925],"y":[0.929]},"o":{"x":[0.75],"y":[0]},"t":0,"s":[4981]},{"i":{"x":[0.281],"y":[1]},"o":{"x":[0.75],"y":[0]},"t":26,"s":[-41.962]},{"t":48,"s":[-2143.962]}],"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-7.081],[0,0],[-17.321,0],[0,0],[-13.439,-8.941],[0,0],[-9.595,14.419],[0,6.183],[0,0],[28.066,22.31],[0,0],[10.778,-13.563]],"o":[[0,0],[0,17.323],[6.44,0],[20.907,-5.54],[0,0],[14.418,9.593],[3.418,-5.148],[0,0],[0,-35.858],[0,0],[-13.558,-10.78],[-4.41,5.542]],"v":[[-309.14,-625.943],[-309.039,1312.44],[-277.678,1343.805],[4.593,1253.117],[48.755,1263.582],[310.531,1559.18],[354.016,1550.439],[359.266,1530.29],[359.14,-143.38],[314.715,-235.444],[-258.262,-650.493],[-302.329,-645.451]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.86405223608,0.86405223608,0.86405223608,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":100,"st":12,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"w","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[930.365,1890.649,0],"ix":2},"a":{"a":0,"k":[910.365,910.649,0],"ix":1},"s":{"a":0,"k":[60,60,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[204,266],[204,1590],[954,1590],[954,266]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.37,"y":1},"o":{"x":0.63,"y":0},"t":31,"s":[{"i":[[0,0],[0,0],[-2.689,-3.414],[0,-1.767],[0,0],[-4.347,0],[-1.418,2.388],[0,0],[-7.474,-4.439],[-0.799,-0.739],[0,0],[-2.94,3.199],[0,1.973],[0,0],[-6.752,-0.637],[0,0],[0,0],[0,0],[4.331,6.828],[0,0],[7.34,-4.657],[1.274,-2.241],[0,0],[8.493,0],[0,0],[0,8.692],[0,0],[-7.451,-0.881]],"o":[[0,0],[6.139,0.827],[1.094,1.388],[0,0],[0,4.347],[2.775,0],[0,0],[4.439,-7.474],[0.936,0.555],[0,0],[3.202,2.939],[1.334,-1.452],[0,0],[0,-7.187],[0,0],[0,0],[0,0],[-8.088,0],[0,0],[-4.654,-7.337],[-2.176,1.38],[0,0],[-4.194,7.388],[0,0],[-8.692,0],[0,0],[0,-7.231],[0,0]],"v":[[-298.136,-175.268],[-177.475,-176.976],[-166.353,-171.664],[-164.726,-164.042],[-164.432,207.196],[-156.563,215.068],[-149.797,211.214],[-25.974,2.624],[-4.405,-2.873],[-1.791,-0.931],[144.769,216.785],[155.888,216.314],[157.963,210.991],[158.041,-161.799],[166.916,-175.512],[307.13,-175.68],[307.043,476.68],[146.6,476.68],[126.661,465.714],[4.388,272.909],[-17.332,268.05],[-22.589,273.567],[-131.176,464.73],[-151.702,476.68],[-291.317,476.68],[-307.043,460.941],[-307.04,-161.725],[-298.136,-175.268]],"c":false}]},{"t":51,"s":[{"i":[[0,0],[0,0],[-2.689,-3.414],[0,-1.767],[0,0],[-4.347,0],[-1.418,2.388],[0,0],[-7.474,-4.439],[-0.799,-0.739],[0,0],[-2.941,3.199],[0,1.973],[0,0],[-5.62,4.478],[0,0],[0,0],[0,0],[4.331,6.828],[0,0],[7.34,-4.657],[1.274,-2.241],[0,0],[8.493,0],[0,0],[0,8.692],[0,0],[-5.681,4.476]],"o":[[0,0],[3.414,-2.689],[1.094,1.388],[0,0],[0,4.347],[2.775,0],[0,0],[4.439,-7.474],[0.936,0.555],[0,0],[3.202,2.939],[1.334,-1.452],[0,0],[0,-7.187],[0,0],[0,0],[0,0],[-8.088,0],[0,0],[-4.653,-7.337],[-2.176,1.38],[0,0],[-4.194,7.388],[0,0],[-8.692,0],[0,0],[0,-7.231],[0,0]],"v":[[-298.034,-9.768],[-177.17,-104.976],[-166.121,-103.664],[-164.432,-98.792],[-164.432,207.196],[-156.563,215.068],[-149.797,211.214],[-25.974,2.624],[-4.405,-2.873],[-1.791,-0.931],[144.825,133.785],[155.944,133.314],[158.019,127.991],[158.019,-346.549],[166.915,-365.012],[307.043,-476.68],[307.043,476.68],[146.6,476.68],[126.661,465.714],[4.388,272.909],[-17.332,268.05],[-22.589,273.567],[-131.176,464.73],[-151.702,476.68],[-291.317,476.68],[-307.043,460.941],[-307.043,8.775],[-298.034,-9.768]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[625.336,910.649],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":23,"op":100,"st":40,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 32","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-364.583,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[104.167,104.167,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-3.5,13],[21.5,-16],[0,0],[0,0],[0,0],[0,0],[-20.5,5],[-14.5,9.5],[18.5,-2.5],[-15.5,3],[0,0],[-49.5,4.5],[0,0],[12.5,-7.5],[-8,4.5],[0,0],[-11,-0.5],[1,12],[0,0],[-9,-2],[0,0],[0,0],[0,-1.5],[-7.5,6.5],[4,-30.5],[-31,16.5],[0,0],[-36,38],[0,0],[-10,4],[0,0],[-14.5,6],[-15,4.5],[0,0],[29.5,-15],[-40,24.5],[31.5,-12.5],[0,0]],"o":[[0,0],[3.5,-13],[-21.5,16],[0,0],[0,0],[0,0],[0,0],[20.5,-5],[14.5,-9.5],[-18.5,2.5],[15.5,-3],[0,0],[49.5,-4.5],[0,0],[-12.5,7.5],[8,-4.5],[0,0],[11,0.5],[-1,-12],[0,0],[9,2],[0,0],[0,0],[0,1.5],[7.5,-6.5],[-4,30.5],[6.5,-12.5],[0,0],[36,-38],[0,0],[10,-4],[0,0],[-22,7.5],[15,-4.5],[0,0],[-29.5,15],[40,-24.5],[-31.5,12.5],[0,0]],"v":[[25,344],[48.5,293],[23.5,304],[-21.5,365],[-55.5,371.5],[-1.501,371.009],[26,377],[48.5,361.5],[76.5,353],[81,316.5],[65,383.5],[89.5,365.5],[122.5,304.5],[148.5,324],[113,344.5],[107,377.5],[140.5,341],[135,373],[168,342],[189,338],[180.5,373],[221,333],[202,376.5],[247.5,336],[237,376.5],[279.5,335],[287.5,366],[313,334.5],[314,355],[343,338],[327,373.5],[347,361],[397,328],[362.5,373],[400,335.5],[356.5,414.5],[421.5,355],[344.5,278],[283.5,324]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":23,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.28],"y":[1]},"o":{"x":[0.72],"y":[0]},"t":84,"s":[0]},{"t":144,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":84,"op":282,"st":84,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"learning","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.95],"y":[1]},"o":{"x":[0.72],"y":[0]},"t":209,"s":[540]},{"t":239,"s":[1162]}],"ix":3},"y":{"a":0,"k":1310,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[96,96,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[11.043,-19.731],[0,0],[3.245,-2.46],[1.518,0],[0,2.251],[-7.379,11.671],[0,1.151],[0.837,0],[5.914,-6.594],[8.688,-12.037],[3.664,-5.757],[-4.449,-1.099],[-8.479,-2.408],[-4.03,-0.419],[1.884,-1.989],[0.994,-0.419],[3.088,0.628],[6.699,1.727],[5.862,1.151],[4.815,0],[1.989,-0.628],[3.507,-1.465],[1.413,0],[0,1.675],[-0.68,1.204],[-1.622,2.512],[-1.832,1.413],[-3.454,0.785],[-5.652,0.052],[-7.379,10.101],[-6.019,6.961],[-4.553,3.559],[-2.669,0],[-1.047,-1.047],[0,-1.884]],"o":[[0,0],[-4.292,5.391],[-3.193,2.46],[-1.256,0],[0,-5.6],[6.228,-10.101],[0,-0.994],[-1.465,0],[-5.914,6.594],[-9.839,13.398],[6.961,0.68],[4.501,1.047],[8.531,2.407],[-4.92,5.6],[-1.832,2.041],[-1.57,0.68],[-3.088,-0.576],[-6.699,-1.727],[-5.809,-1.151],[-2.722,0],[-1.936,0.628],[-3.454,1.413],[-2.041,0],[0,-0.942],[1.727,-2.774],[1.622,-2.512],[1.832,-1.413],[3.454,-0.785],[8.531,-13.084],[7.379,-10.153],[6.071,-6.961],[4.553,-3.611],[1.884,0],[1.047,1.047],[0,6.28]],"v":[[38.546,-27.124],[40.98,-24.69],[29.675,-12.914],[22.609,-9.224],[20.725,-12.6],[31.795,-38.507],[41.137,-55.385],[39.881,-56.877],[28.811,-46.985],[6.908,-19.038],[-13.346,9.695],[3.768,12.365],[23.238,17.546],[42.079,21.785],[31.873,33.168],[27.634,36.858],[20.647,36.937],[5.966,33.482],[-12.875,29.165],[-28.811,27.438],[-35.877,28.38],[-44.041,31.52],[-51.342,33.64],[-54.404,31.127],[-53.384,27.909],[-48.359,19.98],[-43.178,14.092],[-35.249,10.794],[-21.589,9.538],[2.277,-25.239],[22.374,-50.911],[38.311,-66.69],[49.144,-72.107],[53.541,-70.537],[55.111,-66.141]],"c":true},"ix":2},"nm":"Path 11","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[7.379,-2.983],[0,0],[-1.518,-1.727],[-2.46,0],[-2.721,1.204],[-2.721,1.989],[-3.925,3.35],[0,0],[5.181,-3.821],[5.338,0],[2.721,3.611],[0,6.28],[-2.564,4.449],[-5.809,4.972],[-3.925,1.727],[-3.088,0],[-1.413,-1.151],[0,-2.25],[2.46,-3.297],[5.077,-3.245]],"o":[[0,0],[0,3.088],[1.518,1.727],[2.564,0],[2.774,-1.204],[2.721,-1.989],[0,0],[-5.914,7.118],[-5.181,3.768],[-4.658,0],[-2.721,-3.611],[0,-5.495],[2.564,-4.501],[3.978,-3.402],[3.925,-1.727],[2.46,0],[1.465,1.099],[0,3.297],[-2.46,3.245],[-5.024,3.193]],"v":[[63.589,10.48],[63.589,12.443],[65.866,19.666],[71.832,22.256],[79.761,20.451],[88.004,15.662],[97.974,7.654],[97.974,12.443],[81.331,28.851],[65.552,34.503],[54.483,29.086],[50.4,14.249],[54.247,-0.667],[66.808,-14.877],[78.662,-22.57],[89.182,-25.161],[94.991,-23.434],[97.189,-18.409],[93.5,-8.518],[82.195,1.217]],"c":true},"ix":2},"nm":"Path 12","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0.523,-3.088],[-3.35,2.722],[-1.57,2.46],[0,1.151],[1.099,0],[2.146,-1.204],[2.198,-1.936],[1.727,-2.617]],"o":[[4.71,-1.832],[3.35,-2.721],[1.622,-2.512],[0,-1.204],[-0.942,0],[-2.093,1.204],[-2.146,1.936],[-1.727,2.617]],"v":[[64.374,5.378],[76.464,-1.452],[83.844,-9.224],[86.277,-14.72],[84.629,-16.525],[79.997,-14.72],[73.559,-10.009],[67.75,-3.179]],"c":true},"ix":2},"nm":"Path 13","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[-2.983,2.826],[-7.484,3.507],[-4.082,0],[-0.942,-0.942],[-0.523,-1.204],[0,-0.576],[0.733,-0.471],[1.832,-0.628],[-0.576,-1.308],[-0.314,-0.837],[0,-0.628],[2.564,-3.454],[1.047,-1.622],[0,-2.355],[-3.559,0],[-7.798,6.856],[0,0],[5.705,0],[0.89,1.361],[0,2.617],[-2.983,7.851],[3.716,-3.611],[2.407,0],[1.727,1.989],[1.361,3.14],[0,3.402],[-0.471,1.622],[-1.361,1.727]],"o":[[7.955,-7.222],[7.484,-3.507],[1.099,0],[0.942,0.89],[0.576,1.204],[0,0.942],[-0.733,0.419],[0.366,0.837],[0.576,1.308],[0.314,0.785],[0,1.256],[-2.512,3.402],[-0.994,1.622],[0,3.873],[3.664,0],[0,0],[-13.241,14.707],[-2.041,0],[-0.837,-1.308],[0,-4.135],[-7.641,8.479],[-3.716,3.559],[-1.151,0],[-1.727,-1.989],[-1.308,-3.193],[0,-2.46],[0.471,-1.675],[1.361,-1.727]],"v":[[107.16,-6.398],[130.319,-22.492],[147.668,-27.752],[150.73,-26.338],[152.928,-23.198],[153.792,-20.529],[152.693,-18.409],[148.846,-16.839],[150.259,-13.621],[151.593,-10.402],[152.064,-8.282],[148.218,-1.217],[142.879,6.32],[141.388,12.286],[146.726,18.095],[163.919,7.811],[163.919,12.6],[135.5,34.66],[131.104,32.619],[129.848,26.731],[134.322,8.753],[117.287,26.888],[108.102,32.226],[103.784,29.243],[99.152,21.55],[97.189,11.658],[97.896,5.535],[100.644,0.432]],"c":true},"ix":2},"nm":"Path 14","mn":"ADBE Vector Shape - Group","hd":false},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[-6.385,8.95],[5.809,-4.344],[0,-4.606],[-0.576,-1.099],[-0.994,0],[-1.465,0.733],[-4.553,5.181]],"o":[[-13.608,6.228],[-5.757,4.344],[0,1.675],[0.576,1.047],[0.576,0],[5.495,-3.245],[4.553,-5.234]],"v":[[144.764,-15.034],[115.638,0.824],[107.003,14.249],[107.866,18.41],[110.221,19.98],[113.283,18.88],[128.356,6.241]],"c":true},"ix":2},"nm":"Path 15","mn":"ADBE Vector Shape - Group","hd":false},{"ind":5,"ty":"sh","ix":6,"ks":{"a":0,"k":{"i":[[6.176,-5.286],[0,0],[-1.465,2.355],[0.785,0.837],[0,1.675],[-1.57,2.879],[-2.303,2.512],[-1.884,0.733],[-0.68,0],[0,-0.994],[1.622,-2.721],[-2.826,0],[-0.419,-0.209],[-0.576,-1.727],[0,-1.727],[0.994,-1.308],[2.355,-2.46],[1.884,-3.507],[0,-1.779],[-3.036,0],[-7.903,6.49],[0,0],[2.826,-2.617],[2.407,-1.57],[1.884,0],[1.465,1.989],[0,3.821],[-1.151,2.879],[-2.198,3.35],[-2.774,3.35],[1.832,-0.366],[2.041,-0.366],[1.099,-0.209]],"o":[[0,0],[2.826,-3.35],[-1.675,-0.576],[-0.733,-0.837],[0,-1.884],[1.57,-2.879],[2.303,-2.512],[0.733,-0.366],[1.047,0],[0,0.89],[10.781,-1.675],[1.099,0],[0.89,0.471],[0.576,1.727],[0,1.099],[-0.942,1.256],[-2.774,2.983],[-1.884,3.507],[0,2.46],[3.193,0],[0,0],[-4.344,4.553],[-2.826,2.617],[-2.407,1.518],[-2.198,0],[-1.465,-2.041],[0,-2.251],[1.151,-2.879],[2.25,-3.402],[-1.465,0.314],[-1.832,0.314],[-2.041,0.366],[-3.245,6.49]],"v":[[162.506,13.699],[162.506,8.596],[168.943,0.039],[165.253,-2.08],[164.154,-5.849],[166.509,-12.993],[172.319,-21.079],[178.599,-25.946],[180.719,-26.495],[182.289,-25.004],[179.855,-19.587],[200.267,-22.099],[202.543,-21.785],[204.741,-18.488],[205.605,-13.307],[204.113,-9.695],[199.168,-4.122],[192.181,5.613],[189.354,13.542],[193.908,17.232],[210.551,7.497],[210.551,12.757],[199.796,23.512],[191.945,29.793],[185.508,32.069],[180.012,29.086],[177.814,20.294],[179.541,12.6],[184.566,3.258],[192.102,-6.869],[187.156,-5.849],[181.347,-4.828],[176.637,-3.965]],"c":true},"ix":2},"nm":"Path 16","mn":"ADBE Vector Shape - Group","hd":false},{"ind":6,"ty":"sh","ix":7,"ks":{"a":0,"k":{"i":[[2.25,-2.355],[0,-4.03],[-1.832,0],[-2.408,1.57],[-5.181,4.082],[0,0],[3.978,-3.245],[2.512,0],[0,5.652],[-0.523,1.622],[-0.89,1.727],[-1.884,3.245],[-2.46,4.135],[4.501,-4.501],[2.041,-2.564],[2.355,-3.14],[1.099,-1.099],[1.099,0],[1.413,3.507],[0,4.658],[-0.628,1.622],[-1.936,2.983],[-2.617,2.565],[-2.407,0],[-0.994,-0.576],[0,-1.204],[0.314,-0.576],[0.419,-0.471],[1.256,-1.361],[0,-2.721],[-0.994,0],[-1.779,1.989],[-2.826,3.245],[-3.873,3.559],[-4.972,3.297],[-1.832,0],[-0.785,-2.303],[0,-2.355]],"o":[[-9.211,10.258],[0,2.251],[1.518,0],[2.46,-1.622],[0,0],[-7.432,7.327],[-3.978,3.245],[-4.292,0],[0,-1.675],[0.523,-1.622],[0.942,-1.779],[1.884,-3.297],[-2.983,1.518],[-2.931,2.826],[-2.041,2.512],[-2.303,3.088],[-1.047,1.047],[-1.675,0],[-1.413,-3.507],[0,-2.879],[0.68,-1.622],[3.611,-5.548],[2.617,-2.617],[1.675,0],[1.047,0.576],[0,0.523],[-0.314,0.576],[-0.366,0.471],[-8.269,9.159],[0,1.518],[0.628,0],[1.832,-2.041],[2.879,-3.297],[3.925,-3.559],[2.774,-1.936],[0.837,0],[0.837,2.303],[0,1.151]],"v":[[263.149,-6.555],[249.332,14.877],[252.08,18.252],[257.968,15.897],[269.43,7.34],[269.43,12.757],[252.316,28.615],[242.581,33.482],[236.144,25.004],[236.929,20.058],[239.048,15.034],[243.287,7.497],[249.803,-3.65],[238.577,5.378],[231.119,13.464],[224.525,21.942],[219.422,28.223],[216.203,29.793],[211.571,24.533],[209.452,12.286],[210.394,5.535],[214.319,-1.374],[223.661,-13.542],[231.198,-17.467],[235.201,-16.604],[236.772,-13.935],[236.301,-12.286],[235.201,-10.716],[232.768,-7.968],[220.364,9.852],[221.856,12.129],[225.467,9.146],[232.454,1.217],[242.581,-9.067],[255.927,-19.352],[262.835,-22.256],[265.269,-18.802],[266.525,-11.815]],"c":true},"ix":2},"nm":"Path 17","mn":"ADBE Vector Shape - Group","hd":false},{"ind":7,"ty":"sh","ix":8,"ks":{"a":0,"k":{"i":[[2.46,0],[1.204,0.994],[0,1.518],[-2.198,2.826],[-2.355,0],[-1.047,-0.994],[0,-1.832],[2.146,-2.721]],"o":[[-1.308,0],[-1.151,-0.994],[0,-2.303],[2.198,-2.826],[1.57,0],[1.047,0.942],[0,2.251],[-2.146,2.722]],"v":[[301.146,-27.438],[297.378,-28.929],[295.65,-32.697],[298.948,-40.391],[305.778,-44.63],[309.703,-43.139],[311.273,-38.978],[308.054,-31.52]],"c":true},"ix":2},"nm":"Path 18","mn":"ADBE Vector Shape - Group","hd":false},{"ind":8,"ty":"sh","ix":9,"ks":{"a":0,"k":{"i":[[-4.71,3.978],[0,0],[4.396,-3.35],[3.088,0],[2.093,4.501],[0,5.338],[-0.419,1.832],[-0.576,1.204],[-1.57,2.565],[-6.176,0],[0,-2.46],[2.093,-3.559],[1.622,-1.518],[1.413,-1.727],[0,-2.198],[-1.413,-1.256],[-2.251,0],[-2.931,1.779]],"o":[[0,0],[-5.077,6.542],[-4.396,3.297],[-4.292,0],[-2.094,-4.501],[0,-2.041],[0.419,-1.884],[0.576,-1.204],[7.432,-11.566],[3.192,0],[0,1.675],[-2.041,3.559],[-4.553,4.396],[-1.361,1.727],[0,1.884],[1.413,1.256],[2.46,0],[2.983,-1.832]],"v":[[306.484,7.654],[306.484,13.385],[292.275,28.223],[281.048,33.168],[271.471,26.417],[268.331,11.658],[268.959,5.849],[270.45,1.217],[273.669,-4.436],[294.08,-21.785],[298.869,-18.095],[295.729,-10.245],[290.234,-2.63],[281.284,6.555],[279.243,12.443],[281.362,17.153],[286.858,19.038],[294.944,16.368]],"c":true},"ix":2},"nm":"Path 19","mn":"ADBE Vector Shape - Group","hd":false},{"ind":9,"ty":"sh","ix":10,"ks":{"a":0,"k":{"i":[[2.25,-2.355],[0,-4.03],[-1.832,0],[-2.408,1.57],[-5.181,4.082],[0,0],[3.978,-3.245],[2.512,0],[0,5.652],[-0.523,1.622],[-0.89,1.727],[-1.884,3.245],[-2.46,4.135],[4.501,-4.501],[2.041,-2.564],[2.355,-3.14],[1.099,-1.099],[1.099,0],[1.413,3.507],[0,4.658],[-0.628,1.622],[-1.936,2.983],[-2.617,2.565],[-2.408,0],[-0.994,-0.576],[0,-1.204],[0.314,-0.576],[0.419,-0.471],[1.256,-1.361],[0,-2.721],[-0.994,0],[-1.779,1.989],[-2.826,3.245],[-3.873,3.559],[-4.972,3.297],[-1.832,0],[-0.785,-2.303],[0,-2.355]],"o":[[-9.211,10.258],[0,2.251],[1.518,0],[2.46,-1.622],[0,0],[-7.432,7.327],[-3.978,3.245],[-4.292,0],[0,-1.675],[0.523,-1.622],[0.942,-1.779],[1.884,-3.297],[-2.983,1.518],[-2.931,2.826],[-2.041,2.512],[-2.303,3.088],[-1.047,1.047],[-1.675,0],[-1.413,-3.507],[0,-2.879],[0.68,-1.622],[3.611,-5.548],[2.617,-2.617],[1.675,0],[1.047,0.576],[0,0.523],[-0.314,0.576],[-0.366,0.471],[-8.269,9.159],[0,1.518],[0.628,0],[1.832,-2.041],[2.878,-3.297],[3.925,-3.559],[2.774,-1.936],[0.837,0],[0.837,2.303],[0,1.151]],"v":[[358.769,-6.555],[344.952,14.877],[347.699,18.252],[353.587,15.897],[365.049,7.34],[365.049,12.757],[347.935,28.615],[338.2,33.482],[331.763,25.004],[332.548,20.058],[334.667,15.034],[338.907,7.497],[345.423,-3.65],[334.196,5.378],[326.738,13.464],[320.144,21.942],[315.041,28.223],[311.823,29.793],[307.191,24.533],[305.071,12.286],[306.013,5.535],[309.938,-1.374],[319.28,-13.542],[326.817,-17.467],[330.821,-16.604],[332.391,-13.935],[331.92,-12.286],[330.821,-10.716],[328.387,-7.968],[315.983,9.852],[317.475,12.129],[321.086,9.146],[328.073,1.217],[338.2,-9.067],[351.546,-19.352],[358.455,-22.256],[360.888,-18.802],[362.144,-11.815]],"c":true},"ix":2},"nm":"Path 20","mn":"ADBE Vector Shape - Group","hd":false},{"ind":10,"ty":"sh","ix":11,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[6.019,-6.228],[2.931,0],[0,1.936],[-3.821,5.862],[-1.936,2.198],[-1.727,1.622],[-2.879,2.408],[-1.047,0.89],[-2.303,5.652],[3.664,-3.35],[3.088,-1.832],[2.46,0],[2.25,4.239],[0,4.501],[-0.942,2.303],[-2.041,2.617],[-3.193,2.722],[-4.292,2.617],[-4.501,0],[0,-2.46],[7.275,-3.245],[4.396,-3.193],[2.721,-3.611],[0,-2.879],[-0.68,-0.837],[-1.099,0],[-2.355,1.675],[-4.606,4.239],[-3.454,4.292],[-2.251,2.251],[-1.832,0],[-0.628,-0.68],[0,-1.256],[1.936,-4.606],[4.972,-10.415],[0,0]],"o":[[0,0],[-7.327,12.665],[-6.019,6.28],[-1.57,0],[0,-3.768],[1.518,-2.303],[1.989,-2.198],[1.779,-1.57],[2.879,-2.355],[5.234,-10.677],[-4.92,5.234],[-3.664,3.35],[-3.036,1.779],[-2.669,0],[-2.251,-4.292],[0,-2.093],[0.994,-2.303],[2.093,-2.617],[3.192,-2.774],[8.112,-5.077],[3.35,0],[0,3.402],[-4.449,1.936],[-4.344,3.193],[-2.669,3.559],[0,1.413],[0.68,0.837],[1.832,0],[2.355,-1.675],[2.094,-1.936],[3.507,-4.344],[2.303,-2.303],[1.047,0],[0.68,0.628],[0,2.408],[-1.884,4.553],[0,0],[0,0]],"v":[[426.99,12.914],[400.926,34.346],[380.907,62.686],[367.483,72.107],[365.128,69.202],[370.858,54.757],[376.04,48.006],[381.614,42.275],[388.601,36.309],[394.488,31.441],[405.793,6.948],[392.918,19.823],[382.791,27.595],[374.548,30.264],[367.169,23.905],[363.793,10.716],[365.206,4.122],[369.759,-3.258],[377.688,-11.265],[388.915,-19.352],[407.834,-26.967],[412.859,-23.277],[401.946,-13.307],[388.679,-5.613],[378.081,4.593],[374.077,14.249],[375.098,17.624],[377.767,18.88],[384.047,16.368],[394.488,7.497],[402.81,-1.845],[411.446,-11.737],[417.647,-15.191],[420.16,-14.17],[421.18,-11.344],[418.276,-0.824],[407.991,21.628],[426.99,7.811]],"c":true},"ix":2},"nm":"Path 21","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":12,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":84,"op":282,"st":62,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 31","parent":4,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-364.583,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[104.167,104.167,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[12.726,0.749],[-11.5,10.5],[-13,2],[9.5,-11.5],[-7.5,-8],[-5.5,7],[-8.5,19],[4,-13],[6.5,-8.5],[-3.5,-9.5],[-8,7],[0,0],[14.5,4.5],[0.5,-9.5],[-18,5],[0,0],[-23.5,0],[-3.5,6.5],[-60.5,42.5],[0,0],[0,0],[0,0],[9,-7.5],[-7.5,0],[-6,9.5],[-8.5,7.5],[-9.5,11],[5,8],[-14,-20.5],[-11.5,10]],"o":[[-25.5,-1.5],[11.5,-10.5],[3.5,9],[-9.5,11.5],[7.5,8],[5.5,-7],[8.5,-19],[-4,13],[-6.5,8.5],[3.5,9.5],[8,-7],[0,0],[-14.5,-4.5],[-0.5,9.5],[18,-5],[0,0],[23.5,0],[3.5,-6.5],[6,-11.5],[0,0],[0,0],[0,0],[-9,7.5],[7.5,0],[-9.5,19.5],[8.5,-7.5],[9.5,-11],[-5,-8],[14,20.5],[11.5,-10]],"v":[[-381.5,329.5],[-398,316.5],[-350,292],[-355.5,317.5],[-376.5,376],[-363,376],[-318,304],[-281,284],[-296,318.5],[-324.5,363],[-314.5,372.5],[-262,319.5],[-271,370],[-294.5,348.5],[-265,381],[-211,323.5],[-232.5,371],[-183.5,319.5],[-192.5,360],[-165,293.5],[-119,300.5],[-125.5,327.5],[-170,346],[-174,376],[-137.5,343.5],[-145.5,378.5],[-101.5,340.5],[-80,297],[-114,368.5],[-80.5,357]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":23,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.28],"y":[1]},"o":{"x":[0.72],"y":[0]},"t":53,"s":[0]},{"t":113,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":53,"op":282,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"visual","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.95],"y":[1]},"o":{"x":[0.72],"y":[0]},"t":209,"s":[540]},{"t":239,"s":[-16]}],"ix":3},"y":{"a":0,"k":1310,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[96,96,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-3.14],[1.204,-1.936],[2.355,-3.716],[3.35,-5.757],[3.559,-6.961],[1.832,-4.606],[0,-3.297],[-2.774,-1.989],[-3.245,3.611],[-3.14,4.449],[-4.135,6.804],[-3.245,5.705],[0.209,0.471],[0,0.314],[-0.994,2.146],[-1.465,2.041],[-2.983,3.716],[-1.151,0],[0,-2.407],[1.989,-5.181],[3.821,-6.961],[5.705,-8.636],[7.17,-9.368],[3.14,0],[2.25,1.989],[1.622,3.402],[0,3.873],[-0.942,3.611],[-2.041,5.024],[-2.617,4.92],[-2.041,3.402],[-2.617,4.135],[0,0.366],[1.047,0],[5.077,-3.088],[8.112,-6.28],[0.785,-0.733],[0,-0.471],[-0.994,0],[-2.407,0.314],[-1.413,0],[0,-0.837],[0.994,-0.419],[1.727,-0.419],[2.931,-0.733],[3.507,-1.57],[0.523,0],[0.209,0.733],[0,1.675],[-2.46,4.03],[-6.333,4.396],[-6.594,3.297],[-4.763,1.518],[-2.25,0],[-2.146,-2.826]],"o":[[0,0.471],[-1.204,1.884],[-2.355,3.664],[-3.35,5.705],[-4.082,8.008],[-1.779,4.553],[0,2.617],[3.35,-2.617],[3.245,-3.611],[3.193,-4.449],[4.187,-6.804],[-0.157,-0.471],[-0.157,-0.471],[0,-2.46],[0.994,-2.146],[1.518,-2.041],[0.785,-0.994],[1.884,0],[0,2.408],[-1.936,5.129],[-3.821,6.961],[-5.652,8.583],[-7.275,9.473],[-1.779,0],[-2.25,-1.936],[-1.622,-3.402],[0,-2.303],[0.994,-3.611],[2.093,-5.077],[2.721,-5.181],[2.093,-3.402],[2.617,-4.187],[0,-0.785],[-3.036,0],[-5.024,3.088],[-3.088,2.408],[-0.837,0.68],[0,0.628],[0.419,0],[2.408,-0.314],[2.355,0],[0,0.733],[-0.942,0.419],[-1.675,0.419],[-3.14,0.837],[-3.559,1.57],[-0.994,0],[-0.262,-0.733],[0,-7.432],[2.46,-4.082],[7.222,-5.024],[6.594,-3.35],[4.815,-1.57],[2.198,0],[2.198,2.826]],"v":[[-351.939,-56.327],[-353.744,-52.716],[-359.083,-44.316],[-367.64,-30.185],[-378.002,-11.187],[-386.873,7.733],[-389.543,19.509],[-385.382,26.417],[-375.49,17.075],[-365.913,4.985],[-354.922,-11.894],[-343.774,-30.656],[-344.324,-32.069],[-344.559,-33.247],[-343.068,-40.155],[-339.378,-46.436],[-332.626,-55.071],[-329.722,-56.563],[-326.896,-52.952],[-329.879,-41.568],[-338.514,-23.434],[-352.802,-0.039],[-372.036,26.888],[-387.658,41.097],[-393.703,38.114],[-399.513,30.107],[-401.946,19.195],[-400.533,10.323],[-395.98,-2.63],[-388.915,-17.624],[-381.771,-30.499],[-374.705,-41.804],[-370.78,-48.634],[-372.35,-49.812],[-384.518,-45.18],[-404.223,-31.127],[-410.032,-26.417],[-411.289,-24.69],[-409.797,-23.748],[-405.558,-24.219],[-399.827,-24.69],[-396.294,-23.434],[-397.786,-21.707],[-401.789,-20.451],[-408.698,-18.723],[-418.668,-15.112],[-424.791,-12.757],[-426.597,-13.856],[-426.99,-17.467],[-423.3,-34.66],[-410.111,-47.378],[-389.386,-59.86],[-372.35,-67.161],[-361.752,-69.516],[-355.236,-65.277]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[2.46,0],[1.204,0.994],[0,1.518],[-2.198,2.826],[-2.355,0],[-1.047,-0.994],[0,-1.832],[2.146,-2.721]],"o":[[-1.308,0],[-1.151,-0.994],[0,-2.303],[2.198,-2.826],[1.57,0],[1.047,0.942],[0,2.251],[-2.146,2.722]],"v":[[-308.839,-27.438],[-312.608,-28.929],[-314.335,-32.697],[-311.037,-40.391],[-304.207,-44.63],[-300.282,-43.139],[-298.712,-38.978],[-301.931,-31.52]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-4.71,3.978],[0,0],[4.396,-3.35],[3.088,0],[2.093,4.501],[0,5.338],[-0.419,1.832],[-0.576,1.204],[-1.57,2.565],[-6.176,0],[0,-2.46],[2.093,-3.559],[1.622,-1.518],[1.413,-1.727],[0,-2.198],[-1.413,-1.256],[-2.25,0],[-2.931,1.779]],"o":[[0,0],[-5.077,6.542],[-4.396,3.297],[-4.292,0],[-2.093,-4.501],[0,-2.041],[0.419,-1.884],[0.576,-1.204],[7.432,-11.566],[3.193,0],[0,1.675],[-2.041,3.559],[-4.553,4.396],[-1.361,1.727],[0,1.884],[1.413,1.256],[2.46,0],[2.983,-1.832]],"v":[[-303.501,7.654],[-303.501,13.385],[-317.71,28.223],[-328.937,33.168],[-338.514,26.417],[-341.654,11.658],[-341.026,5.849],[-339.535,1.217],[-336.316,-4.436],[-315.905,-21.785],[-311.116,-18.095],[-314.256,-10.245],[-319.752,-2.63],[-328.701,6.555],[-330.742,12.443],[-328.623,17.153],[-323.127,19.038],[-315.041,16.368]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[0,3.925],[0,0],[-5.077,5.705],[-4.396,0],[-1.256,-0.628],[0,0],[0,-1.151],[-0.733,-2.721],[-0.419,-1.832],[0,-2.303],[1.57,-3.193],[2.983,-2.931],[4.71,-3.036],[-1.099,0],[-2.46,1.047],[-2.565,1.779],[-3.454,2.879],[0,0],[5.286,-3.402],[3.821,0],[4.187,4.187],[0.628,2.303]],"o":[[0,0],[13.503,-14.236],[1.151,-6.437],[1.099,0],[0,0],[-0.209,0.628],[0,0.994],[0.785,2.669],[0.419,1.832],[0,3.454],[-1.57,3.14],[-2.983,2.879],[1.884,0.314],[2.146,0],[2.512,-1.099],[2.617,-1.832],[0,0],[-6.699,6.542],[-5.286,3.35],[-4.92,0],[-2.46,-2.512],[-0.576,-2.303]],"v":[[-304.443,9.852],[-304.443,7.968],[-276.574,-21.942],[-268.252,-31.598],[-264.719,-30.656],[-264.248,-29.165],[-264.562,-26.495],[-263.463,-20.922],[-261.658,-14.17],[-261.03,-7.968],[-263.385,2.002],[-270.215,11.108],[-281.755,19.98],[-277.28,20.451],[-270.372,18.88],[-262.757,14.563],[-253.65,7.497],[-253.65,12.757],[-271.628,27.673],[-285.288,32.697],[-298.948,26.417],[-303.579,19.195]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[0.052,0.419],[0,0],[10.624,-10.415],[-1.151,-1.151],[-2.093,-3.297],[-1.884,2.512],[0,4.396],[0.052,0.419]],"o":[[0,0],[-3.245,3.664],[3.14,0.68],[1.151,1.151],[3.559,-1.465],[1.936,-2.564],[0,-0.523],[0,-0.419]],"v":[[-275.161,-2.159],[-276.574,-13.621],[-297.378,7.497],[-290.94,10.245],[-286.073,16.918],[-277.908,10.951],[-275.004,0.51],[-275.082,-0.903]],"c":true},"ix":2},"nm":"Path 5","mn":"ADBE Vector Shape - Group","hd":false},{"ind":5,"ty":"sh","ix":6,"ks":{"a":0,"k":{"i":[[-2.931,6.385],[3.978,-4.239],[2.512,0],[1.675,1.622],[0.942,2.774],[0,3.193],[-0.366,1.675],[-1.099,1.989],[-1.779,2.355],[-2.617,2.983],[-1.047,1.256],[-0.994,0.419],[-2.303,0],[-1.518,-0.471],[0,-0.471],[1.57,-2.46],[3.454,-3.559],[4.501,-3.402],[0,-1.622],[-1.727,0],[-1.465,1.047],[-3.454,3.036],[-2.774,2.355],[-2.669,3.507],[-1.675,1.779],[-1.413,1.204],[-1.308,0],[-0.419,-0.785],[0,-2.407],[0.209,-0.68],[0.68,-1.099],[1.989,-2.879],[2.722,-3.925],[1.099,-2.512],[0,-1.256],[-2.146,0],[-2.355,1.465],[-4.867,3.821],[0,0],[2.931,-2.565],[2.146,-1.308],[1.936,0],[0,5.234],[-1.308,3.35]],"o":[[-7.589,10.049],[-3.925,4.239],[-1.989,0],[-1.675,-1.675],[-0.89,-2.826],[0,-2.093],[0.419,-1.675],[1.099,-2.041],[1.832,-2.355],[1.57,-1.832],[1.099,-1.256],[1.047,-0.471],[1.675,0],[1.518,0.419],[0,0.942],[-1.518,2.46],[-3.402,3.507],[-0.994,3.088],[0,2.251],[0.628,0],[1.465,-1.099],[3.507,-3.088],[2.041,-1.779],[2.721,-3.559],[3.873,-4.082],[1.413,-1.256],[1.256,0],[0.471,0.785],[0,1.099],[-0.209,0.68],[-0.628,1.047],[-1.989,2.879],[-1.465,2.094],[-1.099,2.46],[0,2.722],[1.779,0],[2.355,-1.465],[0,0],[-6.123,5.652],[-2.931,2.564],[-2.146,1.308],[-4.396,0],[0,-1.989],[1.308,-3.402]],"v":[[-216.91,2.316],[-234.259,23.748],[-243.916,30.107],[-249.411,27.673],[-253.336,21],[-254.671,11.972],[-254.121,6.32],[-251.845,0.824],[-247.527,-5.77],[-240.854,-13.778],[-236.929,-18.409],[-233.788,-20.922],[-228.764,-21.628],[-223.975,-20.922],[-221.699,-19.587],[-224.054,-14.484],[-231.512,-5.456],[-243.366,4.907],[-244.858,11.972],[-242.267,15.348],[-239.127,13.778],[-231.747,7.576],[-222.327,-0.589],[-215.261,-8.518],[-208.667,-16.525],[-200.738,-24.454],[-196.655,-26.338],[-194.143,-25.161],[-193.437,-20.372],[-193.751,-17.703],[-195.085,-15.034],[-199.011,-9.146],[-206.076,1.06],[-209.923,7.968],[-211.571,13.542],[-208.353,17.624],[-202.151,15.426],[-191.317,7.497],[-191.317,12.757],[-204.898,25.082],[-212.513,30.892],[-218.637,32.854],[-225.231,25.004],[-223.269,16.996]],"c":true},"ix":2},"nm":"Path 6","mn":"ADBE Vector Shape - Group","hd":false},{"ind":6,"ty":"sh","ix":7,"ks":{"a":0,"k":{"i":[[-2.983,2.826],[-7.484,3.507],[-4.082,0],[-0.942,-0.942],[-0.523,-1.204],[0,-0.576],[0.733,-0.471],[1.832,-0.628],[-0.576,-1.308],[-0.314,-0.837],[0,-0.628],[2.564,-3.454],[1.047,-1.622],[0,-2.355],[-3.559,0],[-7.798,6.856],[0,0],[5.705,0],[0.89,1.361],[0,2.617],[-2.983,7.851],[3.716,-3.611],[2.408,0],[1.727,1.989],[1.361,3.14],[0,3.402],[-0.471,1.622],[-1.361,1.727]],"o":[[7.955,-7.222],[7.484,-3.507],[1.099,0],[0.942,0.89],[0.576,1.204],[0,0.942],[-0.733,0.419],[0.366,0.837],[0.576,1.308],[0.314,0.785],[0,1.256],[-2.512,3.402],[-0.994,1.622],[0,3.873],[3.664,0],[0,0],[-13.241,14.707],[-2.041,0],[-0.837,-1.308],[0,-4.135],[-7.641,8.479],[-3.716,3.559],[-1.151,0],[-1.727,-1.989],[-1.308,-3.193],[0,-2.46],[0.471,-1.675],[1.361,-1.727]],"v":[[-182.839,-6.398],[-159.679,-22.492],[-142.33,-27.752],[-139.268,-26.338],[-137.07,-23.198],[-136.206,-20.529],[-137.306,-18.409],[-141.152,-16.839],[-139.739,-13.621],[-138.405,-10.402],[-137.934,-8.282],[-141.78,-1.217],[-147.119,6.32],[-148.61,12.286],[-143.272,18.095],[-126.079,7.811],[-126.079,12.6],[-154.498,34.66],[-158.894,32.619],[-160.151,26.731],[-155.676,8.753],[-172.711,26.888],[-181.896,32.226],[-186.214,29.243],[-190.846,21.55],[-192.809,11.658],[-192.102,5.535],[-189.354,0.432]],"c":true},"ix":2},"nm":"Path 7","mn":"ADBE Vector Shape - Group","hd":false},{"ind":7,"ty":"sh","ix":8,"ks":{"a":0,"k":{"i":[[-6.385,8.95],[5.809,-4.344],[0,-4.606],[-0.576,-1.099],[-0.994,0],[-1.465,0.733],[-4.553,5.181]],"o":[[-13.608,6.228],[-5.757,4.344],[0,1.675],[0.576,1.047],[0.576,0],[5.495,-3.245],[4.553,-5.234]],"v":[[-145.235,-15.034],[-174.36,0.824],[-182.996,14.249],[-182.132,18.41],[-179.777,19.98],[-176.715,18.88],[-161.642,6.241]],"c":true},"ix":2},"nm":"Path 8","mn":"ADBE Vector Shape - Group","hd":false},{"ind":8,"ty":"sh","ix":9,"ks":{"a":0,"k":{"i":[[-4.867,4.501],[0,0],[6.856,0],[2.146,4.03],[0,6.594],[-2.512,7.327],[-4.187,7.536],[-4.501,5.6],[-3.873,2.251],[-0.89,0],[-0.366,-0.994],[0,-2.355],[5.914,-10.624],[10.572,-11.043],[0,-1.465],[-1.413,-2.094],[-2.565,0],[-1.518,0.994],[-1.675,1.518]],"o":[[0,0],[-10.938,14.497],[-3.716,0],[-2.094,-4.082],[0,-5.338],[2.512,-7.379],[4.239,-7.589],[4.553,-5.652],[1.57,-0.942],[1.099,0],[0.366,0.942],[0,7.275],[-5.914,10.572],[-0.523,2.722],[0,3.35],[1.465,2.093],[1.308,0],[1.518,-1.047],[1.675,-1.518]],"v":[[-89.182,6.791],[-89.182,12.6],[-115.874,34.346],[-124.666,28.301],[-127.806,12.286],[-124.038,-6.712],[-113.989,-29.086],[-100.879,-48.869],[-88.24,-60.724],[-84.55,-62.137],[-82.352,-60.645],[-81.802,-55.699],[-90.673,-28.851],[-115.403,3.572],[-116.188,9.852],[-114.068,18.017],[-108.023,21.157],[-103.784,19.666],[-98.995,15.819]],"c":true},"ix":2},"nm":"Path 9","mn":"ADBE Vector Shape - Group","hd":false},{"ind":9,"ty":"sh","ix":10,"ks":{"a":0,"k":{"i":[[1.675,-7.746],[-2.512,3.402],[-2.251,3.873],[-1.256,3.088],[0,1.57],[0.837,0],[0.89,-0.576],[3.978,-7.641]],"o":[[2.721,-2.669],[2.564,-3.402],[2.25,-3.873],[1.308,-3.088],[0,-1.204],[-0.314,0],[-3.193,2.617],[-3.978,7.589]],"v":[[-112.655,-7.34],[-104.804,-16.447],[-97.582,-27.359],[-92.322,-37.8],[-90.359,-44.787],[-91.616,-46.593],[-93.421,-45.729],[-104.176,-30.342]],"c":true},"ix":2},"nm":"Path 10","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":11,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":52,"op":282,"st":30,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"cortina centro lottie","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[540,1324,0],"ix":2},"a":{"a":0,"k":[540,1920,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":1080,"h":3840,"ip":140,"op":282,"st":140,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"WL","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":540,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.6],"y":[1]},"o":{"x":[0.4],"y":[0]},"t":14,"s":[960]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":43,"s":[769]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.96],"y":[0]},"t":368,"s":[769]},{"t":412,"s":[-901]}],"ix":4}},"a":{"a":0,"k":[960,1920,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.21,0.21,0.21],"y":[1,1,1]},"o":{"x":[0.79,0.79,0.79],"y":[0,0,0]},"t":14,"s":[57,57,100]},{"t":34,"s":[45,45,100]}],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[34.043,-25.532],[4.255,-38.298],[6.383,-2.127],[0,0],[0,0]],"o":[[0,0],[0,0],[-34.043,25.532],[-4.255,38.298],[-6.383,2.127],[0,0],[0,0]],"v":[[966.383,1505.107],[568.511,1777.447],[-231.489,2264.681],[-242.128,4237.021],[2213.191,4302.979],[2242.979,2534.894],[1377.022,1824.256]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"tm":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":118,"s":[1.65]}],"ix":2},"w":1920,"h":3840,"ip":0,"op":219,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Layer 1","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[959.023,2453.553,0],"ix":2},"a":{"a":0,"k":[539.56,969.099,0],"ix":1},"s":{"a":0,"k":[666.667,666.667,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,1.505],[2.484,0],[0,5.768],[-5.569,0],[-0.921,-1.762],[0,0],[-0.544,0],[0,0],[0,-0.544],[0,0],[0.544,0],[0,0]],"o":[[-0.921,1.762],[-5.568,0],[0,-5.768],[2.483,0],[0,0],[0,-0.544],[0,0],[0.544,0],[0,0],[0,0.544],[0,0],[-1.505,0]],"v":[[4.587,11.057],[-1.302,14.261],[-10.716,4.126],[-1.302,-6.01],[4.587,-2.765],[4.587,-13.276],[5.572,-14.261],[9.732,-14.261],[10.716,-13.276],[10.716,12.796],[9.732,13.781],[7.312,13.781]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,2.685],[2.765,0],[0,-2.644],[-2.764,0]],"o":[[0,-2.644],[-2.765,0],[0,2.685],[2.765,0.001]],"v":[[4.587,4.126],[-0.019,-0.802],[-4.626,4.126],[-0.019,9.053]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[545.545,969.698],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.586,0.078],[0.411,0],[0,-4.728],[0,0],[0.544,0],[0,0],[0,0.544],[0,0],[-0.544,0],[0,0],[0,-0.544],[0,0],[-2.685,0],[-0.303,-0.053],[0,-0.481]],"o":[[0,0.591],[-0.323,-0.043],[-3.366,0],[0,0],[0,0.544],[0,0],[-0.544,0],[0,0],[0,-0.544],[0,0],[0.544,0],[0,0],[0.881,-1.802],[0.392,0],[0.473,0.083],[0,0]],"v":[[6.93,-5.121],[5.826,-4.144],[4.766,-4.206],[-0.802,2.284],[-0.802,8.831],[-1.786,9.815],[-5.945,9.815],[-6.93,8.831],[-6.93,-8.509],[-5.945,-9.494],[-1.786,-9.494],[-0.802,-8.509],[-0.802,-6.37],[5.087,-9.815],[6.11,-9.74],[6.93,-8.768]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[527.75,973.664],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,5.528],[-6.65,0],[0,-5.529],[6.73,0]],"o":[[0,-5.528],[6.73,0],[0,5.528],[-6.65,0]],"v":[[-10.716,0],[-0.02,-10.176],[10.716,0],[-0.02,10.176]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,2.725],[2.805,0],[0,-2.685],[-2.765,0]],"o":[[0,-2.685],[-2.765,0],[0,2.725],[2.804,0.001]],"v":[[4.628,0],[-0.019,-4.928],[-4.586,0],[-0.019,4.927]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[508.274,973.824],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0.43,0],[0,0],[0.139,0.394],[0,0],[-0.68,0],[0,0],[-0.128,-0.412],[0,0],[0,0],[-0.432,0],[0,0],[-0.128,-0.412],[0,0],[0,0],[-0.432,0],[0,0],[0.226,-0.641],[0,0],[0.419,0],[0,0],[0.129,0.409]],"o":[[0,0],[-0.129,0.409],[0,0],[-0.417,0],[0,0],[-0.226,-0.641],[0,0],[0.431,0],[0,0],[0,0],[0.127,-0.412],[0,0],[0.431,0],[0,0],[0,0],[0.127,-0.412],[0,0],[0.68,0],[0,0],[-0.138,0.394],[0,0],[-0.43,0],[0,0]],"v":[[0.021,-3.606],[-5.33,13.333],[-6.27,14.022],[-10.179,14.022],[-11.108,13.364],[-20.309,-12.709],[-19.379,-14.022],[-14.646,-14.022],[-13.706,-13.329],[-8.192,4.367],[-2.718,-13.327],[-1.778,-14.022],[1.779,-14.022],[2.719,-13.329],[8.233,4.367],[13.707,-13.327],[14.647,-14.022],[19.379,-14.022],[20.309,-12.709],[11.109,13.364],[10.18,14.022],[6.311,14.022],[5.371,13.333]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[480.366,969.457],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[512,968],"ix":2},"a":{"a":0,"k":[512,968],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.24,0.24],"y":[1,1]},"o":{"x":[0.76,0.76],"y":[0,0]},"t":22,"s":[0,0]},{"i":{"x":[0.24,0.24],"y":[1,1]},"o":{"x":[0.76,0.76],"y":[0,0]},"t":37,"s":[119,119]},{"t":52,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"word","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-0.761,-1.756],[-0.761,-1.023],[-1.678,-1.023],[-1.678,1.756],[-2.476,1.756],[-2.476,-1.023],[-3.393,-1.023],[-3.393,-1.756]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.55,0.234],[2.907,-1.756],[3.393,-1.756],[3.393,1.756],[2.577,1.756],[2.577,0.078],[1.742,1.279],[1.339,1.279],[0.504,0.078],[0.504,1.756],[-0.313,1.756],[-0.313,-1.756],[0.183,-1.756]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[615.727,957.226],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.325,-0.046],[1.464,0],[0,6.45],[0,0],[0.182,0],[0,0],[0,0.544],[0,0],[-0.404,0],[0,1.277],[0,0],[-0.544,0],[0,0],[0,-0.544],[0,0],[-0.182,0],[0,0],[0,-0.544],[0,0],[0.544,0],[0,0],[0,-0.182],[0,0],[-1.522,0],[-0.472,0.034],[0,-0.381]],"o":[[0,0.328],[-0.883,0.125],[-2.885,0],[0,0],[0,-0.182],[0,0],[-0.544,0],[0,0],[0,-0.404],[1.277,0],[0,0],[0,-0.544],[0,0],[0.544,0],[0,0],[0,0.182],[0,0],[0.544,0],[0,0],[0,0.544],[0,0],[-0.182,0],[0,0],[0,2.845],[0.49,0],[0.378,-0.027],[0,0]],"v":[[6.99,11.798],[6.425,12.448],[2.905,12.679],[-3.946,5.869],[-3.946,-2.056],[-4.274,-2.384],[-6.006,-2.384],[-6.99,-3.369],[-6.99,-6.218],[-6.258,-6.951],[-3.946,-9.263],[-3.946,-11.694],[-2.961,-12.679],[1.158,-12.679],[2.142,-11.694],[2.142,-7.279],[2.471,-6.951],[5.563,-6.951],[6.548,-5.966],[6.548,-3.37],[5.563,-2.385],[2.471,-2.385],[2.142,-2.057],[2.142,4.505],[4.787,7.79],[6.291,7.74],[6.99,8.395]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[607.888,971.12],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-3.766],[4.807,0],[0.528,3.754],[-0.593,0],[0,0],[-0.043,-0.323],[-1.627,0],[0,1.281],[3.646,1.202],[0,3.727],[-5.208,0],[-0.51,-3.126],[0.598,0],[0,0],[0.045,0.318],[1.479,0],[0,-1.161],[-3.246,-1.082]],"o":[[0,4.567],[-5.014,0],[-0.083,-0.588],[0,0],[0.326,0],[0.22,1.628],[1.482,0],[0,-1.482],[-3.205,-1.001],[0,-4.006],[5.025,0],[0.096,0.59],[0,0],[-0.321,0],[-0.178,-1.262],[-1.242,0],[0,1.402],[3.406,1.001]],"v":[[8.78,3.966],[0.047,10.175],[-8.77,4.315],[-7.792,3.205],[-3.45,3.205],[-2.801,3.769],[0.247,6.05],[2.771,4.247],[-2.397,1.363],[-8.366,-4.286],[0.166,-10.175],[8.411,-4.766],[7.434,-3.645],[3.06,-3.645],[2.413,-4.195],[-0.074,-6.169],[-2.357,-4.446],[2.571,-1.722]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[591.514,973.824],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.545,0],[0,0],[0,-0.544],[0,0],[0.544,0],[0,0],[0,0.544],[0,0]],"o":[[0,0],[0.544,0],[0,0],[0,0.544],[0,0],[-0.544,0],[0,0],[-0.001,-0.544]],"v":[[-2.079,-9.654],[2.079,-9.654],[3.064,-8.669],[3.064,8.67],[2.079,9.654],[-2.079,9.654],[-3.064,8.67],[-3.064,-8.669]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[577.825,973.824],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.02,0],[0,-1.983],[2.057,0],[0,1.947]],"o":[[2.057,0],[0,1.947],[-2.02,0],[0,-1.984]],"v":[[-0.018,-3.563],[3.617,0],[-0.018,3.563],[-3.617,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[577.824,957.76],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-0.545],[0,0],[0.544,0],[0,0],[0,0.544],[0,0],[-0.544,0],[0,0],[0,-0.544],[0,0],[-0.362,0],[0,0]],"o":[[0,0],[0,0.544],[0,0],[-0.544,0],[0,0],[0,-0.544],[0,0],[0.544,0],[0,0],[0,0.363],[0,0],[0.544,0]],"v":[[7.23,9.398],[7.23,13.036],[6.246,14.021],[-6.246,14.021],[-7.23,13.036],[-7.23,-13.036],[-6.246,-14.021],[-1.726,-14.021],[-0.741,-13.036],[-0.741,7.756],[-0.085,8.412],[6.246,8.412]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[565.822,969.458],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[588,972],"ix":2},"a":{"a":0,"k":[588,972],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.24,0.24],"y":[1,1]},"o":{"x":[0.76,0.76],"y":[0,0]},"t":31,"s":[0,0]},{"i":{"x":[0.24,0.24],"y":[1,1]},"o":{"x":[0.76,0.76],"y":[0,0]},"t":46,"s":[119,119]},{"t":61,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"list","np":6,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":22,"op":219,"st":22,"bm":0}],"markers":[]}
video-editor-html/doctorsimage.webp ADDED
video-editor-html/image.png ADDED
video-editor-html/index.html ADDED
@@ -0,0 +1,687 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Animation Editor</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;700;900&display=swap" rel="stylesheet">
8
+ <style>
9
+ /* Reset and Basic Styles */
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ font-family: 'Montserrat', sans-serif;
15
+ }
16
+
17
+ body {
18
+ width: 100vw;
19
+ height: 100vh;
20
+ background: #121212; /* Darker background */
21
+ color: #eee;
22
+ overflow: hidden; /* Prevent scrollbars */
23
+ }
24
+
25
+ .editor-container {
26
+ display: grid;
27
+ grid-template-columns: 1fr 320px; /* Wider controls panel */
28
+ height: 100vh;
29
+ gap: 1rem; /* Consistent gap */
30
+ padding: 1rem;
31
+ }
32
+
33
+ .preview-container {
34
+ background: #222; /* Darker preview background */
35
+ border-radius: 8px;
36
+ overflow: hidden;
37
+ position: relative;
38
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); /* Subtle shadow */
39
+ }
40
+
41
+ .controls-container {
42
+ background: #222; /* Darker controls background */
43
+ border-radius: 8px;
44
+ padding: 1rem;
45
+ overflow-y: auto;
46
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); /* Subtle shadow */
47
+ }
48
+
49
+ .control-group {
50
+ margin-bottom: 1.5rem;
51
+ padding: 1rem;
52
+ background: #333; /* Slightly lighter control group background */
53
+ border-radius: 6px;
54
+ }
55
+
56
+ .control-group h3 {
57
+ margin-bottom: 0.75rem;
58
+ color: #fff;
59
+ font-size: 1rem;
60
+ font-weight: 600;
61
+ }
62
+
63
+ .control-item {
64
+ margin-bottom: 0.75rem;
65
+ }
66
+
67
+ .control-item label {
68
+ display: block;
69
+ margin-bottom: 0.25rem;
70
+ font-size: 0.8rem;
71
+ color: #aaa;
72
+ }
73
+
74
+ input[type="text"],
75
+ input[type="number"],
76
+ input[type="file"] {
77
+ width: 100%;
78
+ padding: 0.5rem;
79
+ background: #444;
80
+ border: 1px solid #555;
81
+ border-radius: 4px;
82
+ color: #fff;
83
+ font-size: 0.9rem;
84
+ transition: border-color 0.2s, background-color 0.2s; /* Smooth transitions */
85
+ }
86
+
87
+ input[type="text"]:focus,
88
+ input[type="number"]:focus {
89
+ border-color: #4CAF50; /* Highlight on focus */
90
+ background-color: #555;
91
+ outline: none;
92
+ }
93
+
94
+ /* Custom File Input Styling */
95
+ input[type="file"] {
96
+ cursor: pointer;
97
+ position: relative;
98
+ overflow: hidden;
99
+ }
100
+
101
+ input[type="file"]::file-selector-button {
102
+ background-color: #4CAF50;
103
+ color: white;
104
+ border: none;
105
+ padding: 0.5rem 1rem;
106
+ border-radius: 4px;
107
+ cursor: pointer;
108
+ transition: background-color 0.2s;
109
+ margin-right: 0.5rem;
110
+ font-size: 0.9rem;
111
+ }
112
+
113
+ input[type="file"]::file-selector-button:hover {
114
+ background-color: #45a049;
115
+ }
116
+
117
+ /* Button Styles */
118
+ button {
119
+ padding: 0.5rem 1rem;
120
+ background: #4CAF50;
121
+ border: none;
122
+ border-radius: 4px;
123
+ color: white;
124
+ cursor: pointer;
125
+ font-size: 0.9rem;
126
+ transition: background 0.3s;
127
+ }
128
+
129
+ button:hover {
130
+ background: #45a049;
131
+ }
132
+
133
+ .playback-controls {
134
+ display: flex;
135
+ gap: 0.5rem;
136
+ }
137
+
138
+ .timeline-container {
139
+ position: absolute;
140
+ bottom: 0;
141
+ left: 0;
142
+ right: 0;
143
+ background: rgba(0, 0, 0, 0.8);
144
+ padding: 0.5rem;
145
+ display: flex;
146
+ gap: 0.5rem;
147
+ align-items: center;
148
+ }
149
+
150
+ .timeline {
151
+ flex-grow: 1;
152
+ height: 10px; /* Thinner timeline */
153
+ background: #444;
154
+ border-radius: 5px;
155
+ position: relative;
156
+ cursor: pointer;
157
+ }
158
+
159
+ .timeline-progress {
160
+ position: absolute;
161
+ left: 0;
162
+ top: 0;
163
+ height: 100%;
164
+ background: #4CAF50;
165
+ border-radius: 5px;
166
+ pointer-events: none;
167
+ }
168
+
169
+ .speed-control {
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 0.5rem;
173
+ }
174
+
175
+ .speed-control input {
176
+ width: 4rem; /* Wider speed input */
177
+ }
178
+
179
+ iframe {
180
+ width: 100%;
181
+ height: 100%;
182
+ border: none;
183
+ }
184
+
185
+ .save-export {
186
+ margin-top: 1rem;
187
+ display: flex;
188
+ gap: 0.5rem;
189
+ }
190
+
191
+ .save-export button {
192
+ flex: 1;
193
+ }
194
+
195
+ .export-btn {
196
+ background: #2196F3;
197
+ }
198
+
199
+ .export-btn:hover {
200
+ background: #1976D2;
201
+ }
202
+
203
+ .save-btn {
204
+ background: #9C27B0;
205
+ }
206
+
207
+ .save-btn:hover {
208
+ background: #7B1FA2;
209
+ }
210
+
211
+ /* Drag and Drop Styling */
212
+ .drop-zone {
213
+ border: 2px dashed #aaa;
214
+ border-radius: 4px;
215
+ padding: 1rem;
216
+ text-align: center;
217
+ cursor: pointer;
218
+ margin-bottom: 0.5rem;
219
+ transition: border-color 0.2s, background-color 0.2s;
220
+ position: relative;
221
+ min-height: 100px;
222
+ display: flex;
223
+ align-items: center;
224
+ justify-content: center;
225
+ background-position: center;
226
+ background-size: cover;
227
+ background-repeat: no-repeat;
228
+ }
229
+
230
+ .drop-zone.has-image:before {
231
+ content: '';
232
+ position: absolute;
233
+ top: 0;
234
+ left: 0;
235
+ right: 0;
236
+ bottom: 0;
237
+ background-color: rgba(0, 0, 0, 0.4);
238
+ z-index: 1;
239
+ }
240
+
241
+ .drop-zone.has-image:after {
242
+ content: 'Click or drop to change';
243
+ position: absolute;
244
+ color: white;
245
+ font-size: 0.8rem;
246
+ z-index: 2;
247
+ }
248
+
249
+ .drop-zone.drag-over {
250
+ border-color: #4CAF50;
251
+ background-color: rgba(76, 175, 80, 0.1);
252
+ }
253
+
254
+ .drop-zone-text {
255
+ position: relative;
256
+ z-index: 2;
257
+ }
258
+ </style>
259
+ </head>
260
+ <body>
261
+ <div class="editor-container">
262
+ <div class="preview-container">
263
+ <iframe id="preview" src="video-animation copy BUENO BUENO BUENO copy.html"></iframe>
264
+ <div class="timeline-container">
265
+ <div class="playback-controls">
266
+ <button id="playPause">Play</button>
267
+ <button id="reset">Reset</button>
268
+ </div>
269
+ <div class="timeline">
270
+ <div class="timeline-progress"></div>
271
+ </div>
272
+ <div class="speed-control">
273
+ <label>Speed:</label>
274
+ <input type="number" id="speed" value="1" min="0.1" max="2" step="0.1">
275
+ </div>
276
+ </div>
277
+ </div>
278
+ <div class="controls-container">
279
+ <!-- Title Section -->
280
+ <div class="control-group">
281
+ <h3>Title Section</h3>
282
+ <div class="control-item">
283
+ <label>Lista Text</label>
284
+ <input type="text" id="listaText" value="LISTA">
285
+ </div>
286
+ <div class="control-item">
287
+ <label>Profesiones Text</label>
288
+ <input type="text" id="profesionesText" value="PROFESIONES">
289
+ </div>
290
+ </div>
291
+
292
+ <!-- Doctor Section -->
293
+ <div class="control-group">
294
+ <h3>Doctor Section</h3>
295
+ <div class="control-item">
296
+ <label>English Word</label>
297
+ <input type="text" id="doctorEnglish" value="doctor">
298
+ </div>
299
+ <div class="control-item">
300
+ <label>Spanish Word</label>
301
+ <input type="text" id="doctorSpanish" value="médico">
302
+ </div>
303
+ <div class="control-item">
304
+ <label>English Sentence</label>
305
+ <input type="text" id="doctorEnglishSentence" value="He wants to be a doctor.">
306
+ </div>
307
+ <div class="control-item">
308
+ <label>Spanish Sentence</label>
309
+ <input type="text" id="doctorSpanishSentence" value="Él quiere ser médico.">
310
+ </div>
311
+ <div class="control-item">
312
+ <label>Doctor Image</label>
313
+ <div class="drop-zone" id="doctorDropZone">
314
+ <span class="drop-zone-text">Drag and drop or click to upload</span>
315
+ <input type="file" id="doctorImage" accept="image/*" style="display: none;">
316
+ </div>
317
+ </div>
318
+ </div>
319
+
320
+ <!-- Teacher Section -->
321
+ <div class="control-group">
322
+ <h3>Teacher Section</h3>
323
+ <div class="control-item">
324
+ <label>English Word</label>
325
+ <input type="text" id="teacherEnglish" value="teacher">
326
+ </div>
327
+ <div class="control-item">
328
+ <label>Spanish Word</label>
329
+ <input type="text" id="teacherSpanish" value="profesor">
330
+ </div>
331
+ <div class="control-item">
332
+ <label>English Sentence</label>
333
+ <input type="text" id="teacherEnglishSentence" value="She is a teacher.">
334
+ </div>
335
+ <div class="control-item">
336
+ <label>Spanish Sentence</label>
337
+ <input type="text" id="teacherSpanishSentence" value="Ella es profesora.">
338
+ </div>
339
+ <div class="control-item">
340
+ <label>Teacher Image</label>
341
+ <div class="drop-zone" id="teacherDropZone">
342
+ <span class="drop-zone-text">Drag and drop or click to upload</span>
343
+ <input type="file" id="teacherImage" accept="image/*" style="display: none;">
344
+ </div>
345
+ </div>
346
+ </div>
347
+
348
+ <!-- Outro Section -->
349
+ <div class="control-group">
350
+ <h3>Outro Section</h3>
351
+ <div class="control-item">
352
+ <label>Thank You Text</label>
353
+ <input type="text" id="thankYouText" value="Gracias! Thank you!">
354
+ </div>
355
+ <div class="control-item">
356
+ <label>Learned Text</label>
357
+ <input type="text" id="learnedText" value="You've learned new profession vocabulary">
358
+ </div>
359
+ </div>
360
+
361
+ <!-- Save/Export -->
362
+ <div class="save-export">
363
+ <button id="saveBtn" class="save-btn">Save Changes</button>
364
+ <button id="exportBtn" class="export-btn">Export Animation</button>
365
+ </div>
366
+ </div>
367
+ </div>
368
+
369
+ <script>
370
+ document.addEventListener('DOMContentLoaded', function() {
371
+ const preview = document.getElementById('preview');
372
+ const playPauseBtn = document.getElementById('playPause');
373
+ const resetBtn = document.getElementById('reset');
374
+ const timeline = document.querySelector('.timeline');
375
+ const timelineProgress = document.querySelector('.timeline-progress');
376
+ const speedInput = document.getElementById('speed');
377
+ let isPlaying = false;
378
+ let animationStartTime = 0;
379
+ let currentTime = 0;
380
+ const totalDuration = 20000; // 20 seconds total duration
381
+
382
+ // --- Data Management ---
383
+
384
+ // Function to get the animation state (for saving/exporting)
385
+ function getAnimationState() {
386
+ return {
387
+ listaText: document.getElementById('listaText').value,
388
+ profesionesText: document.getElementById('profesionesText').value,
389
+ doctorEnglish: document.getElementById('doctorEnglish').value,
390
+ doctorSpanish: document.getElementById('doctorSpanish').value,
391
+ doctorEnglishSentence: document.getElementById('doctorEnglishSentence').value,
392
+ doctorSpanishSentence: document.getElementById('doctorSpanishSentence').value,
393
+ doctorImage: localStorage.getItem('.doctor-image'), // Get image data URL from localStorage
394
+ teacherEnglish: document.getElementById('teacherEnglish').value,
395
+ teacherSpanish: document.getElementById('teacherSpanish').value,
396
+ teacherEnglishSentence: document.getElementById('teacherEnglishSentence').value,
397
+ teacherSpanishSentence: document.getElementById('teacherSpanishSentence').value,
398
+ teacherImage: localStorage.getItem('.teacher-image'), // Get image data URL from localStorage
399
+ thankYouText: document.getElementById('thankYouText').value,
400
+ learnedText: document.getElementById('learnedText').value,
401
+ speed: speedInput.value,
402
+ };
403
+ }
404
+
405
+ // Function to save the current state to localStorage
406
+ function saveState() {
407
+ const state = getAnimationState();
408
+ localStorage.setItem('animationState', JSON.stringify(state));
409
+ console.log('State saved:', state);
410
+ }
411
+
412
+ // Function to load the state from localStorage
413
+ function loadState() {
414
+ const savedState = localStorage.getItem('animationState');
415
+ if (savedState) {
416
+ const state = JSON.parse(savedState);
417
+ document.getElementById('listaText').value = state.listaText;
418
+ document.getElementById('profesionesText').value = state.profesionesText;
419
+ document.getElementById('doctorEnglish').value = state.doctorEnglish;
420
+ document.getElementById('doctorSpanish').value = state.doctorSpanish;
421
+ document.getElementById('doctorEnglishSentence').value = state.doctorEnglishSentence;
422
+ document.getElementById('doctorSpanishSentence').value = state.doctorSpanishSentence;
423
+ document.getElementById('teacherEnglish').value = state.teacherEnglish;
424
+ document.getElementById('teacherSpanish').value = state.teacherSpanish;
425
+ document.getElementById('teacherEnglishSentence').value = state.teacherEnglishSentence;
426
+ document.getElementById('teacherSpanishSentence').value = state.teacherSpanishSentence;
427
+ document.getElementById('thankYouText').value = state.thankYouText;
428
+ document.getElementById('learnedText').value = state.learnedText;
429
+ speedInput.value = state.speed;
430
+
431
+ // Load images (if available)
432
+ if (state.doctorImage) {
433
+ preview.contentWindow.document.querySelector('.doctor-image').style.backgroundImage = `url(${state.doctorImage})`;
434
+ // Also update the drop zone
435
+ const doctorDropZone = document.getElementById('doctorDropZone');
436
+ doctorDropZone.style.backgroundImage = `url(${state.doctorImage})`;
437
+ doctorDropZone.classList.add('has-image');
438
+ }
439
+ if (state.teacherImage) {
440
+ preview.contentWindow.document.querySelector('.teacher-image').style.backgroundImage = `url(${state.teacherImage})`;
441
+ // Also update the drop zone
442
+ const teacherDropZone = document.getElementById('teacherDropZone');
443
+ teacherDropZone.style.backgroundImage = `url(${state.teacherImage})`;
444
+ teacherDropZone.classList.add('has-image');
445
+ }
446
+
447
+ console.log('State loaded:', state);
448
+ updateAnimation(); // Update the iframe with the loaded state
449
+ }
450
+ }
451
+
452
+ // --- Animation Control ---
453
+
454
+ // Function to update the animation state in the iframe
455
+ function updateAnimation() {
456
+ const previewWindow = preview.contentWindow;
457
+ if (!previewWindow) return;
458
+
459
+ try {
460
+ // Update text content (same as before)
461
+ previewWindow.document.querySelector('.lista-text').textContent = document.getElementById('listaText').value;
462
+ previewWindow.document.querySelector('.profesiones-text').textContent = document.getElementById('profesionesText').value;
463
+ previewWindow.document.querySelector('.english-doctor').textContent = document.getElementById('doctorEnglish').value;
464
+ previewWindow.document.querySelector('.spanish-doctor').textContent = document.getElementById('doctorSpanish').value;
465
+ const doctorEnglishWord = document.getElementById('doctorEnglish').value;
466
+ const doctorEnglishSentence = document.getElementById('doctorEnglishSentence').value;
467
+ previewWindow.document.querySelector('.english-sentence-doctor').innerHTML = doctorEnglishSentence.includes(doctorEnglishWord) ? doctorEnglishSentence.replace(doctorEnglishWord, `<span class="doctor-highlight">${doctorEnglishWord}</span>`) : doctorEnglishSentence;
468
+ const doctorSpanishWord = document.getElementById('doctorSpanish').value;
469
+ const doctorSpanishSentence = document.getElementById('doctorSpanishSentence').value;
470
+ previewWindow.document.querySelector('.spanish-sentence-doctor').innerHTML = doctorSpanishSentence.includes(doctorSpanishWord) ? doctorSpanishSentence.replace(doctorSpanishWord, `<span class="medico-highlight">${doctorSpanishWord}</span>`) : doctorSpanishSentence;
471
+ previewWindow.document.querySelector('.english-teacher').textContent = document.getElementById('teacherEnglish').value;
472
+ previewWindow.document.querySelector('.spanish-teacher').textContent = document.getElementById('teacherSpanish').value;
473
+ const teacherEnglishWord = document.getElementById('teacherEnglish').value;
474
+ const teacherEnglishSentence = document.getElementById('teacherEnglishSentence').value;
475
+ previewWindow.document.querySelector('.english-sentence-teacher').innerHTML = teacherEnglishSentence.includes(teacherEnglishWord) ? teacherEnglishSentence.replace(teacherEnglishWord, `<span class="teacher-highlight">${teacherEnglishWord}</span>`) : teacherEnglishSentence;
476
+ const teacherSpanishWord = document.getElementById('teacherSpanish').value;
477
+ const teacherSpanishSentence = document.getElementById('teacherSpanishSentence').value;
478
+ previewWindow.document.querySelector('.spanish-sentence-teacher').innerHTML = teacherSpanishSentence.includes(teacherSpanishWord) ? teacherSpanishSentence.replace(teacherSpanishWord, `<span class="profesor-highlight">${teacherSpanishWord}</span>`) : teacherSpanishSentence;
479
+ previewWindow.document.querySelector('.thank-you').textContent = document.getElementById('thankYouText').value;
480
+ previewWindow.document.querySelector('.learned').textContent = document.getElementById('learnedText').value;
481
+
482
+ // Update animation speed
483
+ previewWindow.document.documentElement.style.setProperty('--speed-factor', speedInput.value);
484
+ if (previewWindow.animation) {
485
+ previewWindow.animation.setSpeed(parseFloat(speedInput.value));
486
+ }
487
+
488
+ saveState(); // Save state after every update
489
+ } catch (error) {
490
+ console.error('Error updating animation:', error);
491
+ }
492
+ }
493
+
494
+ // --- Event Handlers ---
495
+
496
+ // Function to handle image uploads (with drag and drop)
497
+ function handleImageUpload(file, selector, dropZoneElement) {
498
+ if (file) {
499
+ const reader = new FileReader();
500
+ reader.onload = function(e) {
501
+ const imageUrl = e.target.result;
502
+
503
+ // Update the preview iframe
504
+ preview.contentWindow.document.querySelector(selector).style.backgroundImage = `url(${imageUrl})`;
505
+
506
+ // Update the drop zone to show the thumbnail
507
+ dropZoneElement.style.backgroundImage = `url(${imageUrl})`;
508
+ dropZoneElement.classList.add('has-image');
509
+
510
+ // Update the text inside drop zone
511
+ const textElement = dropZoneElement.querySelector('.drop-zone-text');
512
+ if (textElement) {
513
+ textElement.textContent = 'Click or drop to change';
514
+ }
515
+
516
+ // Save to localStorage
517
+ localStorage.setItem(selector, imageUrl);
518
+ saveState();
519
+ };
520
+ reader.readAsDataURL(file);
521
+ }
522
+ }
523
+
524
+ const doctorDropZone = document.getElementById('doctorDropZone');
525
+ const teacherDropZone = document.getElementById('teacherDropZone');
526
+ const doctorImageInput = document.getElementById('doctorImage');
527
+ const teacherImageInput = document.getElementById('teacherImage');
528
+
529
+ // Drag and drop events
530
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
531
+ doctorDropZone.addEventListener(eventName, preventDefaults, false);
532
+ teacherDropZone.addEventListener(eventName, preventDefaults, false);
533
+ });
534
+
535
+ function preventDefaults(e) {
536
+ e.preventDefault();
537
+ e.stopPropagation();
538
+ }
539
+
540
+ ['dragenter', 'dragover'].forEach(eventName => {
541
+ doctorDropZone.addEventListener(eventName, () => doctorDropZone.classList.add('drag-over'), false);
542
+ teacherDropZone.addEventListener(eventName, () => teacherDropZone.classList.add('drag-over'), false);
543
+ });
544
+
545
+ ['dragleave', 'drop'].forEach(eventName => {
546
+ doctorDropZone.addEventListener(eventName, () => doctorDropZone.classList.remove('drag-over'), false);
547
+ teacherDropZone.addEventListener(eventName, () => teacherDropZone.classList.remove('drag-over'), false);
548
+ });
549
+
550
+ doctorDropZone.addEventListener('drop', (e) => {
551
+ let dt = e.dataTransfer;
552
+ let files = dt.files;
553
+ handleImageUpload(files[0], '.doctor-image', doctorDropZone);
554
+ }, false);
555
+
556
+ teacherDropZone.addEventListener('drop', (e) => {
557
+ let dt = e.dataTransfer;
558
+ let files = dt.files;
559
+ handleImageUpload(files[0], '.teacher-image', teacherDropZone);
560
+ }, false);
561
+
562
+ // Click events for file input
563
+ doctorDropZone.addEventListener('click', () => doctorImageInput.click());
564
+ teacherDropZone.addEventListener('click', () => teacherImageInput.click());
565
+
566
+ doctorImageInput.addEventListener('change', (e) => handleImageUpload(e.target.files[0], '.doctor-image', doctorDropZone));
567
+ teacherImageInput.addEventListener('change', (e) => handleImageUpload(e.target.files[0], '.teacher-image', teacherDropZone));
568
+
569
+ // Handle text input changes
570
+ document.querySelectorAll('input[type="text"]').forEach(input => {
571
+ input.addEventListener('input', updateAnimation);
572
+ });
573
+
574
+ // Handle speed changes
575
+ speedInput.addEventListener('input', updateAnimation);
576
+
577
+ // Play/Pause functionality
578
+ playPauseBtn.addEventListener('click', function() {
579
+ const previewWindow = preview.contentWindow;
580
+ if (!previewWindow) return;
581
+
582
+ isPlaying = !isPlaying;
583
+ playPauseBtn.textContent = isPlaying ? 'Pause' : 'Play';
584
+
585
+ if (isPlaying) {
586
+ // If we're at the end, reset first
587
+ if (currentTime >= totalDuration) {
588
+ resetAnimation();
589
+ }
590
+ animationStartTime = Date.now() - currentTime;
591
+ requestAnimationFrame(updateTimeline);
592
+
593
+ // Start or resume animations in the iframe
594
+ if(previewWindow.startAnimationSequence && !previewWindow.animationStarted) {
595
+ previewWindow.startAnimationSequence();
596
+ } else if (previewWindow.resumeAnimation) {
597
+ previewWindow.resumeAnimation();
598
+ }
599
+ } else {
600
+ // Pause animations in the iframe
601
+ if (previewWindow.pauseAnimation) {
602
+ previewWindow.pauseAnimation();
603
+ }
604
+ }
605
+ });
606
+
607
+ // Reset functionality
608
+ resetBtn.addEventListener('click', resetAnimation);
609
+
610
+ function resetAnimation() {
611
+ currentTime = 0;
612
+ animationStartTime = Date.now();
613
+ timelineProgress.style.width = '0%';
614
+ // Instead of reloading, we now reset the animation using the iframe's functions
615
+ const previewWindow = preview.contentWindow;
616
+ if (previewWindow && previewWindow.animation) {
617
+ previewWindow.animation.goToAndStop(0, true); // Reset Lottie animation
618
+ // Reset CSS animations by removing the 'paused' class (if present)
619
+ previewWindow.document.querySelector('.video-container').classList.remove('paused');
620
+ // And restarting the sequence
621
+ if (previewWindow.startAnimationSequence){
622
+ previewWindow.animationStarted = false; // Allow it to be started again
623
+ previewWindow.startAnimationSequence();
624
+ }
625
+
626
+ }
627
+
628
+ isPlaying = false;
629
+ playPauseBtn.textContent = 'Play';
630
+ updateAnimation(); // Ensure iframe is updated
631
+ }
632
+
633
+ // Timeline scrubbing (no actual seeking, just visual feedback)
634
+ timeline.addEventListener('click', function(e) {
635
+ const rect = timeline.getBoundingClientRect();
636
+ const x = e.clientX - rect.left;
637
+ const percentage = x / rect.width;
638
+ currentTime = percentage * totalDuration;
639
+ timelineProgress.style.width = `${percentage * 100}%`;
640
+ console.log('Timeline clicked at percentage:', percentage); // For debugging
641
+ });
642
+
643
+ // Update timeline progress
644
+ function updateTimeline() {
645
+ if (!isPlaying) return;
646
+
647
+ currentTime = Date.now() - animationStartTime;
648
+ const progress = Math.min(currentTime / totalDuration, 1);
649
+ timelineProgress.style.width = `${progress * 100}%`;
650
+
651
+ if (progress < 1) {
652
+ requestAnimationFrame(updateTimeline);
653
+ } else {
654
+ isPlaying = false;
655
+ playPauseBtn.textContent = 'Play';
656
+ }
657
+ }
658
+
659
+ // Save changes (this button is now redundant, as we save on every change)
660
+ saveBtn.addEventListener('click', function() {
661
+ alert('Changes saved to local storage!'); // Keep the alert for user feedback
662
+ });
663
+
664
+ // Export animation (placeholder for now)
665
+ exportBtn.addEventListener('click', function() {
666
+ // Placeholder for export functionality
667
+ // Ideally, this would trigger a server-side process to generate a video
668
+ alert('Export functionality is not yet implemented. In a full implementation, this would generate a video file of your animation.');
669
+ });
670
+
671
+ // --- Initialization ---
672
+
673
+ // Initialize when iframe loads
674
+ preview.addEventListener('load', function() {
675
+ loadState(); // Load saved state
676
+ updateAnimation(); // Update the iframe
677
+
678
+ // If the animation hasn't started yet (e.g., after a refresh), start it
679
+ const previewWindow = preview.contentWindow;
680
+ if (previewWindow && !previewWindow.animationStarted) {
681
+ // Don't auto-start: previewWindow.startAnimationSequence();
682
+ }
683
+ });
684
+ });
685
+ </script>
686
+ </body>
687
+ </html>
video-editor-html/video-animation copy BUENO BUENO BUENO copy.html ADDED
@@ -0,0 +1,1267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Visual Learning - Professions Video</title>
7
+ <!-- Add Lottie library -->
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.10.2/lottie.min.js"></script>
9
+ <!-- Add Montserrat font -->
10
+ <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;700;900&display=swap" rel="stylesheet">
11
+ <link rel="preload" href="./doctorsimage.webp" as="image">
12
+ <link rel="preload" href="../frames/final/frame_00300_sec_12_f00.jpg" as="image">
13
+ <link rel="preload" href="https://flagcdn.com/w80/es.png" as="image">
14
+ <link rel="preload" href="https://flagcdn.com/w80/gb.png" as="image">
15
+ <style>
16
+ * {
17
+ margin: 0;
18
+ padding: 0;
19
+ box-sizing: border-box;
20
+ /* Global animation speed adjustment - now 20% faster than before */
21
+ animation-duration: calc(var(--original-duration, 1s) * 1) !important;
22
+ }
23
+
24
+ body {
25
+ width: 100vw;
26
+ height: 100vh;
27
+ display: flex;
28
+ justify-content: center;
29
+ align-items: center;
30
+ background-color: #000;
31
+ overflow: hidden;
32
+ font-family: 'Montserrat', sans-serif;
33
+ }
34
+
35
+ .video-container {
36
+ position: relative;
37
+ width: 100%;
38
+ max-width: 1920px;
39
+ aspect-ratio: 16 / 9;
40
+ background: linear-gradient(135deg, #3B82F6 0%, #60A5FA 100%);
41
+ overflow: hidden;
42
+ transform: translateZ(0);
43
+ will-change: opacity;
44
+ -webkit-backface-visibility: hidden;
45
+ backface-visibility: hidden;
46
+ }
47
+
48
+ /* Background video */
49
+ .background-video {
50
+ position: absolute;
51
+ top: 0;
52
+ left: 0;
53
+ width: 100%;
54
+ height: 100%;
55
+ object-fit: cover;
56
+ z-index: 0;
57
+ }
58
+
59
+ /* Background Elements - Reduced number and simplified */
60
+ .bg-shape {
61
+ position: absolute;
62
+ border-radius: 50%;
63
+ opacity: 0.2;
64
+ background-color: #00BFFF;
65
+ filter: blur(30px);
66
+ will-change: transform;
67
+ transform: translateZ(0);
68
+ }
69
+
70
+ .bg-shape-1 {
71
+ width: 40%;
72
+ height: 40%;
73
+ top: -10%;
74
+ right: -10%;
75
+ }
76
+
77
+ .bg-shape-2 {
78
+ width: 35%;
79
+ height: 35%;
80
+ bottom: -10%;
81
+ right: 20%;
82
+ }
83
+
84
+ .bg-shape-3 {
85
+ width: 30%;
86
+ height: 30%;
87
+ bottom: -10%;
88
+ left: 10%;
89
+ }
90
+
91
+ /* Logo Section */
92
+ .logo-container {
93
+ position: absolute;
94
+ top: 50%;
95
+ left: 50%;
96
+ transform: translate(-50%, -50%);
97
+ width: 800px;
98
+ height: 800px;
99
+ display: flex;
100
+ justify-content: center;
101
+ align-items: center;
102
+ opacity: 1;
103
+ z-index: 10;
104
+ }
105
+
106
+ /* Lottie container */
107
+ #lottie-container {
108
+ width: 100%;
109
+ height: 100%;
110
+ position: relative;
111
+ opacity: 1;
112
+ transition: opacity 1s ease-out;
113
+ }
114
+
115
+ /* Lista Profesiones Section */
116
+ .title-container {
117
+ position: absolute;
118
+ top: 50%;
119
+ left: 50%;
120
+ transform: translate(-50%, -50%);
121
+ display: flex;
122
+ flex-direction: column;
123
+ align-items: center;
124
+ opacity: 0;
125
+ z-index: 3;
126
+ }
127
+
128
+ .lista-wrapper {
129
+ position: relative;
130
+ overflow: hidden;
131
+ margin-bottom: -0.5em;
132
+ width: 180px;
133
+ height: 70px;
134
+ transform: translateX(-30px) rotate(-5deg);
135
+ animation: showListaContainer 0.625s 1.25s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
136
+ animation-play-state: paused;
137
+ z-index: 2;
138
+ opacity: 0;
139
+ }
140
+
141
+ .lista-slice-wrapper {
142
+ position: absolute;
143
+ top: 0;
144
+ right: 0;
145
+ width: 100%;
146
+ height: 100%;
147
+ animation: sliceListaWidth 0.625s 4.5s ease-in-out forwards;
148
+ animation-play-state: paused;
149
+ overflow: hidden;
150
+ }
151
+
152
+ .lista-box {
153
+ position: absolute;
154
+ top: 0;
155
+ left: 0;
156
+ right: 0;
157
+ background-color: #0E3E54;
158
+ padding: 0.5em 1em;
159
+ box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
160
+ height: 100%;
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ }
165
+
166
+ .lista-text {
167
+ font-family: 'Montserrat', sans-serif;
168
+ font-size: 3.5vw;
169
+ font-weight: 900;
170
+ color: white;
171
+ letter-spacing: -0.02em;
172
+ text-transform: uppercase;
173
+ white-space: nowrap;
174
+ }
175
+
176
+ .profesiones-wrapper {
177
+ position: relative;
178
+ overflow: hidden;
179
+ width: 420px;
180
+ height: 70px;
181
+ transform: translateX(30px) rotate(-5deg);
182
+ animation: showProfesionesContainer 0.625s 1.375s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
183
+ animation-play-state: paused;
184
+ z-index: 1;
185
+ opacity: 0;
186
+ }
187
+
188
+ .profesiones-slice-wrapper {
189
+ position: absolute;
190
+ top: 0;
191
+ left: 0;
192
+ width: 100%;
193
+ height: 100%;
194
+ animation: sliceProfesionesWidth 0.625s 4.5s ease-in-out forwards;
195
+ animation-play-state: paused;
196
+ overflow: hidden;
197
+ }
198
+
199
+ .profesiones-box {
200
+ position: absolute;
201
+ top: 0;
202
+ left: 0;
203
+ right: 0;
204
+ background-color: white;
205
+ padding: 0.5em 1em;
206
+ box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
207
+ height: 100%;
208
+ display: flex;
209
+ align-items: center;
210
+ justify-content: center;
211
+ }
212
+
213
+ .profesiones-text {
214
+ font-family: 'Montserrat', sans-serif;
215
+ font-size: 3.5vw;
216
+ font-weight: 900;
217
+ color: #0E3E54;
218
+ letter-spacing: -0.02em;
219
+ text-transform: uppercase;
220
+ opacity: 0;
221
+ animation: fadeIn 0.5s 1.5s forwards, fadeOut 0.375s 4.5s forwards;
222
+ animation-play-state: paused;
223
+ white-space: nowrap;
224
+ }
225
+
226
+ /* Flags for Lista Profesiones */
227
+ .flags-list-container {
228
+ position: absolute;
229
+ bottom: 10%;
230
+ left: 10%;
231
+ display: flex;
232
+ gap: 10px;
233
+ opacity: 0;
234
+ z-index: 5;
235
+ animation: fadeIn 0.5s 1.75s forwards;
236
+ animation-play-state: paused;
237
+ }
238
+
239
+ .spain-flag-container {
240
+ opacity: 0;
241
+ animation: fadeIn 0.5s 1.875s forwards, fadeOut 0.625s 4.5s forwards;
242
+ animation-play-state: paused;
243
+ }
244
+
245
+ .uk-flag-container {
246
+ opacity: 0;
247
+ animation: fadeIn 0.5s 1.75s forwards, fadeOut 0.625s 4.5s forwards;
248
+ animation-play-state: paused;
249
+ }
250
+
251
+ .flag {
252
+ width: 50px;
253
+ height: 50px;
254
+ border-radius: 50%;
255
+ overflow: hidden;
256
+ border: 2px solid white;
257
+ box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.2);
258
+ display: flex;
259
+ justify-content: center;
260
+ align-items: center;
261
+ }
262
+
263
+ .flag img {
264
+ width: 100%;
265
+ height: 100%;
266
+ object-fit: cover;
267
+ will-change: transform;
268
+ transform: translateZ(0);
269
+ }
270
+
271
+ /* Doctor Section */
272
+ .doctor-section {
273
+ opacity: 0;
274
+ animation: fadeIn 0.25s 0.5s forwards, fadeOut 0.25s 1.5s forwards;
275
+ animation-play-state: paused;
276
+ z-index: 5;
277
+ pointer-events: none; /* Prevent interaction during animation */
278
+ will-change: opacity; /* Add hardware acceleration for smoother transitions */
279
+ -webkit-backface-visibility: hidden;
280
+ backface-visibility: hidden;
281
+ animation-fill-mode: forwards;
282
+ position: absolute;
283
+ width: 100%;
284
+ height: 100%;
285
+ }
286
+
287
+ /* Separate animations for doctor and teacher cards */
288
+ .doctor-card {
289
+ position: absolute;
290
+ top: 50%;
291
+ left: 25%;
292
+ transform: translate(-50%, -50%) scale(0);
293
+ width: 30%;
294
+ aspect-ratio: 1 / 1;
295
+ background-color: white;
296
+ border-radius: 15px;
297
+ display: flex;
298
+ justify-content: center;
299
+ align-items: center;
300
+ overflow: hidden;
301
+ animation: simpleScaleIn 0.3s 0.5s cubic-bezier(0.25, 0.1, 0.25, 1) forwards,
302
+ simpleScaleOut 0.3s 1.4s cubic-bezier(0.25, 0.1, 0.25, 1) forwards;
303
+ animation-play-state: paused;
304
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.2);
305
+ will-change: transform, opacity;
306
+ z-index: 1;
307
+ -webkit-backface-visibility: hidden;
308
+ backface-visibility: hidden;
309
+ transform-style: preserve-3d;
310
+ animation-fill-mode: forwards;
311
+ transform-origin: center center;
312
+ }
313
+
314
+ .teacher-card {
315
+ position: absolute;
316
+ top: 50%;
317
+ left: 25%;
318
+ transform: translate(-50%, -50%) scale(0);
319
+ width: 30%;
320
+ aspect-ratio: 1 / 1;
321
+ background-color: white;
322
+ border-radius: 15px;
323
+ display: flex;
324
+ justify-content: center;
325
+ align-items: center;
326
+ overflow: hidden;
327
+ animation: simpleScaleIn 0.3s 1.6s cubic-bezier(0.25, 0.1, 0.25, 1) forwards,
328
+ simpleScaleOut 0.3s 2.4s cubic-bezier(0.25, 0.1, 0.25, 1) forwards;
329
+ animation-play-state: paused;
330
+ box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.2);
331
+ will-change: transform, opacity;
332
+ z-index: 1;
333
+ -webkit-backface-visibility: hidden;
334
+ backface-visibility: hidden;
335
+ transform-style: preserve-3d;
336
+ animation-fill-mode: forwards;
337
+ transform-origin: center center;
338
+ }
339
+
340
+ .doctor-card:hover, .teacher-card:hover {
341
+ box-shadow: 0px 8px 20px rgba(0, 0, 0, 0.3);
342
+ }
343
+
344
+ .doctor-image {
345
+ width: 100%;
346
+ height: 100%;
347
+ background-image: url('./doctorsimage.webp');
348
+ background-size: 85%;
349
+ background-position: center;
350
+ background-repeat: no-repeat;
351
+ will-change: transform;
352
+ transform: translateZ(0);
353
+ -webkit-backface-visibility: hidden;
354
+ backface-visibility: hidden;
355
+ }
356
+
357
+ .teacher-image {
358
+ width: 100%;
359
+ height: 100%;
360
+ background-image: url('./doctorsimage.webp');
361
+ background-size: 85%;
362
+ background-position: center;
363
+ background-repeat: no-repeat;
364
+ will-change: transform;
365
+ transform: translateZ(0);
366
+ -webkit-backface-visibility: hidden;
367
+ backface-visibility: hidden;
368
+ }
369
+
370
+ .doctor-card:hover .doctor-image, .teacher-card:hover .teacher-image {
371
+ transform: scale(1.05);
372
+ }
373
+
374
+ .doctor-vocabulary {
375
+ position: absolute;
376
+ top: 50%;
377
+ right: 25%;
378
+ transform: translateY(-50%);
379
+ display: flex;
380
+ flex-direction: column;
381
+ align-items: flex-start;
382
+ }
383
+
384
+ .english-doctor {
385
+ position: relative;
386
+ font-family: 'Montserrat', sans-serif;
387
+ font-size: 7vw;
388
+ font-weight: 900;
389
+ color: white;
390
+ letter-spacing: -0.02em;
391
+ text-transform: lowercase;
392
+ margin-bottom: 0.1em;
393
+ opacity: 0;
394
+ transform: scale(0);
395
+ animation: scaleInText 0.3s 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) forwards,
396
+ fadeOut 0.25s 1.4s forwards;
397
+ animation-play-state: paused;
398
+ }
399
+
400
+ .spanish-doctor {
401
+ position: relative;
402
+ font-family: 'Montserrat', sans-serif;
403
+ font-size: 5vw;
404
+ font-weight: 900;
405
+ color: #0E3E54;
406
+ letter-spacing: -0.02em;
407
+ text-transform: lowercase;
408
+ opacity: 0;
409
+ transform: scale(0);
410
+ animation: scaleInText 0.3s 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards,
411
+ fadeOut 0.25s 1.4s forwards;
412
+ animation-play-state: paused;
413
+ }
414
+
415
+ /* Replace SVG X overlay with canvas */
416
+ .card-x-overlay, .teacher-x-overlay {
417
+ position: absolute;
418
+ top: 50%;
419
+ left: 25%;
420
+ transform: translate(-50%, -50%);
421
+ width: 30%;
422
+ aspect-ratio: 1 / 1;
423
+ pointer-events: none;
424
+ opacity: 0;
425
+ }
426
+
427
+ .card-x-overlay {
428
+ animation: fadeIn 0.2s 0.7s forwards, fadeOut 0.2s 0.9s forwards;
429
+ animation-play-state: paused;
430
+ }
431
+
432
+ .teacher-x-overlay {
433
+ animation: fadeIn 0.2s 1.7s forwards, fadeOut 0.2s 1.9s forwards;
434
+ animation-play-state: paused;
435
+ }
436
+
437
+ /* Doctor Phrase Section */
438
+ .doctor-phrase-section {
439
+ opacity: 0;
440
+ animation: fadeIn 0.25s 0.9s forwards, fadeOut 0.25s 1.9s forwards;
441
+ animation-play-state: paused;
442
+ position: absolute;
443
+ width: 100%;
444
+ height: 100%;
445
+ }
446
+
447
+ .english-sentence-doctor {
448
+ position: absolute;
449
+ top: 35%;
450
+ width: 100%;
451
+ text-align: center;
452
+ font-family: 'Montserrat', sans-serif;
453
+ font-size: 6vw;
454
+ font-weight: 900;
455
+ color: white;
456
+ letter-spacing: -0.02em;
457
+ opacity: 0;
458
+ animation: fadeIn 0.25s 0.95s forwards, fadeOut 0.25s 1.85s forwards;
459
+ animation-play-state: paused;
460
+ }
461
+
462
+ .spanish-sentence-doctor {
463
+ position: absolute;
464
+ top: 55%;
465
+ width: 100%;
466
+ text-align: center;
467
+ font-family: 'Montserrat', sans-serif;
468
+ font-size: 5.5vw;
469
+ font-weight: 900;
470
+ color: #0E3E54;
471
+ letter-spacing: -0.02em;
472
+ opacity: 0;
473
+ animation: fadeIn 0.25s 1.0s forwards, fadeOut 0.25s 1.85s forwards;
474
+ animation-play-state: paused;
475
+ }
476
+
477
+ .doctor-highlight, .medico-highlight, .teacher-highlight, .profesor-highlight {
478
+ display: inline-block;
479
+ position: relative;
480
+ padding: 0 0.3em;
481
+ margin-left: 0.1em;
482
+ z-index: 2;
483
+ }
484
+
485
+ .doctor-highlight::before, .medico-highlight::before, .teacher-highlight::before, .profesor-highlight::before {
486
+ content: '';
487
+ position: absolute;
488
+ top: 0;
489
+ left: 0;
490
+ width: 100%;
491
+ height: 100%;
492
+ transform: scale(0);
493
+ transform-origin: center;
494
+ z-index: -1;
495
+ border-radius: 2px;
496
+ }
497
+
498
+ .doctor-highlight {
499
+ color: white;
500
+ }
501
+
502
+ .medico-highlight {
503
+ color: #0E1A2B;
504
+ }
505
+
506
+ .teacher-highlight {
507
+ color: white;
508
+ }
509
+
510
+ .profesor-highlight {
511
+ color: #0E1A2B;
512
+ }
513
+
514
+ /* Teacher Section */
515
+ .teacher-section {
516
+ opacity: 0;
517
+ animation: fadeIn 0.25s 1.05s forwards, fadeOut 0.25s 2.05s forwards;
518
+ animation-play-state: paused;
519
+ z-index: 5;
520
+ pointer-events: none; /* Prevent interaction during animation */
521
+ will-change: opacity; /* Add hardware acceleration for smoother transitions */
522
+ -webkit-backface-visibility: hidden;
523
+ backface-visibility: hidden;
524
+ animation-fill-mode: forwards;
525
+ position: absolute;
526
+ width: 100%;
527
+ height: 100%;
528
+ }
529
+
530
+ .teacher-vocabulary {
531
+ position: absolute;
532
+ top: 50%;
533
+ right: 25%;
534
+ transform: translateY(-50%);
535
+ display: flex;
536
+ flex-direction: column;
537
+ align-items: flex-start;
538
+ }
539
+
540
+ .english-teacher {
541
+ position: relative;
542
+ font-family: 'Montserrat', sans-serif;
543
+ font-size: 7vw;
544
+ font-weight: 900;
545
+ color: white;
546
+ letter-spacing: -0.02em;
547
+ text-transform: lowercase;
548
+ margin-bottom: 0.1em;
549
+ opacity: 0;
550
+ transform: scale(0);
551
+ animation: scaleInText 0.3s 1.1s cubic-bezier(0.34, 1.56, 0.64, 1) forwards,
552
+ fadeOut 0.25s 2.0s forwards;
553
+ animation-play-state: paused;
554
+ }
555
+
556
+ .spanish-teacher {
557
+ position: relative;
558
+ font-family: 'Montserrat', sans-serif;
559
+ font-size: 5vw;
560
+ font-weight: 900;
561
+ color: #0E3E54;
562
+ letter-spacing: -0.02em;
563
+ text-transform: lowercase;
564
+ opacity: 0;
565
+ transform: scale(0);
566
+ animation: scaleInText 0.3s 1.15s cubic-bezier(0.34, 1.56, 0.64, 1) forwards,
567
+ fadeOut 0.25s 2.0s forwards;
568
+ animation-play-state: paused;
569
+ }
570
+
571
+ /* Teacher Phrase Section */
572
+ .teacher-phrase-section {
573
+ opacity: 0;
574
+ animation: fadeIn 0.25s 1.05s forwards, fadeOut 0.25s 2.05s forwards;
575
+ animation-play-state: paused;
576
+ position: absolute;
577
+ width: 100%;
578
+ height: 100%;
579
+ }
580
+
581
+ .english-sentence-teacher {
582
+ position: absolute;
583
+ top: 35%;
584
+ width: 100%;
585
+ text-align: center;
586
+ font-family: 'Montserrat', sans-serif;
587
+ font-size: 6vw;
588
+ font-weight: 900;
589
+ color: white;
590
+ letter-spacing: -0.02em;
591
+ opacity: 0;
592
+ animation: fadeIn 0.25s 1.1s forwards, fadeOut 0.25s 2.0s forwards;
593
+ animation-play-state: paused;
594
+ }
595
+
596
+ .spanish-sentence-teacher {
597
+ position: absolute;
598
+ top: 55%;
599
+ width: 100%;
600
+ text-align: center;
601
+ font-family: 'Montserrat', sans-serif;
602
+ font-size: 5.5vw;
603
+ font-weight: 900;
604
+ color: #0E3E54;
605
+ letter-spacing: -0.02em;
606
+ opacity: 0;
607
+ animation: fadeIn 0.25s 1.15s forwards, fadeOut 0.25s 2.0s forwards;
608
+ animation-play-state: paused;
609
+ }
610
+
611
+ /* Outro Section */
612
+ .outro-section {
613
+ opacity: 0;
614
+ animation: fadeIn 0.3s 2.1s forwards;
615
+ animation-play-state: paused;
616
+ }
617
+
618
+ .message {
619
+ position: absolute;
620
+ top: 40%;
621
+ left: 50%;
622
+ transform: translateX(-50%);
623
+ text-align: center;
624
+ opacity: 0;
625
+ animation: fadeIn 0.3s 2.15s forwards;
626
+ animation-play-state: paused;
627
+ width: 80%;
628
+ }
629
+
630
+ .thank-you {
631
+ font-family: 'Montserrat', sans-serif;
632
+ font-size: 5vw;
633
+ font-weight: 900;
634
+ color: white;
635
+ letter-spacing: -0.02em;
636
+ margin-bottom: 0.3em;
637
+ }
638
+
639
+ .learned {
640
+ font-family: 'Montserrat', sans-serif;
641
+ font-size: 2.5vw;
642
+ font-weight: 700;
643
+ color: #0E3E54;
644
+ letter-spacing: 0.02em;
645
+ }
646
+
647
+ .flags-outro-container {
648
+ position: absolute;
649
+ bottom: 15%;
650
+ left: 50%;
651
+ transform: translateX(-50%);
652
+ display: flex;
653
+ gap: 20px;
654
+ opacity: 0;
655
+ animation: fadeIn 0.3s 2.2s forwards;
656
+ animation-play-state: paused;
657
+ }
658
+
659
+ /* Common Flag Styles */
660
+ .outro-flag {
661
+ width: 60px;
662
+ height: 60px;
663
+ border-radius: 50%;
664
+ overflow: hidden;
665
+ border: 3px solid white;
666
+ display: flex;
667
+ justify-content: center;
668
+ align-items: center;
669
+ }
670
+
671
+ .outro-flag img {
672
+ width: 100%;
673
+ height: 100%;
674
+ object-fit: cover;
675
+ will-change: transform;
676
+ transform: translateZ(0);
677
+ }
678
+
679
+ /* Animations */
680
+ @keyframes fadeIn {
681
+ from { opacity: 0; }
682
+ to { opacity: 1; }
683
+ }
684
+
685
+ @keyframes fadeOut {
686
+ from { opacity: 1; visibility: visible; }
687
+ to { opacity: 0; visibility: hidden; }
688
+ }
689
+
690
+ @keyframes fadeInOut {
691
+ 0% { opacity: 0; }
692
+ 10% { opacity: 1; }
693
+ 90% { opacity: 1; }
694
+ 100% { opacity: 0; }
695
+ }
696
+
697
+ @keyframes slideDown {
698
+ from {
699
+ transform: translateY(-60px);
700
+ opacity: 0;
701
+ }
702
+ to {
703
+ transform: translateY(0);
704
+ opacity: 1;
705
+ }
706
+ }
707
+
708
+ @keyframes showListaContainer {
709
+ from {
710
+ transform: translateX(-30px) rotate(-5deg) scale(0);
711
+ opacity: 0;
712
+ }
713
+ to {
714
+ transform: translateX(-30px) rotate(-5deg) scale(1);
715
+ opacity: 1;
716
+ }
717
+ }
718
+
719
+ @keyframes showProfesionesContainer {
720
+ from {
721
+ transform: translateX(30px) rotate(-5deg) scale(0);
722
+ opacity: 0;
723
+ }
724
+ to {
725
+ transform: translateX(30px) rotate(-5deg) scale(1);
726
+ opacity: 1;
727
+ }
728
+ }
729
+
730
+ @keyframes sliceListaWidth {
731
+ 0% { width: 100%; }
732
+ 100% { width: 0%; }
733
+ }
734
+
735
+ @keyframes sliceProfesionesWidth {
736
+ 0% { width: 100%; }
737
+ 100% { width: 0%; }
738
+ }
739
+
740
+ @keyframes scaleInText {
741
+ from {
742
+ transform: scale(0);
743
+ opacity: 0;
744
+ }
745
+ to {
746
+ transform: scale(1);
747
+ opacity: 1;
748
+ }
749
+ }
750
+
751
+ @keyframes textWobble {
752
+ 0%, 100% { transform: translate(0, 0) rotate(0deg); }
753
+ 25% { transform: translate(0px, 0px) rotate(0deg); }
754
+ 50% { transform: translate(0px, 0px) rotate(0deg); }
755
+ 75% { transform: translate(0px, 0px) rotate(0deg); }
756
+ }
757
+
758
+ @keyframes floatAnimation {
759
+ 0% { opacity: 0.2; transform: translate(0, 0) rotate(0deg); }
760
+ 25% { opacity: 0.25; transform: translate(3px, -3px) rotate(1deg); }
761
+ 50% { opacity: 0.2; transform: translate(5px, 3px) rotate(0deg); }
762
+ 75% { opacity: 0.25; transform: translate(-3px, 5px) rotate(-1deg); }
763
+ 100% { opacity: 0.2; transform: translate(0, 0) rotate(0deg); }
764
+ }
765
+
766
+ @keyframes elasticScale {
767
+ 0% { transform: scale(0); }
768
+ 70% { transform: scale(1.05); }
769
+ 85% { transform: scale(0.98); }
770
+ 100% { transform: scale(1); }
771
+ }
772
+
773
+ @keyframes scaleHighlightBackground {
774
+ from {
775
+ transform: scale(0);
776
+ }
777
+ to {
778
+ transform: scale(1);
779
+ }
780
+ }
781
+
782
+ .doctor-highlight::before {
783
+ background-color: #0E1A2B;
784
+ animation: scaleHighlightBackground 0.25s 1.7s forwards;
785
+ animation-play-state: paused;
786
+ }
787
+
788
+ .medico-highlight::before {
789
+ background-color: white;
790
+ animation: scaleHighlightBackground 0.25s 1.8s forwards;
791
+ animation-play-state: paused;
792
+ }
793
+
794
+ .teacher-highlight::before {
795
+ background-color: #0E1A2B;
796
+ animation: scaleHighlightBackground 0.25s 1.2s forwards;
797
+ animation-play-state: paused;
798
+ }
799
+
800
+ .profesor-highlight::before {
801
+ background-color: white;
802
+ animation: scaleHighlightBackground 0.25s 1.25s forwards;
803
+ animation-play-state: paused;
804
+ }
805
+
806
+ /* Optimize image loading */
807
+ img {
808
+ content-visibility: auto;
809
+ }
810
+
811
+ /* Optimize animations */
812
+ @media (prefers-reduced-motion: reduce) {
813
+ * {
814
+ animation-duration: 0.01ms !important;
815
+ animation-iteration-count: 1 !important;
816
+ transition-duration: 0.01ms !important;
817
+ scroll-behavior: auto !important;
818
+ }
819
+ }
820
+
821
+ /* New enhanced card animations */
822
+ @keyframes cardAnimation {
823
+ 0% {
824
+ transform: translate(-50%, -50%) scale(0);
825
+ opacity: 0;
826
+ visibility: visible;
827
+ }
828
+ 100% {
829
+ transform: translate(-50%, -50%) scale(1);
830
+ opacity: 1;
831
+ visibility: visible;
832
+ }
833
+ }
834
+
835
+ @keyframes cardExitAnimation {
836
+ 0% {
837
+ transform: translate(-50%, -50%) scale(1);
838
+ opacity: 1;
839
+ visibility: visible;
840
+ }
841
+ 100% {
842
+ transform: translate(-50%, -50%) scale(0);
843
+ opacity: 0;
844
+ visibility: hidden;
845
+ }
846
+ }
847
+
848
+ /* Add simple scale animations with rotation */
849
+ @keyframes simpleScaleIn {
850
+ 0% {
851
+ transform: translate(-50%, -50%) scale(0) rotate(45deg);
852
+ opacity: 0;
853
+ }
854
+ 100% {
855
+ transform: translate(-50%, -50%) scale(1) rotate(0deg);
856
+ opacity: 1;
857
+ }
858
+ }
859
+
860
+ @keyframes simpleScaleOut {
861
+ 0% {
862
+ transform: translate(-50%, -50%) scale(1) rotate(0deg);
863
+ opacity: 1;
864
+ }
865
+ 100% {
866
+ transform: translate(-50%, -50%) scale(0) rotate(-45deg);
867
+ opacity: 0;
868
+ }
869
+ }
870
+
871
+ /* Add a class to control animation play state */
872
+ .paused * {
873
+ animation-play-state: paused !important;
874
+ }
875
+ </style>
876
+ </head>
877
+ <body>
878
+ <div class="video-container">
879
+ <!-- Background video -->
880
+ <video class="background-video" autoplay muted loop>
881
+ <source src="../src/html/fondo_1.mp4" type="video/mp4">
882
+ </video>
883
+
884
+ <!-- Background shapes - reduced number -->
885
+ <div class="bg-shape bg-shape-1"></div>
886
+ <div class="bg-shape bg-shape-2"></div>
887
+ <div class="bg-shape bg-shape-3"></div>
888
+
889
+ <!-- Intro Section (0-5s) -->
890
+ <div class="logo-container">
891
+ <div id="lottie-container"></div>
892
+ </div>
893
+
894
+ <!-- Lista Profesiones Section (5-10s) -->
895
+ <div class="title-container">
896
+ <div class="lista-wrapper">
897
+ <div class="lista-slice-wrapper">
898
+ <div class="lista-box">
899
+ <div class="lista-text">LISTA</div>
900
+ </div>
901
+ </div>
902
+ </div>
903
+ <div class="profesiones-wrapper">
904
+ <div class="profesiones-slice-wrapper">
905
+ <div class="profesiones-box">
906
+ <div class="profesiones-text">PROFESIONES</div>
907
+ </div>
908
+ </div>
909
+ </div>
910
+ </div>
911
+
912
+ <div class="flags-list-container">
913
+ <!-- Flag of Spain -->
914
+ <div class="flag spain-flag-container">
915
+ <img src="https://flagcdn.com/w80/es.png" alt="Spain Flag">
916
+ </div>
917
+
918
+ <!-- UK flag -->
919
+ <div class="flag uk-flag-container">
920
+ <img src="https://flagcdn.com/w80/gb.png" alt="UK Flag">
921
+ </div>
922
+ </div>
923
+
924
+ <!-- Doctor Section (10-15s) -->
925
+ <div class="doctor-section">
926
+ <div class="doctor-card">
927
+ <div class="doctor-image"></div>
928
+ </div>
929
+
930
+ <div class="doctor-vocabulary">
931
+ <div class="english-doctor">doctor</div>
932
+ <div class="spanish-doctor">médico</div>
933
+ </div>
934
+
935
+ <canvas class="card-x-overlay" width="100" height="100"></canvas>
936
+ </div>
937
+
938
+ <!-- Doctor Phrase Section (15-20s) -->
939
+ <div class="doctor-phrase-section">
940
+ <div class="english-sentence-doctor">
941
+ He wants to be a <span class="doctor-highlight">doctor</span>
942
+ </div>
943
+
944
+ <div class="spanish-sentence-doctor">
945
+ Él quiere ser <span class="medico-highlight">médico</span>
946
+ </div>
947
+ </div>
948
+
949
+ <!-- Teacher Section (20-25s) -->
950
+ <div class="teacher-section">
951
+ <div class="teacher-card">
952
+ <div class="teacher-image"></div>
953
+ </div>
954
+
955
+ <div class="teacher-vocabulary">
956
+ <div class="english-teacher">teacher</div>
957
+ <div class="spanish-teacher">profesor</div>
958
+ </div>
959
+
960
+ <canvas class="teacher-x-overlay" width="100" height="100"></canvas>
961
+ </div>
962
+
963
+ <!-- Teacher Phrase Section (25-30s) -->
964
+ <div class="teacher-phrase-section">
965
+ <div class="english-sentence-teacher">
966
+ She is a <span class="teacher-highlight">teacher</span>
967
+ </div>
968
+
969
+ <div class="spanish-sentence-teacher">
970
+ Ella es <span class="profesor-highlight">profesor</span>
971
+ </div>
972
+ </div>
973
+
974
+ <!-- Outro Section (30s+) -->
975
+ <div class="outro-section">
976
+ <div class="message">
977
+ <div class="thank-you">Gracias! Thank you!</div>
978
+ <div class="learned">You've learned new profession vocabulary</div>
979
+ </div>
980
+
981
+ <div class="flags-outro-container">
982
+ <!-- Spanish flag -->
983
+ <div class="outro-flag">
984
+ <img src="https://flagcdn.com/w80/es.png" alt="Spain Flag">
985
+ </div>
986
+
987
+ <!-- UK flag -->
988
+ <div class="outro-flag">
989
+ <img src="https://flagcdn.com/w80/gb.png" alt="UK Flag">
990
+ </div>
991
+ </div>
992
+ </div>
993
+ </div>
994
+
995
+ <script>
996
+ let animation; // Store the Lottie animation object globally
997
+ let animationStarted = false;
998
+ let isPaused = false; // Track pause state
999
+
1000
+ // Function to get all elements that have CSS animations
1001
+ function getAnimatedElements() {
1002
+ return document.querySelectorAll(
1003
+ '.bg-shape, .lista-wrapper, .lista-slice-wrapper, .profesiones-wrapper, ' +
1004
+ '.profesiones-slice-wrapper, .profesiones-text, .flags-list-container, ' +
1005
+ '.spain-flag-container, .uk-flag-container, .doctor-section, .doctor-card, ' +
1006
+ '.doctor-image, .doctor-vocabulary, .english-doctor, .spanish-doctor, ' +
1007
+ '.card-x-overlay, .doctor-phrase-section, .english-sentence-doctor, ' +
1008
+ '.spanish-sentence-doctor, .teacher-section, .teacher-card, .teacher-image, ' +
1009
+ '.teacher-vocabulary, .english-teacher, .spanish-teacher, .teacher-x-overlay, ' +
1010
+ '.teacher-phrase-section, .english-sentence-teacher, .spanish-sentence-teacher, ' +
1011
+ '.outro-section, .message, .thank-you, .learned, .flags-outro-container, .outro-flag'
1012
+ );
1013
+ }
1014
+
1015
+ // Function to pause the animation
1016
+ function pauseAnimation() {
1017
+ if (animation) {
1018
+ animation.pause(); // Pause Lottie animation
1019
+ }
1020
+ // Pause CSS animations by adding the 'paused' class to the video container
1021
+ document.querySelector('.video-container').classList.add('paused');
1022
+ isPaused = true;
1023
+ console.log('Animation paused');
1024
+ }
1025
+
1026
+ // Function to resume the animation
1027
+ function resumeAnimation() {
1028
+ if (animation) {
1029
+ animation.play(); // Resume Lottie animation
1030
+ }
1031
+ // Resume CSS animations by removing the 'paused' class
1032
+ document.querySelector('.video-container').classList.remove('paused');
1033
+ isPaused = false;
1034
+ console.log('Animation resumed');
1035
+ }
1036
+
1037
+ // Function to toggle pause/resume
1038
+ function togglePause() {
1039
+ if (isPaused) {
1040
+ resumeAnimation();
1041
+ } else {
1042
+ pauseAnimation();
1043
+ }
1044
+ }
1045
+ document.addEventListener('DOMContentLoaded', function() {
1046
+ // Set global animation speed factor
1047
+ document.documentElement.style.setProperty('--speed-factor', '1');
1048
+
1049
+ // Preload images
1050
+ preloadImages();
1051
+
1052
+ // Use canvas renderer instead of SVG for better performance
1053
+ animation = lottie.loadAnimation({
1054
+ container: document.getElementById('lottie-container'),
1055
+ renderer: 'svg', // Changed back to svg
1056
+ loop: false,
1057
+ autoplay: true, // Don't autoplay initially
1058
+ path: 'SplashScreen 3.json',
1059
+ rendererSettings: {
1060
+ progressiveLoad: true,
1061
+ hideOnTransparent: true
1062
+ }
1063
+ });
1064
+
1065
+ // Set animation speed to 1.0x (faster than previous 0.9x)
1066
+ animation.setSpeed(1.0);
1067
+
1068
+ animation.addEventListener('DOMLoaded', function() {
1069
+ console.log('Lottie animation loaded');
1070
+ // Start animation sequence if not already started
1071
+ if (!animationStarted) {
1072
+ // Don't auto-start: startAnimationSequence();
1073
+ }
1074
+ });
1075
+
1076
+ // Add event listener to ensure animations start in the right sequence
1077
+ animation.addEventListener('complete', function() {
1078
+ console.log('Lottie animation complete, starting text animations');
1079
+ // Start animation sequence if not already started
1080
+ if (!animationStarted) {
1081
+ startAnimationSequence();
1082
+ }
1083
+
1084
+ // Clean up Lottie resources when done
1085
+ setTimeout(() => {
1086
+ animation.destroy();
1087
+ }, 1000);
1088
+ });
1089
+
1090
+ // Function to start the animation sequence based on original timing
1091
+ function startAnimationSequence() {
1092
+ animationStarted = true;
1093
+
1094
+ // Draw X on canvas elements
1095
+ drawXOnCanvas();
1096
+
1097
+ // Make the Lottie animation fade out after 1 second (0.8s * 1.25 for slower speed)
1098
+ setTimeout(function() {
1099
+ const lottieContainer = document.getElementById('lottie-container');
1100
+ lottieContainer.style.opacity = '0';
1101
+ lottieContainer.style.transition = 'opacity 0.5s ease'; // 0.4s * 1.25
1102
+
1103
+ // Ensure the title container becomes visible immediately after Lottie fades
1104
+ setTimeout(function() {
1105
+ const titleContainer = document.querySelector('.title-container');
1106
+ if (titleContainer) {
1107
+ titleContainer.style.opacity = '1';
1108
+ titleContainer.style.transition = 'opacity 0.375s ease'; // 0.3s * 1.25
1109
+
1110
+ // Start animations in batches to improve performance
1111
+ startAnimationsInBatches();
1112
+ }
1113
+ }, 500); // 400ms * 1.25
1114
+ }, 1000); // 800ms * 1.25
1115
+ }
1116
+
1117
+ // Function to start animations in batches for better performance
1118
+ function startAnimationsInBatches() {
1119
+ // First batch - LISTA PROFESIONES and flags
1120
+ const firstBatch = document.querySelectorAll(
1121
+ '.lista-wrapper, .lista-slice-wrapper, .profesiones-wrapper, ' +
1122
+ '.profesiones-slice-wrapper, .profesiones-text, ' +
1123
+ '.flags-list-container, .spain-flag-container, .uk-flag-container'
1124
+ );
1125
+
1126
+ firstBatch.forEach(el => {
1127
+ if (el) el.style.animationPlayState = 'running';
1128
+ });
1129
+
1130
+ // Start doctor section immediately after
1131
+ setTimeout(() => {
1132
+ // Second batch - Doctor section
1133
+ const secondBatch = document.querySelectorAll(
1134
+ '.doctor-section, .doctor-card, .doctor-image'
1135
+ );
1136
+
1137
+ secondBatch.forEach(el => {
1138
+ if (el) el.style.animationPlayState = 'running';
1139
+ });
1140
+
1141
+ // Doctor text appears immediately with card
1142
+ const textBatch = document.querySelectorAll(
1143
+ '.english-doctor, .spanish-doctor, .card-x-overlay'
1144
+ );
1145
+
1146
+ textBatch.forEach(el => {
1147
+ if (el) el.style.animationPlayState = 'running';
1148
+ });
1149
+
1150
+ // Doctor phrase section starts immediately with doctor
1151
+ const thirdBatch = document.querySelectorAll(
1152
+ '.doctor-phrase-section, .english-sentence-doctor, ' +
1153
+ '.spanish-sentence-doctor'
1154
+ );
1155
+
1156
+ thirdBatch.forEach(el => {
1157
+ if (el) el.style.animationPlayState = 'running';
1158
+ });
1159
+
1160
+ // Handle pseudo-elements for doctor highlights
1161
+ const styleSheet1 = document.createElement('style');
1162
+ styleSheet1.textContent = `
1163
+ .doctor-highlight::before, .medico-highlight::before {
1164
+ animation-play-state: running !important;
1165
+ }
1166
+ `;
1167
+ document.head.appendChild(styleSheet1);
1168
+
1169
+ // Teacher section and teacher phrases start immediately
1170
+ const teacherBatch = document.querySelectorAll(
1171
+ '.teacher-section, .teacher-card, .teacher-image, .english-teacher, .spanish-teacher, ' +
1172
+ '.teacher-x-overlay, .teacher-phrase-section, .english-sentence-teacher, ' +
1173
+ '.spanish-sentence-teacher'
1174
+ );
1175
+
1176
+ teacherBatch.forEach(el => {
1177
+ if (el) el.style.animationPlayState = 'running';
1178
+ });
1179
+
1180
+ // Handle pseudo-elements for teacher highlights
1181
+ const styleSheet2 = document.createElement('style');
1182
+ styleSheet2.textContent = `
1183
+ .teacher-highlight::before, .profesor-highlight::before {
1184
+ animation-play-state: running !important;
1185
+ }
1186
+ `;
1187
+ document.head.appendChild(styleSheet2);
1188
+
1189
+ // Outro section starts immediately too
1190
+ const outroBatch = document.querySelectorAll(
1191
+ '.outro-section, .message, .flags-outro-container'
1192
+ );
1193
+
1194
+ outroBatch.forEach(el => {
1195
+ if (el) el.style.animationPlayState = 'running';
1196
+ });
1197
+ }, 50); // Almost immediate
1198
+ }
1199
+
1200
+ // Add to the startAnimationSequence function
1201
+ function drawXOnCanvas() {
1202
+ // Draw X on doctor card canvas
1203
+ const doctorCanvas = document.querySelector('.card-x-overlay');
1204
+ if (doctorCanvas && doctorCanvas.getContext) {
1205
+ const ctx = doctorCanvas.getContext('2d');
1206
+ ctx.strokeStyle = 'rgba(150, 150, 150, 0.8)';
1207
+ ctx.lineWidth = 3;
1208
+ ctx.setLineDash([5, 5]);
1209
+ ctx.beginPath();
1210
+ ctx.moveTo(0, 0);
1211
+ ctx.lineTo(100, 100);
1212
+ ctx.stroke();
1213
+ ctx.beginPath();
1214
+ ctx.moveTo(100, 0);
1215
+ ctx.lineTo(0, 100);
1216
+ ctx.stroke();
1217
+ }
1218
+
1219
+ // Draw X on teacher card canvas
1220
+ const teacherCanvas = document.querySelector('.teacher-x-overlay');
1221
+ if (teacherCanvas && teacherCanvas.getContext) {
1222
+ const ctx = teacherCanvas.getContext('2d');
1223
+ ctx.strokeStyle = 'rgba(150, 150, 150, 0.8)';
1224
+ ctx.lineWidth = 3;
1225
+ ctx.setLineDash([5, 5]);
1226
+ ctx.beginPath();
1227
+ ctx.moveTo(0, 0);
1228
+ ctx.lineTo(100, 100);
1229
+ ctx.stroke();
1230
+ ctx.beginPath();
1231
+ ctx.moveTo(100, 0);
1232
+ ctx.lineTo(0, 100);
1233
+ ctx.stroke();
1234
+ }
1235
+ }
1236
+
1237
+ // Preload images function
1238
+ function preloadImages() {
1239
+ const imagesToPreload = [
1240
+ './doctorsimage.webp',
1241
+ 'https://flagcdn.com/w80/es.png',
1242
+ 'https://flagcdn.com/w80/gb.png'
1243
+ ];
1244
+
1245
+ // Also preload the video
1246
+ const video = document.createElement('video');
1247
+ video.preload = 'auto';
1248
+ video.src = '../src/html/fondo_1.mp4';
1249
+ video.style.display = 'none';
1250
+ document.body.appendChild(video);
1251
+ console.log('Preloading video: ../src/html/fondo_1.mp4');
1252
+
1253
+ imagesToPreload.forEach(src => {
1254
+ const img = new Image();
1255
+ img.src = src;
1256
+ console.log('Preloading image:', src);
1257
+
1258
+ // Add error handling for image loading
1259
+ img.onerror = () => {
1260
+ console.error('Failed to load image:', src);
1261
+ };
1262
+ });
1263
+ }
1264
+ });
1265
+ </script>
1266
+ </body>
1267
+ </html>