skydy / index.html
Skyd3d's picture
Add 1 files
30d19bc verified
raw
history blame
35.6 kB
<!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>
<!-- Sakura Petals Animation -->
<div id="sakura-container"></div>
<!-- Header -->
<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 Content -->
<main class="container mx-auto px-4 py-8">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Character Preview -->
<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>
<!-- Customization Panel -->
<div class="lg:col-span-2">
<div class="panel">
<!-- Category Tabs -->
<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>
<!-- Items Grid -->
<div id="itemsGrid" class="p-6">
<!-- Items will be loaded here dynamically -->
</div>
</div>
</div>
</div>
</main>
<!-- Admin Modal -->
<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>
// Three.js variables
let scene, camera, renderer, controls;
let character = new THREE.Group();
let currentParts = {
head: null,
body: null,
hair: null,
accessory: null,
weapon: null
};
// Sample data for character items (with 3D model references)
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' }
]
};
// DOM elements
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');
// Initialize Three.js scene
function initThreeJS() {
// Create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
// Add lights
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);
// Create camera
camera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000);
camera.position.set(0, 100, 300);
// Create renderer
const canvas = document.getElementById('renderCanvas');
renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
// Add controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 150;
controls.maxDistance = 500;
// Add character container
scene.add(character);
// Add floor grid
const gridHelper = new THREE.GridHelper(200, 20, 0x888888, 0x888888);
gridHelper.position.y = -10;
scene.add(gridHelper);
// Start animation loop
animate();
}
// Animation loop
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
// Load a 3D model
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);
});
});
}
// Add a part to the character
function addCharacterPart(partType, item) {
// Remove existing part if any
if (currentParts[partType]) {
character.remove(currentParts[partType]);
}
// Load and add new part
loadModel(item.objUrl, item.mtlUrl, (model) => {
// Position the model based on part type
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;
});
}
// Initialize the app
function init() {
initThreeJS();
// Load first category by default
loadCategoryItems('head');
// Set up event listeners
setupEventListeners();
// Create sakura petals
createSakuraPetals();
}
// Set up all event listeners
function setupEventListeners() {
// Category buttons
categoryButtons.forEach(button => {
button.addEventListener('click', () => {
const category = button.dataset.category;
// Update active button
categoryButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
// Load items for this category
loadCategoryItems(category);
});
});
// Admin button
adminBtn.addEventListener('click', () => {
adminModal.style.display = 'flex';
});
// Close admin modal
closeAdminModal.addEventListener('click', () => {
adminModal.style.display = 'none';
});
// Randomize button
randomizeBtn.addEventListener('click', randomizeCharacter);
// Download button
downloadBtn.addEventListener('click', downloadCharacter);
// Add item button
addItemBtn.addEventListener('click', addNewItem);
// Remove item button
removeItemBtn.addEventListener('click', removeItem);
// Model files upload
modelFilesInput.addEventListener('change', handleFileUpload);
// Window resize
window.addEventListener('resize', onWindowResize);
}
// Load items for a specific category
function loadCategoryItems(category) {
itemsGrid.innerHTML = '';
// Add "None" option
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', () => {
// Remove part from character
if (currentParts[category]) {
character.remove(currentParts[category]);
currentParts[category] = null;
}
// Update selected state
document.querySelectorAll(`.item-thumbnail`).forEach(item => {
item.classList.remove('selected');
});
noneItem.classList.add('selected');
});
itemsGrid.appendChild(noneItem);
// Add items for this category
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', () => {
// Add part to character
addCharacterPart(category, item);
// Update selected state
document.querySelectorAll(`.item-thumbnail`).forEach(item => {
item.classList.remove('selected');
});
itemElement.classList.add('selected');
});
itemsGrid.appendChild(itemElement);
});
}
// Randomize all character parts
function randomizeCharacter() {
Object.keys(characterItems).forEach(category => {
if (characterItems[category].length > 0) {
// Get random item from this category
const randomIndex = Math.floor(Math.random() * characterItems[category].length);
const randomItem = characterItems[category][randomIndex];
// Add part to character
addCharacterPart(category, randomItem);
}
});
// Reload current category to update selected states
const currentCategory = document.querySelector('.category-btn.active').dataset.category;
loadCategoryItems(currentCategory);
}
// Download character image
function downloadCharacter() {
// Temporarily disable controls
controls.enabled = false;
// Render to canvas
renderer.render(scene, camera);
const canvas = renderer.domElement;
// Create download link
const link = document.createElement('a');
link.download = 'my-anime-character.png';
link.href = canvas.toDataURL('image/png');
link.click();
// Re-enable controls
controls.enabled = true;
// Show success message
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);
}
// Handle file upload
function handleFileUpload(e) {
const files = e.target.files;
if (files.length === 0) return;
uploadStatus.textContent = 'Uploading...';
uploadProgress.style.width = '0%';
// Simulate upload progress (in a real app, you would upload to a server)
let progress = 0;
const interval = setInterval(() => {
progress += 5;
uploadProgress.style.width = `${progress}%`;
if (progress >= 100) {
clearInterval(interval);
uploadStatus.textContent = 'Upload complete!';
// In a real app, you would get the server URLs here
setTimeout(() => {
document.getElementById('adminImageUrl').value = 'models/uploaded_model.obj';
}, 500);
}
}, 100);
}
// Add new item to a category
function addNewItem() {
const category = document.getElementById('adminCategory').value;
const name = document.getElementById('adminItemName').value.trim();
if (!name) {
alert('Please fill in all fields');
return;
}
// Generate ID
const id = `${category}${characterItems[category].length + 1}`;
// Add to our data (in a real app, you would save to a database)
characterItems[category].push({
id,
name,
objUrl: 'models/uploaded_model.obj',
mtlUrl: 'models/uploaded_model.mtl',
previewImg: 'https://i.imgur.com/JQ6o5aX.png' // Default preview image
});
// Clear form
document.getElementById('adminItemName').value = '';
modelFilesInput.value = '';
uploadProgress.style.width = '0%';
uploadStatus.textContent = '';
// Reload items if this is the current category
const currentCategory = document.querySelector('.category-btn.active').dataset.category;
if (currentCategory === category) {
loadCategoryItems(category);
}
// Show success notification
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);
}
// Remove item from a category
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;
}
// Find item index
const itemIndex = characterItems[category].findIndex(item => item.name === name);
if (itemIndex === -1) {
alert('Item not found');
return;
}
// Remove item
characterItems[category].splice(itemIndex, 1);
// Clear selection if it was this item
if (currentParts[category] && currentParts[category].userData.id === name) {
character.remove(currentParts[category]);
currentParts[category] = null;
}
// Clear form
document.getElementById('adminItemName').value = '';
modelFilesInput.value = '';
// Reload items if this is the current category
const currentCategory = document.querySelector('.category-btn.active').dataset.category;
if (currentCategory === category) {
loadCategoryItems(category);
}
// Show success notification
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);
}
// Handle window resize
function onWindowResize() {
const canvas = document.getElementById('renderCanvas');
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
}
// Create sakura petals animation
function createSakuraPetals() {
for (let i = 0; i < 30; i++) {
createSakuraPetal();
}
}
function createSakuraPetal() {
const petal = document.createElement('div');
petal.className = 'sakura';
// Random position
const startX = Math.random() * window.innerWidth;
const startY = -20;
// Random size
const size = Math.random() * 10 + 5;
// Random rotation
const rotation = Math.random() * 360;
// Random animation duration
const duration = Math.random() * 10 + 10;
// Random delay
const delay = Math.random() * 5;
// Set initial styles
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)`;
// Add to container
sakuraContainer.appendChild(petal);
// Animate
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'
});
// Restart animation when finished
animation.onfinish = () => {
petal.remove();
createSakuraPetal();
};
}
// Initialize the app when DOM is loaded
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>