Eyes
${cc_escapeHTML(cc_data.eyes) || 'N/A'}
Height / Build
${cc_escapeHTML(cc_data.height) || 'N/A'}
Distinguishing Features
${cc_escapeHTML(cc_data.features) || 'N/A'}
Personality
Strengths
${cc_data.strengths.length > 0 ? cc_data.strengths.map(s => `- ${cc_escapeHTML(s)}
`).join('') : `- No strengths listed.
`}
Weaknesses / Flaws
${cc_data.weaknesses.length > 0 ? cc_data.weaknesses.map(w => `- ${cc_escapeHTML(w)}
`).join('') : `- No weaknesses listed.
`}
Narrative
Backstory
${cc_escapeHTML(cc_data.backstory) || 'No backstory provided.'}
Goal / Motivation
${cc_escapeHTML(cc_data.motivation) || 'No motivation provided.'}
`;
}
/**
* Reads all values from the config tab and updates the cc_data object
*/
function cc_updateDataFromConfig() {
if (!cc_inputName) return;
cc_data.name = cc_inputName.value;
cc_data.age = cc_inputAge.value;
cc_data.gender = cc_inputGender.value;
cc_data.role = cc_inputRole.value;
cc_data.hair = cc_inputHair.value;
cc_data.eyes = cc_inputEyes.value;
cc_data.height = cc_inputHeight.value;
cc_data.features = cc_inputFeatures.value;
cc_data.backstory = cc_inputBackstory.value;
cc_data.motivation = cc_inputMotivation.value;
cc_data.strengths = [];
cc_strengthsContainer.querySelectorAll('.cc-input-strength').forEach(input => {
if (input.value) cc_data.strengths.push(input.value);
});
cc_data.weaknesses = [];
cc_weaknessesContainer.querySelectorAll('.cc-input-weakness').forEach(input => {
if (input.value) cc_data.weaknesses.push(input.value);
});
}
/**
* Generates and downloads a multi-page PDF of the dashboard
*/
async function cc_downloadPDF() {
// Check for required libraries
if (typeof jspdf === 'undefined' || typeof html2canvas === 'undefined') {
console.error("CC Tool Error: jsPDF or html2canvas library not loaded.");
alert("Error: PDF libraries failed to load. Please check console.");
return;
}
// 1. Render the full-size report into the clone
cc_renderDashboard(cc_pdfRenderClone);
const { jsPDF } = window.jspdf;
try {
// 2. Render canvas from the clone
const canvas = await html2canvas(cc_pdfRenderClone, {
scale: 2, // High resolution
useCORS: true,
windowWidth: cc_pdfRenderClone.scrollWidth,
windowHeight: cc_pdfRenderClone.scrollHeight
});
const imgData = canvas.toDataURL('image/png');
const imgWidth = canvas.width;
const imgHeight = canvas.height;
// Use 'pt' for units. A4 is 595.28 x 841.89
const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
// Scale image height to fit pdf width
const ratio = imgHeight / imgWidth;
const scaledImgHeight = (pdfWidth / imgWidth) * imgHeight;
let heightLeft = scaledImgHeight;
let position = 0; // y-position of the image on the page
const margin = 40; // 40pt margin
const contentWidth = pdfWidth - (margin * 2);
const contentHeight = (contentWidth * imgHeight) / imgWidth;
// 3. Add the first page
pdf.addImage(imgData, 'PNG', margin, position + margin, contentWidth, contentHeight);
heightLeft -= (pdfHeight - margin * 2);
// 4. Add subsequent pages if needed
while (heightLeft > 0) {
position -= (pdfHeight - margin * 2); // Move the image's y-position up
pdf.addPage();
pdf.addImage(imgData, 'PNG', margin, position + margin, contentWidth, contentHeight);
heightLeft -= (pdfHeight - margin * 2);
}
const safeName = (cc_data.name || 'character_profile').replace(/[^a-z0-9]/gi, '_').toLowerCase();
pdf.save(`${safeName}.pdf`);
} catch (error) {
console.error("CC Tool Error: PDF generation failed.", error);
alert("An error occurred while generating the PDF. Please try again.");
}
}
// --- EVENT LISTENERS ---
// Tab link clicks
cc_tabLinks.forEach((link, index) => {
link.addEventListener('click', () => cc_switchTab(index));
});
// Next/Prev button clicks
if (cc_prevButton) {
cc_prevButton.addEventListener('click', () => {
if (cc_currentTab > 0) cc_switchTab(cc_currentTab - 1);
});
}
if (cc_nextButton) {
cc_nextButton.addEventListener('click', () => {
if (cc_currentTab < cc_tabLinks.length - 1) cc_switchTab(cc_currentTab + 1);
});
}
// PDF download
if (cc_downloadPdfButton) {
cc_downloadPdfButton.addEventListener('click', cc_downloadPDF);
}
// --- Config Tab "Add" Buttons ---
if (cc_addStrengthButton) {
cc_addStrengthButton.addEventListener('click', () => cc_addStrengthInput('', cc_strengthCounter++));
}
if (cc_addWeaknessButton) {
cc_addWeaknessButton.addEventListener('click', () => cc_addWeaknessInput('', cc_weaknessCounter++));
}
// --- Config Tab "Remove" Buttons (Event Delegation) ---
if (cc_configTab) {
cc_configTab.addEventListener('click', (e) => {
if (e.target.classList.contains('cc-remove-item')) {
e.target.closest('.flex').remove();
}
});
// Auto-update dashboard on config changes (change event)
cc_configTab.addEventListener('change', () => {
cc_updateDataFromConfig();
if (cc_currentTab === 0) {
cc_renderDashboard(cc_dashboardOutput);
}
});
// Also listen for keyup for textareas/inputs for faster preview
cc_configTab.addEventListener('keyup', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
cc_updateDataFromConfig();
if (cc_currentTab === 0) {
cc_renderDashboard(cc_dashboardOutput);
}
}
});
}
// --- INITIALIZATION ---
cc_initSampleData();
cc_renderConfig();
cc_renderDashboard(cc_dashboardOutput);
// Set initial tab state
cc_tabPanes.forEach((pane, index) => {
pane.classList.toggle('hidden', index !== 0);
pane.classList.toggle('cc-active', index === 0);
});
});