luigi12345 commited on
Commit
ea48e56
·
verified ·
1 Parent(s): 2397e6d

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +2795 -19
index.html CHANGED
@@ -1,19 +1,2795 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, maximum-scale=1.0, user-scalable=no">
6
+ <title>Flashcard Animation Studio</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css">
9
+ <style>
10
+ :root {
11
+ --primary-color: #2196F3;
12
+ --primary-dark: #1976D2;
13
+ --primary-light: #BBDEFB;
14
+ --accent-color: #FF4081;
15
+ --text-color: #212121;
16
+ --text-secondary: #757575;
17
+ --white: #FFFFFF;
18
+ --card-bg: #FAFAFA;
19
+ --border-color: #E0E0E0;
20
+ --shadow-color: rgba(0, 0, 0, 0.1);
21
+ --success-color: #4CAF50;
22
+ --warning-color: #FFC107;
23
+ --error-color: #F44336;
24
+ --header-height: 60px;
25
+ }
26
+
27
+ * {
28
+ margin: 0;
29
+ padding: 0;
30
+ box-sizing: border-box;
31
+ -webkit-font-smoothing: antialiased;
32
+ -moz-osx-font-smoothing: grayscale;
33
+ -webkit-tap-highlight-color: transparent;
34
+ }
35
+
36
+ @font-face {
37
+ font-family: 'Inter';
38
+ src: url('https://cdnjs.cloudflare.com/ajax/libs/inter-ui/3.19.3/Inter.var.woff2') format('woff2-variations');
39
+ font-weight: 100 900;
40
+ font-style: normal;
41
+ }
42
+
43
+ body {
44
+ margin: 0;
45
+ padding: 0;
46
+ font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
47
+ background-color: var(--background-color);
48
+ color: var(--text-color);
49
+ overflow: hidden;
50
+ width: 100vw;
51
+ height: 100vh;
52
+ position: relative;
53
+ }
54
+
55
+ /* App Structure */
56
+ .app-container {
57
+ display: flex;
58
+ flex-direction: column;
59
+ height: 100vh;
60
+ width: 100vw;
61
+ overflow: hidden;
62
+ position: absolute;
63
+ top: 0;
64
+ left: 0;
65
+ }
66
+
67
+ .app-header {
68
+ height: var(--header-height);
69
+ background-color: var(--primary-color);
70
+ color: var(--white);
71
+ display: flex;
72
+ align-items: center;
73
+ padding: 0 20px;
74
+ position: relative;
75
+ z-index: 10;
76
+ box-shadow: 0 2px 4px var(--shadow-color);
77
+ }
78
+
79
+ .app-title {
80
+ font-weight: 600;
81
+ font-size: 18px;
82
+ flex: 1;
83
+ }
84
+
85
+ .app-actions {
86
+ display: flex;
87
+ gap: 12px;
88
+ align-items: center;
89
+ }
90
+
91
+ .main-content {
92
+ display: flex;
93
+ flex: 1;
94
+ overflow: hidden;
95
+ height: calc(100vh - var(--header-height));
96
+ width: 100%;
97
+ }
98
+
99
+ .slots-panel {
100
+ width: 280px;
101
+ background-color: var(--white);
102
+ border-right: 1px solid var(--border-color);
103
+ display: flex;
104
+ flex-direction: column;
105
+ height: 100%;
106
+ transform: translateZ(0);
107
+ }
108
+
109
+ .canvas-area {
110
+ flex: 1;
111
+ background: linear-gradient(135deg, #2196F3, #42a5f5);
112
+ position: relative;
113
+ overflow: hidden;
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ transform: translateZ(0);
118
+ width: 100%;
119
+ height: 100%;
120
+ min-height: 500px;
121
+ padding: 30px 20px;
122
+ }
123
+
124
+ /* Slot Management */
125
+ .slots-header {
126
+ padding: 15px;
127
+ border-bottom: 1px solid var(--border-color);
128
+ display: flex;
129
+ justify-content: space-between;
130
+ align-items: center;
131
+ }
132
+
133
+ .slots-title {
134
+ font-weight: 600;
135
+ font-size: 16px;
136
+ }
137
+
138
+ .slots-list {
139
+ flex: 1;
140
+ overflow-y: auto;
141
+ padding: 10px;
142
+ -webkit-overflow-scrolling: touch;
143
+ }
144
+
145
+ .slot-item {
146
+ border: 1px solid var(--border-color);
147
+ border-radius: 8px;
148
+ margin-bottom: 12px;
149
+ overflow: hidden;
150
+ transition: all 0.2s;
151
+ cursor: pointer;
152
+ background: var(--card-bg);
153
+ position: relative;
154
+ }
155
+
156
+ .slot-item:hover {
157
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
158
+ }
159
+
160
+ .slot-item.active {
161
+ border-color: var(--primary-color);
162
+ box-shadow: 0 0 0 2px var(--primary-light);
163
+ }
164
+
165
+ .slot-header {
166
+ padding: 12px;
167
+ display: flex;
168
+ justify-content: space-between;
169
+ align-items: center;
170
+ background-color: var(--white);
171
+ border-bottom: 1px solid var(--border-color);
172
+ }
173
+
174
+ .slot-title {
175
+ font-weight: 500;
176
+ font-size: 14px;
177
+ }
178
+
179
+ .slot-actions {
180
+ display: flex;
181
+ gap: 8px;
182
+ }
183
+
184
+ .slot-action {
185
+ width: 24px;
186
+ height: 24px;
187
+ display: flex;
188
+ align-items: center;
189
+ justify-content: center;
190
+ border-radius: 4px;
191
+ cursor: pointer;
192
+ color: var(--text-secondary);
193
+ transition: all 0.2s;
194
+ }
195
+
196
+ .slot-action:hover {
197
+ background-color: var(--primary-light);
198
+ color: var(--primary-color);
199
+ }
200
+
201
+ .slot-preview {
202
+ padding: 10px;
203
+ display: flex;
204
+ height: 80px;
205
+ align-items: center;
206
+ }
207
+
208
+ .slot-thumbnail {
209
+ width: 60px;
210
+ height: 60px;
211
+ border-radius: 4px;
212
+ object-fit: cover;
213
+ background-color: var(--primary-light);
214
+ display: flex;
215
+ align-items: center;
216
+ justify-content: center;
217
+ font-size: 24px;
218
+ color: var(--primary-color);
219
+ }
220
+
221
+ .slot-info {
222
+ margin-left: 10px;
223
+ flex: 1;
224
+ }
225
+
226
+ .slot-name {
227
+ font-weight: 500;
228
+ font-size: 14px;
229
+ margin-bottom: 4px;
230
+ }
231
+
232
+ .slot-details {
233
+ font-size: 12px;
234
+ color: var(--text-secondary);
235
+ }
236
+
237
+ .animation-badge {
238
+ display: inline-block;
239
+ padding: 2px 6px;
240
+ background-color: var(--primary-light);
241
+ color: var(--primary-color);
242
+ border-radius: 4px;
243
+ font-size: 10px;
244
+ font-weight: 500;
245
+ margin-top: 4px;
246
+ }
247
+
248
+ /* Canvas Container Styles */
249
+ .canvas-container {
250
+ position: relative;
251
+ width: 100%;
252
+ height: 100%;
253
+ min-height: 450px;
254
+ background-color: rgba(255, 255, 255, 0.1);
255
+ border-radius: 0;
256
+ overflow: hidden;
257
+ box-shadow: none;
258
+ display: flex;
259
+ justify-content: center;
260
+ align-items: center;
261
+ }
262
+
263
+ .canvas-area {
264
+ flex: 1;
265
+ background: linear-gradient(135deg, #2196F3, #42a5f5);
266
+ position: relative;
267
+ overflow: hidden;
268
+ display: flex;
269
+ align-items: center;
270
+ justify-content: center;
271
+ transform: translateZ(0);
272
+ width: 100%;
273
+ height: 100%;
274
+ min-height: 500px;
275
+ padding: 30px 20px;
276
+ }
277
+
278
+ /* Flashcard Styles */
279
+ .flashcard {
280
+ position: absolute;
281
+ width: 28vw;
282
+ height: 28vw;
283
+ max-width: 300px;
284
+ max-height: 300px;
285
+ border-radius: 24px;
286
+ background-color: white;
287
+ box-shadow: 0 0 0 8px rgba(255, 255, 255, 0.9);
288
+ overflow: hidden;
289
+ z-index: 5;
290
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
291
+ }
292
+
293
+ .flashcard-left {
294
+ top: 15%;
295
+ left: 8%;
296
+ }
297
+
298
+ .flashcard-right {
299
+ top: 45%;
300
+ right: 8%;
301
+ }
302
+
303
+ .card-content {
304
+ width: 100%;
305
+ height: 100%;
306
+ border-radius: 16px;
307
+ overflow: hidden;
308
+ }
309
+
310
+ /* Label Styles */
311
+ .label-container {
312
+ position: absolute;
313
+ z-index: 10;
314
+ display: flex;
315
+ flex-direction: column;
316
+ align-items: center;
317
+ transition: transform 0.2s ease;
318
+ }
319
+
320
+ .label-left {
321
+ top: 20%;
322
+ right: 12%;
323
+ }
324
+
325
+ .label-right {
326
+ top: 50%;
327
+ left: 12%;
328
+ }
329
+
330
+ .label {
331
+ padding: 12px 20px;
332
+ width: 28vw;
333
+ max-width: 260px;
334
+ background-color: #002D72;
335
+ text-align: center;
336
+ color: white;
337
+ font-size: clamp(22px, 3.5vw, 32px);
338
+ font-weight: bold;
339
+ border-radius: 3px;
340
+ }
341
+
342
+ .translation {
343
+ color: white;
344
+ font-size: clamp(18px, 3vw, 26px);
345
+ text-align: center;
346
+ width: 28vw;
347
+ max-width: 260px;
348
+ margin-top: 8px;
349
+ text-shadow: 0 1px 3px rgba(0,0,0,0.3);
350
+ }
351
+
352
+ /* Style Selector */
353
+ .style-selector {
354
+ position: absolute;
355
+ bottom: 20px;
356
+ left: 50%;
357
+ transform: translateX(-50%);
358
+ background-color: rgba(255, 255, 255, 0.95);
359
+ border-radius: 30px;
360
+ padding: 10px;
361
+ display: flex;
362
+ gap: 8px;
363
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
364
+ z-index: 5;
365
+ flex-wrap: wrap;
366
+ justify-content: center;
367
+ max-width: 90%;
368
+ }
369
+
370
+ .style-option {
371
+ padding: 8px 16px;
372
+ border-radius: 20px;
373
+ font-size: 14px;
374
+ font-weight: 500;
375
+ cursor: pointer;
376
+ transition: all 0.2s;
377
+ white-space: nowrap;
378
+ }
379
+
380
+ .style-option:hover {
381
+ background-color: rgba(33, 150, 243, 0.1);
382
+ }
383
+
384
+ .style-option.active {
385
+ background-color: var(--primary-color);
386
+ color: white;
387
+ }
388
+
389
+ /* Responsive adjustments */
390
+ @media (max-width: 1200px) {
391
+ .flashcard, .label, .translation {
392
+ width: 180px;
393
+ }
394
+
395
+ .flashcard {
396
+ height: 180px;
397
+ }
398
+
399
+ .flashcard-left {
400
+ transform: translate(calc(-100% - 20px), -50%);
401
+ }
402
+
403
+ .flashcard-right {
404
+ transform: translate(20px, -50%);
405
+ }
406
+
407
+ .label-left {
408
+ transform: translate(20px, -50%);
409
+ }
410
+
411
+ .label-right {
412
+ transform: translate(calc(-100% - 20px), -50%);
413
+ }
414
+ }
415
+
416
+ @media (max-width: 992px) {
417
+ .canvas-container {
418
+ min-height: 400px;
419
+ }
420
+
421
+ .canvas-area {
422
+ min-height: 450px;
423
+ }
424
+
425
+ .style-selector {
426
+ bottom: 10px;
427
+ padding: 8px;
428
+ }
429
+
430
+ .style-option {
431
+ padding: 6px 12px;
432
+ font-size: 13px;
433
+ }
434
+
435
+ .main-content {
436
+ flex-direction: column;
437
+ }
438
+
439
+ .slots-panel {
440
+ width: 100%;
441
+ height: 180px;
442
+ border-right: none;
443
+ border-bottom: 1px solid var(--border-color);
444
+ }
445
+
446
+ .slots-list {
447
+ display: flex;
448
+ padding: 10px;
449
+ overflow-x: auto;
450
+ overflow-y: hidden;
451
+ }
452
+
453
+ .slot-item {
454
+ width: 200px;
455
+ flex-shrink: 0;
456
+ margin-right: 10px;
457
+ margin-bottom: 0;
458
+ }
459
+ }
460
+
461
+ @media (max-width: 768px) {
462
+ .canvas-container {
463
+ min-height: 350px;
464
+ }
465
+
466
+ .canvas-area {
467
+ min-height: 400px;
468
+ }
469
+
470
+ .flashcard, .label, .translation {
471
+ width: 150px;
472
+ }
473
+
474
+ .flashcard {
475
+ height: 150px;
476
+ }
477
+
478
+ .label {
479
+ font-size: 20px;
480
+ }
481
+
482
+ .translation {
483
+ font-size: 18px;
484
+ }
485
+
486
+ .flashcard-left {
487
+ transform: translate(calc(-100% - 15px), -50%);
488
+ }
489
+
490
+ .flashcard-right {
491
+ transform: translate(15px, -50%);
492
+ }
493
+
494
+ .label-left {
495
+ transform: translate(15px, -50%);
496
+ }
497
+
498
+ .label-right {
499
+ transform: translate(calc(-100% - 15px), -50%);
500
+ }
501
+
502
+ .app-title {
503
+ font-size: 16px;
504
+ }
505
+
506
+ .btn {
507
+ padding: 6px 10px;
508
+ font-size: 13px;
509
+ }
510
+ }
511
+
512
+ @media (max-width: 576px) {
513
+ .canvas-container {
514
+ min-height: 300px;
515
+ }
516
+
517
+ .canvas-area {
518
+ min-height: 350px;
519
+ padding: 20px 10px;
520
+ }
521
+
522
+ .flashcard, .label, .translation {
523
+ width: 130px;
524
+ }
525
+
526
+ .flashcard {
527
+ height: 130px;
528
+ }
529
+
530
+ .label {
531
+ font-size: 18px;
532
+ padding: 8px 4px;
533
+ }
534
+
535
+ .translation {
536
+ font-size: 16px;
537
+ }
538
+
539
+ .flashcard-left {
540
+ transform: translate(calc(-100% - 10px), -50%);
541
+ }
542
+
543
+ .flashcard-right {
544
+ transform: translate(10px, -50%);
545
+ }
546
+
547
+ .label-left {
548
+ transform: translate(10px, -50%);
549
+ }
550
+
551
+ .label-right {
552
+ transform: translate(calc(-100% - 10px), -50%);
553
+ }
554
+
555
+ .style-selector {
556
+ bottom: 5px;
557
+ padding: 5px;
558
+ }
559
+
560
+ .style-option {
561
+ padding: 5px 10px;
562
+ font-size: 12px;
563
+ border-radius: 15px;
564
+ }
565
+
566
+ .slots-panel {
567
+ height: 160px;
568
+ }
569
+
570
+ .slot-item {
571
+ width: 170px;
572
+ }
573
+ }
574
+
575
+ /* Animation Styles */
576
+ .anim-slideIn {
577
+ animation: slideIn 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
578
+ }
579
+
580
+ .anim-fadeGrow {
581
+ animation: fadeGrow 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
582
+ }
583
+
584
+ .anim-flip {
585
+ animation: flip 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
586
+ }
587
+
588
+ .anim-bounce {
589
+ animation: bounce 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
590
+ }
591
+
592
+ .anim-rotate {
593
+ animation: rotate 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
594
+ }
595
+
596
+ @keyframes slideIn {
597
+ from { transform: translateX(-40px); opacity: 0; }
598
+ to { transform: translateX(0); opacity: 1; }
599
+ }
600
+
601
+ @keyframes fadeGrow {
602
+ from { transform: scale(0.8); opacity: 0; }
603
+ to { transform: scale(1); opacity: 1; }
604
+ }
605
+
606
+ @keyframes flip {
607
+ from { transform: rotateY(90deg); opacity: 0; }
608
+ to { transform: rotateY(0); opacity: 1; }
609
+ }
610
+
611
+ @keyframes bounce {
612
+ 0% { transform: translateY(30px); opacity: 0; }
613
+ 50% { transform: translateY(-10px); opacity: 1; }
614
+ 100% { transform: translateY(0); opacity: 1; }
615
+ }
616
+
617
+ @keyframes rotate {
618
+ from { transform: rotate(-90deg) scale(0.8); opacity: 0; }
619
+ to { transform: rotate(0) scale(1); opacity: 1; }
620
+ }
621
+
622
+ /* Animation delay classes */
623
+ .delay-0 { animation-delay: 0s; }
624
+ .delay-1 { animation-delay: 0.2s; }
625
+ .delay-2 { animation-delay: 0.4s; }
626
+ .delay-3 { animation-delay: 0.6s; }
627
+
628
+ /* Controls */
629
+ .animation-controls {
630
+ position: absolute;
631
+ bottom: 20px;
632
+ left: 50%;
633
+ transform: translateX(-50%);
634
+ background-color: rgba(0, 0, 0, 0.6);
635
+ border-radius: 30px;
636
+ padding: 8px;
637
+ display: flex;
638
+ gap: 8px;
639
+ z-index: 20;
640
+ -webkit-backdrop-filter: blur(10px);
641
+ backdrop-filter: blur(10px);
642
+ }
643
+
644
+ .property-editor {
645
+ position: absolute;
646
+ top: 20px;
647
+ right: 20px;
648
+ width: 280px;
649
+ background-color: white;
650
+ border-radius: 8px;
651
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
652
+ overflow: hidden;
653
+ z-index: 10;
654
+ transform: translateZ(0);
655
+ opacity: 0;
656
+ pointer-events: none;
657
+ transition: opacity 0.3s, transform 0.3s;
658
+ }
659
+
660
+ .property-editor.active {
661
+ opacity: 1;
662
+ pointer-events: all;
663
+ transform: translateZ(0) translateY(0);
664
+ }
665
+
666
+ .property-header {
667
+ padding: 12px 15px;
668
+ display: flex;
669
+ justify-content: space-between;
670
+ align-items: center;
671
+ border-bottom: 1px solid var(--border-color);
672
+ background-color: var(--card-bg);
673
+ }
674
+
675
+ .property-title {
676
+ font-weight: 600;
677
+ font-size: 14px;
678
+ }
679
+
680
+ .property-close {
681
+ width: 24px;
682
+ height: 24px;
683
+ display: flex;
684
+ align-items: center;
685
+ justify-content: center;
686
+ border-radius: 12px;
687
+ cursor: pointer;
688
+ color: var(--text-secondary);
689
+ }
690
+
691
+ .property-close:hover {
692
+ background-color: var(--border-color);
693
+ }
694
+
695
+ .property-content {
696
+ padding: 15px;
697
+ max-height: 400px;
698
+ overflow-y: auto;
699
+ -webkit-overflow-scrolling: touch;
700
+ }
701
+
702
+ .property-group {
703
+ margin-bottom: 15px;
704
+ }
705
+
706
+ .property-label {
707
+ font-size: 12px;
708
+ font-weight: 500;
709
+ margin-bottom: 6px;
710
+ color: var(--text-secondary);
711
+ }
712
+
713
+ .property-field {
714
+ width: 100%;
715
+ padding: 8px 10px;
716
+ border: 1px solid var(--border-color);
717
+ border-radius: 4px;
718
+ font-size: 14px;
719
+ transition: border-color 0.2s;
720
+ }
721
+
722
+ .property-field:focus {
723
+ border-color: var(--primary-color);
724
+ outline: none;
725
+ }
726
+
727
+ .image-picker {
728
+ border: 2px dashed var(--border-color);
729
+ padding: 15px;
730
+ text-align: center;
731
+ cursor: pointer;
732
+ border-radius: 4px;
733
+ transition: all 0.2s;
734
+ }
735
+
736
+ .image-picker:hover {
737
+ border-color: var(--primary-color);
738
+ background-color: var(--primary-light);
739
+ }
740
+
741
+ .image-preview {
742
+ margin-top: 10px;
743
+ width: 100%;
744
+ height: 120px;
745
+ border-radius: 4px;
746
+ object-fit: cover;
747
+ }
748
+
749
+ /* Preview Mode */
750
+ .preview-overlay {
751
+ position: fixed;
752
+ top: 0;
753
+ left: 0;
754
+ width: 100%;
755
+ height: 100%;
756
+ background-color: rgba(0, 0, 0, 0.9);
757
+ z-index: 1000;
758
+ display: flex;
759
+ align-items: center;
760
+ justify-content: center;
761
+ opacity: 0;
762
+ visibility: hidden;
763
+ transition: opacity 0.3s, visibility 0.3s;
764
+ }
765
+
766
+ .preview-overlay.active {
767
+ opacity: 1;
768
+ visibility: visible;
769
+ }
770
+
771
+ .preview-container {
772
+ width: 80%;
773
+ height: 80%;
774
+ max-width: 1200px;
775
+ max-height: 800px;
776
+ background: linear-gradient(135deg, #2196F3, #42a5f5);
777
+ border-radius: 12px;
778
+ overflow: hidden;
779
+ position: relative;
780
+ transform: scale(0.9);
781
+ opacity: 0;
782
+ transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.3s;
783
+ }
784
+
785
+ .preview-overlay.active .preview-container {
786
+ transform: scale(1);
787
+ opacity: 1;
788
+ }
789
+
790
+ .preview-controls {
791
+ position: absolute;
792
+ bottom: 30px;
793
+ left: 50%;
794
+ transform: translateX(-50%);
795
+ background-color: rgba(0, 0, 0, 0.6);
796
+ border-radius: 30px;
797
+ padding: 10px;
798
+ display: flex;
799
+ gap: 15px;
800
+ z-index: 10;
801
+ -webkit-backdrop-filter: blur(10px);
802
+ backdrop-filter: blur(10px);
803
+ }
804
+
805
+ .preview-control {
806
+ width: 40px;
807
+ height: 40px;
808
+ border-radius: 50%;
809
+ background-color: rgba(255, 255, 255, 0.2);
810
+ color: white;
811
+ display: flex;
812
+ align-items: center;
813
+ justify-content: center;
814
+ cursor: pointer;
815
+ transition: background-color 0.2s;
816
+ font-size: 16px;
817
+ }
818
+
819
+ .preview-control:hover {
820
+ background-color: rgba(255, 255, 255, 0.3);
821
+ }
822
+
823
+ .preview-close {
824
+ position: absolute;
825
+ top: 20px;
826
+ right: 20px;
827
+ width: 40px;
828
+ height: 40px;
829
+ border-radius: 50%;
830
+ background-color: rgba(0, 0, 0, 0.5);
831
+ display: flex;
832
+ align-items: center;
833
+ justify-content: center;
834
+ cursor: pointer;
835
+ z-index: 10;
836
+ color: white;
837
+ -webkit-backdrop-filter: blur(5px);
838
+ backdrop-filter: blur(5px);
839
+ }
840
+
841
+ .preview-progress {
842
+ position: absolute;
843
+ bottom: 0;
844
+ left: 0;
845
+ height: 4px;
846
+ background-color: var(--primary-dark);
847
+ }
848
+
849
+ /* Blur Transition */
850
+ .blur-transition {
851
+ animation: blurTransition 1.5s ease forwards;
852
+ }
853
+
854
+ @keyframes blurTransition {
855
+ 0% { filter: blur(0); opacity: 1; }
856
+ 40% { filter: blur(10px); opacity: 0; }
857
+ 60% { filter: blur(10px); opacity: 0; }
858
+ 100% { filter: blur(0); opacity: 1; }
859
+ }
860
+
861
+ /* Buttons */
862
+ .btn {
863
+ padding: 8px 12px;
864
+ border-radius: 6px;
865
+ border: none;
866
+ font-weight: 500;
867
+ font-size: 14px;
868
+ cursor: pointer;
869
+ display: inline-flex;
870
+ align-items: center;
871
+ justify-content: center;
872
+ gap: 6px;
873
+ transition: all 0.2s;
874
+ }
875
+
876
+ .btn-primary {
877
+ background-color: var(--primary-color);
878
+ color: white;
879
+ }
880
+
881
+ .btn-primary:hover {
882
+ background-color: var(--primary-dark);
883
+ }
884
+
885
+ .btn-outline {
886
+ background-color: transparent;
887
+ border: 1px solid var(--border-color);
888
+ color: var(--text-color);
889
+ }
890
+
891
+ .btn-outline:hover {
892
+ background-color: var(--card-bg);
893
+ }
894
+
895
+ .btn-sm {
896
+ padding: 4px 8px;
897
+ font-size: 12px;
898
+ }
899
+
900
+ .btn:active {
901
+ transform: scale(0.98);
902
+ }
903
+
904
+ /* Layout Templates */
905
+ .layout-templates {
906
+ display: flex;
907
+ gap: 10px;
908
+ margin-top: 10px;
909
+ flex-wrap: wrap;
910
+ }
911
+
912
+ .layout-template {
913
+ width: 60px;
914
+ height: 40px;
915
+ border: 1px solid var(--border-color);
916
+ border-radius: 4px;
917
+ cursor: pointer;
918
+ overflow: hidden;
919
+ position: relative;
920
+ }
921
+
922
+ .layout-template:hover {
923
+ border-color: var(--primary-color);
924
+ }
925
+
926
+ .layout-template.active {
927
+ border-color: var(--primary-color);
928
+ box-shadow: 0 0 0 2px var(--primary-light);
929
+ }
930
+
931
+ .layout-preview {
932
+ width: 100%;
933
+ height: 100%;
934
+ display: flex;
935
+ flex-wrap: wrap;
936
+ padding: 2px;
937
+ }
938
+
939
+ .layout-item {
940
+ background-color: var(--primary-light);
941
+ border-radius: 2px;
942
+ }
943
+
944
+ /* Template 1: 2-2 Split */
945
+ .layout-t1 .layout-item:nth-child(1) {
946
+ width: 45%;
947
+ height: 100%;
948
+ }
949
+
950
+ .layout-t1 .layout-item:nth-child(2) {
951
+ width: 45%;
952
+ height: 100%;
953
+ margin-left: 5%;
954
+ }
955
+
956
+ /* Template 2: L Shape */
957
+ .layout-t2 .layout-item:nth-child(1) {
958
+ width: 60%;
959
+ height: 100%;
960
+ }
961
+
962
+ .layout-t2 .layout-item:nth-child(2) {
963
+ width: 35%;
964
+ height: 45%;
965
+ margin-left: 5%;
966
+ }
967
+
968
+ .layout-t2 .layout-item:nth-child(3) {
969
+ width: 35%;
970
+ height: 45%;
971
+ margin-left: 5%;
972
+ margin-top: 5%;
973
+ }
974
+
975
+ /* Template 3: Z Pattern */
976
+ .layout-t3 .layout-item:nth-child(1) {
977
+ width: 45%;
978
+ height: 45%;
979
+ }
980
+
981
+ .layout-t3 .layout-item:nth-child(2) {
982
+ width: 45%;
983
+ height: 45%;
984
+ margin-left: 50%;
985
+ }
986
+
987
+ .layout-t3 .layout-item:nth-child(3) {
988
+ width: 45%;
989
+ height: 45%;
990
+ margin-top: 5%;
991
+ }
992
+
993
+ .layout-t3 .layout-item:nth-child(4) {
994
+ width: 45%;
995
+ height: 45%;
996
+ margin-left: 50%;
997
+ margin-top: 5%;
998
+ }
999
+
1000
+ /* Draggable Elements */
1001
+ .draggable {
1002
+ cursor: move;
1003
+ user-select: none;
1004
+ }
1005
+
1006
+ .draggable:hover {
1007
+ box-shadow: 0 0 0 2px var(--primary-color);
1008
+ }
1009
+
1010
+ .dragging {
1011
+ opacity: 0.8;
1012
+ z-index: 100 !important;
1013
+ }
1014
+
1015
+ /* Timing Controls */
1016
+ .timing-controls {
1017
+ margin-top: 15px;
1018
+ padding-top: 15px;
1019
+ border-top: 1px solid var(--border-color);
1020
+ }
1021
+
1022
+ .timing-slider {
1023
+ width: 100%;
1024
+ margin: 8px 0;
1025
+ }
1026
+
1027
+ .timing-value {
1028
+ font-size: 12px;
1029
+ color: var(--text-secondary);
1030
+ text-align: right;
1031
+ }
1032
+
1033
+ /* Scene Duration Badge */
1034
+ .duration-badge {
1035
+ display: inline-block;
1036
+ padding: 2px 6px;
1037
+ background-color: var(--primary-light);
1038
+ color: var(--primary-dark);
1039
+ border-radius: 4px;
1040
+ font-size: 10px;
1041
+ font-weight: 500;
1042
+ margin-left: 8px;
1043
+ }
1044
+ </style>
1045
+ </head>
1046
+ <body>
1047
+ <div class="app-container">
1048
+ <!-- Header -->
1049
+ <header class="app-header">
1050
+ <div class="app-title">Flashcard Animation Studio</div>
1051
+ <div class="app-actions">
1052
+ <button class="btn btn-outline" id="saveBtn">
1053
+ <i class="fas fa-save"></i> Save
1054
+ </button>
1055
+ <button class="btn btn-primary" id="previewBtn">
1056
+ <i class="fas fa-play"></i> Preview
1057
+ </button>
1058
+ </div>
1059
+ </header>
1060
+
1061
+ <!-- Main Content -->
1062
+ <div class="main-content">
1063
+ <!-- Slots Panel -->
1064
+ <div class="slots-panel">
1065
+ <div class="slots-header">
1066
+ <div class="slots-title">Animation Slots</div>
1067
+ <div class="slot-action" id="addSlotBtn">
1068
+ <i class="fas fa-plus"></i>
1069
+ </div>
1070
+ </div>
1071
+ <div class="slots-list" id="slotsList">
1072
+ <!-- Slots will be dynamically added here -->
1073
+ </div>
1074
+ </div>
1075
+
1076
+ <!-- Canvas Area -->
1077
+ <div class="canvas-area">
1078
+ <div class="canvas-container" id="canvas">
1079
+ <!-- Default content -->
1080
+ <div class="flashcard flashcard-left" id="leftCard">
1081
+ <div class="card-content">
1082
+ <!-- SVG or image will be injected dynamically -->
1083
+ </div>
1084
+ </div>
1085
+
1086
+ <div class="label-container label-left" id="leftLabel">
1087
+ <div class="label">Kitty</div>
1088
+ <div class="translation">mountain</div>
1089
+ </div>
1090
+
1091
+ <div class="flashcard flashcard-right" id="rightCard">
1092
+ <div class="card-content">
1093
+ <!-- SVG or image will be injected dynamically -->
1094
+ </div>
1095
+ </div>
1096
+
1097
+ <div class="label-container label-right" id="rightLabel">
1098
+ <div class="label">playa</div>
1099
+ <div class="translation">beach</div>
1100
+ </div>
1101
+ </div>
1102
+
1103
+ <!-- Animation Style Selector -->
1104
+ <div class="style-selector" id="styleSelector">
1105
+ <div class="style-option active" data-style="slideIn">Slide In</div>
1106
+ <div class="style-option" data-style="fadeGrow">Fade & Grow</div>
1107
+ <div class="style-option" data-style="flip">Flip</div>
1108
+ <div class="style-option" data-style="bounce">Bounce</div>
1109
+ <div class="style-option" data-style="rotate">Rotate In</div>
1110
+ </div>
1111
+ </div>
1112
+ </div>
1113
+
1114
+ <!-- Property Editor -->
1115
+ <div class="property-editor" id="propertyEditor">
1116
+ <div class="property-header">
1117
+ <div class="property-title">Edit Element</div>
1118
+ <div class="property-close" id="closePropertyEditor">
1119
+ <i class="fas fa-times"></i>
1120
+ </div>
1121
+ </div>
1122
+ <div class="property-content">
1123
+ <!-- Text Element Properties -->
1124
+ <div id="textProperties">
1125
+ <div class="property-group">
1126
+ <div class="property-label">Text Content</div>
1127
+ <input type="text" class="property-field" id="textContent">
1128
+ </div>
1129
+ <div class="property-group">
1130
+ <div class="property-label">Font Size</div>
1131
+ <input type="range" class="property-field" id="fontSize" min="12" max="48" step="1">
1132
+ </div>
1133
+ <div class="property-group">
1134
+ <div class="property-label">Text Color</div>
1135
+ <input type="color" class="property-field" id="textColor">
1136
+ </div>
1137
+ <div class="property-group">
1138
+ <div class="property-label">Background Color</div>
1139
+ <input type="color" class="property-field" id="textBgColor">
1140
+ </div>
1141
+ </div>
1142
+
1143
+ <!-- Image Element Properties -->
1144
+ <div id="imageProperties">
1145
+ <div class="property-group">
1146
+ <div class="property-label">Image</div>
1147
+ <div class="image-picker" id="imagePicker">
1148
+ <i class="fas fa-upload"></i>
1149
+ <p>Click to upload image</p>
1150
+ <input type="file" id="imageUpload" accept="image/*" style="display: none;">
1151
+ </div>
1152
+ <img id="currentImage" class="image-preview">
1153
+ </div>
1154
+ <div class="property-group">
1155
+ <div class="property-label">Opacity</div>
1156
+ <input type="range" class="property-field" id="imageOpacity" min="0" max="1" step="0.1" value="1">
1157
+ </div>
1158
+ </div>
1159
+
1160
+ <!-- Common Properties -->
1161
+ <div class="property-group">
1162
+ <div class="property-label">Position</div>
1163
+ <div style="display: flex; gap: 10px;">
1164
+ <div style="flex: 1;">
1165
+ <small>X</small>
1166
+ <input type="number" class="property-field" id="positionX" style="width: 100%;">
1167
+ </div>
1168
+ <div style="flex: 1;">
1169
+ <small>Y</small>
1170
+ <input type="number" class="property-field" id="positionY" style="width: 100%;">
1171
+ </div>
1172
+ </div>
1173
+ </div>
1174
+ <div class="property-group">
1175
+ <div class="property-label">Size</div>
1176
+ <div style="display: flex; gap: 10px;">
1177
+ <div style="flex: 1;">
1178
+ <small>Width</small>
1179
+ <input type="number" class="property-field" id="elementWidth" style="width: 100%;">
1180
+ </div>
1181
+ <div style="flex: 1;">
1182
+ <small>Height</small>
1183
+ <input type="number" class="property-field" id="elementHeight" style="width: 100%;">
1184
+ </div>
1185
+ </div>
1186
+ </div>
1187
+ <div class="property-group">
1188
+ <div class="property-label">Animation Delay</div>
1189
+ <select class="property-field" id="animationDelay">
1190
+ <option value="0">No delay</option>
1191
+ <option value="1">Short (0.2s)</option>
1192
+ <option value="2">Medium (0.4s)</option>
1193
+ <option value="3">Long (0.6s)</option>
1194
+ </select>
1195
+ </div>
1196
+
1197
+ <!-- Timing Controls -->
1198
+ <div class="timing-controls">
1199
+ <div class="property-group">
1200
+ <div class="property-label">Animation Duration (seconds)</div>
1201
+ <input type="range" class="timing-slider property-field" id="animationDuration" min="0.5" max="5" step="0.1" value="0.8">
1202
+ <div class="timing-value" id="animationDurationValue">0.8s</div>
1203
+ </div>
1204
+
1205
+ <div class="property-group">
1206
+ <div class="property-label">Scene Duration (seconds)</div>
1207
+ <input type="range" class="timing-slider property-field" id="sceneDuration" min="1" max="10" step="0.5" value="3">
1208
+ <div class="timing-value" id="sceneDurationValue">3s</div>
1209
+ </div>
1210
+ </div>
1211
+ </div>
1212
+ </div>
1213
+
1214
+ <!-- Preview Mode -->
1215
+ <div class="preview-overlay" id="previewOverlay">
1216
+ <div class="preview-container" id="previewContainer">
1217
+ <!-- Content will be dynamically generated here -->
1218
+ <div class="preview-close" id="closePreviewBtn">
1219
+ <i class="fas fa-times"></i>
1220
+ </div>
1221
+ <div class="preview-controls">
1222
+ <div class="preview-control" id="prevSlotBtn">
1223
+ <i class="fas fa-step-backward"></i>
1224
+ </div>
1225
+ <div class="preview-control" id="playPauseBtn">
1226
+ <i class="fas fa-pause"></i>
1227
+ </div>
1228
+ <div class="preview-control" id="nextSlotBtn">
1229
+ <i class="fas fa-step-forward"></i>
1230
+ </div>
1231
+ </div>
1232
+ <div class="preview-progress" id="previewProgress"></div>
1233
+ </div>
1234
+ </div>
1235
+ </div>
1236
+
1237
+ <script>
1238
+ document.addEventListener('DOMContentLoaded', function() {
1239
+ // App State
1240
+ const appState = {
1241
+ slots: [],
1242
+ currentSlotId: null,
1243
+ activeElement: null,
1244
+ previewMode: false,
1245
+ previewCurrentSlot: 0,
1246
+ previewPlaying: false,
1247
+ previewTimer: null,
1248
+ previewDuration: 3000, // 3 seconds per slot
1249
+ defaultVisible: true // Flag to control default content visibility
1250
+ };
1251
+
1252
+ // DOM Elements
1253
+ const elements = {
1254
+ canvas: document.getElementById('canvas'),
1255
+ slotsList: document.getElementById('slotsList'),
1256
+ addSlotBtn: document.getElementById('addSlotBtn'),
1257
+ saveBtn: document.getElementById('saveBtn'),
1258
+ previewBtn: document.getElementById('previewBtn'),
1259
+ styleSelector: document.getElementById('styleSelector'),
1260
+ previewOverlay: document.getElementById('previewOverlay'),
1261
+ leftCard: document.getElementById('leftCard'),
1262
+ rightCard: document.getElementById('rightCard'),
1263
+ leftLabel: document.getElementById('leftLabel'),
1264
+ rightLabel: document.getElementById('rightLabel'),
1265
+ propertyEditor: document.getElementById('propertyEditor'),
1266
+ closePropertyEditor: document.getElementById('closePropertyEditor'),
1267
+ textProperties: document.getElementById('textProperties'),
1268
+ imageProperties: document.getElementById('imageProperties'),
1269
+ textContent: document.getElementById('textContent'),
1270
+ fontSize: document.getElementById('fontSize'),
1271
+ textColor: document.getElementById('textColor'),
1272
+ textBgColor: document.getElementById('textBgColor'),
1273
+ imagePicker: document.getElementById('imagePicker'),
1274
+ imageUpload: document.getElementById('imageUpload'),
1275
+ currentImage: document.getElementById('currentImage'),
1276
+ imageOpacity: document.getElementById('imageOpacity'),
1277
+ positionX: document.getElementById('positionX'),
1278
+ positionY: document.getElementById('positionY'),
1279
+ elementWidth: document.getElementById('elementWidth'),
1280
+ elementHeight: document.getElementById('elementHeight'),
1281
+ animationDelay: document.getElementById('animationDelay'),
1282
+ styleOptions: document.querySelectorAll('.style-option'),
1283
+ previewContainer: document.getElementById('previewContainer'),
1284
+ closePreviewBtn: document.getElementById('closePreviewBtn'),
1285
+ playPauseBtn: document.getElementById('playPauseBtn'),
1286
+ prevSlotBtn: document.getElementById('prevSlotBtn'),
1287
+ nextSlotBtn: document.getElementById('nextSlotBtn'),
1288
+ previewProgress: document.getElementById('previewProgress')
1289
+ };
1290
+
1291
+ // SVG Content
1292
+ const svgContent = {
1293
+ mountain: `
1294
+ <svg viewBox="0 0 220 220" xmlns="http://www.w3.org/2000/svg">
1295
+ <defs>
1296
+ <linearGradient id="skyGradient" x1="0%" y1="0%" x2="0%" y2="100%">
1297
+ <stop offset="0%" stop-color="#7EC2F3" />
1298
+ <stop offset="100%" stop-color="#B3E5FC" />
1299
+ </linearGradient>
1300
+ <linearGradient id="mountainGradient1" x1="0%" y1="0%" x2="100%" y2="100%">
1301
+ <stop offset="0%" stop-color="#78909C" />
1302
+ <stop offset="100%" stop-color="#B0BEC5" />
1303
+ </linearGradient>
1304
+ <linearGradient id="mountainGradient2" x1="0%" y1="0%" x2="100%" y2="100%">
1305
+ <stop offset="0%" stop-color="#607D8B" />
1306
+ <stop offset="100%" stop-color="#90A4AE" />
1307
+ </linearGradient>
1308
+ </defs>
1309
+
1310
+ <!-- Sky background -->
1311
+ <rect x="0" y="0" width="220" height="220" fill="url(#skyGradient)" />
1312
+
1313
+ <!-- Mountains -->
1314
+ <polygon points="0,110 70,40 140,95 220,70 220,125" fill="url(#mountainGradient1)" />
1315
+ <polygon points="0,125 35,80 105,55 175,90 220,75 220,125" fill="url(#mountainGradient2)" />
1316
+
1317
+ <!-- Green hills -->
1318
+ <rect x="0" y="125" width="220" height="95" fill="#8BC34A" />
1319
+
1320
+ <!-- Trees with detail -->
1321
+ <g>
1322
+ <polygon points="55,125 65,125 60,90" fill="#33691E" />
1323
+ <polygon points="80,125 95,125 87.5,80" fill="#33691E" />
1324
+ <polygon points="110,125 125,125 117.5,95" fill="#33691E" />
1325
+ <polygon points="40,125 50,125 45,100" fill="#33691E" />
1326
+ </g>
1327
+ </svg>
1328
+ `,
1329
+ beach: `
1330
+ <svg viewBox="0 0 220 220" xmlns="http://www.w3.org/2000/svg">
1331
+ <defs>
1332
+ <linearGradient id="skyGradient2" x1="0%" y1="0%" x2="0%" y2="100%">
1333
+ <stop offset="0%" stop-color="#7EC2F3" />
1334
+ <stop offset="100%" stop-color="#B3E5FC" />
1335
+ </linearGradient>
1336
+ <linearGradient id="oceanGradient" x1="0%" y1="0%" x2="0%" y2="100%">
1337
+ <stop offset="0%" stop-color="#29B6F6" />
1338
+ <stop offset="100%" stop-color="#0277BD" />
1339
+ </linearGradient>
1340
+ </defs>
1341
+
1342
+ <!-- Sky background -->
1343
+ <rect x="0" y="0" width="220" height="220" fill="url(#skyGradient2)" />
1344
+
1345
+ <!-- Sand -->
1346
+ <rect x="0" y="95" width="220" height="125" fill="#FFCC80" />
1347
+
1348
+ <!-- Ocean -->
1349
+ <path d="M0,130 C55,117 165,124 220,110 L220,220 L0,220 Z" fill="url(#oceanGradient)" />
1350
+ <path d="M0,130 C55,117 165,124 220,110" stroke="white" stroke-width="2" fill="none" />
1351
+
1352
+ <!-- Beach Items -->
1353
+ <g>
1354
+ <!-- Umbrella -->
1355
+ <line x1="120" y1="145" x2="120" y2="115" stroke="#8D6E63" stroke-width="2" />
1356
+ <path d="M105,115 A15,5 0 0 0 135,115" fill="#EF5350" />
1357
+
1358
+ <!-- Towels -->
1359
+ <rect x="85" y="160" width="20" height="14" fill="#FDD835" />
1360
+ <rect x="140" y="155" width="20" height="14" fill="#42A5F6" />
1361
+ <rect x="170" y="165" width="20" height="14" fill="#EF5350" />
1362
+ </g>
1363
+ </svg>
1364
+ `
1365
+ };
1366
+
1367
+ // Initialize the application
1368
+ function init() {
1369
+ // Set SVG content for cards
1370
+ if (elements.leftCard) {
1371
+ elements.leftCard.querySelector('.card-content').innerHTML = svgContent.mountain;
1372
+ }
1373
+
1374
+ if (elements.rightCard) {
1375
+ elements.rightCard.querySelector('.card-content').innerHTML = svgContent.beach;
1376
+ }
1377
+
1378
+ // Load data from storage
1379
+ loadFromStorage();
1380
+
1381
+ // Event listeners
1382
+ setupEventListeners();
1383
+
1384
+ // Render initial slots
1385
+ renderSlotsList();
1386
+
1387
+ // If no slots, create a default one
1388
+ if (appState.slots.length === 0) {
1389
+ addNewSlot();
1390
+ }
1391
+
1392
+ // Select first slot
1393
+ if (appState.slots.length > 0 && !appState.currentSlotId) {
1394
+ selectSlot(appState.slots[0].id);
1395
+ }
1396
+
1397
+ // Apply initial animation
1398
+ resetAndAnimateDefaultContent('slideIn');
1399
+ }
1400
+
1401
+ // Setup event listeners
1402
+ function setupEventListeners() {
1403
+ // Add new slot
1404
+ if (elements.addSlotBtn) {
1405
+ elements.addSlotBtn.addEventListener('click', addNewSlot);
1406
+ }
1407
+
1408
+ // Save button
1409
+ if (elements.saveBtn) {
1410
+ elements.saveBtn.addEventListener('click', function() {
1411
+ saveToStorage();
1412
+ showMessage('Project saved successfully!');
1413
+ });
1414
+ }
1415
+
1416
+ // Preview button
1417
+ if (elements.previewBtn) {
1418
+ elements.previewBtn.addEventListener('click', togglePreview);
1419
+ }
1420
+
1421
+ // Style selector
1422
+ if (elements.styleSelector) {
1423
+ elements.styleSelector.addEventListener('click', function(e) {
1424
+ if (e.target.classList.contains('style-option')) {
1425
+ const style = e.target.dataset.style;
1426
+ selectAnimationStyle(style);
1427
+ resetAndAnimateDefaultContent(style);
1428
+ }
1429
+ });
1430
+ }
1431
+
1432
+ // Close property editor
1433
+ if (elements.closePropertyEditor) {
1434
+ elements.closePropertyEditor.addEventListener('click', closePropertyEditor);
1435
+ }
1436
+
1437
+ // Preview controls
1438
+ if (elements.closePreviewBtn) {
1439
+ elements.closePreviewBtn.addEventListener('click', closePreview);
1440
+ }
1441
+
1442
+ if (elements.playPauseBtn) {
1443
+ elements.playPauseBtn.addEventListener('click', togglePlayPause);
1444
+ }
1445
+
1446
+ if (elements.prevSlotBtn) {
1447
+ elements.prevSlotBtn.addEventListener('click', goToPreviousSlot);
1448
+ }
1449
+
1450
+ if (elements.nextSlotBtn) {
1451
+ elements.nextSlotBtn.addEventListener('click', goToNextSlot);
1452
+ }
1453
+
1454
+ // Make canvas elements clickable for editing
1455
+ if (elements.canvas) {
1456
+ elements.canvas.addEventListener('click', function(e) {
1457
+ const clickableElements = [
1458
+ elements.leftCard,
1459
+ elements.rightCard,
1460
+ elements.leftLabel,
1461
+ elements.rightLabel
1462
+ ];
1463
+
1464
+ for (const el of clickableElements) {
1465
+ if (el && el.contains(e.target) || el === e.target) {
1466
+ openPropertyEditor(el);
1467
+ break;
1468
+ }
1469
+ }
1470
+ });
1471
+ }
1472
+
1473
+ // Slots management - delegate to slotsList
1474
+ if (elements.slotsList) {
1475
+ elements.slotsList.addEventListener('click', function(e) {
1476
+ // Handle delete button click
1477
+ if (e.target.closest('.slot-delete')) {
1478
+ const slotItem = e.target.closest('.slot-item');
1479
+ if (slotItem && slotItem.dataset.id) {
1480
+ deleteSlot(slotItem.dataset.id);
1481
+ e.stopPropagation();
1482
+ return;
1483
+ }
1484
+ }
1485
+
1486
+ // Handle slot selection
1487
+ const slotItem = e.target.closest('.slot-item');
1488
+ if (slotItem && slotItem.dataset.id) {
1489
+ selectSlot(slotItem.dataset.id);
1490
+ }
1491
+ });
1492
+ }
1493
+
1494
+ // Setup draggable elements
1495
+ setupDraggableElements();
1496
+
1497
+ // Setup timing control listeners
1498
+ setupTimingControls();
1499
+ }
1500
+
1501
+ // Setup draggable elements
1502
+ function setupDraggableElements() {
1503
+ const draggableElements = [
1504
+ elements.leftCard,
1505
+ elements.rightCard,
1506
+ elements.leftLabel,
1507
+ elements.rightLabel
1508
+ ];
1509
+
1510
+ draggableElements.forEach(element => {
1511
+ if (!element) return;
1512
+
1513
+ // Add draggable class
1514
+ element.classList.add('draggable');
1515
+
1516
+ // Variables to track dragging
1517
+ let isDragging = false;
1518
+ let startX, startY;
1519
+ let startLeft, startTop;
1520
+
1521
+ // Mouse down event
1522
+ element.addEventListener('mousedown', function(e) {
1523
+ // Only handle left mouse button
1524
+ if (e.button !== 0) return;
1525
+
1526
+ isDragging = true;
1527
+ element.classList.add('dragging');
1528
+
1529
+ // Get initial positions
1530
+ startX = e.clientX;
1531
+ startY = e.clientY;
1532
+
1533
+ // Get element's current position
1534
+ const rect = element.getBoundingClientRect();
1535
+ startLeft = rect.left;
1536
+ startTop = rect.top;
1537
+
1538
+ // Prevent default behavior
1539
+ e.preventDefault();
1540
+ });
1541
+
1542
+ // Mouse move event (on document to capture all movements)
1543
+ document.addEventListener('mousemove', function(e) {
1544
+ if (!isDragging) return;
1545
+
1546
+ // Calculate new position
1547
+ const deltaX = e.clientX - startX;
1548
+ const deltaY = e.clientY - startY;
1549
+
1550
+ // Update element position
1551
+ const canvasRect = elements.canvas.getBoundingClientRect();
1552
+ const newLeft = (startLeft - canvasRect.left + deltaX) / canvasRect.width * 100;
1553
+ const newTop = (startTop - canvasRect.top + deltaY) / canvasRect.height * 100;
1554
+
1555
+ // Apply new position
1556
+ element.style.left = `${newLeft}%`;
1557
+ element.style.top = `${newTop}%`;
1558
+
1559
+ // Remove fixed positioning classes
1560
+ if (element === elements.leftCard) {
1561
+ element.classList.remove('flashcard-left');
1562
+ } else if (element === elements.rightCard) {
1563
+ element.classList.remove('flashcard-right');
1564
+ } else if (element === elements.leftLabel) {
1565
+ element.classList.remove('label-left');
1566
+ } else if (element === elements.rightLabel) {
1567
+ element.classList.remove('label-right');
1568
+ }
1569
+
1570
+ // Update position in property editor if open
1571
+ if (appState.activeElement === element) {
1572
+ elements.positionX.value = Math.round(newLeft);
1573
+ elements.positionY.value = Math.round(newTop);
1574
+ }
1575
+
1576
+ // Save position to current slot
1577
+ saveElementPosition(element, newLeft, newTop);
1578
+ });
1579
+
1580
+ // Mouse up event
1581
+ document.addEventListener('mouseup', function() {
1582
+ if (!isDragging) return;
1583
+
1584
+ isDragging = false;
1585
+ element.classList.remove('dragging');
1586
+
1587
+ // Save to storage
1588
+ saveToStorage();
1589
+ });
1590
+ });
1591
+ }
1592
+
1593
+ // Save element position to current slot
1594
+ function saveElementPosition(element, left, top) {
1595
+ if (!appState.currentSlotId) return;
1596
+
1597
+ const slot = findSlotById(appState.currentSlotId);
1598
+ if (!slot) return;
1599
+
1600
+ // Initialize elements array if needed
1601
+ if (!slot.elements) {
1602
+ slot.elements = [];
1603
+ }
1604
+
1605
+ // Find element in slot or create new entry
1606
+ let elementData = slot.elements.find(el => el.id === element.id);
1607
+ if (!elementData) {
1608
+ elementData = { id: element.id };
1609
+ slot.elements.push(elementData);
1610
+ }
1611
+
1612
+ // Update position
1613
+ elementData.left = left;
1614
+ elementData.top = top;
1615
+ }
1616
+
1617
+ // Setup timing control listeners
1618
+ function setupTimingControls() {
1619
+ // Animation duration slider
1620
+ if (elements.animationDuration) {
1621
+ elements.animationDuration.addEventListener('input', function() {
1622
+ const value = parseFloat(this.value);
1623
+ elements.animationDurationValue.textContent = `${value}s`;
1624
+
1625
+ // Update current slot
1626
+ if (appState.currentSlotId) {
1627
+ const slot = findSlotById(appState.currentSlotId);
1628
+ if (slot) {
1629
+ slot.animationDuration = value;
1630
+
1631
+ // Update animation duration for all elements
1632
+ updateAnimationDuration(value);
1633
+
1634
+ // Save to storage
1635
+ saveToStorage();
1636
+ }
1637
+ }
1638
+ });
1639
+ }
1640
+
1641
+ // Scene duration slider
1642
+ if (elements.sceneDuration) {
1643
+ elements.sceneDuration.addEventListener('input', function() {
1644
+ const value = parseFloat(this.value);
1645
+ elements.sceneDurationValue.textContent = `${value}s`;
1646
+
1647
+ // Update current slot
1648
+ if (appState.currentSlotId) {
1649
+ const slot = findSlotById(appState.currentSlotId);
1650
+ if (slot) {
1651
+ slot.duration = value;
1652
+
1653
+ // Update slots list to show new duration
1654
+ renderSlotsList();
1655
+
1656
+ // Save to storage
1657
+ saveToStorage();
1658
+ }
1659
+ }
1660
+ });
1661
+ }
1662
+
1663
+ // Setup text content update
1664
+ setupTextContentListeners();
1665
+
1666
+ // Setup position and size update
1667
+ setupPositionSizeListeners();
1668
+ }
1669
+
1670
+ // Setup text content listeners
1671
+ function setupTextContentListeners() {
1672
+ // Text content field
1673
+ if (elements.textContent) {
1674
+ elements.textContent.addEventListener('input', function() {
1675
+ updateElementText();
1676
+ });
1677
+
1678
+ elements.textContent.addEventListener('change', function() {
1679
+ updateElementText();
1680
+ saveToStorage();
1681
+ });
1682
+ }
1683
+
1684
+ // Text color field
1685
+ if (elements.textColor) {
1686
+ elements.textColor.addEventListener('input', function() {
1687
+ updateElementStyle('color', this.value);
1688
+ });
1689
+
1690
+ elements.textColor.addEventListener('change', function() {
1691
+ updateElementStyle('color', this.value);
1692
+ saveToStorage();
1693
+ });
1694
+ }
1695
+
1696
+ // Background color field
1697
+ if (elements.textBgColor) {
1698
+ elements.textBgColor.addEventListener('input', function() {
1699
+ updateElementStyle('backgroundColor', this.value);
1700
+ });
1701
+
1702
+ elements.textBgColor.addEventListener('change', function() {
1703
+ updateElementStyle('backgroundColor', this.value);
1704
+ saveToStorage();
1705
+ });
1706
+ }
1707
+
1708
+ // Font size field
1709
+ if (elements.fontSize) {
1710
+ elements.fontSize.addEventListener('input', function() {
1711
+ updateElementStyle('fontSize', `${this.value}px`);
1712
+ });
1713
+
1714
+ elements.fontSize.addEventListener('change', function() {
1715
+ updateElementStyle('fontSize', `${this.value}px`);
1716
+ saveToStorage();
1717
+ });
1718
+ }
1719
+ }
1720
+
1721
+ // Setup position and size listeners
1722
+ function setupPositionSizeListeners() {
1723
+ // Position X field
1724
+ if (elements.positionX) {
1725
+ elements.positionX.addEventListener('input', function() {
1726
+ updateElementPosition();
1727
+ });
1728
+
1729
+ elements.positionX.addEventListener('change', function() {
1730
+ updateElementPosition();
1731
+ saveToStorage();
1732
+ });
1733
+ }
1734
+
1735
+ // Position Y field
1736
+ if (elements.positionY) {
1737
+ elements.positionY.addEventListener('input', function() {
1738
+ updateElementPosition();
1739
+ });
1740
+
1741
+ elements.positionY.addEventListener('change', function() {
1742
+ updateElementPosition();
1743
+ saveToStorage();
1744
+ });
1745
+ }
1746
+
1747
+ // Width field
1748
+ if (elements.elementWidth) {
1749
+ elements.elementWidth.addEventListener('input', function() {
1750
+ updateElementSize();
1751
+ });
1752
+
1753
+ elements.elementWidth.addEventListener('change', function() {
1754
+ updateElementSize();
1755
+ saveToStorage();
1756
+ });
1757
+ }
1758
+
1759
+ // Height field
1760
+ if (elements.elementHeight) {
1761
+ elements.elementHeight.addEventListener('input', function() {
1762
+ updateElementSize();
1763
+ });
1764
+
1765
+ elements.elementHeight.addEventListener('change', function() {
1766
+ updateElementSize();
1767
+ saveToStorage();
1768
+ });
1769
+ }
1770
+ }
1771
+
1772
+ // Update element text content
1773
+ function updateElementText() {
1774
+ if (!appState.activeElement) return;
1775
+
1776
+ const textValue = elements.textContent.value;
1777
+
1778
+ // Check if it's a label container
1779
+ if (appState.activeElement.classList.contains('label-container')) {
1780
+ // Update the label text
1781
+ const labelEl = appState.activeElement.querySelector('.label');
1782
+ if (labelEl) {
1783
+ labelEl.textContent = textValue;
1784
+
1785
+ // Save text to slot data
1786
+ saveElementText(appState.activeElement.id, 'label', textValue);
1787
+ }
1788
+ } else if (appState.activeElement.classList.contains('flashcard')) {
1789
+ // For flashcards, we might want to update a title or caption
1790
+ // This is a placeholder for future functionality
1791
+ console.log('Updating flashcard text:', textValue);
1792
+ }
1793
+ }
1794
+
1795
+ // Update element style
1796
+ function updateElementStyle(property, value) {
1797
+ if (!appState.activeElement) return;
1798
+
1799
+ // For label containers, update the label element
1800
+ if (appState.activeElement.classList.contains('label-container')) {
1801
+ const labelEl = appState.activeElement.querySelector('.label');
1802
+ if (labelEl) {
1803
+ labelEl.style[property] = value;
1804
+
1805
+ // Save style to slot data
1806
+ saveElementStyle(appState.activeElement.id, property, value);
1807
+ }
1808
+ } else if (appState.activeElement.classList.contains('flashcard')) {
1809
+ // For flashcards, update the card itself
1810
+ appState.activeElement.style[property] = value;
1811
+
1812
+ // Save style to slot data
1813
+ saveElementStyle(appState.activeElement.id, property, value);
1814
+ }
1815
+ }
1816
+
1817
+ // Update element position
1818
+ function updateElementPosition() {
1819
+ if (!appState.activeElement) return;
1820
+
1821
+ const x = parseFloat(elements.positionX.value);
1822
+ const y = parseFloat(elements.positionY.value);
1823
+
1824
+ if (!isNaN(x) && !isNaN(y)) {
1825
+ appState.activeElement.style.left = `${x}%`;
1826
+ appState.activeElement.style.top = `${y}%`;
1827
+
1828
+ // Save position to current slot
1829
+ saveElementPosition(appState.activeElement, x, y);
1830
+ }
1831
+ }
1832
+
1833
+ // Update element size
1834
+ function updateElementSize() {
1835
+ if (!appState.activeElement) return;
1836
+
1837
+ const width = parseFloat(elements.elementWidth.value);
1838
+ const height = parseFloat(elements.elementHeight.value);
1839
+
1840
+ if (!isNaN(width)) {
1841
+ appState.activeElement.style.width = `${width}px`;
1842
+
1843
+ // Save width to slot data
1844
+ saveElementSize(appState.activeElement.id, 'width', width);
1845
+ }
1846
+
1847
+ if (!isNaN(height)) {
1848
+ appState.activeElement.style.height = `${height}px`;
1849
+
1850
+ // Save height to slot data
1851
+ saveElementSize(appState.activeElement.id, 'height', height);
1852
+ }
1853
+ }
1854
+
1855
+ // Save element text to current slot
1856
+ function saveElementText(elementId, textType, value) {
1857
+ if (!appState.currentSlotId) return;
1858
+
1859
+ const slot = findSlotById(appState.currentSlotId);
1860
+ if (!slot) return;
1861
+
1862
+ // Initialize elements array if needed
1863
+ if (!slot.elements) {
1864
+ slot.elements = [];
1865
+ }
1866
+
1867
+ // Find element in slot or create new entry
1868
+ let elementData = slot.elements.find(el => el.id === elementId);
1869
+ if (!elementData) {
1870
+ elementData = { id: elementId };
1871
+ slot.elements.push(elementData);
1872
+ }
1873
+
1874
+ // Initialize textContent object if needed
1875
+ if (!elementData.textContent) {
1876
+ elementData.textContent = {};
1877
+ }
1878
+
1879
+ // Update text content
1880
+ elementData.textContent[textType] = value;
1881
+ }
1882
+
1883
+ // Save element style to current slot
1884
+ function saveElementStyle(elementId, property, value) {
1885
+ if (!appState.currentSlotId) return;
1886
+
1887
+ const slot = findSlotById(appState.currentSlotId);
1888
+ if (!slot) return;
1889
+
1890
+ // Initialize elements array if needed
1891
+ if (!slot.elements) {
1892
+ slot.elements = [];
1893
+ }
1894
+
1895
+ // Find element in slot or create new entry
1896
+ let elementData = slot.elements.find(el => el.id === elementId);
1897
+ if (!elementData) {
1898
+ elementData = { id: elementId };
1899
+ slot.elements.push(elementData);
1900
+ }
1901
+
1902
+ // Initialize styles object if needed
1903
+ if (!elementData.styles) {
1904
+ elementData.styles = {};
1905
+ }
1906
+
1907
+ // Update style
1908
+ elementData.styles[property] = value;
1909
+ }
1910
+
1911
+ // Save element size to current slot
1912
+ function saveElementSize(elementId, dimension, value) {
1913
+ if (!appState.currentSlotId) return;
1914
+
1915
+ const slot = findSlotById(appState.currentSlotId);
1916
+ if (!slot) return;
1917
+
1918
+ // Initialize elements array if needed
1919
+ if (!slot.elements) {
1920
+ slot.elements = [];
1921
+ }
1922
+
1923
+ // Find element in slot or create new entry
1924
+ let elementData = slot.elements.find(el => el.id === elementId);
1925
+ if (!elementData) {
1926
+ elementData = { id: elementId };
1927
+ slot.elements.push(elementData);
1928
+ }
1929
+
1930
+ // Update dimension
1931
+ elementData[dimension] = value;
1932
+ }
1933
+
1934
+ // Update animation duration for all elements
1935
+ function updateAnimationDuration(duration) {
1936
+ // Update CSS animation duration for all animated elements
1937
+ const animatedElements = document.querySelectorAll('.anim-slideIn, .anim-fadeGrow, .anim-flip, .anim-bounce, .anim-rotate');
1938
+ animatedElements.forEach(el => {
1939
+ el.style.animationDuration = `${duration}s`;
1940
+ });
1941
+ }
1942
+
1943
+ // Reset and animate default content with the specified style
1944
+ function resetAndAnimateDefaultContent(style) {
1945
+ if (!appState.defaultVisible) return;
1946
+
1947
+ const defaultElements = [
1948
+ elements.leftCard,
1949
+ elements.leftLabel,
1950
+ elements.rightCard,
1951
+ elements.rightLabel
1952
+ ];
1953
+
1954
+ // Reset animations and hide elements
1955
+ defaultElements.forEach(el => {
1956
+ if (el) {
1957
+ // Remove existing animation classes
1958
+ el.className = el.className.split(' ')
1959
+ .filter(c => !c.startsWith('anim-') && !c.startsWith('delay-'))
1960
+ .join(' ');
1961
+
1962
+ // Hide element
1963
+ el.style.opacity = '0';
1964
+ el.style.visibility = 'hidden';
1965
+ }
1966
+ });
1967
+
1968
+ // Apply animations with delay
1969
+ setTimeout(() => {
1970
+ defaultElements.forEach((el, index) => {
1971
+ if (el) {
1972
+ setTimeout(() => {
1973
+ // Show element
1974
+ el.style.opacity = '1';
1975
+ el.style.visibility = 'visible';
1976
+
1977
+ // Apply animation classes
1978
+ el.classList.add(`anim-${style}`);
1979
+ el.classList.add(`delay-${index}`);
1980
+ }, index * 200);
1981
+ }
1982
+ });
1983
+ }, 100);
1984
+ }
1985
+
1986
+ // Select animation style button
1987
+ function selectAnimationStyle(style) {
1988
+ const allStyleOptions = elements.styleSelector.querySelectorAll('.style-option');
1989
+ allStyleOptions.forEach(option => {
1990
+ option.classList.remove('active');
1991
+ if (option.dataset.style === style) {
1992
+ option.classList.add('active');
1993
+ }
1994
+ });
1995
+
1996
+ // Update current slot with new style
1997
+ if (appState.currentSlotId) {
1998
+ const slot = findSlotById(appState.currentSlotId);
1999
+ if (slot) {
2000
+ slot.animationStyle = style;
2001
+ saveToStorage();
2002
+ }
2003
+ }
2004
+ }
2005
+
2006
+ // App initialization
2007
+ init();
2008
+
2009
+ // Function to toggle preview mode
2010
+ function togglePreview() {
2011
+ appState.previewMode = !appState.previewMode;
2012
+
2013
+ if (appState.previewMode) {
2014
+ // Open preview
2015
+ elements.previewOverlay.classList.add('active');
2016
+ startPreview();
2017
+ } else {
2018
+ // Close preview
2019
+ closePreview();
2020
+ }
2021
+ }
2022
+
2023
+ // Function to close preview
2024
+ function closePreview() {
2025
+ elements.previewOverlay.classList.remove('active');
2026
+ appState.previewMode = false;
2027
+
2028
+ // Stop any running timers
2029
+ if (appState.previewTimer) {
2030
+ clearTimeout(appState.previewTimer);
2031
+ appState.previewTimer = null;
2032
+ }
2033
+ }
2034
+
2035
+ // Function to start preview
2036
+ function startPreview() {
2037
+ // Reset preview state
2038
+ appState.previewCurrentSlot = 0;
2039
+ appState.previewPlaying = true;
2040
+
2041
+ // Clear any existing timer
2042
+ if (appState.previewTimer) {
2043
+ clearTimeout(appState.previewTimer);
2044
+ }
2045
+
2046
+ // Render first slot
2047
+ renderPreviewSlot(appState.previewCurrentSlot);
2048
+
2049
+ // Update play/pause button
2050
+ updatePlayPauseButton();
2051
+ }
2052
+
2053
+ // Function to render preview slot
2054
+ function renderPreviewSlot(index) {
2055
+ if (index < 0 || index >= appState.slots.length) return;
2056
+
2057
+ const slot = appState.slots[index];
2058
+
2059
+ // Clone the canvas content for preview
2060
+ const previewContent = document.createElement('div');
2061
+ previewContent.className = 'canvas-container';
2062
+
2063
+ // Clear previous content
2064
+ elements.previewContainer.innerHTML = '';
2065
+ elements.previewContainer.appendChild(previewContent);
2066
+
2067
+ // Add controls back
2068
+ const controlsHTML = `
2069
+ <div class="preview-close" id="closePreviewBtn">
2070
+ <i class="fas fa-times"></i>
2071
+ </div>
2072
+ <div class="preview-controls">
2073
+ <div class="preview-control" id="prevSlotBtn">
2074
+ <i class="fas fa-step-backward"></i>
2075
+ </div>
2076
+ <div class="preview-control" id="playPauseBtn">
2077
+ <i class="fas fa-${appState.previewPlaying ? 'pause' : 'play'}"></i>
2078
+ </div>
2079
+ <div class="preview-control" id="nextSlotBtn">
2080
+ <i class="fas fa-step-forward"></i>
2081
+ </div>
2082
+ </div>
2083
+ <div class="preview-progress" id="previewProgress"></div>
2084
+ `;
2085
+ elements.previewContainer.insertAdjacentHTML('beforeend', controlsHTML);
2086
+
2087
+ // Re-attach event listeners to controls
2088
+ document.getElementById('closePreviewBtn').addEventListener('click', closePreview);
2089
+ document.getElementById('playPauseBtn').addEventListener('click', togglePlayPause);
2090
+ document.getElementById('prevSlotBtn').addEventListener('click', goToPreviousSlot);
2091
+ document.getElementById('nextSlotBtn').addEventListener('click', goToNextSlot);
2092
+
2093
+ // Get the animation style and duration
2094
+ const style = slot.animationStyle || 'slideIn';
2095
+ const animationDuration = slot.animationDuration || 0.8;
2096
+ const sceneDuration = slot.duration || 3;
2097
+
2098
+ // Create elements based on slot data
2099
+ createPreviewElements(previewContent, slot, style, animationDuration);
2100
+
2101
+ // Start progress bar animation
2102
+ startProgressAnimation(sceneDuration);
2103
+
2104
+ // Schedule next slot if playing
2105
+ if (appState.previewPlaying) {
2106
+ appState.previewTimer = setTimeout(() => {
2107
+ if (index < appState.slots.length - 1) {
2108
+ appState.previewCurrentSlot++;
2109
+ renderPreviewSlot(appState.previewCurrentSlot);
2110
+ } else {
2111
+ // End of preview, loop back to start
2112
+ appState.previewCurrentSlot = 0;
2113
+ renderPreviewSlot(appState.previewCurrentSlot);
2114
+ }
2115
+ }, sceneDuration * 1000);
2116
+ }
2117
+ }
2118
+
2119
+ // Create preview elements
2120
+ function createPreviewElements(container, slot, style, animationDuration) {
2121
+ // Default elements if no custom positions
2122
+ if (!slot.elements || slot.elements.length === 0) {
2123
+ // Create default elements
2124
+ const leftCard = document.createElement('div');
2125
+ leftCard.className = `flashcard flashcard-left anim-${style} delay-0`;
2126
+ leftCard.style.animationDuration = `${animationDuration}s`;
2127
+ leftCard.innerHTML = '<div class="card-content">' + svgContent.mountain + '</div>';
2128
+
2129
+ const leftLabel = document.createElement('div');
2130
+ leftLabel.className = `label-container label-left anim-${style} delay-1`;
2131
+ leftLabel.style.animationDuration = `${animationDuration}s`;
2132
+ leftLabel.innerHTML = '<div class="label">Kitty</div><div class="translation">mountain</div>';
2133
+
2134
+ const rightCard = document.createElement('div');
2135
+ rightCard.className = `flashcard flashcard-right anim-${style} delay-2`;
2136
+ rightCard.style.animationDuration = `${animationDuration}s`;
2137
+ rightCard.innerHTML = '<div class="card-content">' + svgContent.beach + '</div>';
2138
+
2139
+ const rightLabel = document.createElement('div');
2140
+ rightLabel.className = `label-container label-right anim-${style} delay-3`;
2141
+ rightLabel.style.animationDuration = `${animationDuration}s`;
2142
+ rightLabel.innerHTML = '<div class="label">playa</div><div class="translation">beach</div>';
2143
+
2144
+ container.appendChild(leftCard);
2145
+ container.appendChild(leftLabel);
2146
+ container.appendChild(rightCard);
2147
+ container.appendChild(rightLabel);
2148
+ } else {
2149
+ // Create elements with custom positions
2150
+ const elementMap = {
2151
+ 'leftCard': {
2152
+ className: 'flashcard',
2153
+ content: '<div class="card-content">' + svgContent.mountain + '</div>',
2154
+ delay: 0
2155
+ },
2156
+ 'rightCard': {
2157
+ className: 'flashcard',
2158
+ content: '<div class="card-content">' + svgContent.beach + '</div>',
2159
+ delay: 2
2160
+ },
2161
+ 'leftLabel': {
2162
+ className: 'label-container',
2163
+ content: '<div class="label">Kitty</div><div class="translation">mountain</div>',
2164
+ delay: 1
2165
+ },
2166
+ 'rightLabel': {
2167
+ className: 'label-container',
2168
+ content: '<div class="label">playa</div><div class="translation">beach</div>',
2169
+ delay: 3
2170
+ }
2171
+ };
2172
+
2173
+ slot.elements.forEach(elementData => {
2174
+ const template = elementMap[elementData.id];
2175
+ if (!template) return;
2176
+
2177
+ const element = document.createElement('div');
2178
+ element.className = `${template.className} anim-${style} delay-${template.delay}`;
2179
+ element.style.animationDuration = `${animationDuration}s`;
2180
+
2181
+ // Apply custom text content if available
2182
+ let content = template.content;
2183
+ if (elementData.textContent && elementData.textContent.label) {
2184
+ // For label containers, update the label text
2185
+ if (template.className === 'label-container') {
2186
+ const labelText = elementData.textContent.label;
2187
+ const translationText = elementData.textContent.translation ||
2188
+ (elementData.id === 'leftLabel' ? 'mountain' : 'beach');
2189
+
2190
+ content = `<div class="label">${labelText}</div><div class="translation">${translationText}</div>`;
2191
+ }
2192
+ }
2193
+
2194
+ element.innerHTML = content;
2195
+
2196
+ // Apply position
2197
+ if (typeof elementData.left === 'number' && typeof elementData.top === 'number') {
2198
+ element.style.position = 'absolute';
2199
+ element.style.left = `${elementData.left}%`;
2200
+ element.style.top = `${elementData.top}%`;
2201
+ }
2202
+
2203
+ // Apply custom styles if available
2204
+ if (elementData.styles) {
2205
+ Object.entries(elementData.styles).forEach(([property, value]) => {
2206
+ // For label containers, apply styles to the label element
2207
+ if (template.className === 'label-container' &&
2208
+ (property === 'color' || property === 'backgroundColor' || property === 'fontSize')) {
2209
+ // We need to wait for the element to be in the DOM
2210
+ setTimeout(() => {
2211
+ const labelEl = element.querySelector('.label');
2212
+ if (labelEl) {
2213
+ labelEl.style[property] = value;
2214
+ }
2215
+ }, 0);
2216
+ } else {
2217
+ element.style[property] = value;
2218
+ }
2219
+ });
2220
+ }
2221
+
2222
+ // Apply custom size if available
2223
+ if (typeof elementData.width === 'number') {
2224
+ element.style.width = `${elementData.width}px`;
2225
+ }
2226
+
2227
+ if (typeof elementData.height === 'number') {
2228
+ element.style.height = `${elementData.height}px`;
2229
+ }
2230
+
2231
+ container.appendChild(element);
2232
+ });
2233
+ }
2234
+ }
2235
+
2236
+ // Start progress bar animation
2237
+ function startProgressAnimation(duration) {
2238
+ const progressBar = document.getElementById('previewProgress');
2239
+ if (!progressBar) return;
2240
+
2241
+ // Reset progress
2242
+ progressBar.style.width = '0%';
2243
+
2244
+ // Animate progress
2245
+ progressBar.style.transition = `width ${duration}s linear`;
2246
+
2247
+ // Force reflow
2248
+ progressBar.offsetWidth;
2249
+
2250
+ // Start animation
2251
+ progressBar.style.width = '100%';
2252
+ }
2253
+
2254
+ // Function to toggle play/pause
2255
+ function togglePlayPause() {
2256
+ appState.previewPlaying = !appState.previewPlaying;
2257
+
2258
+ // Update button icon
2259
+ updatePlayPauseButton();
2260
+
2261
+ if (appState.previewPlaying) {
2262
+ // Resume playback
2263
+ const slot = appState.slots[appState.previewCurrentSlot];
2264
+ const remainingTime = getRemainingTime();
2265
+
2266
+ if (remainingTime > 0) {
2267
+ // Continue with remaining time
2268
+ appState.previewTimer = setTimeout(() => {
2269
+ if (appState.previewCurrentSlot < appState.slots.length - 1) {
2270
+ appState.previewCurrentSlot++;
2271
+ renderPreviewSlot(appState.previewCurrentSlot);
2272
+ } else {
2273
+ // End of preview, loop back to start
2274
+ appState.previewCurrentSlot = 0;
2275
+ renderPreviewSlot(appState.previewCurrentSlot);
2276
+ }
2277
+ }, remainingTime * 1000);
2278
+ } else {
2279
+ // Move to next slot
2280
+ if (appState.previewCurrentSlot < appState.slots.length - 1) {
2281
+ appState.previewCurrentSlot++;
2282
+ } else {
2283
+ appState.previewCurrentSlot = 0;
2284
+ }
2285
+ renderPreviewSlot(appState.previewCurrentSlot);
2286
+ }
2287
+ } else {
2288
+ // Pause playback
2289
+ if (appState.previewTimer) {
2290
+ clearTimeout(appState.previewTimer);
2291
+ appState.previewTimer = null;
2292
+ }
2293
+
2294
+ // Pause progress bar animation
2295
+ const progressBar = document.getElementById('previewProgress');
2296
+ if (progressBar) {
2297
+ const computedStyle = window.getComputedStyle(progressBar);
2298
+ const width = parseFloat(computedStyle.width) / parseFloat(computedStyle.parentElement.offsetWidth) * 100;
2299
+ progressBar.style.transition = 'none';
2300
+ progressBar.style.width = `${width}%`;
2301
+ }
2302
+ }
2303
+ }
2304
+
2305
+ // Update play/pause button icon
2306
+ function updatePlayPauseButton() {
2307
+ const playPauseBtn = document.getElementById('playPauseBtn');
2308
+ if (playPauseBtn) {
2309
+ playPauseBtn.innerHTML = `<i class="fas fa-${appState.previewPlaying ? 'pause' : 'play'}"></i>`;
2310
+ }
2311
+ }
2312
+
2313
+ // Get remaining time for current slot
2314
+ function getRemainingTime() {
2315
+ const progressBar = document.getElementById('previewProgress');
2316
+ if (!progressBar) return 0;
2317
+
2318
+ const computedStyle = window.getComputedStyle(progressBar);
2319
+ const width = parseFloat(computedStyle.width) / parseFloat(computedStyle.parentElement.offsetWidth) * 100;
2320
+ const slot = appState.slots[appState.previewCurrentSlot];
2321
+ const totalDuration = slot.duration || 3;
2322
+
2323
+ return totalDuration * (1 - width / 100);
2324
+ }
2325
+
2326
+ // Function to go to previous slot
2327
+ function goToPreviousSlot() {
2328
+ // Clear current timer
2329
+ if (appState.previewTimer) {
2330
+ clearTimeout(appState.previewTimer);
2331
+ appState.previewTimer = null;
2332
+ }
2333
+
2334
+ // Go to previous slot
2335
+ appState.previewCurrentSlot = Math.max(0, appState.previewCurrentSlot - 1);
2336
+ renderPreviewSlot(appState.previewCurrentSlot);
2337
+ }
2338
+
2339
+ // Function to go to next slot
2340
+ function goToNextSlot() {
2341
+ // Clear current timer
2342
+ if (appState.previewTimer) {
2343
+ clearTimeout(appState.previewTimer);
2344
+ appState.previewTimer = null;
2345
+ }
2346
+
2347
+ // Go to next slot
2348
+ appState.previewCurrentSlot = Math.min(appState.slots.length - 1, appState.previewCurrentSlot + 1);
2349
+ renderPreviewSlot(appState.previewCurrentSlot);
2350
+ }
2351
+
2352
+ // Function to close property editor
2353
+ function closePropertyEditor() {
2354
+ elements.propertyEditor.classList.remove('active');
2355
+ }
2356
+
2357
+ // Function to add a new slot
2358
+ function addNewSlot() {
2359
+ // Create new slot object
2360
+ const newSlot = {
2361
+ id: Date.now().toString(),
2362
+ name: `Scene ${appState.slots.length + 1}`,
2363
+ animationStyle: 'slideIn',
2364
+ duration: 3, // Default scene duration in seconds
2365
+ animationDuration: 0.8, // Default animation duration in seconds
2366
+ elements: [] // Will store element data
2367
+ };
2368
+
2369
+ // Add to slots array
2370
+ appState.slots.push(newSlot);
2371
+
2372
+ // Render slots list
2373
+ renderSlotsList();
2374
+
2375
+ // Select the new slot
2376
+ selectSlot(newSlot.id);
2377
+
2378
+ // Save to storage
2379
+ saveToStorage();
2380
+ }
2381
+
2382
+ // Function to delete a slot
2383
+ function deleteSlot(slotId) {
2384
+ // Find index of slot
2385
+ const index = appState.slots.findIndex(slot => slot.id === slotId);
2386
+ if (index === -1) return;
2387
+
2388
+ // Remove slot
2389
+ appState.slots.splice(index, 1);
2390
+
2391
+ // If deleted slot was selected, select another slot
2392
+ if (appState.currentSlotId === slotId) {
2393
+ appState.currentSlotId = appState.slots.length > 0 ? appState.slots[0].id : null;
2394
+ }
2395
+
2396
+ // Render slots list
2397
+ renderSlotsList();
2398
+
2399
+ // If there are slots, select one
2400
+ if (appState.slots.length > 0 && appState.currentSlotId) {
2401
+ selectSlot(appState.currentSlotId);
2402
+ }
2403
+
2404
+ // Save to storage
2405
+ saveToStorage();
2406
+ }
2407
+
2408
+ // Function to render slots list
2409
+ function renderSlotsList() {
2410
+ elements.slotsList.innerHTML = '';
2411
+
2412
+ appState.slots.forEach(slot => {
2413
+ const slotElement = document.createElement('div');
2414
+ slotElement.className = `slot-item ${slot.id === appState.currentSlotId ? 'active' : ''}`;
2415
+ slotElement.dataset.id = slot.id;
2416
+
2417
+ // Get duration or use default
2418
+ const duration = slot.duration || 3;
2419
+
2420
+ slotElement.innerHTML = `
2421
+ <div class="slot-header">
2422
+ <div class="slot-name">${slot.name}</div>
2423
+ <div class="slot-delete">
2424
+ <i class="fas fa-trash"></i>
2425
+ </div>
2426
+ </div>
2427
+ <div class="slot-preview">
2428
+ <div class="slot-info">
2429
+ <div class="slot-details">
2430
+ <i class="fas fa-film"></i> ${slot.animationStyle || 'slideIn'}
2431
+ <span class="duration-badge">${duration}s</span>
2432
+ </div>
2433
+ </div>
2434
+ </div>
2435
+ `;
2436
+
2437
+ elements.slotsList.appendChild(slotElement);
2438
+ });
2439
+ }
2440
+
2441
+ // Function to select a slot
2442
+ function selectSlot(slotId) {
2443
+ // Update current slot ID
2444
+ appState.currentSlotId = slotId;
2445
+
2446
+ // Update active class in slots list
2447
+ const allSlots = elements.slotsList.querySelectorAll('.slot-item');
2448
+ allSlots.forEach(slotElement => {
2449
+ if (slotElement.dataset.id === slotId) {
2450
+ slotElement.classList.add('active');
2451
+ } else {
2452
+ slotElement.classList.remove('active');
2453
+ }
2454
+ });
2455
+
2456
+ // Get selected slot
2457
+ const slot = findSlotById(slotId);
2458
+ if (!slot) return;
2459
+
2460
+ // Set animation style
2461
+ const style = slot.animationStyle || 'slideIn';
2462
+ selectAnimationStyle(style);
2463
+
2464
+ // Update timing controls
2465
+ updateTimingControlsFromSlot(slot);
2466
+
2467
+ // Apply element positions from slot data
2468
+ applyElementPositionsFromSlot(slot);
2469
+ }
2470
+
2471
+ // Update timing controls from slot data
2472
+ function updateTimingControlsFromSlot(slot) {
2473
+ // Update animation duration
2474
+ if (elements.animationDuration && slot.animationDuration) {
2475
+ elements.animationDuration.value = slot.animationDuration;
2476
+ elements.animationDurationValue.textContent = `${slot.animationDuration}s`;
2477
+ updateAnimationDuration(slot.animationDuration);
2478
+ }
2479
+
2480
+ // Update scene duration
2481
+ if (elements.sceneDuration && slot.duration) {
2482
+ elements.sceneDuration.value = slot.duration;
2483
+ elements.sceneDurationValue.textContent = `${slot.duration}s`;
2484
+ }
2485
+ }
2486
+
2487
+ // Apply element positions from slot data
2488
+ function applyElementPositionsFromSlot(slot) {
2489
+ if (!slot.elements || !Array.isArray(slot.elements)) return;
2490
+
2491
+ // Map of element IDs to DOM elements
2492
+ const elementMap = {
2493
+ 'leftCard': elements.leftCard,
2494
+ 'rightCard': elements.rightCard,
2495
+ 'leftLabel': elements.leftLabel,
2496
+ 'rightLabel': elements.rightLabel
2497
+ };
2498
+
2499
+ // Apply positions to elements
2500
+ slot.elements.forEach(elementData => {
2501
+ const element = elementMap[elementData.id] || document.getElementById(elementData.id);
2502
+ if (!element) return;
2503
+
2504
+ // Apply position if available
2505
+ if (typeof elementData.left === 'number' && typeof elementData.top === 'number') {
2506
+ element.style.left = `${elementData.left}%`;
2507
+ element.style.top = `${elementData.top}%`;
2508
+
2509
+ // Remove fixed positioning classes
2510
+ if (element === elements.leftCard) {
2511
+ element.classList.remove('flashcard-left');
2512
+ } else if (element === elements.rightCard) {
2513
+ element.classList.remove('flashcard-right');
2514
+ } else if (element === elements.leftLabel) {
2515
+ element.classList.remove('label-left');
2516
+ } else if (element === elements.rightLabel) {
2517
+ element.classList.remove('label-right');
2518
+ }
2519
+ }
2520
+
2521
+ // Apply text content if available
2522
+ if (elementData.textContent) {
2523
+ // For label containers, update the label text
2524
+ if (element.classList.contains('label-container')) {
2525
+ if (elementData.textContent.label) {
2526
+ const labelEl = element.querySelector('.label');
2527
+ if (labelEl) {
2528
+ labelEl.textContent = elementData.textContent.label;
2529
+ }
2530
+ }
2531
+
2532
+ if (elementData.textContent.translation) {
2533
+ const translationEl = element.querySelector('.translation');
2534
+ if (translationEl) {
2535
+ translationEl.textContent = elementData.textContent.translation;
2536
+ }
2537
+ }
2538
+ }
2539
+ }
2540
+
2541
+ // Apply styles if available
2542
+ if (elementData.styles) {
2543
+ Object.entries(elementData.styles).forEach(([property, value]) => {
2544
+ // For label containers, apply styles to the label element
2545
+ if (element.classList.contains('label-container') &&
2546
+ (property === 'color' || property === 'backgroundColor' || property === 'fontSize')) {
2547
+ const labelEl = element.querySelector('.label');
2548
+ if (labelEl) {
2549
+ labelEl.style[property] = value;
2550
+ }
2551
+ } else {
2552
+ element.style[property] = value;
2553
+ }
2554
+ });
2555
+ }
2556
+
2557
+ // Apply size if available
2558
+ if (typeof elementData.width === 'number') {
2559
+ element.style.width = `${elementData.width}px`;
2560
+ }
2561
+
2562
+ if (typeof elementData.height === 'number') {
2563
+ element.style.height = `${elementData.height}px`;
2564
+ }
2565
+ });
2566
+ }
2567
+
2568
+ // Function to find slot by ID
2569
+ function findSlotById(id) {
2570
+ return appState.slots.find(slot => slot.id === id);
2571
+ }
2572
+
2573
+ // Function to open property editor
2574
+ function openPropertyEditor(element) {
2575
+ if (!element) return;
2576
+
2577
+ // Show property editor
2578
+ elements.propertyEditor.classList.add('active');
2579
+
2580
+ // Store active element
2581
+ appState.activeElement = element;
2582
+
2583
+ // Get current slot
2584
+ const slot = findSlotById(appState.currentSlotId);
2585
+ if (!slot) return;
2586
+
2587
+ // Load element properties
2588
+ loadElementProperties(element);
2589
+
2590
+ // Load timing values from slot
2591
+ updateTimingControlsFromSlot(slot);
2592
+ }
2593
+
2594
+ // Load element properties into editor
2595
+ function loadElementProperties(element) {
2596
+ // Get element position
2597
+ const rect = element.getBoundingClientRect();
2598
+ const canvasRect = elements.canvas.getBoundingClientRect();
2599
+
2600
+ // Calculate position as percentage of canvas
2601
+ const posX = (rect.left - canvasRect.left) / canvasRect.width * 100;
2602
+ const posY = (rect.top - canvasRect.top) / canvasRect.height * 100;
2603
+
2604
+ // Update position fields
2605
+ if (elements.positionX) elements.positionX.value = Math.round(posX);
2606
+ if (elements.positionY) elements.positionY.value = Math.round(posY);
2607
+
2608
+ // Update size fields
2609
+ if (elements.elementWidth) elements.elementWidth.value = Math.round(rect.width);
2610
+ if (elements.elementHeight) elements.elementHeight.value = Math.round(rect.height);
2611
+
2612
+ // Show/hide appropriate property sections
2613
+ if (element.classList.contains('label-container')) {
2614
+ // Show text properties, hide image properties
2615
+ elements.textProperties.style.display = 'block';
2616
+ elements.imageProperties.style.display = 'none';
2617
+
2618
+ // Load text content
2619
+ const labelEl = element.querySelector('.label');
2620
+ const translationEl = element.querySelector('.translation');
2621
+
2622
+ if (labelEl && elements.textContent) {
2623
+ elements.textContent.value = labelEl.textContent;
2624
+ }
2625
+
2626
+ // Add translation field if it doesn't exist
2627
+ if (!document.getElementById('translationContent')) {
2628
+ const translationGroup = document.createElement('div');
2629
+ translationGroup.className = 'property-group';
2630
+ translationGroup.innerHTML = `
2631
+ <div class="property-label">Translation</div>
2632
+ <input type="text" class="property-field" id="translationContent">
2633
+ `;
2634
+
2635
+ // Insert after text content
2636
+ const textContentGroup = elements.textContent.closest('.property-group');
2637
+ textContentGroup.parentNode.insertBefore(translationGroup, textContentGroup.nextSibling);
2638
+
2639
+ // Add event listeners
2640
+ const translationInput = document.getElementById('translationContent');
2641
+ translationInput.addEventListener('input', updateTranslationText);
2642
+ translationInput.addEventListener('change', function() {
2643
+ updateTranslationText();
2644
+ saveToStorage();
2645
+ });
2646
+ }
2647
+
2648
+ // Set translation value
2649
+ const translationInput = document.getElementById('translationContent');
2650
+ if (translationEl && translationInput) {
2651
+ translationInput.value = translationEl.textContent;
2652
+ }
2653
+
2654
+ // Load text color and background
2655
+ if (labelEl && elements.textColor) {
2656
+ const computedStyle = window.getComputedStyle(labelEl);
2657
+ elements.textColor.value = rgbToHex(computedStyle.color);
2658
+ elements.textBgColor.value = rgbToHex(computedStyle.backgroundColor);
2659
+ }
2660
+ } else if (element.classList.contains('flashcard')) {
2661
+ // Show image properties, hide text properties
2662
+ elements.textProperties.style.display = 'none';
2663
+ elements.imageProperties.style.display = 'block';
2664
+
2665
+ // Load image preview if available
2666
+ const imgEl = element.querySelector('img');
2667
+ if (imgEl && elements.currentImage) {
2668
+ elements.currentImage.src = imgEl.src;
2669
+ }
2670
+
2671
+ // Remove translation field if it exists
2672
+ const translationGroup = document.getElementById('translationContent')?.closest('.property-group');
2673
+ if (translationGroup) {
2674
+ translationGroup.remove();
2675
+ }
2676
+ }
2677
+
2678
+ // Load animation delay
2679
+ const delayClass = Array.from(element.classList).find(cls => cls.startsWith('delay-'));
2680
+ if (delayClass && elements.animationDelay) {
2681
+ const delay = delayClass.split('-')[1];
2682
+ elements.animationDelay.value = delay;
2683
+ }
2684
+ }
2685
+
2686
+ // Update translation text
2687
+ function updateTranslationText() {
2688
+ if (!appState.activeElement) return;
2689
+
2690
+ const translationInput = document.getElementById('translationContent');
2691
+ if (!translationInput) return;
2692
+
2693
+ const translationValue = translationInput.value;
2694
+
2695
+ // Check if it's a label container
2696
+ if (appState.activeElement.classList.contains('label-container')) {
2697
+ // Update the translation text
2698
+ const translationEl = appState.activeElement.querySelector('.translation');
2699
+ if (translationEl) {
2700
+ translationEl.textContent = translationValue;
2701
+
2702
+ // Save text to slot data
2703
+ saveElementText(appState.activeElement.id, 'translation', translationValue);
2704
+ }
2705
+ }
2706
+ }
2707
+
2708
+ // Convert RGB to Hex color
2709
+ function rgbToHex(rgb) {
2710
+ // Default to black if not available
2711
+ if (!rgb || rgb === 'transparent') return '#000000';
2712
+
2713
+ // Check if already in hex format
2714
+ if (rgb.startsWith('#')) return rgb;
2715
+
2716
+ // Extract RGB values
2717
+ const rgbMatch = rgb.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
2718
+ if (rgbMatch) {
2719
+ const r = parseInt(rgbMatch[1], 10).toString(16).padStart(2, '0');
2720
+ const g = parseInt(rgbMatch[2], 10).toString(16).padStart(2, '0');
2721
+ const b = parseInt(rgbMatch[3], 10).toString(16).padStart(2, '0');
2722
+ return `#${r}${g}${b}`;
2723
+ }
2724
+
2725
+ return '#000000';
2726
+ }
2727
+
2728
+ // Function to save data to storage
2729
+ function saveToStorage() {
2730
+ try {
2731
+ const dataToSave = {
2732
+ slots: appState.slots,
2733
+ currentSlotId: appState.currentSlotId
2734
+ };
2735
+ localStorage.setItem('flashcardAnimatorData', JSON.stringify(dataToSave));
2736
+ console.log('Data saved to storage');
2737
+ } catch (error) {
2738
+ console.error('Error saving to storage:', error);
2739
+ }
2740
+ }
2741
+
2742
+ // Function to load data from storage
2743
+ function loadFromStorage() {
2744
+ try {
2745
+ const savedData = localStorage.getItem('flashcardAnimatorData');
2746
+ if (savedData) {
2747
+ const parsedData = JSON.parse(savedData);
2748
+ appState.slots = parsedData.slots || [];
2749
+ appState.currentSlotId = parsedData.currentSlotId || null;
2750
+ console.log('Data loaded from storage');
2751
+ }
2752
+ } catch (error) {
2753
+ console.error('Error loading from storage:', error);
2754
+ }
2755
+ }
2756
+
2757
+ // Function to show a message to the user
2758
+ function showMessage(text, duration = 3000) {
2759
+ // Create message element if it doesn't exist
2760
+ let messageElement = document.getElementById('app-message');
2761
+ if (!messageElement) {
2762
+ messageElement = document.createElement('div');
2763
+ messageElement.id = 'app-message';
2764
+ messageElement.style.cssText = `
2765
+ position: fixed;
2766
+ bottom: 20px;
2767
+ left: 50%;
2768
+ transform: translateX(-50%);
2769
+ background-color: rgba(0, 0, 0, 0.8);
2770
+ color: white;
2771
+ padding: 10px 20px;
2772
+ border-radius: 4px;
2773
+ font-size: 14px;
2774
+ z-index: 1000;
2775
+ opacity: 0;
2776
+ transition: opacity 0.3s;
2777
+ `;
2778
+ document.body.appendChild(messageElement);
2779
+ }
2780
+
2781
+ // Set message text
2782
+ messageElement.textContent = text;
2783
+
2784
+ // Show message
2785
+ messageElement.style.opacity = '1';
2786
+
2787
+ // Hide after duration
2788
+ setTimeout(() => {
2789
+ messageElement.style.opacity = '0';
2790
+ }, duration);
2791
+ }
2792
+ });
2793
+ </script>
2794
+ </body>
2795
+ </html>