310 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			310 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| let configData = {};
 | |
| let currentTab = 'body';
 | |
| const currentFilename = {};
 | |
| const palettes = {};
 | |
| 
 | |
| // Tab switching and palette rendering
 | |
| function showTab(tab) {
 | |
|     currentTab = tab;
 | |
|   
 | |
|     // Activate corresponding tab content
 | |
|     document.querySelectorAll('.tab-content').forEach(c =>
 | |
|       c.classList.remove('active')
 | |
|     );
 | |
|     document.getElementById(tab)?.classList.add('active');
 | |
|   
 | |
|     // Activate corresponding tab button
 | |
|     document.querySelectorAll('.tab-button').forEach(b =>
 | |
|       b.classList.remove('active')
 | |
|     );
 | |
|     document
 | |
|       .querySelector(`.tab-button[data-tab="${tab}"]`)
 | |
|       ?.classList.add('active');
 | |
|   
 | |
|     renderPalette();
 | |
|   
 | |
|     const dependencyKey = configData.dependencies?.[tab];
 | |
|     if (dependencyKey) {
 | |
|       filterDependentThumbnails(tab, dependencyKey);
 | |
|     }
 | |
|   }
 | |
|   
 | |
| 
 | |
| function renderPalette() {
 | |
|   const palette = document.getElementById('colorPalette');
 | |
|   palette.innerHTML = '';
 | |
| 
 | |
|   const colors = palettes[currentTab] || [];
 | |
| 
 | |
|   if (colors.length) {
 | |
|     palette.style.display = 'flex';
 | |
| 
 | |
|     colors.forEach(color => {
 | |
|       const btn = document.createElement('button');
 | |
|       btn.className = 'color-swatch';
 | |
|       btn.style.background = color;
 | |
|       btn.onclick = () => applyColor(color);
 | |
|       palette.appendChild(btn);
 | |
|     });
 | |
|   } else {
 | |
|     palette.style.display = 'none';
 | |
|   }
 | |
| }
 | |
| 
 | |
