Skyd3d commited on
Commit
3b9552c
·
verified ·
1 Parent(s): 30d19bc

Add 1 files

Browse files
Files changed (1) hide show
  1. index.html +457 -110
index.html CHANGED
@@ -12,19 +12,34 @@
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 {
@@ -36,6 +51,8 @@
36
  transition: all 0.3s ease;
37
  position: relative;
38
  overflow: hidden;
 
 
39
  }
40
 
41
  .category-btn::before {
@@ -65,6 +82,8 @@
65
  background: rgba(255, 255, 255, 0.8);
66
  border-radius: 10px;
67
  overflow: hidden;
 
 
68
  }
69
 
70
  .item-thumbnail:hover {
@@ -227,9 +246,76 @@
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
 
@@ -241,7 +327,7 @@
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>
247
  </div>
@@ -299,17 +385,44 @@
299
  </div>
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">
@@ -327,12 +440,21 @@
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>
@@ -364,33 +486,19 @@
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
@@ -404,9 +512,14 @@
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() {
@@ -479,35 +592,60 @@
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
@@ -522,6 +660,9 @@
522
 
523
  // Create sakura petals
524
  createSakuraPetals();
 
 
 
525
  }
526
 
527
  // Set up all event listeners
@@ -561,10 +702,80 @@
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
@@ -599,9 +810,22 @@
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
  `;
@@ -656,26 +880,24 @@
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;
@@ -683,43 +905,136 @@
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
  }
705
 
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;
@@ -728,15 +1043,7 @@
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
@@ -745,7 +1052,7 @@
745
  const name = document.getElementById('adminItemName').value.trim();
746
 
747
  if (!name) {
748
- alert('Please enter the name of the item to remove');
749
  return;
750
  }
751
 
@@ -753,22 +1060,31 @@
753
  const itemIndex = characterItems[category].findIndex(item => item.name === name);
754
 
755
  if (itemIndex === -1) {
756
- alert('Item not found');
757
  return;
758
  }
759
 
 
 
 
760
  // Remove item
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;
@@ -777,15 +1093,27 @@
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
@@ -851,6 +1179,25 @@
851
  };
852
  }
853
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
854
  // Initialize the app when DOM is loaded
855
  document.addEventListener('DOMContentLoaded', init);
856
  </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-color: #ffecf5;
16
+ font-family: 'Comic Sans MS', cursive, sans-serif;
17
+ overflow-x: hidden;
18
+ }
19
+
20
+ .anime-bg {
21
+ position: fixed;
22
+ top: 0;
23
+ left: 0;
24
+ width: 100%;
25
+ height: 100%;
26
+ z-index: -1;
27
+ opacity: 0.3;
28
+ background-image: url('https://images.unsplash.com/photo-1633613286848-e6f43bbafb8d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80');
29
  background-size: cover;
30
+ background-position: center;
31
  background-attachment: fixed;
 
32
  }
33
 
34
  .character-container {
35
  position: relative;
36
  width: 100%;
37
  height: 500px;
38
+ background-color: rgba(255, 255, 255, 0.5);
39
  border-radius: 15px;
40
  overflow: hidden;
41
+ backdrop-filter: blur(5px);
42
+ border: 2px solid rgba(255, 255, 255, 0.8);
43
  }
44
 
45
  #renderCanvas {
 
51
  transition: all 0.3s ease;
52
  position: relative;
53
  overflow: hidden;
54
+ background: rgba(255, 255, 255, 0.3);
55
+ backdrop-filter: blur(5px);
56
  }
57
 
58
  .category-btn::before {
 
82
  background: rgba(255, 255, 255, 0.8);
83
  border-radius: 10px;
84
  overflow: hidden;
85
+ backdrop-filter: blur(5px);
86
+ border: 1px solid rgba(255, 255, 255, 0.5);
87
  }
88
 
89
  .item-thumbnail:hover {
 
246
  pointer-events: none;
247
  z-index: -1;
248
  }
249
+
250
+ .upload-preview {
251
+ width: 100%;
252
+ height: 150px;
253
+ background-color: rgba(255, 255, 255, 0.5);
254
+ border-radius: 10px;
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ margin-bottom: 10px;
259
+ overflow: hidden;
260
+ }
261
+
262
+ .upload-preview img {
263
+ max-width: 100%;
264
+ max-height: 100%;
265
+ object-fit: contain;
266
+ }
267
+
268
+ .upload-instructions {
269
+ background: rgba(255, 255, 255, 0.7);
270
+ padding: 15px;
271
+ border-radius: 10px;
272
+ margin-bottom: 15px;
273
+ border-left: 4px solid #ff6b9d;
274
+ }
275
+
276
+ .file-info {
277
+ margin-top: 10px;
278
+ font-size: 0.9rem;
279
+ color: #555;
280
+ }
281
+
282
+ .file-info span {
283
+ font-weight: bold;
284
+ color: #ff6b9d;
285
+ }
286
+
287
+ .upload-section {
288
+ background: rgba(255, 255, 255, 0.8);
289
+ border-radius: 15px;
290
+ padding: 20px;
291
+ margin-bottom: 20px;
292
+ border: 2px dashed #ff6b9d;
293
+ text-align: center;
294
+ }
295
+
296
+ .upload-icon {
297
+ font-size: 3rem;
298
+ color: #ff6b9d;
299
+ margin-bottom: 10px;
300
+ }
301
+
302
+ .upload-title {
303
+ font-size: 1.5rem;
304
+ font-weight: bold;
305
+ color: #ff6b9d;
306
+ margin-bottom: 10px;
307
+ }
308
+
309
+ .upload-subtitle {
310
+ color: #666;
311
+ margin-bottom: 20px;
312
+ }
313
  </style>
314
  </head>
315
  <body>
316
+ <!-- Anime Background -->
317
+ <div class="anime-bg"></div>
318
+
319
  <!-- Sakura Petals Animation -->
320
  <div id="sakura-container"></div>
321
 
 
327
  <i class="fas fa-star mr-2"></i> Anime Character Customizer
328
  </h1>
329
  <button id="adminBtn" class="btn-secondary flex items-center">
330
+ <i class="fas fa-upload mr-2"></i> Upload Models
331
  </button>
332
  </div>
333
  </div>
 
385
  </div>
386
  </main>
387
 
388
+ <!-- Upload Modal -->
389
  <div id="adminModal" class="modal">
390
  <div class="modal-content">
391
  <div class="flex justify-between items-center mb-6">
392
+ <h3 class="text-xl font-bold text-pink-800">Upload 3D Models</h3>
393
  <button id="closeAdminModal" class="text-pink-800 hover:text-pink-600 text-xl">
394
  <i class="fas fa-times"></i>
395
  </button>
396
  </div>
397
 
398
  <div class="space-y-6">
399
+ <!-- Upload Section -->
400
+ <div class="upload-section">
401
+ <div class="upload-icon">
402
+ <i class="fas fa-cloud-upload-alt"></i>
403
+ </div>
404
+ <div class="upload-title">Upload Your 3D Models</div>
405
+ <div class="upload-subtitle">Drag & drop your OBJ and MTL files here or click to browse</div>
406
+
407
+ <div class="file-upload">
408
+ <div class="file-upload-btn">
409
+ <i class="fas fa-file-upload mr-2"></i> Select Files
410
+ </div>
411
+ <input type="file" id="modelFiles" accept=".obj,.mtl" multiple>
412
+ </div>
413
+ </div>
414
+
415
+ <div class="upload-instructions">
416
+ <h4 class="font-bold text-pink-800 mb-2">How to upload 3D models:</h4>
417
+ <ol class="list-decimal pl-5 space-y-1">
418
+ <li>Select the category for your model</li>
419
+ <li>Enter a name for your model</li>
420
+ <li>Upload both OBJ and MTL files (required)</li>
421
+ <li>Optionally upload a preview image</li>
422
+ <li>Click "Add Item" to save</li>
423
+ </ol>
424
+ </div>
425
+
426
  <div>
427
  <label class="block text-sm font-medium text-pink-800 mb-2">Category</label>
428
  <select id="adminCategory" class="w-full p-3 border-2 border-pink-300 rounded-lg bg-white">
 
440
  </div>
441
 
442
  <div>
443
+ <label class="block text-sm font-medium text-pink-800 mb-2">Preview Image (Optional)</label>
444
+ <div class="upload-preview">
445
+ <span class="text-gray-500">No image selected</span>
446
+ </div>
447
  <div class="file-upload">
448
  <div class="file-upload-btn">
449
+ <i class="fas fa-image mr-2"></i> Choose Preview Image
450
  </div>
451
+ <input type="file" id="previewImage" accept="image/*">
452
+ </div>
453
+ </div>
454
+
455
+ <div id="fileUploadInfo">
456
+ <div id="fileInfo" class="file-info hidden">
457
+ Selected files: <span id="selectedFiles"></span>
458
  </div>
459
  <div class="progress-bar mt-2">
460
  <div id="uploadProgress" class="progress"></div>
 
486
  weapon: null
487
  };
488
 
489
+ // Sample data for character items (initially empty)
490
+ let characterItems = {
491
+ head: [],
492
+ body: [],
493
+ hair: [],
494
+ accessory: [],
495
+ weapon: []
496
+ };
497
+
498
+ // Store uploaded files in memory (in a real app, you'd upload to a server)
499
+ let uploadedFiles = {
500
+ models: {},
501
+ previews: {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  };
503
 
504
  // DOM elements
 
512
  const addItemBtn = document.getElementById('addItemBtn');
513
  const removeItemBtn = document.getElementById('removeItemBtn');
514
  const modelFilesInput = document.getElementById('modelFiles');
515
+ const previewImageInput = document.getElementById('previewImage');
516
  const uploadProgress = document.getElementById('uploadProgress');
517
  const uploadStatus = document.getElementById('uploadStatus');
518
  const sakuraContainer = document.getElementById('sakura-container');
519
+ const uploadPreview = document.querySelector('.upload-preview');
520
+ const fileInfo = document.getElementById('fileInfo');
521
+ const selectedFiles = document.getElementById('selectedFiles');
522
+ const fileUploadInfo = document.getElementById('fileUploadInfo');
523
 
524
  // Initialize Three.js scene
525
  function initThreeJS() {
 
592
  character.remove(currentParts[partType]);
593
  }
594
 
595
+ // Check if we have the model in memory
596
+ if (uploadedFiles.models[item.id]) {
597
+ const modelData = uploadedFiles.models[item.id];
598
+
599
+ // Create a material loader
600
+ const mtlLoader = new THREE.MTLLoader();
601
+ const mtlContent = modelData.mtl;
602
+ const materials = mtlLoader.parse(mtlContent);
603
+ materials.preload();
604
+
605
+ // Create an object loader
606
+ const objLoader = new THREE.OBJLoader();
607
+ objLoader.setMaterials(materials);
608
+
609
+ // Parse the OBJ content
610
+ const model = objLoader.parse(modelData.obj);
611
+
612
  // Position the model based on part type
613
+ positionModel(partType, model);
614
+
615
+ // Store the ID for reference
616
+ model.userData = { id: item.id };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617
 
618
  character.add(model);
619
  currentParts[partType] = model;
620
+ } else {
621
+ console.error("Model not found in memory:", item.id);
622
+ }
623
+ }
624
+
625
+ // Position model based on part type
626
+ function positionModel(partType, model) {
627
+ switch(partType) {
628
+ case 'head':
629
+ model.position.set(0, 80, 0);
630
+ model.scale.set(10, 10, 10);
631
+ break;
632
+ case 'body':
633
+ model.position.set(0, 0, 0);
634
+ model.scale.set(10, 10, 10);
635
+ break;
636
+ case 'hair':
637
+ model.position.set(0, 90, 0);
638
+ model.scale.set(10, 10, 10);
639
+ break;
640
+ case 'accessory':
641
+ model.position.set(0, 100, 0);
642
+ model.scale.set(8, 8, 8);
643
+ break;
644
+ case 'weapon':
645
+ model.position.set(20, 50, 0);
646
+ model.scale.set(5, 5, 5);
647
+ break;
648
+ }
649
  }
650
 
651
  // Initialize the app
 
660
 
661
  // Create sakura petals
662
  createSakuraPetals();
663
+
664
+ // Load any saved items from localStorage
665
+ loadSavedItems();
666
  }
667
 
668
  // Set up all event listeners
 
702
  removeItemBtn.addEventListener('click', removeItem);
703
 
704
  // Model files upload
705
+ modelFilesInput.addEventListener('change', handleModelUpload);
706
+
707
+ // Preview image upload
708
+ previewImageInput.addEventListener('change', handlePreviewImageUpload);
709
 
710
  // Window resize
711
  window.addEventListener('resize', onWindowResize);
712
+
713
+ // Drag and drop for model files
714
+ setupDragAndDrop();
715
+ }
716
+
717
+ // Set up drag and drop functionality
718
+ function setupDragAndDrop() {
719
+ const uploadSection = document.querySelector('.upload-section');
720
+
721
+ // Prevent default drag behaviors
722
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
723
+ uploadSection.addEventListener(eventName, preventDefaults, false);
724
+ document.body.addEventListener(eventName, preventDefaults, false);
725
+ });
726
+
727
+ // Highlight drop area when item is dragged over it
728
+ ['dragenter', 'dragover'].forEach(eventName => {
729
+ uploadSection.addEventListener(eventName, highlight, false);
730
+ });
731
+
732
+ ['dragleave', 'drop'].forEach(eventName => {
733
+ uploadSection.addEventListener(eventName, unhighlight, false);
734
+ });
735
+
736
+ // Handle dropped files
737
+ uploadSection.addEventListener('drop', handleDrop, false);
738
+ }
739
+
740
+ function preventDefaults(e) {
741
+ e.preventDefault();
742
+ e.stopPropagation();
743
+ }
744
+
745
+ function highlight() {
746
+ document.querySelector('.upload-section').style.borderColor = '#ff3d8b';
747
+ document.querySelector('.upload-section').style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
748
+ }
749
+
750
+ function unhighlight() {
751
+ document.querySelector('.upload-section').style.borderColor = '#ff6b9d';
752
+ document.querySelector('.upload-section').style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
753
+ }
754
+
755
+ function handleDrop(e) {
756
+ const dt = e.dataTransfer;
757
+ const files = dt.files;
758
+
759
+ // Filter for only OBJ and MTL files
760
+ const validFiles = Array.from(files).filter(file =>
761
+ file.name.endsWith('.obj') || file.name.endsWith('.mtl')
762
+ );
763
+
764
+ if (validFiles.length > 0) {
765
+ // Create a new FileList object
766
+ const dataTransfer = new DataTransfer();
767
+ validFiles.forEach(file => dataTransfer.items.add(file));
768
+
769
+ // Assign to our file input
770
+ modelFilesInput.files = dataTransfer.files;
771
+
772
+ // Trigger the change event
773
+ const event = new Event('change');
774
+ modelFilesInput.dispatchEvent(event);
775
+ } else {
776
+ uploadStatus.textContent = 'Please drop OBJ or MTL files only';
777
+ uploadStatus.style.color = 'red';
778
+ }
779
  }
780
 
781
  // Load items for a specific category
 
810
  characterItems[category].forEach(item => {
811
  const itemElement = document.createElement('div');
812
  itemElement.className = `item-thumbnail flex flex-col items-center justify-center cursor-pointer ${currentParts[category] && currentParts[category].userData.id === item.id ? 'selected' : ''}`;
813
+
814
+ // Check if we have a preview image
815
+ let previewContent = '';
816
+ if (uploadedFiles.previews[item.id]) {
817
+ previewContent = `<img src="${uploadedFiles.previews[item.id]}" alt="${item.name}" class="w-full h-full object-cover">`;
818
+ } else {
819
+ previewContent = `
820
+ <div class="w-full h-full flex items-center justify-center bg-gradient-to-br from-pink-100 to-pink-200">
821
+ <i class="fas fa-cube text-pink-500 text-4xl"></i>
822
+ </div>
823
+ `;
824
+ }
825
+
826
  itemElement.innerHTML = `
827
+ <div class="w-full h-32 rounded-lg mb-2 overflow-hidden">
828
+ ${previewContent}
829
  </div>
830
  <span class="text-sm font-medium text-pink-800">${item.name}</span>
831
  `;
 
880
  controls.enabled = true;
881
 
882
  // Show success message
883
+ showNotification('Character downloaded!', 'green');
 
 
 
 
 
 
 
 
884
  }
885
 
886
+ // Handle model file upload
887
+ function handleModelUpload(e) {
888
  const files = e.target.files;
889
  if (files.length === 0) return;
890
 
891
+ // Show selected files
892
+ const fileNames = Array.from(files).map(file => file.name).join(', ');
893
+ selectedFiles.textContent = fileNames;
894
+ fileInfo.classList.remove('hidden');
895
+
896
+ uploadStatus.textContent = 'Uploading model files...';
897
+ uploadStatus.style.color = '#ff6b9d';
898
  uploadProgress.style.width = '0%';
899
 
900
+ // We'll simulate upload progress
901
  let progress = 0;
902
  const interval = setInterval(() => {
903
  progress += 5;
 
905
 
906
  if (progress >= 100) {
907
  clearInterval(interval);
908
+ uploadStatus.textContent = 'Model files uploaded successfully!';
909
+ uploadStatus.style.color = '#9dff6b';
910
 
911
+ // Process the files
912
+ processModelFiles(files);
 
 
913
  }
914
  }, 100);
915
  }
916
 
917
+ // Process uploaded model files
918
+ function processModelFiles(files) {
919
+ const objFile = Array.from(files).find(file => file.name.endsWith('.obj'));
920
+ const mtlFile = Array.from(files).find(file => file.name.endsWith('.mtl'));
921
+
922
+ if (!objFile || !mtlFile) {
923
+ uploadStatus.textContent = 'Error: Need both OBJ and MTL files';
924
+ uploadStatus.style.color = 'red';
925
+ return;
926
+ }
927
+
928
+ // Read the files
929
+ const objReader = new FileReader();
930
+ const mtlReader = new FileReader();
931
+
932
+ objReader.onload = function(e) {
933
+ const objContent = e.target.result;
934
+
935
+ mtlReader.onload = function(e) {
936
+ const mtlContent = e.target.result;
937
+
938
+ // Generate a temporary ID for the model
939
+ const tempId = 'temp_' + Date.now();
940
+
941
+ // Store in memory (in a real app, you'd upload to server)
942
+ uploadedFiles.models[tempId] = {
943
+ obj: objContent,
944
+ mtl: mtlContent
945
+ };
946
+
947
+ // Store the temp ID in the input for reference
948
+ modelFilesInput.dataset.tempId = tempId;
949
+
950
+ uploadStatus.textContent = 'Model files processed and ready!';
951
+ uploadStatus.style.color = '#9dff6b';
952
+ };
953
+
954
+ mtlReader.readAsText(mtlFile);
955
+ };
956
+
957
+ objReader.readAsText(objFile);
958
+ }
959
+
960
+ // Handle preview image upload
961
+ function handlePreviewImageUpload(e) {
962
+ const file = e.target.files[0];
963
+ if (!file) return;
964
+
965
+ const reader = new FileReader();
966
+ reader.onload = function(e) {
967
+ const imageUrl = e.target.result;
968
+
969
+ // Display preview
970
+ uploadPreview.innerHTML = `<img src="${imageUrl}" alt="Preview">`;
971
+
972
+ // Generate a temporary ID for the preview
973
+ const tempId = 'temp_' + Date.now();
974
+
975
+ // Store in memory (in a real app, you'd upload to server)
976
+ uploadedFiles.previews[tempId] = imageUrl;
977
+
978
+ // Store the temp ID in the input for reference
979
+ previewImageInput.dataset.tempId = tempId;
980
+
981
+ uploadStatus.textContent = 'Preview image uploaded!';
982
+ uploadStatus.style.color = '#9dff6b';
983
+ };
984
+ reader.readAsDataURL(file);
985
+ }
986
+
987
  // Add new item to a category
988
  function addNewItem() {
989
  const category = document.getElementById('adminCategory').value;
990
  const name = document.getElementById('adminItemName').value.trim();
991
 
992
  if (!name) {
993
+ showNotification('Please enter an item name', 'red');
994
+ return;
995
+ }
996
+
997
+ // Check if we have model files
998
+ const modelTempId = modelFilesInput.dataset.tempId;
999
+ if (!modelTempId || !uploadedFiles.models[modelTempId]) {
1000
+ showNotification('Please upload model files (OBJ + MTL)', 'red');
1001
  return;
1002
  }
1003
 
1004
  // Generate ID
1005
+ const id = `${category}_${Date.now()}`;
1006
+
1007
+ // Get preview image if available
1008
+ const previewTempId = previewImageInput.dataset.tempId;
1009
+ if (previewTempId && uploadedFiles.previews[previewTempId]) {
1010
+ // Move from temp storage to permanent
1011
+ uploadedFiles.previews[id] = uploadedFiles.previews[previewTempId];
1012
+ delete uploadedFiles.previews[previewTempId];
1013
+ }
1014
 
1015
+ // Move model from temp storage to permanent
1016
+ uploadedFiles.models[id] = uploadedFiles.models[modelTempId];
1017
+ delete uploadedFiles.models[modelTempId];
1018
+
1019
+ // Add to our data
1020
  characterItems[category].push({
1021
  id,
1022
+ name
 
 
 
1023
  });
1024
 
1025
+ // Save to localStorage
1026
+ saveItemsToStorage();
1027
+
1028
  // Clear form
1029
  document.getElementById('adminItemName').value = '';
1030
  modelFilesInput.value = '';
1031
+ modelFilesInput.dataset.tempId = '';
1032
+ previewImageInput.value = '';
1033
+ previewImageInput.dataset.tempId = '';
1034
+ uploadPreview.innerHTML = '<span class="text-gray-500">No image selected</span>';
1035
  uploadProgress.style.width = '0%';
1036
  uploadStatus.textContent = '';
1037
+ fileInfo.classList.add('hidden');
1038
 
1039
  // Reload items if this is the current category
1040
  const currentCategory = document.querySelector('.category-btn.active').dataset.category;
 
1043
  }
1044
 
1045
  // Show success notification
1046
+ showNotification('Item added successfully!', 'blue');
 
 
 
 
 
 
 
 
1047
  }
1048
 
1049
  // Remove item from a category
 
1052
  const name = document.getElementById('adminItemName').value.trim();
1053
 
1054
  if (!name) {
1055
+ showNotification('Please enter the name of the item to remove', 'red');
1056
  return;
1057
  }
1058
 
 
1060
  const itemIndex = characterItems[category].findIndex(item => item.name === name);
1061
 
1062
  if (itemIndex === -1) {
1063
+ showNotification('Item not found', 'red');
1064
  return;
1065
  }
1066
 
1067
+ // Get item ID
1068
+ const itemId = characterItems[category][itemIndex].id;
1069
+
1070
  // Remove item
1071
  characterItems[category].splice(itemIndex, 1);
1072
 
1073
+ // Remove from memory
1074
+ delete uploadedFiles.models[itemId];
1075
+ delete uploadedFiles.previews[itemId];
1076
+
1077
  // Clear selection if it was this item
1078
+ if (currentParts[category] && currentParts[category].userData.id === itemId) {
1079
  character.remove(currentParts[category]);
1080
  currentParts[category] = null;
1081
  }
1082
 
1083
+ // Save to localStorage
1084
+ saveItemsToStorage();
1085
+
1086
  // Clear form
1087
  document.getElementById('adminItemName').value = '';
 
1088
 
1089
  // Reload items if this is the current category
1090
  const currentCategory = document.querySelector('.category-btn.active').dataset.category;
 
1093
  }
1094
 
1095
  // Show success notification
1096
+ showNotification('Item removed successfully!', 'red');
1097
+ }
1098
+
1099
+ // Save items to localStorage
1100
+ function saveItemsToStorage() {
1101
+ localStorage.setItem('animeCharacterItems', JSON.stringify(characterItems));
1102
 
1103
+ // We can't save the files to localStorage due to size limits
1104
+ // In a real app, you'd upload to a server
1105
+ }
1106
+
1107
+ // Load saved items from localStorage
1108
+ function loadSavedItems() {
1109
+ const savedItems = localStorage.getItem('animeCharacterItems');
1110
+ if (savedItems) {
1111
+ characterItems = JSON.parse(savedItems);
1112
+
1113
+ // Reload current category
1114
+ const currentCategory = document.querySelector('.category-btn.active').dataset.category;
1115
+ loadCategoryItems(currentCategory);
1116
+ }
1117
  }
1118
 
1119
  // Handle window resize
 
1179
  };
1180
  }
1181
 
1182
+ // Show notification
1183
+ function showNotification(message, color) {
1184
+ const colors = {
1185
+ blue: 'bg-blue-500',
1186
+ green: 'bg-green-500',
1187
+ red: 'bg-red-500'
1188
+ };
1189
+
1190
+ const notification = document.createElement('div');
1191
+ notification.className = `fixed bottom-4 right-4 ${colors[color] || 'bg-blue-500'} text-white px-4 py-2 rounded-lg shadow-lg flex items-center`;
1192
+ notification.innerHTML = `<i class="fas fa-check-circle mr-2"></i> ${message}`;
1193
+ document.body.appendChild(notification);
1194
+
1195
+ setTimeout(() => {
1196
+ notification.classList.add('opacity-0', 'transition-opacity', 'duration-500');
1197
+ setTimeout(() => notification.remove(), 500);
1198
+ }, 3000);
1199
+ }
1200
+
1201
  // Initialize the app when DOM is loaded
1202
  document.addEventListener('DOMContentLoaded', init);
1203
  </script>