|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Anime Character Customizer</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/OBJLoader.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/MTLLoader.min.js"></script> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<style> |
|
body { |
|
background-image: url('https://i.imgur.com/X3QKJ0L.jpg'); |
|
background-size: cover; |
|
background-attachment: fixed; |
|
font-family: 'Comic Sans MS', cursive, sans-serif; |
|
} |
|
|
|
.character-container { |
|
position: relative; |
|
width: 100%; |
|
height: 500px; |
|
background-color: rgba(255, 255, 255, 0.2); |
|
border-radius: 15px; |
|
overflow: hidden; |
|
} |
|
|
|
#renderCanvas { |
|
width: 100%; |
|
height: 100%; |
|
} |
|
|
|
.category-btn { |
|
transition: all 0.3s ease; |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
.category-btn::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: -100%; |
|
width: 100%; |
|
height: 100%; |
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent); |
|
transition: 0.5s; |
|
} |
|
|
|
.category-btn:hover::before { |
|
left: 100%; |
|
} |
|
|
|
.category-btn.active { |
|
background-color: #ff6b9d; |
|
color: white; |
|
transform: translateY(-3px); |
|
box-shadow: 0 10px 20px rgba(255, 107, 157, 0.3); |
|
} |
|
|
|
.item-thumbnail { |
|
transition: all 0.3s ease; |
|
background: rgba(255, 255, 255, 0.8); |
|
border-radius: 10px; |
|
overflow: hidden; |
|
} |
|
|
|
.item-thumbnail:hover { |
|
transform: scale(1.05) rotate(2deg); |
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); |
|
} |
|
|
|
.item-thumbnail.selected { |
|
border: 3px solid #ff6b9d; |
|
background: rgba(255, 107, 157, 0.1); |
|
} |
|
|
|
.modal { |
|
display: none; |
|
position: fixed; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background-color: rgba(0,0,0,0.7); |
|
z-index: 1000; |
|
justify-content: center; |
|
align-items: center; |
|
backdrop-filter: blur(5px); |
|
} |
|
|
|
.modal-content { |
|
animation: modalFadeIn 0.3s ease-out; |
|
background: linear-gradient(135deg, #ffcce6, #ff99cc); |
|
padding: 2rem; |
|
border-radius: 15px; |
|
width: 90%; |
|
max-width: 500px; |
|
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3); |
|
border: 2px solid white; |
|
} |
|
|
|
@keyframes modalFadeIn { |
|
from { |
|
opacity: 0; |
|
transform: translateY(-20px); |
|
} |
|
to { |
|
opacity: 1; |
|
transform: translateY(0); |
|
} |
|
} |
|
|
|
#itemsGrid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); |
|
gap: 1.5rem; |
|
padding: 1.5rem; |
|
} |
|
|
|
.btn-primary { |
|
background: linear-gradient(45deg, #ff6b9d, #ff8fab); |
|
color: white; |
|
border: none; |
|
border-radius: 50px; |
|
padding: 10px 20px; |
|
font-weight: bold; |
|
box-shadow: 0 5px 15px rgba(255, 107, 157, 0.4); |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.btn-primary:hover { |
|
transform: translateY(-3px); |
|
box-shadow: 0 8px 20px rgba(255, 107, 157, 0.6); |
|
} |
|
|
|
.btn-secondary { |
|
background: linear-gradient(45deg, #6b9dff, #8fabff); |
|
color: white; |
|
border: none; |
|
border-radius: 50px; |
|
padding: 10px 20px; |
|
font-weight: bold; |
|
box-shadow: 0 5px 15px rgba(107, 157, 255, 0.4); |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.btn-secondary:hover { |
|
transform: translateY(-3px); |
|
box-shadow: 0 8px 20px rgba(107, 157, 255, 0.6); |
|
} |
|
|
|
.header { |
|
background: rgba(255, 255, 255, 0.2); |
|
backdrop-filter: blur(10px); |
|
border-bottom: 1px solid rgba(255, 255, 255, 0.3); |
|
} |
|
|
|
.panel { |
|
background: rgba(255, 255, 255, 0.7); |
|
backdrop-filter: blur(10px); |
|
border-radius: 20px; |
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); |
|
border: 1px solid rgba(255, 255, 255, 0.5); |
|
} |
|
|
|
.file-upload { |
|
position: relative; |
|
overflow: hidden; |
|
display: inline-block; |
|
width: 100%; |
|
} |
|
|
|
.file-upload-btn { |
|
width: 100%; |
|
padding: 15px; |
|
background: linear-gradient(45deg, #9dff6b, #abff8f); |
|
color: white; |
|
border-radius: 10px; |
|
text-align: center; |
|
cursor: pointer; |
|
font-weight: bold; |
|
box-shadow: 0 5px 15px rgba(157, 255, 107, 0.4); |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.file-upload-btn:hover { |
|
background: linear-gradient(45deg, #8fec5d, #9dec7d); |
|
transform: translateY(-3px); |
|
box-shadow: 0 8px 20px rgba(157, 255, 107, 0.6); |
|
} |
|
|
|
.file-upload input[type="file"] { |
|
position: absolute; |
|
left: 0; |
|
top: 0; |
|
opacity: 0; |
|
width: 100%; |
|
height: 100%; |
|
cursor: pointer; |
|
} |
|
|
|
.progress-bar { |
|
height: 10px; |
|
background: rgba(255, 255, 255, 0.5); |
|
border-radius: 5px; |
|
margin-top: 10px; |
|
overflow: hidden; |
|
} |
|
|
|
.progress { |
|
height: 100%; |
|
background: linear-gradient(90deg, #ff6b9d, #ff8fab); |
|
width: 0%; |
|
transition: width 0.3s ease; |
|
} |
|
|
|
.sakura { |
|
position: absolute; |
|
width: 10px; |
|
height: 10px; |
|
background-color: #ffb6c1; |
|
border-radius: 50% 0 50% 50%; |
|
opacity: 0.7; |
|
pointer-events: none; |
|
z-index: -1; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
|
|
<div id="sakura-container"></div> |
|
|
|
|
|
<header class="header shadow-sm sticky top-0 z-50"> |
|
<div class="container mx-auto px-4 py-4"> |
|
<div class="flex justify-between items-center"> |
|
<h1 class="text-3xl font-bold text-pink-600 flex items-center"> |
|
<i class="fas fa-star mr-2"></i> Anime Character Customizer |
|
</h1> |
|
<button id="adminBtn" class="btn-secondary flex items-center"> |
|
<i class="fas fa-user-cog mr-2"></i> Admin Panel |
|
</button> |
|
</div> |
|
</div> |
|
</header> |
|
|
|
|
|
<main class="container mx-auto px-4 py-8"> |
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> |
|
|
|
<div class="lg:col-span-1"> |
|
<div class="panel p-6 sticky top-24"> |
|
<h2 class="text-2xl font-bold mb-6 text-center text-pink-600">Your Character</h2> |
|
<div class="character-container"> |
|
<canvas id="renderCanvas"></canvas> |
|
</div> |
|
<div class="flex justify-between items-center mt-6"> |
|
<button id="randomizeBtn" class="btn-primary flex items-center"> |
|
<i class="fas fa-random mr-2"></i> Randomize |
|
</button> |
|
<button id="downloadBtn" class="btn-secondary flex items-center"> |
|
<i class="fas fa-download mr-2"></i> Download |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="lg:col-span-2"> |
|
<div class="panel"> |
|
|
|
<div class="flex border-b border-pink-200"> |
|
<button class="category-btn active px-6 py-4 font-medium" data-category="head"> |
|
<i class="fas fa-head-side-mask mr-2"></i> Heads |
|
</button> |
|
<button class="category-btn px-6 py-4 font-medium" data-category="body"> |
|
<i class="fas fa-tshirt mr-2"></i> Bodies |
|
</button> |
|
<button class="category-btn px-6 py-4 font-medium" data-category="hair"> |
|
<i class="fas fa-cut mr-2"></i> Hair |
|
</button> |
|
<button class="category-btn px-6 py-4 font-medium" data-category="accessory"> |
|
<i class="fas fa-scarf mr-2"></i> Accessories |
|
</button> |
|
<button class="category-btn px-6 py-4 font-medium" data-category="weapon"> |
|
<i class="fas fa-sword mr-2"></i> Weapons |
|
</button> |
|
</div> |
|
|
|
|
|
<div id="itemsGrid" class="p-6"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</main> |
|
|
|
|
|
<div id="adminModal" class="modal"> |
|
<div class="modal-content"> |
|
<div class="flex justify-between items-center mb-6"> |
|
<h3 class="text-xl font-bold text-pink-800">Admin Panel</h3> |
|
<button id="closeAdminModal" class="text-pink-800 hover:text-pink-600 text-xl"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
|
|
<div class="space-y-6"> |
|
<div> |
|
<label class="block text-sm font-medium text-pink-800 mb-2">Category</label> |
|
<select id="adminCategory" class="w-full p-3 border-2 border-pink-300 rounded-lg bg-white"> |
|
<option value="head">Head</option> |
|
<option value="body">Body</option> |
|
<option value="hair">Hair</option> |
|
<option value="accessory">Accessory</option> |
|
<option value="weapon">Weapon</option> |
|
</select> |
|
</div> |
|
|
|
<div> |
|
<label class="block text-sm font-medium text-pink-800 mb-2">Item Name</label> |
|
<input type="text" id="adminItemName" class="w-full p-3 border-2 border-pink-300 rounded-lg" placeholder="Enter item name"> |
|
</div> |
|
|
|
<div> |
|
<label class="block text-sm font-medium text-pink-800 mb-2">3D Model Files</label> |
|
<div class="file-upload"> |
|
<div class="file-upload-btn"> |
|
<i class="fas fa-upload mr-2"></i> Choose OBJ/MTL Files |
|
</div> |
|
<input type="file" id="modelFiles" accept=".obj,.mtl" multiple> |
|
</div> |
|
<div class="progress-bar mt-2"> |
|
<div id="uploadProgress" class="progress"></div> |
|
</div> |
|
<p id="uploadStatus" class="text-xs text-pink-800 mt-1"></p> |
|
</div> |
|
|
|
<div class="flex space-x-4"> |
|
<button id="addItemBtn" class="btn-primary flex-1"> |
|
<i class="fas fa-plus mr-2"></i> Add Item |
|
</button> |
|
<button id="removeItemBtn" class="btn-secondary flex-1"> |
|
<i class="fas fa-trash mr-2"></i> Remove Item |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
let scene, camera, renderer, controls; |
|
let character = new THREE.Group(); |
|
let currentParts = { |
|
head: null, |
|
body: null, |
|
hair: null, |
|
accessory: null, |
|
weapon: null |
|
}; |
|
|
|
|
|
const characterItems = { |
|
head: [ |
|
{ id: 'head1', name: 'Anime Girl Head', objUrl: 'models/head1.obj', mtlUrl: 'models/head1.mtl', previewImg: 'https://i.imgur.com/JQ6o5aX.png' }, |
|
{ id: 'head2', name: 'Cat Girl Head', objUrl: 'models/head2.obj', mtlUrl: 'models/head2.mtl', previewImg: 'https://i.imgur.com/V8L9JtT.png' }, |
|
{ id: 'head3', name: 'Demon Head', objUrl: 'models/head3.obj', mtlUrl: 'models/head3.mtl', previewImg: 'https://i.imgur.com/9Xz8K2b.png' } |
|
], |
|
body: [ |
|
{ id: 'body1', name: 'School Uniform', objUrl: 'models/body1.obj', mtlUrl: 'models/body1.mtl', previewImg: 'https://i.imgur.com/pL3QvEH.png' }, |
|
{ id: 'body2', name: 'Armor', objUrl: 'models/body2.obj', mtlUrl: 'models/body2.mtl', previewImg: 'https://i.imgur.com/mN7Z3Yq.png' }, |
|
{ id: 'body3', name: 'Casual Outfit', objUrl: 'models/body3.obj', mtlUrl: 'models/body3.mtl', previewImg: 'https://i.imgur.com/L8jKQ1W.png' } |
|
], |
|
hair: [ |
|
{ id: 'hair1', name: 'Twin Tails', objUrl: 'models/hair1.obj', mtlUrl: 'models/hair1.mtl', previewImg: 'https://i.imgur.com/5w4bQeD.png' }, |
|
{ id: 'hair2', name: 'Long Straight', objUrl: 'models/hair2.obj', mtlUrl: 'models/hair2.mtl', previewImg: 'https://i.imgur.com/3RkLQ9z.png' }, |
|
{ id: 'hair3', name: 'Short Bob', objUrl: 'models/hair3.obj', mtlUrl: 'models/hair3.mtl', previewImg: 'https://i.imgur.com/JQ9vL2X.png' } |
|
], |
|
accessory: [ |
|
{ id: 'acc1', name: 'Cat Ears', objUrl: 'models/acc1.obj', mtlUrl: 'models/acc1.mtl', previewImg: 'https://i.imgur.com/V8L9JtT.png' }, |
|
{ id: 'acc2', name: 'Glasses', objUrl: 'models/acc2.obj', mtlUrl: 'models/acc2.mtl', previewImg: 'https://i.imgur.com/9Xz8K2b.png' }, |
|
{ id: 'acc3', name: 'Ribbon', objUrl: 'models/acc3.obj', mtlUrl: 'models/acc3.mtl', previewImg: 'https://i.imgur.com/JQ6o5aX.png' } |
|
], |
|
weapon: [ |
|
{ id: 'wep1', name: 'Katana', objUrl: 'models/wep1.obj', mtlUrl: 'models/wep1.mtl', previewImg: 'https://i.imgur.com/mN7Z3Yq.png' }, |
|
{ id: 'wep2', name: 'Magic Staff', objUrl: 'models/wep2.obj', mtlUrl: 'models/wep2.mtl', previewImg: 'https://i.imgur.com/pL3QvEH.png' }, |
|
{ id: 'wep3', name: 'Gun', objUrl: 'models/wep3.obj', mtlUrl: 'models/wep3.mtl', previewImg: 'https://i.imgur.com/5w4bQeD.png' } |
|
] |
|
}; |
|
|
|
|
|
const categoryButtons = document.querySelectorAll('.category-btn'); |
|
const itemsGrid = document.getElementById('itemsGrid'); |
|
const adminBtn = document.getElementById('adminBtn'); |
|
const adminModal = document.getElementById('adminModal'); |
|
const closeAdminModal = document.getElementById('closeAdminModal'); |
|
const randomizeBtn = document.getElementById('randomizeBtn'); |
|
const downloadBtn = document.getElementById('downloadBtn'); |
|
const addItemBtn = document.getElementById('addItemBtn'); |
|
const removeItemBtn = document.getElementById('removeItemBtn'); |
|
const modelFilesInput = document.getElementById('modelFiles'); |
|
const uploadProgress = document.getElementById('uploadProgress'); |
|
const uploadStatus = document.getElementById('uploadStatus'); |
|
const sakuraContainer = document.getElementById('sakura-container'); |
|
|
|
|
|
function initThreeJS() { |
|
|
|
scene = new THREE.Scene(); |
|
scene.background = new THREE.Color(0xf0f0f0); |
|
|
|
|
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); |
|
scene.add(ambientLight); |
|
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); |
|
directionalLight.position.set(100, 100, 50); |
|
scene.add(directionalLight); |
|
|
|
|
|
camera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000); |
|
camera.position.set(0, 100, 300); |
|
|
|
|
|
const canvas = document.getElementById('renderCanvas'); |
|
renderer = new THREE.WebGLRenderer({ canvas, antialias: true }); |
|
renderer.setPixelRatio(window.devicePixelRatio); |
|
renderer.setSize(canvas.clientWidth, canvas.clientHeight); |
|
|
|
|
|
controls = new THREE.OrbitControls(camera, renderer.domElement); |
|
controls.enableDamping = true; |
|
controls.dampingFactor = 0.05; |
|
controls.minDistance = 150; |
|
controls.maxDistance = 500; |
|
|
|
|
|
scene.add(character); |
|
|
|
|
|
const gridHelper = new THREE.GridHelper(200, 20, 0x888888, 0x888888); |
|
gridHelper.position.y = -10; |
|
scene.add(gridHelper); |
|
|
|
|
|
animate(); |
|
} |
|
|
|
|
|
function animate() { |
|
requestAnimationFrame(animate); |
|
controls.update(); |
|
renderer.render(scene, camera); |
|
} |
|
|
|
|
|
function loadModel(objUrl, mtlUrl, callback) { |
|
const mtlLoader = new THREE.MTLLoader(); |
|
mtlLoader.load(mtlUrl, (materials) => { |
|
materials.preload(); |
|
|
|
const objLoader = new THREE.OBJLoader(); |
|
objLoader.setMaterials(materials); |
|
objLoader.load(objUrl, (object) => { |
|
if (callback) callback(object); |
|
}); |
|
}); |
|
} |
|
|
|
|
|
function addCharacterPart(partType, item) { |
|
|
|
if (currentParts[partType]) { |
|
character.remove(currentParts[partType]); |
|
} |
|
|
|
|
|
loadModel(item.objUrl, item.mtlUrl, (model) => { |
|
|
|
switch(partType) { |
|
case 'head': |
|
model.position.set(0, 80, 0); |
|
model.scale.set(10, 10, 10); |
|
break; |
|
case 'body': |
|
model.position.set(0, 0, 0); |
|
model.scale.set(10, 10, 10); |
|
break; |
|
case 'hair': |
|
model.position.set(0, 90, 0); |
|
model.scale.set(10, 10, 10); |
|
break; |
|
case 'accessory': |
|
model.position.set(0, 100, 0); |
|
model.scale.set(8, 8, 8); |
|
break; |
|
case 'weapon': |
|
model.position.set(20, 50, 0); |
|
model.scale.set(5, 5, 5); |
|
break; |
|
} |
|
|
|
character.add(model); |
|
currentParts[partType] = model; |
|
}); |
|
} |
|
|
|
|
|
function init() { |
|
initThreeJS(); |
|
|
|
|
|
loadCategoryItems('head'); |
|
|
|
|
|
setupEventListeners(); |
|
|
|
|
|
createSakuraPetals(); |
|
} |
|
|
|
|
|
function setupEventListeners() { |
|
|
|
categoryButtons.forEach(button => { |
|
button.addEventListener('click', () => { |
|
const category = button.dataset.category; |
|
|
|
categoryButtons.forEach(btn => btn.classList.remove('active')); |
|
button.classList.add('active'); |
|
|
|
loadCategoryItems(category); |
|
}); |
|
}); |
|
|
|
|
|
adminBtn.addEventListener('click', () => { |
|
adminModal.style.display = 'flex'; |
|
}); |
|
|
|
|
|
closeAdminModal.addEventListener('click', () => { |
|
adminModal.style.display = 'none'; |
|
}); |
|
|
|
|
|
randomizeBtn.addEventListener('click', randomizeCharacter); |
|
|
|
|
|
downloadBtn.addEventListener('click', downloadCharacter); |
|
|
|
|
|
addItemBtn.addEventListener('click', addNewItem); |
|
|
|
|
|
removeItemBtn.addEventListener('click', removeItem); |
|
|
|
|
|
modelFilesInput.addEventListener('change', handleFileUpload); |
|
|
|
|
|
window.addEventListener('resize', onWindowResize); |
|
} |
|
|
|
|
|
function loadCategoryItems(category) { |
|
itemsGrid.innerHTML = ''; |
|
|
|
|
|
const noneItem = document.createElement('div'); |
|
noneItem.className = `item-thumbnail flex flex-col items-center justify-center cursor-pointer ${!currentParts[category] ? 'selected' : ''}`; |
|
noneItem.innerHTML = ` |
|
<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"> |
|
<i class="fas fa-times text-pink-500 text-4xl"></i> |
|
</div> |
|
<span class="text-sm font-medium text-pink-800">None</span> |
|
`; |
|
noneItem.addEventListener('click', () => { |
|
|
|
if (currentParts[category]) { |
|
character.remove(currentParts[category]); |
|
currentParts[category] = null; |
|
} |
|
|
|
|
|
document.querySelectorAll(`.item-thumbnail`).forEach(item => { |
|
item.classList.remove('selected'); |
|
}); |
|
noneItem.classList.add('selected'); |
|
}); |
|
itemsGrid.appendChild(noneItem); |
|
|
|
|
|
characterItems[category].forEach(item => { |
|
const itemElement = document.createElement('div'); |
|
itemElement.className = `item-thumbnail flex flex-col items-center justify-center cursor-pointer ${currentParts[category] && currentParts[category].userData.id === item.id ? 'selected' : ''}`; |
|
itemElement.innerHTML = ` |
|
<div class="w-full h-32 bg-gradient-to-br from-pink-100 to-pink-200 rounded-lg mb-2 overflow-hidden"> |
|
<img src="${item.previewImg}" alt="${item.name}" class="w-full h-full object-cover"> |
|
</div> |
|
<span class="text-sm font-medium text-pink-800">${item.name}</span> |
|
`; |
|
itemElement.addEventListener('click', () => { |
|
|
|
addCharacterPart(category, item); |
|
|
|
|
|
document.querySelectorAll(`.item-thumbnail`).forEach(item => { |
|
item.classList.remove('selected'); |
|
}); |
|
itemElement.classList.add('selected'); |
|
}); |
|
itemsGrid.appendChild(itemElement); |
|
}); |
|
} |
|
|
|
|
|
function randomizeCharacter() { |
|
Object.keys(characterItems).forEach(category => { |
|
if (characterItems[category].length > 0) { |
|
|
|
const randomIndex = Math.floor(Math.random() * characterItems[category].length); |
|
const randomItem = characterItems[category][randomIndex]; |
|
|
|
|
|
addCharacterPart(category, randomItem); |
|
} |
|
}); |
|
|
|
|
|
const currentCategory = document.querySelector('.category-btn.active').dataset.category; |
|
loadCategoryItems(currentCategory); |
|
} |
|
|
|
|
|
function downloadCharacter() { |
|
|
|
controls.enabled = false; |
|
|
|
|
|
renderer.render(scene, camera); |
|
const canvas = renderer.domElement; |
|
|
|
|
|
const link = document.createElement('a'); |
|
link.download = 'my-anime-character.png'; |
|
link.href = canvas.toDataURL('image/png'); |
|
link.click(); |
|
|
|
|
|
controls.enabled = true; |
|
|
|
|
|
const notification = document.createElement('div'); |
|
notification.className = 'fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg'; |
|
notification.innerHTML = '<i class="fas fa-check-circle mr-2"></i> Character downloaded!'; |
|
document.body.appendChild(notification); |
|
|
|
setTimeout(() => { |
|
notification.classList.add('opacity-0', 'transition-opacity', 'duration-500'); |
|
setTimeout(() => notification.remove(), 500); |
|
}, 3000); |
|
} |
|
|
|
|
|
function handleFileUpload(e) { |
|
const files = e.target.files; |
|
if (files.length === 0) return; |
|
|
|
uploadStatus.textContent = 'Uploading...'; |
|
uploadProgress.style.width = '0%'; |
|
|
|
|
|
let progress = 0; |
|
const interval = setInterval(() => { |
|
progress += 5; |
|
uploadProgress.style.width = `${progress}%`; |
|
|
|
if (progress >= 100) { |
|
clearInterval(interval); |
|
uploadStatus.textContent = 'Upload complete!'; |
|
|
|
|
|
setTimeout(() => { |
|
document.getElementById('adminImageUrl').value = 'models/uploaded_model.obj'; |
|
}, 500); |
|
} |
|
}, 100); |
|
} |
|
|
|
|
|
function addNewItem() { |
|
const category = document.getElementById('adminCategory').value; |
|
const name = document.getElementById('adminItemName').value.trim(); |
|
|
|
if (!name) { |
|
alert('Please fill in all fields'); |
|
return; |
|
} |
|
|
|
|
|
const id = `${category}${characterItems[category].length + 1}`; |
|
|
|
|
|
characterItems[category].push({ |
|
id, |
|
name, |
|
objUrl: 'models/uploaded_model.obj', |
|
mtlUrl: 'models/uploaded_model.mtl', |
|
previewImg: 'https://i.imgur.com/JQ6o5aX.png' |
|
}); |
|
|
|
|
|
document.getElementById('adminItemName').value = ''; |
|
modelFilesInput.value = ''; |
|
uploadProgress.style.width = '0%'; |
|
uploadStatus.textContent = ''; |
|
|
|
|
|
const currentCategory = document.querySelector('.category-btn.active').dataset.category; |
|
if (currentCategory === category) { |
|
loadCategoryItems(category); |
|
} |
|
|
|
|
|
const notification = document.createElement('div'); |
|
notification.className = 'fixed bottom-4 right-4 bg-blue-500 text-white px-4 py-2 rounded-lg shadow-lg'; |
|
notification.innerHTML = '<i class="fas fa-check-circle mr-2"></i> Item added successfully!'; |
|
document.body.appendChild(notification); |
|
|
|
setTimeout(() => { |
|
notification.classList.add('opacity-0', 'transition-opacity', 'duration-500'); |
|
setTimeout(() => notification.remove(), 500); |
|
}, 3000); |
|
} |
|
|
|
|
|
function removeItem() { |
|
const category = document.getElementById('adminCategory').value; |
|
const name = document.getElementById('adminItemName').value.trim(); |
|
|
|
if (!name) { |
|
alert('Please enter the name of the item to remove'); |
|
return; |
|
} |
|
|
|
|
|
const itemIndex = characterItems[category].findIndex(item => item.name === name); |
|
|
|
if (itemIndex === -1) { |
|
alert('Item not found'); |
|
return; |
|
} |
|
|
|
|
|
characterItems[category].splice(itemIndex, 1); |
|
|
|
|
|
if (currentParts[category] && currentParts[category].userData.id === name) { |
|
character.remove(currentParts[category]); |
|
currentParts[category] = null; |
|
} |
|
|
|
|
|
document.getElementById('adminItemName').value = ''; |
|
modelFilesInput.value = ''; |
|
|
|
|
|
const currentCategory = document.querySelector('.category-btn.active').dataset.category; |
|
if (currentCategory === category) { |
|
loadCategoryItems(category); |
|
} |
|
|
|
|
|
const notification = document.createElement('div'); |
|
notification.className = 'fixed bottom-4 right-4 bg-red-500 text-white px-4 py-2 rounded-lg shadow-lg'; |
|
notification.innerHTML = '<i class="fas fa-check-circle mr-2"></i> Item removed successfully!'; |
|
document.body.appendChild(notification); |
|
|
|
setTimeout(() => { |
|
notification.classList.add('opacity-0', 'transition-opacity', 'duration-500'); |
|
setTimeout(() => notification.remove(), 500); |
|
}, 3000); |
|
} |
|
|
|
|
|
function onWindowResize() { |
|
const canvas = document.getElementById('renderCanvas'); |
|
camera.aspect = canvas.clientWidth / canvas.clientHeight; |
|
camera.updateProjectionMatrix(); |
|
renderer.setSize(canvas.clientWidth, canvas.clientHeight); |
|
} |
|
|
|
|
|
function createSakuraPetals() { |
|
for (let i = 0; i < 30; i++) { |
|
createSakuraPetal(); |
|
} |
|
} |
|
|
|
function createSakuraPetal() { |
|
const petal = document.createElement('div'); |
|
petal.className = 'sakura'; |
|
|
|
|
|
const startX = Math.random() * window.innerWidth; |
|
const startY = -20; |
|
|
|
|
|
const size = Math.random() * 10 + 5; |
|
|
|
|
|
const rotation = Math.random() * 360; |
|
|
|
|
|
const duration = Math.random() * 10 + 10; |
|
|
|
|
|
const delay = Math.random() * 5; |
|
|
|
|
|
petal.style.left = `${startX}px`; |
|
petal.style.top = `${startY}px`; |
|
petal.style.width = `${size}px`; |
|
petal.style.height = `${size}px`; |
|
petal.style.transform = `rotate(${rotation}deg)`; |
|
|
|
|
|
sakuraContainer.appendChild(petal); |
|
|
|
|
|
const animation = petal.animate([ |
|
{ top: `${startY}px`, left: `${startX}px`, opacity: 0 }, |
|
{ opacity: 0.7 }, |
|
{ top: `${window.innerHeight}px`, left: `${startX + (Math.random() * 200 - 100)}px`, opacity: 0 } |
|
], { |
|
duration: duration * 1000, |
|
delay: delay * 1000, |
|
easing: 'linear' |
|
}); |
|
|
|
|
|
animation.onfinish = () => { |
|
petal.remove(); |
|
createSakuraPetal(); |
|
}; |
|
} |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', init); |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Skyd3d/skydy" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |