Skyd3d commited on
Commit
30d19bc
·
verified ·
1 Parent(s): 56eb434

Add 1 files

Browse files
Files changed (1) hide show
  1. index.html +561 -153
index.html CHANGED
@@ -3,65 +3,105 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Character Customizer</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
 
 
 
 
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
 
 
 
 
 
 
 
10
  .character-container {
11
  position: relative;
12
- width: 300px;
13
  height: 500px;
14
- margin: 0 auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
16
- .character-layer {
 
 
17
  position: absolute;
18
  top: 0;
19
- left: 0;
20
  width: 100%;
21
  height: 100%;
22
- background-size: contain;
23
- background-repeat: no-repeat;
24
- background-position: center;
25
  }
 
 
 
 
 
26
  .category-btn.active {
27
- background-color: #3b82f6;
28
  color: white;
 
 
29
  }
 
30
  .item-thumbnail {
31
- transition: all 0.2s ease;
 
 
 
32
  }
 
33
  .item-thumbnail:hover {
34
- transform: scale(1.05);
35
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
36
  }
 
37
  .item-thumbnail.selected {
38
- border: 3px solid #3b82f6;
39
- }
40
- #characterPreview {
41
- background-color: #f3f4f6;
42
- border-radius: 10px;
43
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
44
  }
45
- .upload-modal {
 
46
  display: none;
47
  position: fixed;
48
  top: 0;
49
  left: 0;
50
  width: 100%;
51
  height: 100%;
52
- background-color: rgba(0,0,0,0.5);
53
  z-index: 1000;
54
  justify-content: center;
55
  align-items: center;
 
56
  }
57
- .upload-modal-content {
 
58
  animation: modalFadeIn 0.3s ease-out;
59
- background-color: white;
60
  padding: 2rem;
61
- border-radius: 0.5rem;
62
  width: 90%;
63
  max-width: 500px;
 
 
64
  }
 
65
  @keyframes modalFadeIn {
66
  from {
67
  opacity: 0;
@@ -72,21 +112,135 @@
72
  transform: translateY(0);
73
  }
74
  }
 
75
  #itemsGrid {
76
  display: grid;
77
- grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
78
- gap: 1rem;
79
- padding: 1rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }
81
  </style>
82
  </head>
83
- <body class="bg-gray-100 min-h-screen">
 
 
 
84
  <!-- Header -->
85
- <header class="bg-white shadow-sm">
86
- <div class="container mx-auto px-4 py-6">
87
  <div class="flex justify-between items-center">
88
- <h1 class="text-3xl font-bold text-blue-600">Character Customizer</h1>
89
- <button id="adminBtn" class="bg-gray-800 text-white px-4 py-2 rounded-lg flex items-center">
 
 
90
  <i class="fas fa-user-cog mr-2"></i> Admin Panel
91
  </button>
92
  </div>
@@ -98,22 +252,16 @@
98
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
99
  <!-- Character Preview -->
100
  <div class="lg:col-span-1">
101
- <div class="bg-white rounded-xl shadow-lg p-6 sticky top-8">
102
- <h2 class="text-2xl font-bold mb-6 text-center">Your Character</h2>
103
- <div id="characterPreview" class="character-container mb-6">
104
- <!-- Character layers will be added here dynamically -->
105
- <div id="baseCharacter" class="character-layer" style="background-image: url('https://via.placeholder.com/300x500?text=Base+Character');"></div>
106
- <div id="faceMaskLayer" class="character-layer"></div>
107
- <div id="pantsLayer" class="character-layer"></div>
108
- <div id="hairLayer" class="character-layer"></div>
109
- <div id="capeLayer" class="character-layer"></div>
110
- <div id="backpackLayer" class="character-layer"></div>
111
  </div>
112
- <div class="flex justify-between items-center">
113
- <button id="randomizeBtn" class="bg-blue-100 text-blue-600 px-4 py-2 rounded-lg flex items-center">
114
  <i class="fas fa-random mr-2"></i> Randomize
115
  </button>
116
- <button id="downloadBtn" class="bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center">
117
  <i class="fas fa-download mr-2"></i> Download
118
  </button>
119
  </div>
@@ -122,23 +270,23 @@
122
 
123
  <!-- Customization Panel -->
124
  <div class="lg:col-span-2">
125
- <div class="bg-white rounded-xl shadow-lg overflow-hidden">
126
  <!-- Category Tabs -->
127
- <div class="flex border-b">
128
- <button class="category-btn active px-6 py-4 font-medium" data-category="faceMask">
129
- <i class="fas fa-mask mr-2"></i> Face Masks
130
  </button>
131
- <button class="category-btn px-6 py-4 font-medium" data-category="pants">
132
- <i class="fas fa-tshirt mr-2"></i> Pants
133
  </button>
134
  <button class="category-btn px-6 py-4 font-medium" data-category="hair">
135
  <i class="fas fa-cut mr-2"></i> Hair
136
  </button>
137
- <button class="category-btn px-6 py-4 font-medium" data-category="cape">
138
- <i class="fas fa-scarf mr-2"></i> Capes
139
  </button>
140
- <button class="category-btn px-6 py-4 font-medium" data-category="backpack">
141
- <i class="fas fa-backpack mr-2"></i> Backpacks
142
  </button>
143
  </div>
144
 
@@ -152,43 +300,52 @@
152
  </main>
153
 
154
  <!-- Admin Modal -->
155
- <div id="adminModal" class="upload-modal">
156
- <div class="upload-modal-content">
157
- <div class="flex justify-between items-center mb-4">
158
- <h3 class="text-xl font-bold">Admin Panel</h3>
159
- <button id="closeAdminModal" class="text-gray-500 hover:text-gray-700">
160
  <i class="fas fa-times"></i>
161
  </button>
162
  </div>
163
 
164
- <div class="space-y-4">
165
  <div>
166
- <label class="block text-sm font-medium text-gray-700 mb-1">Category</label>
167
- <select id="adminCategory" class="w-full p-2 border rounded">
168
- <option value="faceMask">Face Mask</option>
169
- <option value="pants">Pants</option>
170
  <option value="hair">Hair</option>
