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); |