build-a-borrower/scripts.js

310 lines
9.2 KiB
JavaScript
Raw Permalink Normal View History

2025-05-08 01:15:53 +02:00
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);