171
- <option value="cape">Cape</option>
172
- <option value="backpack">Backpack</option>
173
  </select>
174
  </div>
175
 
176
  <div>
177
- <label class="block text-sm font-medium text-gray-700 mb-1">Item Name</label>
178
- <input type="text" id="adminItemName" class="w-full p-2 border rounded" placeholder="Enter item name">
179
  </div>
180
 
181
  <div>
182
- <label class="block text-sm font-medium text-gray-700 mb-1">Image URL</label>
183
- <input type="text" id="adminImageUrl" class="w-full p-2 border rounded" placeholder="Enter image URL">
 
 
 
 
 
 
 
 
 
184
  </div>
185
 
186
  <div class="flex space-x-4">
187
- <button id="addItemBtn" class="bg-blue-600 text-white px-4 py-2 rounded flex-1">
188
- Add Item
189
  </button>
190
- <button id="removeItemBtn" class="bg-red-600 text-white px-4 py-2 rounded flex-1">
191
- Remove Item
192
  </button>
193
  </div>
194
  </div>
@@ -196,49 +353,46 @@
196
  </div>
197
 
198
  <script>
199
- // Sample data for character items
 
 
 
 
 
 
 
 
 
 
 
200
  const characterItems = {
201
- faceMask: [
202
- { id: 'mask1', name: 'Surgical Mask', imageUrl: 'https://via.placeholder.com/150?text=Surgical+Mask' },
203
- { id: 'mask2', name: 'Gas Mask', imageUrl: 'https://via.placeholder.com/150?text=Gas+Mask' },
204
- { id: 'mask3', name: 'Ninja Mask', imageUrl: 'https://via.placeholder.com/150?text=Ninja+Mask' },
205
- { id: 'mask4', name: 'Bandana', imageUrl: 'https://via.placeholder.com/150?text=Bandana' }
206
  ],
207
- pants: [
208
- { id: 'pants1', name: 'Jeans', imageUrl: 'https://via.placeholder.com/150?text=Jeans' },
209
- { id: 'pants2', name: 'Cargo Pants', imageUrl: 'https://via.placeholder.com/150?text=Cargo+Pants' },
210
- { id: 'pants3', name: 'Shorts', imageUrl: 'https://via.placeholder.com/150?text=Shorts' },
211
- { id: 'pants4', name: 'Leather Pants', imageUrl: 'https://via.placeholder.com/150?text=Leather+Pants' }
212
  ],
213
  hair: [
214
- { id: 'hair1', name: 'Short Hair', imageUrl: 'https://via.placeholder.com/150?text=Short+Hair' },
215
- { id: 'hair2', name: 'Long Hair', imageUrl: 'https://via.placeholder.com/150?text=Long+Hair' },
216
- { id: 'hair3', name: 'Ponytail', imageUrl: 'https://via.placeholder.com/150?text=Ponytail' },
217
- { id: 'hair4', name: 'Mohawk', imageUrl: 'https://via.placeholder.com/150?text=Mohawk' }
218
  ],
219
- cape: [
220
- { id: 'cape1', name: 'Red Cape', imageUrl: 'https://via.placeholder.com/150?text=Red+Cape' },
221
- { id: 'cape2', name: 'Black Cape', imageUrl: 'https://via.placeholder.com/150?text=Black+Cape' },
222
- { id: 'cape3', name: 'Royal Cape', imageUrl: 'https://via.placeholder.com/150?text=Royal+Cape' },
223
- { id: 'cape4', name: 'Tattered Cape', imageUrl: 'https://via.placeholder.com/150?text=Tattered+Cape' }
224
  ],
225
- backpack: [
226
- { id: 'backpack1', name: 'School Backpack', imageUrl: 'https://via.placeholder.com/150?text=School+Backpack' },
227
- { id: 'backpack2', name: 'Hiking Backpack', imageUrl: 'https://via.placeholder.com/150?text=Hiking+Backpack' },
228
- { id: 'backpack3', name: 'Military Backpack', imageUrl: 'https://via.placeholder.com/150?text=Military+Backpack' },
229
- { id: 'backpack4', name: 'Jetpack', imageUrl: 'https://via.placeholder.com/150?text=Jetpack' }
230
  ]
231
  };
232
-
233
- // Current selections
234
- const currentSelections = {
235
- faceMask: null,
236
- pants: null,
237
- hair: null,
238
- cape: null,
239
- backpack: null
240
- };
241
-
242
  // DOM elements
243
  const categoryButtons = document.querySelectorAll('.category-btn');
244
  const itemsGrid = document.getElementById('itemsGrid');
@@ -249,16 +403,127 @@
249
  const downloadBtn = document.getElementById('downloadBtn');
250
  const addItemBtn = document.getElementById('addItemBtn');
251
  const removeItemBtn = document.getElementById('removeItemBtn');
252
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  // Initialize the app
254
  function init() {
 
 
255
  // Load first category by default
256
- loadCategoryItems('faceMask');
257
 
258
  // Set up event listeners
259
  setupEventListeners();
 
 
 
260
  }
261
-
262
  // Set up all event listeners
263
  function setupEventListeners() {
264
  // Category buttons
@@ -272,47 +537,55 @@
272
  loadCategoryItems(category);
273
  });
274
  });
275
-
276
  // Admin button
277
  adminBtn.addEventListener('click', () => {
278
  adminModal.style.display = 'flex';
279
  });
280
-
281
  // Close admin modal
282
  closeAdminModal.addEventListener('click', () => {
283
  adminModal.style.display = 'none';
284
  });
285
-
286
  // Randomize button
287
  randomizeBtn.addEventListener('click', randomizeCharacter);
288
-
289
  // Download button
290
  downloadBtn.addEventListener('click', downloadCharacter);
291
-
292
  // Add item button
293
  addItemBtn.addEventListener('click', addNewItem);
294
-
295
  // Remove item button
296
  removeItemBtn.addEventListener('click', removeItem);
 
 
 
 
 
 
297
  }
298
-
299
  // Load items for a specific category
