Character Design Tool

Character Design Tool

Character Design Tool

Build Your Character

Edit Features

No features defined. Add categories and options below.

'; generateDescription(); // Clear description return; } cdtConfig.categories.forEach(category => { const formGroup = document.createElement('div'); formGroup.className = 'cdt-form-group'; const label = document.createElement('label'); label.htmlFor = `select-${category.id}`; label.textContent = category.name; const select = document.createElement('select'); select.id = `select-${category.id}`; select.className = 'cdt-select'; select.dataset.categoryId = category.id; select.onchange = handleOptionSelect; // Attach event listener // Add default option const defaultOption = document.createElement('option'); defaultOption.value = ''; defaultOption.textContent = `-- Select ${category.name} --`; select.appendChild(defaultOption); // Add category options category.options.forEach(option => { const opt = document.createElement('option'); opt.value = option.id; opt.textContent = option.name; // Pre-select if it's in the state if (cdtState.selectedOptions[category.id] === option.id) { opt.selected = true; } select.appendChild(opt); }); formGroup.appendChild(label); formGroup.appendChild(select); builderGrid.appendChild(formGroup); }); generateDescription(); // Update description after rendering selects } function generateDescription() { if (!descriptionOutput) return; let description = "Character Profile:\n"; let hasSelection = false; cdtConfig.categories.forEach(category => { const selectedOptionId = cdtState.selectedOptions[category.id]; if (selectedOptionId) { const option = findOption(category.id, selectedOptionId); if (option) { description += `- ${category.name}: ${option.name}\n`; hasSelection = true; } } }); descriptionOutput.value = hasSelection ? description : "Select features above to generate a description..."; } function renderConfig() { if (!categoryList || !optionList || !optionsTitle || !addOptionBtn) return; // 1. Render Categories categoryList.innerHTML = ''; if(cdtConfig.categories.length === 0) { categoryList.innerHTML = '

No categories yet.