| function filterDependentThumbnails(tabId, dependsOn) {
 | |
|   const raw = currentFilename[dependsOn] || '';
 | |
|   const base = raw.split('_')[0].toLowerCase();
 | |
| 
 | |
|   const thumbs = document.querySelectorAll(`#${tabId} .thumbnail`);
 | |
|   thumbs.forEach(img => {
 | |
|     const name = img.src.split('/').pop().split('.')[0].toLowerCase();
 | |
|     if (
 | |
|       name === 'none' ||
 | |
|       !base ||
 | |
|       name.startsWith(base + '_')
 | |
|     ) {
 | |
|       img.style.display = '';
 | |
|     } else {
 | |
|       img.style.display = 'none';
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| function pickRandom(arr) {
 | |
|   return arr[Math.floor(Math.random() * arr.length)] || null;
 | |
| }
 | |
| 
 | |
| function randomizeAvatar() {
 | |
|     const baseLayers = Object.keys(configData.categories)
 | |
|       .filter(cat => !Object.keys(configData.dependencies || {}).includes(cat));
 | |
|   
 | |
|     // Step 1: Randomize base layers
 | |
|     baseLayers.forEach(feature => {
 | |
|       const validImages = configData.categories[feature]
 | |
|         .filter(name => name !== 'none');
 | |
|   
 | |
|       const choice = pickRandom(validImages);
 | |
|   
 | |
|       if (choice) {
 | |
|         const fullImagePath = `assets/${feature}/${choice}.png`;
 | |
|         updateLayer(feature, fullImagePath);
 | |
|       } else {
 | |
|         const fallbackImage = `assets/${feature}/default.png`;
 | |
|         updateLayer(feature, fallbackImage);
 | |
|       }
 | |
|     });
 | |
|   
 | |
|     // Step 2: Wait a moment before applying dependent layers
 | |
|     setTimeout(() => {
 | |
|       const dependents = configData.dependencies || {};
 | |
|       Object.entries(dependents).forEach(([depTab, baseTab]) => {
 | |
|         const raw = currentFilename[baseTab] || '';
 | |
|         const base = raw.split('_')[0].toLowerCase();
 | |
|         const options = configData.categories[depTab] || [];
 | |
|         const valid = options.filter(opt => opt.startsWith(base + '_'));
 | |
|   
 | |
|         const choice = pickRandom(valid);
 | |
|         if (choice) {
 | |
|           const fullImagePath = `assets/${depTab}/${choice}.png`;
 | |
|           updateLayer(depTab, fullImagePath);
 | |
|         } else {
 | |
|           removeLayer(depTab);
 | |
|         }
 | |
|       });
 | |
|     }, 100); // Slight delay ensures base layers have updated currentFilename
 | |
|   }
 | |
|   
 | |
| 
 | |
| async function loadAssetConfig() {
 | |
|     const response = await fetch('assets/config.json');
 | |
|     const config = await response.json();
 | |
|     configData = config;
 | |
|   
 | |
|     Object.assign(palettes, config.colors || {});
 | |
|     const tabWrapper = document.getElementById('tabs');
 | |
|     const contentWrapper = document.getElementById('tabContents');
 | |
|   
 | |
|     Object.keys(config.categories).forEach((category, index) => {
 | |
|         // Create tab button
 | |
|         const btn = document.createElement('button');
 | |
|         btn.className = 'tab-button';
 | |
|         btn.textContent = category.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
 | |
|         btn.onclick = () => showTab(category);
 | |
|         if (index === 0) btn.classList.add('active');
 | |
|         tabWrapper.appendChild(btn);
 | |
|         btn.setAttribute('data-tab', category);
 | |
|             
 | |
|         // Create tab content div
 | |
|         const div = document.createElement('div');
 | |
|         div.id = category;
 | |
|         div.className = 'tab-content';
 | |
|         if (index === 0) div.classList.add('active');
 | |
|         contentWrapper.appendChild(div);
 | |
|     
 | |
|         // Add 'none' thumbnail
 | |
|         const noneImg = document.createElement('img');
 | |
|         noneImg.src = `assets/none.png`;
 | |
|         noneImg.classList.add('thumbnail');
 | |
|         noneImg.onclick = () => removeLayer(category);
 | |
|         div.appendChild(noneImg);
 | |
|     
 | |
|         // Add each asset
 | |
|         config.categories[category].forEach(name => {
 | |
|             const fullSrc = `assets/${category}/${name}.png`;
 | |
|             const thumbSrc = `assets/thumbnails/${category}/${name}_thumb.png`;
 | |
|           
 | |
|             const thumbImg = document.createElement('img');
 | |
|             thumbImg.src = thumbSrc;
 | |
|             thumbImg.classList.add('thumbnail');
 | |
|             thumbImg.onclick = () => updateLayer(category, fullSrc);
 | |
|             div.appendChild(thumbImg);
 | |
|           
 | |
|             // Fallback for thumbnail not loading
 | |
|             thumbImg.onerror = () => {
 | |
|             thumbImg.src = fullSrc;
 | |
|             };
 | |
|           });
 | |
|           
 | |
|         
 | |
|         
 | |
|     });
 | |
|     const avatarContainer = document.getElementById('avatarContainer');
 | |
|     avatarContainer.innerHTML = ''; // Clear any existing layers
 | |
| 
 | |
|     config.layers.forEach(layer => {
 | |
|         const img = document.createElement('img');
 | |
|         img.id = 'layer-' + layer;
 | |
|         img.src = '';
 | |
|         img.style.display = 'none';  // hide by default
 | |
|         img.alt = layer.replace(/_/g, ' ');
 | |
|         avatarContainer.appendChild(img);
 | |
|     });
 | |
|     randomizeAvatar();
 | |
|   }
 | |
|   function updateLayer(layer, src) {
 | |
|     const img = document.getElementById('layer-' + layer);
 | |
|     if (img) {
 | |
|       const newFilename = src.split('/').pop().split('.')[0];
 | |
|       currentFilename[layer] = newFilename;
 | |
|       img.src = src;
 | |
|       img.style.display = '';
 | |
|   
 | |
|       // Check and fix dependent layers if any
 | |
|       Object.entries(configData.dependencies || {}).forEach(([dependent, base]) => {
 | |
|         if (base === layer) {
 | |
|           const dependentImg = document.getElementById('layer-' + dependent);
 | |
|           const dependentFilename = currentFilename[dependent] || '';
 | |
|           const basePrefix = newFilename.split('_')[0].toLowerCase();
 | |
|   
 | |
|           // If dependent layer is incompatible, reset it
 | |
|           if (
 | |
|             dependentFilename &&
 | |
|             dependentFilename !== 'none' &&
 | |
|             !dependentFilename.startsWith(basePrefix + '_')
 | |
|           ) {
 | |
|             removeLayer(dependent);
 | |
|             currentFilename[dependent] = 'none';
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     } else {
 | |
|       console.error(`Layer ${layer} not found`);
 | |
|     }
 | |
|   }
 | |
|   
 | |
| 
 | |
|   function removeLayer(layer) {
 | |
|     const img = document.getElementById('layer-' + layer);
 | |
|     if (img) {
 | |
|       img.style.display = 'none';
 | |
|       currentFilename[layer] = 'none';
 | |
|     }
 | |
|   }
 | |
|   
 | |
| 
 | |
| function applyColor(color) {
 | |
|   const fname = currentFilename[currentTab];
 | |
|   document.getElementById('layer-' + currentTab).src = `assets/${currentTab}/${fname}_${color}.png`;
 | |
| }
 | |
| 
 | |
| function scrollTabs(dir) {
 | |
|   document.getElementById('tabs').scrollLeft += dir * 100;
 | |
| }
 | |
| 
 | |
| function downloadAvatar() {
 | |
|     const orderedLayers = configData.layers || [];
 | |
|     
 | |
|     const srcs = orderedLayers
 | |
|       .map(id => document.getElementById('layer-' + id))
 | |
|       .filter(el => el && el.src && !el.src.endsWith('none.png') && el.style.display !== 'none')
 | |
|       .map(el => el.src);
 | |
| 
 | |
| 
 | |
|   if (!srcs.length) {
 | |
|     alert("Nothing to download!");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const first = new Image();
 | |
|   first.onload = () => {
 | |
|     const W = first.naturalWidth;
 | |
|     const H = first.naturalHeight;
 | |
|     const canvas = document.createElement('canvas');
 | |
|     canvas.width = W;
 | |
|     canvas.height = H;
 | |
|     const ctx = canvas.getContext('2d');
 | |
| 
 | |
|     const draws = srcs.map(src =>
 | |
|       new Promise((res, rej) => {
 | |
|         const img = new Image();
 | |
|         img.onload = () => {
 | |
|           ctx.drawImage(img, 0, 0, W, H);
 | |
|           res();
 | |
|         };
 | |
|         img.onerror = rej;
 | |
|         img.src = src;
 | |
|       })
 | |
|     );
 | |
| 
 | |
|     Promise.all(draws).then(() => {
 | |
|       canvas.toBlob(blob => {
 | |
|         const link = document.createElement('a');
 | |
|         link.download = 'avatar.png';
 | |
|         link.href = URL.createObjectURL(blob);
 | |
|         link.click();
 | |
|       });
 | |
|     });
 | |
|   };
 | |
|   first.onerror = () => alert("Failed to load image.");
 | |
|   first.src = srcs[0];
 | |
| }
 | |
| 
 | |
| // Initialize the app
 | |
| window.addEventListener('DOMContentLoaded', () => {
 | |
|   loadAssetConfig();
 | |
| });
 | |
| 
 | |
| // Leaf animation remains unchanged...
 | |
| 
 | |
|     
 | |
|     // Falling leaf animation
 | |
|     function createLeaf() {
 | |
|       const leaf = document.createElement('div');
 | |
|       leaf.classList.add('leaf');
 | |
|       leaf.style.left = Math.random() * window.innerWidth + 'px';
 | |
|       leaf.style.animationDuration = (5 + Math.random() * 5) + 's';
 | |
|       leaf.style.animationDelay = Math.random() * 5 + 's';
 | |
|       document.body.appendChild(leaf);
 | |
|     }
 | |
|     
 | |
|     // Spawn initial leaves
 | |
|     for (let i = 0; i < 15; i++) {
 | |
|       createLeaf();
 | |
|     }
 | |
|     
 | |
|     // Maintain leaf count
 | |
|     setInterval(() => {
 | |
|       if (document.querySelectorAll('.leaf').length < 30) {
 | |
|           createLeaf();
 | |
|       }
 | |
|     }, 1000); |