300
  function loadCategoryItems(category) {
301
  itemsGrid.innerHTML = '';
302
 
303
  // Add "None" option
304
  const noneItem = document.createElement('div');
305
- noneItem.className = `item-thumbnail bg-gray-100 rounded-lg p-4 flex flex-col items-center justify-center cursor-pointer ${!currentSelections[category] ? 'selected' : ''}`;
306
  noneItem.innerHTML = `
307
- <div class="w-16 h-16 bg-gray-200 rounded-full mb-2 flex items-center justify-center">
308
- <i class="fas fa-times text-gray-500"></i>
309
  </div>
310
- <span class="text-sm font-medium">None</span>
311
  `;
312
  noneItem.addEventListener('click', () => {
313
- // Remove selection
314
- currentSelections[category] = null;
315
- document.getElementById(`${category}Layer`).style.backgroundImage = '';
 
 
316
 
317
  // Update selected state
318
  document.querySelectorAll(`.item-thumbnail`).forEach(item => {
@@ -325,15 +598,16 @@
325
  // Add items for this category
326
  characterItems[category].forEach(item => {
327
  const itemElement = document.createElement('div');
328
- itemElement.className = `item-thumbnail bg-gray-100 rounded-lg p-4 flex flex-col items-center justify-center cursor-pointer ${currentSelections[category] === item.id ? 'selected' : ''}`;
329
  itemElement.innerHTML = `
330
- <img src="${item.imageUrl}" alt="${item.name}" class="w-16 h-16 object-contain mb-2">
331
- <span class="text-sm font-medium">${item.name}</span>
 
 
332
  `;
333
  itemElement.addEventListener('click', () => {
334
- // Update selection
335
- currentSelections[category] = item.id;
336
- document.getElementById(`${category}Layer`).style.backgroundImage = `url('${item.imageUrl}')`;
337
 
338
  // Update selected state
339
  document.querySelectorAll(`.item-thumbnail`).forEach(item => {
@@ -344,8 +618,8 @@
344
  itemsGrid.appendChild(itemElement);
345
  });
346
  }
347
-
348
- // Randomize all character items
349
  function randomizeCharacter() {
350
  Object.keys(characterItems).forEach(category => {
351
  if (characterItems[category].length > 0) {
@@ -353,9 +627,8 @@
353
  const randomIndex = Math.floor(Math.random() * characterItems[category].length);
354
  const randomItem = characterItems[category][randomIndex];
355
 
356
- // Update selection
357
- currentSelections[category] = randomItem.id;
358
- document.getElementById(`${category}Layer`).style.backgroundImage = `url('${randomItem.imageUrl}')`;
359
  }
360
  });
361
 
@@ -363,19 +636,69 @@
363
  const currentCategory = document.querySelector('.category-btn.active').dataset.category;
364
  loadCategoryItems(currentCategory);
365
  }
366
-
367
  // Download character image
368
  function downloadCharacter() {
369
- alert('Character downloaded! (This would normally generate and download an image)');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  }
371
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  // Add new item to a category
373
  function addNewItem() {
374
  const category = document.getElementById('adminCategory').value;
375
  const name = document.getElementById('adminItemName').value.trim();
376
- const imageUrl = document.getElementById('adminImageUrl').value.trim();
377
 
378
- if (!name || !imageUrl) {
379
  alert('Please fill in all fields');
380
  return;
381
  }
@@ -383,16 +706,20 @@
383
  // Generate ID
384
  const id = `${category}${characterItems[category].length + 1}`;
385
 
386
- // Add to our data
387
  characterItems[category].push({
388
  id,
389
  name,
390
- imageUrl
 
 
391
  });
392
 
393
  // Clear form
394
  document.getElementById('adminItemName').value = '';
395
- document.getElementById('adminImageUrl').value = '';
 
 
396
 
397
  // Reload items if this is the current category
398
  const currentCategory = document.querySelector('.category-btn.active').dataset.category;
@@ -400,9 +727,18 @@
400
  loadCategoryItems(category);
401
  }
402
 
403
- alert('Item added successfully!');
 
 
 
 
 
 
 
 
 
404
  }
405
-
406
  // Remove item from a category
407
  function removeItem() {
408
  const category = document.getElementById('adminCategory').value;
@@ -425,14 +761,14 @@
425
  characterItems[category].splice(itemIndex, 1);
426
 
427
  // Clear selection if it was this item
428
- if (currentSelections[category] === name) {
429
- currentSelections[category] = null;
430
- document.getElementById(`${category}Layer`).style.backgroundImage = '';
431
  }
432
 
433
  // Clear form
434
  document.getElementById('adminItemName').value = '';
435
- document.getElementById('adminImageUrl').value = '';
436
 
437
  // Reload items if this is the current category
438
  const currentCategory = document.querySelector('.category-btn.active').dataset.category;
@@ -440,9 +776,81 @@
440
  loadCategoryItems(category);
441
  }
442
 
443
- alert('Item removed successfully!');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  }
445
-
446
  // Initialize the app when DOM is loaded
447
  document.addEventListener('DOMContentLoaded', init);
448
  </script>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Anime Character Customizer</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/OBJLoader.min.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/MTLLoader.min.js"></script>
12
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
13
  <style>
14
+ body {
15
+ background-image: url('https://i.imgur.com/X3QKJ0L.jpg');
16
+ background-size: cover;
17
+ background-attachment: fixed;
18
+ font-family: 'Comic Sans MS', cursive, sans-serif;
19
+ }
20
+
21
  .character-container {
22
  position: relative;
23
+ width: 100%;
24
  height: 500px;
25
+ background-color: rgba(255, 255, 255, 0.2);
26
+ border-radius: 15px;
27
+ overflow: hidden;
28
+ }
29
+
30
+ #renderCanvas {
31
+ width: 100%;
32
+ height: 100%;
33
+ }
34
+
35
+ .category-btn {
36
+ transition: all 0.3s ease;
37
+ position: relative;
38
+ overflow: hidden;
39
  }
40
+
41
+ .category-btn::before {
42
+ content: '';
43
  position: absolute;
44
  top: 0;
45
+ left: -100%;
46
  width: 100%;
47
  height: 100%;
48
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
49
+ transition: 0.5s;
 
50
  }
51
+
52
+ .category-btn:hover::before {
53
+ left: 100%;
54
+ }
55
+
56
  .category-btn.active {
57
+ background-color: #ff6b9d;
58
  color: white;
59
+ transform: translateY(-3px);
60
+ box-shadow: 0 10px 20px rgba(255, 107, 157, 0.3);
61
  }
62
+
63
  .item-thumbnail {
64
+ transition: all 0.3s ease;
65
+ background: rgba(255, 255, 255, 0.8);
66
+ border-radius: 10px;
67
+ overflow: hidden;
68
  }
69
+
70
  .item-thumbnail:hover {
71
+ transform: scale(1.05) rotate(2deg);
72
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
73
  }
74
+
75
  .item-thumbnail.selected {
76
+ border: 3px solid #ff6b9d;
77
+ background: rgba(255, 107, 157, 0.1);
 
 
 
 
78
  }
79
+
80
+ .modal {
81
  display: none;
82
  position: fixed;
83
  top: 0;
84
  left: 0;
85
  width: 100%;
86
  height: 100%;
87
+ background-color: rgba(0,0,0,0.7);
88
  z-index: 1000;
89
  justify-content: center;
90
  align-items: center;
91
+ backdrop-filter: blur(5px);
92
  }
93
+
94
+ .modal-content {
95
  animation: modalFadeIn 0.3s ease-out;
96
+ background: linear-gradient(135deg, #ffcce6, #ff99cc);
97
  padding: 2rem;
98
+ border-radius: 15px;
99
  width: 90%;
100
  max-width: 500px;
101
+ box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3);
102
+ border: 2px solid white;
103
  }
104
+
105
  @keyframes modalFadeIn {
106
  from {
107
  opacity: 0;
 
112
  transform: translateY(0);
113
  }
114
  }
115
+
116
  #itemsGrid {
117
  display: grid;
118
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
119
+ gap: 1.5rem;
120
+ padding: 1.5rem;
121
+ }
122
+
123
+ .btn-primary {
124
+ background: linear-gradient(45deg, #ff6b9d, #ff8fab);
125
+ color: white;
126
+ border: none;
127
+ border-radius: 50px;
128
+ padding: 10px 20px;
129
+ font-weight: bold;
130
+ box-shadow: 0 5px 15px rgba(255, 107, 157, 0.4);
131
+ transition: all 0.3s ease;
132
+ }
133
+
134
+ .btn-primary:hover {
135
+ transform: translateY(-3px);
136
+ box-shadow: 0 8px 20px rgba(255, 107, 157, 0.6);
137
+ }
138
+
139
+ .btn-secondary {
140
+ background: linear-gradient(45deg, #6b9dff, #8fabff);
141
+ color: white;
142
+ border: none;
143
+ border-radius: 50px;
144
+ padding: 10px 20px;
145
+ font-weight: bold;
146
+ box-shadow: 0 5px 15px rgba(107, 157, 255, 0.4);
147
+ transition: all 0.3s ease;
148
+ }
149
+
150
+ .btn-secondary:hover {
151
+ transform: translateY(-3px);
152
+ box-shadow: 0 8px 20px rgba(107, 157, 255, 0.6);
153
+ }
154
+
155
+ .header {
156
+ background: rgba(255, 255, 255, 0.2);
157
+ backdrop-filter: blur(10px);
158
+ border-bottom: 1px solid rgba(255, 255, 255, 0.3);
159
+ }
160
+
161
+ .panel {
162
+ background: rgba(255, 255, 255, 0.7);
163
+ backdrop-filter: blur(10px);
164
+ border-radius: 20px;
165
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
166
+ border: 1px solid rgba(255, 255, 255, 0.5);
167
+ }
168
+
169
+ .file-upload {
170
+ position: relative;
171
+ overflow: hidden;
172
+ display: inline-block;
173
+ width: 100%;
174
+ }
175
+
176
+ .file-upload-btn {
177
+ width: 100%;
178
+ padding: 15px;
179
+ background: linear-gradient(45deg, #9dff6b, #abff8f);
180
+ color: white;
181
+ border-radius: 10px;
182
+ text-align: center;
183
+ cursor: pointer;
184
+ font-weight: bold;
185
+ box-shadow: 0 5px 15px rgba(157, 255, 107, 0.4);
186
+ transition: all 0.3s ease;
187
+ }
188
+
189
+ .file-upload-btn:hover {
190
+ background: linear-gradient(45deg, #8fec5d, #9dec7d);
191
+ transform: translateY(-3px);
192
+ box-shadow: 0 8px 20px rgba(157, 255, 107, 0.6);
193
+ }
194
+
195
+ .file-upload input[type="file"] {
196
+ position: absolute;
197
+ left: 0;
198
+ top: 0;
199
+ opacity: 0;
200
+ width: 100%;
201
+ height: 100%;
202
+ cursor: pointer;
203
+ }
204
+
205
+ .progress-bar {
206
+ height: 10px;
207
+ background: rgba(255, 255, 255, 0.5);
208
+ border-radius: 5px;
209
+ margin-top: 10px;
210
+ overflow: hidden;
211
+ }
212
+
213
+ .progress {
214
+ height: 100%;
215
+ background: linear-gradient(90deg, #ff6b9d, #ff8fab);
216
+ width: 0%;
217
+ transition: width 0.3s ease;
218
+ }
219
+
220
+ .sakura {
221
+ position: absolute;
222
+ width: 10px;
223
+ height: 10px;
224
+ background-color: #ffb6c1;
225
+ border-radius: 50% 0 50% 50%;
226
+ opacity: 0.7;
227
+ pointer-events: none;
228
+ z-index: -1;
229
  }
230
  </style>
231
  </head>
232
+ <body>
233
+ <!-- Sakura Petals Animation -->
234
+ <div id="sakura-container"></div>
235
+
236
  <!-- Header -->
237
+ <header class="header shadow-sm sticky top-0 z-50">
238
+ <div class="container mx-auto px-4 py-4">
239
  <div class="flex justify-between items-center">
240
+ <h1 class="text-3xl font-bold text-pink-600 flex items-center">
241
+ <i class="fas fa-star mr-2"></i> Anime Character Customizer
242
+ </h1>
243
+ <button id="adminBtn" class="btn-secondary flex items-center">
244
  <i class="fas fa-user-cog mr-2"></i> Admin Panel
245
  </button>
246
  </div>
 
252
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
253
  <!-- Character Preview -->
254
  <div class="lg:col-span-1">
255
+ <div class="panel p-6 sticky top-24">
256
+ <h2 class="text-2xl font-bold mb-6 text-center text-pink-600">Your Character</h2>
257
+ <div class="character-container">
258
+ <canvas id="renderCanvas"></canvas>
 
 
 
 
 
 
259
  </div>
260
+ <div class="flex justify-between items-center mt-6">
261
+ <button id="randomizeBtn" class="btn-primary flex items-center">
262
  <i class="fas fa-random mr-2"></i> Randomize
263
  </button>
264
+ <button id="downloadBtn" class="btn-secondary flex items-center">
265
  <i class="fas fa-download mr-2"></i> Download
266
  </button>
267
  </div>
 
270
 
271
  <!-- Customization Panel -->
272
  <div class="lg:col-span-2">
273
+ <div class="panel">
274
  <!-- Category Tabs -->
275
+ <div class="flex border-b border-pink-200">
276
+ <button class="category-btn active px-6 py-4 font-medium" data-category="head">
277
+ <i class="fas fa-head-side-mask mr-2"></i> Heads
278
  </button>
279
+ <button class="category-btn px-6 py-4 font-medium" data-category="body">
280
+ <i class="fas fa-tshirt mr-2"></i> Bodies
281
  </button>
282
  <button class="category-btn px-6 py-4 font-medium" data-category="hair">
283
  <i class="fas fa-cut mr-2"></i> Hair
284
  </button>
285
+ <button class="category-btn px-6 py-4 font-medium" data-category="accessory">
286
+ <i class="fas fa-scarf mr-2"></i> Accessories
287
  </button>
288
+ <button class="category-btn px-6 py-4 font-medium" data-category="weapon">
289
+ <i class="fas fa-sword mr-2"></i> Weapons
290
  </button>
291
  </div>
292
 
 
300
  </main>
301
 
302
  <!-- Admin Modal -->
303
+ <div id="adminModal" class="modal">
304
+ <div class="modal-content">
305
+ <div class="flex justify-between items-center mb-6">
306
+ <h3 class="text-xl font-bold text-pink-800">Admin Panel</h3>
307
+ <button id="closeAdminModal" class="text-pink-800 hover:text-pink-600 text-xl">
308
  <i class="fas fa-times"></i>
309
  </button>
310
  </div>
311
 
312
+ <div class="space-y-6">
313
  <div>
314
+ <label class="block text-sm font-medium text-pink-800 mb-2">Category</label>
315
+ <select id="adminCategory" class="w-full p-3 border-2 border-pink-300 rounded-lg bg-white">
316
+ <option value="head">Head</option>
317
+ <option value="body">Body</option>
318
  <option value="hair">Hair</option>
319
+ <option value="accessory">Accessory</option>
320
+ <option value="weapon">Weapon</option>
321
  </select>
322
  </div>
323
 
324
  <div>
325
+ <label class="block text-sm font-medium text-pink-800 mb-2">Item Name</label>
326
+ <input type="text" id="adminItemName" class="w-full p-3 border-2 border-pink-300 rounded-lg" placeholder="Enter item name">
327
  </div>
328
 
329
  <div>
330
+ <label class="block text-sm font-medium text-pink-800 mb-2">3D Model Files</label>
331
+ <div class="file-upload">
332
+ <div class="file-upload-btn">
333
+ <i class="fas fa-upload mr-2"></i> Choose OBJ/MTL Files
334
+ </div>
335
+ <input type="file" id="modelFiles" accept=".obj,.mtl" multiple>
336
+ </div>
337
+ <div class="progress-bar mt-2">
338
+ <div id="uploadProgress" class="progress"></div>
339
+ </div>
340
+ <p id="uploadStatus" class="text-xs text-pink-800 mt-1"></p>
341
  </div>
342
 
343
  <div class="flex space-x-4">
344
+ <button id="addItemBtn" class="btn-primary flex-1">
345
+ <i class="fas fa-plus mr-2"></i> Add Item
346
  </button>
347
+ <button id="removeItemBtn" class="btn-secondary flex-1">
348
+ <i class="fas fa-trash mr-2"></i> Remove Item
349
  </button>
350
  </div>
351
  </div>
 
353
  </div>
354
 
355
  <script>
356
+ // Three.js variables
357
+ let scene, camera, renderer, controls;
358
+ let character = new THREE.Group();
359
+ let currentParts = {
360
+ head: null,
361
+ body: null,
362
+ hair: null,
363
+ accessory: null,
364
+ weapon: null
365
+ };
366
+
367
+ // Sample data for character items (with 3D model references)
368
  const characterItems = {
369
+ head: [
370
+ { id: 'head1', name: 'Anime Girl Head', objUrl: 'models/head1.obj', mtlUrl: 'models/head1.mtl', previewImg: 'https://i.imgur.com/JQ6o5aX.png' },
371
+ { id: 'head2', name: 'Cat Girl Head', objUrl: 'models/head2.obj', mtlUrl: 'models/head2.mtl', previewImg: 'https://i.imgur.com/V8L9JtT.png' },
372
+ { id: 'head3', name: 'Demon Head', objUrl: 'models/head3.obj', mtlUrl: 'models/head3.mtl', previewImg: 'https://i.imgur.com/9Xz8K2b.png' }
 
373
  ],
374
+ body: [
375
+ { id: 'body1', name: 'School Uniform', objUrl: 'models/body1.obj', mtlUrl: 'models/body1.mtl', previewImg: 'https://i.imgur.com/pL3QvEH.png' },
376
+ { id: 'body2', name: 'Armor', objUrl: 'models/body2.obj', mtlUrl: 'models/body2.mtl', previewImg: 'https://i.imgur.com/mN7Z3Yq.png' },
377
+ { id: 'body3', name: 'Casual Outfit', objUrl: 'models/body3.obj', mtlUrl: 'models/body3.mtl', previewImg: 'https://i.imgur.com/L8jKQ1W.png' }
 
378
  ],
379
  hair: [
380
+ { id: 'hair1', name: 'Twin Tails', objUrl: 'models/hair1.obj', mtlUrl: 'models/hair1.mtl', previewImg: 'https://i.imgur.com/5w4bQeD.png' },
381
+ { id: 'hair2', name: 'Long Straight', objUrl: 'models/hair2.obj', mtlUrl: 'models/hair2.mtl', previewImg: 'https://i.imgur.com/3RkLQ9z.png' },
382
+ { id: 'hair3', name: 'Short Bob', objUrl: 'models/hair3.obj', mtlUrl: 'models/hair3.mtl', previewImg: 'https://i.imgur.com/JQ9vL2X.png' }
 
383
  ],
384
+ accessory: [
385
+ { id: 'acc1', name: 'Cat Ears', objUrl: 'models/acc1.obj', mtlUrl: 'models/acc1.mtl', previewImg: 'https://i.imgur.com/V8L9JtT.png' },
386
+ { id: 'acc2', name: 'Glasses', objUrl: 'models/acc2.obj', mtlUrl: 'models/acc2.mtl', previewImg: 'https://i.imgur.com/9Xz8K2b.png' },
387
+ { id: 'acc3', name: 'Ribbon', objUrl: 'models/acc3.obj', mtlUrl: 'models/acc3.mtl', previewImg: 'https://i.imgur.com/JQ6o5aX.png' }
 
388
  ],
389
+ weapon: [
390
+ { id: 'wep1', name: 'Katana', objUrl: 'models/wep1.obj', mtlUrl: 'models/wep1.mtl', previewImg: 'https://i.imgur.com/mN7Z3Yq.png' },
391
+ { id: 'wep2', name: 'Magic Staff', objUrl: 'models/wep2.obj', mtlUrl: 'models/wep2.mtl', previewImg: 'https://i.imgur.com/pL3QvEH.png' },
392
+ { id: 'wep3', name: 'Gun', objUrl: 'models/wep3.obj', mtlUrl: 'models/wep3.mtl', previewImg: 'https://i.imgur.com/5w4bQeD.png' }
 
393
  ]
394
  };
395
+
 
 
 
 
 
 
 
 
 
396
  // DOM elements
397
  const categoryButtons = document.querySelectorAll('.category-btn');
398
  const itemsGrid = document.getElementById('itemsGrid');
 
403
  const downloadBtn = document.getElementById('downloadBtn');
404
  const addItemBtn = document.getElementById('addItemBtn');
405
  const removeItemBtn = document.getElementById('removeItemBtn');
406
+ const modelFilesInput = document.getElementById('modelFiles');
407
+ const uploadProgress = document.getElementById('uploadProgress');
408
+ const uploadStatus = document.getElementById('uploadStatus');
409
+ const sakuraContainer = document.getElementById('sakura-container');
410
+
411
+ // Initialize Three.js scene
412
+ function initThreeJS() {
413
+ // Create scene
414
+ scene = new THREE.Scene();
415
+ scene.background = new THREE.Color(0xf0f0f0);
416
+
417
+ // Add lights
418
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
419
+ scene.add(ambientLight);
420
+
421
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
422
+ directionalLight.position.set(100, 100, 50);
423
+ scene.add(directionalLight);
424
+
425
+ // Create camera
426
+ camera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000);
427
+ camera.position.set(0, 100, 300);
428
+
429
+ // Create renderer
430
+ const canvas = document.getElementById('renderCanvas');
431
+ renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
432
+ renderer.setPixelRatio(window.devicePixelRatio);
433
+ renderer.setSize(canvas.clientWidth, canvas.clientHeight);
434
+
435
+ // Add controls
436
+ controls = new THREE.OrbitControls(camera, renderer.domElement);
437
+ controls.enableDamping = true;
438
+ controls.dampingFactor = 0.05;
439
+ controls.minDistance = 150;
440
+ controls.maxDistance = 500;
441
+
442
+ // Add character container
443
+ scene.add(character);
444
+
445
+ // Add floor grid
446
+ const gridHelper = new THREE.GridHelper(200, 20, 0x888888, 0x888888);
447
+ gridHelper.position.y = -10;
448
+ scene.add(gridHelper);
449
+
450
+ // Start animation loop
451
+ animate();
452
+ }
453
+
454
+ // Animation loop
455
+ function animate() {
456
+ requestAnimationFrame(animate);
457
+ controls.update();
458
+ renderer.render(scene, camera);
459
+ }
460
+
461
+ // Load a 3D model
462
+ function loadModel(objUrl, mtlUrl, callback) {
463
+ const mtlLoader = new THREE.MTLLoader();
464
+ mtlLoader.load(mtlUrl, (materials) => {
465
+ materials.preload();
466
+
467
+ const objLoader = new THREE.OBJLoader();
468
+ objLoader.setMaterials(materials);
469
+ objLoader.load(objUrl, (object) => {
470
+ if (callback) callback(object);
471
+ });
472
+ });
473
+ }
474
+
475
+ // Add a part to the character
476
+ function addCharacterPart(partType, item) {
477
+ // Remove existing part if any
478
+ if (currentParts[partType]) {
479
+ character.remove(currentParts[partType]);
480
+ }
481
+
482
+ // Load and add new part
483
+ loadModel(item.objUrl, item.mtlUrl, (model) => {
484
+ // Position the model based on part type
485
+ switch(partType) {
486
+ case 'head':
487
+ model.position.set(0, 80, 0);
488
+ model.scale.set(10, 10, 10);
489
+ break;
490
+ case 'body':
491
+ model.position.set(0, 0, 0);
492
+ model.scale.set(10, 10, 10);
493
+ break;
494
+ case 'hair':
495
+ model.position.set(0, 90, 0);
496
+ model.scale.set(10, 10, 10);
497
+ break;
498
+ case 'accessory':
499
+ model.position.set(0, 100, 0);
500
+ model.scale.set(8, 8, 8);
501
+ break;
502
+ case 'weapon':
503
+ model.position.set(20, 50, 0);
504
+ model.scale.set(5, 5, 5);
505
+ break;
506
+ }
507
+
508
+ character.add(model);
509
+ currentParts[partType] = model;
510
+ });
511
+ }
512
+
513
  // Initialize the app
514
  function init() {
515
+ initThreeJS();
516
+
517
  // Load first category by default
518
+ loadCategoryItems('head');
519
 
520
  // Set up event listeners
521
  setupEventListeners();
522
+
523
+ // Create sakura petals
524
+ createSakuraPetals();
525
  }
526
+
527
  // Set up all event listeners
528
  function setupEventListeners() {
529
  // Category buttons
 
537
  loadCategoryItems(category);
538
  });
539
  });
540
+
541
  // Admin button
542
  adminBtn.addEventListener('click', () => {
543
  adminModal.style.display = 'flex';
544
  });
545
+
546
  // Close admin modal
547
  closeAdminModal.addEventListener('click', () => {
548
  adminModal.style.display = 'none';
549
  });
550
+
551
  // Randomize button
552
  randomizeBtn.addEventListener('click', randomizeCharacter);
553
+
554
  // Download button
555
  downloadBtn.addEventListener('click', downloadCharacter);
556
+
557
  // Add item button
558
  addItemBtn.addEventListener('click', addNewItem);
559
+
560
  // Remove item button
561
  removeItemBtn.addEventListener('click', removeItem);
562
+
563
+ // Model files upload
564
+ modelFilesInput.addEventListener('change', handleFileUpload);
565
+
566
+ // Window resize
567
+ window.addEventListener('resize', onWindowResize);
568
  }
569
+
570
  // Load items for a specific category
571
  function loadCategoryItems(category) {
572
  itemsGrid.innerHTML = '';
573
 
574
  // Add "None" option
575
  const noneItem = document.createElement('div');
576
+ noneItem.className = `item-thumbnail flex flex-col items-center justify-center cursor-pointer ${!currentParts[category] ? 'selected' : ''}`;
577
  noneItem.innerHTML = `
578
+ <div class="w-full h-32 bg-gradient-to-br from-pink-100 to-pink-200 rounded-lg mb-2 flex items-center justify-center">
579
+ <i class="fas fa-times text-pink-500 text-4xl"></i>
580
  </div>
581
+ <span class="text-sm font-medium text-pink-800">None</span>
582
  `;
583
  noneItem.addEventListener('click', () => {
584
+ // Remove part from character
585
+ if (currentParts[category]) {
586
+ character.remove(currentParts[category]);
587
+ currentParts[category] = null;
588
+ }
589
 
590
  // Update selected state
591
  document.querySelectorAll(`.item-thumbnail`).forEach(item => {
 
598
  // Add items for this category
599
  characterItems[category].forEach(item => {
600
  const itemElement = document.createElement('div');
601
+ itemElement.className = `item-thumbnail flex flex-col items-center justify-center cursor-pointer ${currentParts[category] && currentParts[category].userData.id === item.id ? 'selected' : ''}`;
602
  itemElement.innerHTML = `
603
+ <div class="w-full h-32 bg-gradient-to-br from-pink-100 to-pink-200 rounded-lg mb-2 overflow-hidden">
604
+ <img src="${item.previewImg}" alt="${item.name}" class="w-full h-full object-cover">
605
+ </div>
606
+ <span class="text-sm font-medium text-pink-800">${item.name}</span>
607
  `;
608
  itemElement.addEventListener('click', () => {
609
+ // Add part to character
610
+ addCharacterPart(category, item);
 
611
 
612
  // Update selected state
613
  document.querySelectorAll(`.item-thumbnail`).forEach(item => {
 
618
  itemsGrid.appendChild(itemElement);
619
  });
620
  }
621
+
622
+ // Randomize all character parts
623
  function randomizeCharacter() {
624
  Object.keys(characterItems).forEach(category => {
625
  if (characterItems[category].length > 0) {
 
627
  const randomIndex = Math.floor(Math.random() * characterItems[category].length);
628
  const randomItem = characterItems[category][randomIndex];
629
 
630
+ // Add part to character
631
+ addCharacterPart(category, randomItem);
 
632
  }
633
  });
634
 
 
636
  const currentCategory = document.querySelector('.category-btn.active').dataset.category;
637
  loadCategoryItems(currentCategory);
638
  }
639
+
640
  // Download character image
641
  function downloadCharacter() {
642
+ // Temporarily disable controls
643
+ controls.enabled = false;
644
+
645
+ // Render to canvas
646
+ renderer.render(scene, camera);
647
+ const canvas = renderer.domElement;
648
+
649
+ // Create download link
650
+ const link = document.createElement('a');
651
+ link.download = 'my-anime-character.png';
652
+ link.href = canvas.toDataURL('image/png');
653
+ link.click();
654
+
655
+ // Re-enable controls
656
+ controls.enabled = true;
657
+
658
+ // Show success message
659
+ const notification = document.createElement('div');
660
+ notification.className = 'fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg';
661
+ notification.innerHTML = '<i class="fas fa-check-circle mr-2"></i> Character downloaded!';
662
+ document.body.appendChild(notification);
663
+
664
+ setTimeout(() => {
665
+ notification.classList.add('opacity-0', 'transition-opacity', 'duration-500');
666
+ setTimeout(() => notification.remove(), 500);
667
+ }, 3000);
668
  }
669
+
670
+ // Handle file upload
671
+ function handleFileUpload(e) {
672
+ const files = e.target.files;
673
+ if (files.length === 0) return;
674
+
675
+ uploadStatus.textContent = 'Uploading...';
676
+ uploadProgress.style.width = '0%';
677
+
678
+ // Simulate upload progress (in a real app, you would upload to a server)
679
+ let progress = 0;
680
+ const interval = setInterval(() => {
681
+ progress += 5;
682
+ uploadProgress.style.width = `${progress}%`;
683
+
684
+ if (progress >= 100) {
685
+ clearInterval(interval);
686
+ uploadStatus.textContent = 'Upload complete!';
687
+
688
+ // In a real app, you would get the server URLs here
689
+ setTimeout(() => {
690
+ document.getElementById('adminImageUrl').value = 'models/uploaded_model.obj';
691
+ }, 500);
692
+ }
693
+ }, 100);
694
+ }
695
+
696
  // Add new item to a category
697
  function addNewItem() {
698
  const category = document.getElementById('adminCategory').value;
699
  const name = document.getElementById('adminItemName').value.trim();
 
700
 
701
+ if (!name) {
702
  alert('Please fill in all fields');
703
  return;
704
  }
 
706
  // Generate ID
707
  const id = `${category}${characterItems[category].length + 1}`;
708
 
709
+ // Add to our data (in a real app, you would save to a database)
710
  characterItems[category].push({
711
  id,
712
  name,
713
+ objUrl: 'models/uploaded_model.obj',
714
+ mtlUrl: 'models/uploaded_model.mtl',
715
+ previewImg: 'https://i.imgur.com/JQ6o5aX.png' // Default preview image
716
  });
717
 
718
  // Clear form
719
  document.getElementById('adminItemName').value = '';
720
+ modelFilesInput.value = '';
721
+ uploadProgress.style.width = '0%';
722
+ uploadStatus.textContent = '';
723
 
724
  // Reload items if this is the current category
725
  const currentCategory = document.querySelector('.category-btn.active').dataset.category;
 
727
  loadCategoryItems(category);
728
  }
729
 
730
+ // Show success notification
731
+ const notification = document.createElement('div');
732
+ notification.className = 'fixed bottom-4 right-4 bg-blue-500 text-white px-4 py-2 rounded-lg shadow-lg';
733
+ notification.innerHTML = '<i class="fas fa-check-circle mr-2"></i> Item added successfully!';
734
+ document.body.appendChild(notification);
735
+
736
+ setTimeout(() => {
737
+ notification.classList.add('opacity-0', 'transition-opacity', 'duration-500');
738
+ setTimeout(() => notification.remove(), 500);
739
+ }, 3000);
740
  }
741
+
742
  // Remove item from a category
743
  function removeItem() {
744
  const category = document.getElementById('adminCategory').value;
 
761
  characterItems[category].splice(itemIndex, 1);
762
 
763
  // Clear selection if it was this item
764
+ if (currentParts[category] && currentParts[category].userData.id === name) {
765
+ character.remove(currentParts[category]);
766
+ currentParts[category] = null;
767
  }
768
 
769
  // Clear form
770
  document.getElementById('adminItemName').value = '';
771
+ modelFilesInput.value = '';
772
 
773
  // Reload items if this is the current category
774
  const currentCategory = document.querySelector('.category-btn.active').dataset.category;
 
776
  loadCategoryItems(category);
777
  }
778
 
779
+ // Show success notification
780
+ const notification = document.createElement('div');
781
+ notification.className = 'fixed bottom-4 right-4 bg-red-500 text-white px-4 py-2 rounded-lg shadow-lg';
782
+ notification.innerHTML = '<i class="fas fa-check-circle mr-2"></i> Item removed successfully!';
783
+ document.body.appendChild(notification);
784
+
785
+ setTimeout(() => {
786
+ notification.classList.add('opacity-0', 'transition-opacity', 'duration-500');
787
+ setTimeout(() => notification.remove(), 500);
788
+ }, 3000);
789
+ }
790
+
791
+ // Handle window resize
792
+ function onWindowResize() {
793
+ const canvas = document.getElementById('renderCanvas');
794
+ camera.aspect = canvas.clientWidth / canvas.clientHeight;
795
+ camera.updateProjectionMatrix();
796
+ renderer.setSize(canvas.clientWidth, canvas.clientHeight);
797
+ }
798
+
799
+ // Create sakura petals animation
800
+ function createSakuraPetals() {
801
+ for (let i = 0; i < 30; i++) {
802
+ createSakuraPetal();
803
+ }
804
+ }
805
+
806
+ function createSakuraPetal() {
807
+ const petal = document.createElement('div');
808
+ petal.className = 'sakura';
809
+
810
+ // Random position
811
+ const startX = Math.random() * window.innerWidth;
812
+ const startY = -20;
813
+
814
+ // Random size
815
+ const size = Math.random() * 10 + 5;
816
+
817
+ // Random rotation
818
+ const rotation = Math.random() * 360;
819
+
820
+ // Random animation duration
821
+ const duration = Math.random() * 10 + 10;
822
+
823
+ // Random delay
824
+ const delay = Math.random() * 5;
825
+
826
+ // Set initial styles
827
+ petal.style.left = `${startX}px`;
828
+ petal.style.top = `${startY}px`;
829
+ petal.style.width = `${size}px`;
830
+ petal.style.height = `${size}px`;
831
+ petal.style.transform = `rotate(${rotation}deg)`;
832
+
833
+ // Add to container
834
+ sakuraContainer.appendChild(petal);
835
+
836
+ // Animate
837
+ const animation = petal.animate([
838
+ { top: `${startY}px`, left: `${startX}px`, opacity: 0 },
839
+ { opacity: 0.7 },
840
+ { top: `${window.innerHeight}px`, left: `${startX + (Math.random() * 200 - 100)}px`, opacity: 0 }
841
+ ], {
842
+ duration: duration * 1000,
843
+ delay: delay * 1000,
844
+ easing: 'linear'
845
+ });
846
+
847
+ // Restart animation when finished
848
+ animation.onfinish = () => {
849
+ petal.remove();
850
+ createSakuraPetal();
851
+ };
852
  }
853
+
854
  // Initialize the app when DOM is loaded
855
  document.addEventListener('DOMContentLoaded', init);
856
  </script>