'; } cdtConfig.categories.forEach(category => { const item = document.createElement('div'); item.className = 'cdt-list-item'; if (category.id === cdtState.selectedCategoryId) { item.classList.add('selected'); } item.dataset.categoryId = category.id; item.onclick = handleCategorySelect; // Select category on click item.innerHTML = ` ${category.name}
`; categoryList.appendChild(item); }); // 2. Render Options for selected category optionList.innerHTML = ''; addOptionBtn.disabled = !cdtState.selectedCategoryId; if (cdtState.selectedCategoryId) { const category = findCategory(cdtState.selectedCategoryId); if (category) { optionsTitle.textContent = `Options for ${category.name}`; if (category.options.length === 0) { optionList.innerHTML = '

No options yet.

'; } category.options.forEach(option => { const item = document.createElement('div'); item.className = 'cdt-list-item'; item.dataset.optionId = option.id; item.innerHTML = ` ${option.name}
`; optionList.appendChild(item); }); } else { // Should not happen if state is managed correctly optionsTitle.textContent = 'Options for...'; optionList.innerHTML = '

Error: Selected category not found.

'; cdtState.selectedCategoryId = null; // Reset selection } } else { optionsTitle.textContent = 'Options for...'; optionList.innerHTML = '

Select a category to view/edit options.

'; } } function fullRefreshUI() { renderBuilder(); renderConfig(); } // === EVENT HANDLERS (Must be global for onclick) === window.cdtToggleEditSection = () => { if (!editContent || !toggleEditText) return; cdtState.editSectionVisible = !cdtState.editSectionVisible; editContent.style.display = cdtState.editSectionVisible ? 'block' : 'none'; toggleEditText.textContent = cdtState.editSectionVisible ? 'Hide' : 'Show'; const icon = editContent.previousElementSibling.querySelector('button i'); if (icon) { icon.className = cdtState.editSectionVisible ? 'fas fa-chevron-up' : 'fas fa-chevron-down'; } }; // --- Builder Handlers --- function handleOptionSelect(event) { const selectElement = event.target; const categoryId = selectElement.dataset.categoryId; const optionId = selectElement.value; if (optionId) { cdtState.selectedOptions[categoryId] = optionId; } else { delete cdtState.selectedOptions[categoryId]; // Remove if default is selected } generateDescription(); // Update description immediately } // --- Config Handlers --- window.cdtAddCategory = () => { if (!newCategoryNameInput) return; const name = newCategoryNameInput.value.trim(); if (!name) { alert('Please enter a category name.'); return; } // Check for duplicates if (cdtConfig.categories.some(c => c.name.toLowerCase() === name.toLowerCase())) { alert('A category with this name already exists.'); return; } cdtConfig.categories.push({ id: 'c' + Date.now(), name, options: [] }); newCategoryNameInput.value = ''; renderConfig(); renderBuilder(); // Update the builder dropdowns }; function handleCategorySelect(event) { const item = event.currentTarget; // The div that was clicked cdtState.selectedCategoryId = item.dataset.categoryId; renderConfig(); // Re-render to show selection highlight and options } // Make it available globally for inline onclick window.handleCategorySelect = handleCategorySelect; window.cdtDeleteCategory = (id) => { if (!confirm('Are you sure you want to delete this category and all its options?')) return; cdtConfig.categories = cdtConfig.categories.filter(c => c.id !== id); // Deselect if the deleted category was selected if (cdtState.selectedCategoryId === id) { cdtState.selectedCategoryId = null; } // Remove selections for this category from the character delete cdtState.selectedOptions[id]; renderConfig(); renderBuilder(); }; window.cdtAddOption = () => { if (!newOptionNameInput || !cdtState.selectedCategoryId) return; const name = newOptionNameInput.value.trim(); if (!name) { alert('Please enter an option name.'); return; } const category = findCategory(cdtState.selectedCategoryId); if (!category) { alert('Error: No category selected.'); return; } // Check for duplicates within the category if (category.options.some(o => o.name.toLowerCase() === name.toLowerCase())) { alert('An option with this name already exists in this category.'); return; } category.options.push({ id: 'o' + Date.now(), name }); newOptionNameInput.value = ''; renderConfig(); // Re-render options list renderBuilder(); // Update the corresponding dropdown }; window.cdtDeleteOption = (categoryId, optionId) => { // No confirmation needed for quick deletion const category = findCategory(categoryId); if (category) { category.options = category.options.filter(o => o.id !== optionId); // If this option was selected for the character, remove it if (cdtState.selectedOptions[categoryId] === optionId) { delete cdtState.selectedOptions[categoryId]; } renderConfig(); renderBuilder(); } }; // --- PDF Download --- window.cdtDownloadPDF = () => { if (!jsPDF || !jsPDF.autoTable) { alert('PDF library not loaded.'); return; } try { const doc = new jsPDF(); const pageMargin = 15; const pageWidth = doc.internal.pageSize.getWidth(); const contentWidth = pageWidth - pageMargin * 2; let y = 20; // 1. Title doc.setFontSize(20); doc.text("Character Sheet", pageWidth / 2, y, { align: 'center' }); y += 15; // 2. Features Table doc.setFontSize(16); doc.text("Selected Features", pageMargin, y); y += 10; const tableHead = [["Category", "Selection"]]; const tableBody = []; let hasSelections = false; cdtConfig.categories.forEach(category => { const selectedOptionId = cdtState.selectedOptions[category.id]; const option = selectedOptionId ? findOption(category.id, selectedOptionId) : null; if (option) { tableBody.push([category.name, option.name]); hasSelections = true; } else { tableBody.push([category.name, "(Not Selected)"]); } }); if (tableBody.length > 0) { doc.autoTable({ startY: y, head: tableHead, body: tableBody, theme: 'striped', headStyles: { fillColor: [0, 95, 204] }, // Primary color }); y = doc.autoTable.previous.finalY + 15; } else { doc.setFontSize(12); doc.text("No categories defined yet.", pageMargin, y); y += 10; } // 3. Description if (hasSelections && descriptionOutput) { doc.setFontSize(16); doc.text("Character Description", pageMargin, y); y += 10; doc.setFontSize(11); doc.setFont(undefined, 'normal'); const splitText = doc.splitTextToSize( descriptionOutput.value, contentWidth ); doc.text(splitText, pageMargin, y); } else if (!hasSelections) { doc.setFontSize(12); doc.text("No features selected to generate a description.", pageMargin, y); } doc.save("Character_Sheet.pdf"); } catch (e) { console.error("Error generating PDF:", e); alert("An error occurred while generating the PDF."); } }; // === INITIALIZATION === fullRefreshUI(); // Initially hide edit section if (editContent) editContent.style.display = 'none'; if (toggleEditText) toggleEditText.textContent = 'Show'; });