Fantasy World Generator
Define core concepts and generate random elements for your next campaign.
1. Foundational Theme
2. Core Elements (Races, Deities, Factions)
Key Races/Factions
Deities/Organizations
3. Random Element Generation
Click a button above to generate a random element!
Add races/factions.
'; } races.forEach((item, index) => { const li = document.createElement('li'); li.className = 'fwg-list-item'; li.innerHTML = `${item}`; raceList.appendChild(li); }); if (deities.length === 0) { deityList.innerHTML = 'Add deities/orgs.
'; } deities.forEach((item, index) => { const li = document.createElement('li'); li.className = 'fwg-list-item'; li.innerHTML = `${item}`; deityList.appendChild(li); }); } function addItem(inputType, listState) { const item = inputType.value.trim(); if (item && !listState.includes(item)) { listState.push(item); inputType.value = ''; renderLists(); } } function removeItem(index, listState, listType) { listState.splice(index, 1); renderLists(); } // Event delegation for removal container.addEventListener('click', (e) => { const target = e.target; if (target.classList.contains('fwg-btn-red') && target.dataset.index) { const index = parseInt(target.dataset.index); if (target.dataset.type === 'race') { removeItem(index, races, 'race'); } else if (target.dataset.type === 'deity') { removeItem(index, deities, 'deity'); } } }); // --- Generation Handlers --- function handleGenerateCity() { const prefix = getRandom(RANDOM_DATA.CityPrefix); const suffix = getRandom(RANDOM_DATA.CitySuffix); outputPlaceholder.classList.add('hidden'); outputCity.innerHTML = `City Name: ${prefix}${suffix}`; } function handleGenerateNpc() { const title = getRandom(RANDOM_DATA.NpcTitles); const role = getRandom(RANDOM_DATA.NpcRoles); outputPlaceholder.classList.add('hidden'); outputNpc.innerHTML = `NPC Concept: ${role}, known as "${title}"`; } function handleGenerateArtifact() { const adj = getRandom(RANDOM_DATA.ArtifactAdjective); const noun = getRandom(RANDOM_DATA.ArtifactNoun); outputPlaceholder.classList.add('hidden'); outputArtifact.innerHTML = `Key Artifact: The ${adj} ${noun} of ${getRandom(races) || 'The Ancients'}`; } // --- PDF Generation (Ensured functionality) --- function downloadPDF() { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Error: jsPDF library not loaded.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('portrait', 'pt', 'a4'); const margin = 40; const pageWidth = doc.internal.pageSize.getWidth(); let currentY = margin; // --- Header --- doc.setFontSize(24); doc.setFont('helvetica', 'bold'); doc.setTextColor(124, 58, 237); doc.text(worldNameInput.value || 'Untitled Fantasy World', pageWidth / 2, currentY, { align: 'center' }); currentY += 15; doc.setFontSize(14); doc.text("World Lore Document", pageWidth / 2, currentY, { align: 'center' }); currentY += 30; // --- Section 1: Core Theme --- doc.setFontSize(16); doc.setFont('helvetica', 'bold'); doc.setTextColor(124, 58, 237); doc.text("1. Foundational Theme", margin, currentY); currentY += 5; const themeData = [ ["Magic System:", magicTypeSelect.value], ["Technology Level:", techLevelSelect.value], ["Primary Conflict:", conflictTypeSelect.value], ]; doc.autoTable({ startY: currentY, body: themeData, theme: 'striped', styles: { cellPadding: 3, fontSize: 10 }, columnStyles: { 0: { fontStyle: 'bold', fillColor: [245, 243, 255] } } }); currentY = doc.autoTable.previous.finalY + 10; doc.setFontSize(10); doc.setFont('helvetica', 'bold'); doc.text("Key World Hook/Pitch:", margin, currentY); currentY += 12; doc.setFont('helvetica', 'normal'); const hookLines = doc.splitTextToSize(keyHookTextarea.value || 'N/A', pageWidth - margin * 2); doc.text(hookLines, margin, currentY); currentY += hookLines.length * 12 + 15; // --- Section 2: Core Elements (Tables) --- doc.setFontSize(16); doc.setFont('helvetica', 'bold'); doc.text("2. Core World Elements", margin, currentY); currentY += 15; const elementData = [ ["Key Races/Factions", races.join('\n')], ["Deities/Organizations", deities.join('\n')] ]; doc.autoTable({ startY: currentY, body: elementData, theme: 'grid', styles: { cellPadding: 4, fontSize: 10, minCellHeight: 20 }, columnStyles: { 0: { fontStyle: 'bold', fillColor: [245, 243, 255], cellWidth: 100 } } }); currentY = doc.autoTable.previous.finalY + 20; // --- Section 3: Generated Elements --- doc.setFontSize(16); doc.setFont('helvetica', 'bold'); doc.text("3. Generated Elements (for reference)", margin, currentY); currentY += 15; const generatedData = [ ["City Name:", outputCity.textContent.replace('City Name:', '').trim() || 'N/A'], ["NPC Concept:", outputNpc.textContent.replace('NPC Concept:', '').trim() || 'N/A'], ["Key Artifact:", outputArtifact.textContent.replace('Key Artifact:', '').trim() || 'N/A'] ]; doc.autoTable({ startY: currentY, body: generatedData, theme: 'striped', styles: { cellPadding: 4, fontSize: 10, minCellHeight: 15 }, columnStyles: { 0: { fontStyle: 'bold', fillColor: [255, 247, 237], textColor: [180, 83, 9] } } }); doc.save('fantasy-world-document.pdf'); } // --- Event Listeners and Initial Load --- addRaceBtn.addEventListener('click', () => addItem(raceInput, races)); addDeityBtn.addEventListener('click', () => addItem(deityInput, deities)); generateCityBtn.addEventListener('click', handleGenerateCity); generateNpcBtn.addEventListener('click', handleGenerateNpc); generateArtifactBtn.addEventListener('click', handleGenerateArtifact); pdfBtn.addEventListener('click', downloadPDF); renderLists(); });