Public Art Proposal Template

Public Art Proposal Template

Public Art Proposal Template

Structure your proposal covering vision, budget, and logistics.

Comprehensive Proposal Outline

${pap_escapeHTML(pap_data.summary) || 'Executive Summary/Concept Required.'}

`; targetDiv.innerHTML += identityHTML; // 2. Site & Technical Details let technicalHTML = `

2. Site & Technical Details

Proposed Location: ${pap_escapeHTML(pap_data.location) || 'N/A'}
Medium: ${pap_escapeHTML(pap_data.medium) || 'N/A'}

Dimensions & Structural Requirements:

${pap_escapeHTML(pap_data.specs) || 'Dimensions and technical specifications are required.'}

`; targetDiv.innerHTML += technicalHTML; // 3. Budget & Timeline let budgetTableHTML = ''; let totalSum = 0; if (pap_data.budgetBreakdown.length > 0) { budgetTableHTML = pap_data.budgetBreakdown.map(item => { totalSum += item.amount; return ` ${pap_escapeHTML(item.category)} ${pap_formatCurrency(item.amount)} `; }).join(''); } let timelineText = pap_data.duration > 0 ? `Estimated duration: ${pap_data.duration} months.` : 'Timeline/Duration not specified.'; let budgetHTML = `

3. Budget & Timeline

Total Project Cost

${pap_formatCurrency(pap_data.totalBudget)}

${timelineText}

Detailed Breakdown

${budgetTableHTML || ''}
Category Amount (USD)
No breakdown items added.
TOTAL SUM ${pap_formatCurrency(totalSum)}
`; targetDiv.innerHTML += budgetHTML; // 4. Maintenance Plan let maintenanceHTML = `

4. Maintenance Plan

Routine Care & Longevity:

${pap_escapeHTML(pap_data.maintenance) || 'Maintenance plan is required.'}

`; targetDiv.innerHTML += maintenanceHTML; } /** * Renders a clone for PDF generation */ function pap_renderPdfClone() { pap_pdfRenderClone.innerHTML = `

Public Art Proposal

`; // Render data into the clone's content area const contentArea = pap_pdfRenderClone.querySelector('.space-y-6'); pap_renderDashboard(contentArea, true); } /** * Generates and downloads a PDF of the proposal */ async function pap_downloadPDF() { if (!pap_data.title || !pap_data.artist || !pap_data.summary) { alert("Please complete the Project Identity section before downloading."); return; } if (typeof jspdf === 'undefined' || typeof html2canvas === 'undefined') { console.error("PAP Tool Error: jsPDF or html2canvas library not loaded."); alert("Error: PDF libraries failed to load. Please check console."); return; } pap_renderPdfClone(); // Create and populate the clone const { jsPDF } = window.jspdf; try { // Target the entire clone div const canvas = await html2canvas(pap_pdfRenderClone, { scale: 1.5, // Increase resolution slightly useCORS: true, windowWidth: pap_pdfRenderClone.scrollWidth, windowHeight: pap_pdfRenderClone.scrollHeight // Capture full height }); const imgData = canvas.toDataURL('image/png'); const imgWidth = canvas.width; const imgHeight = canvas.height; 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, handle multiple pages const margin = 40; const contentWidth = pdfWidth - (margin * 2); const contentHeight = (contentWidth * imgHeight) / imgWidth; let heightLeft = contentHeight; let position = 0; // y-position of the image slice on the page // Add the first page pdf.addImage(imgData, 'PNG', margin, position + margin, contentWidth, contentHeight); heightLeft -= (pdfHeight - margin * 2); // 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 = (pap_data.title || 'public_art_proposal').replace(/[^a-z0-9]/gi, '_').toLowerCase(); pdf.save(`${safeName}.pdf`); } catch (error) { console.error("PAP Tool Error: PDF generation failed.", error); alert("An error occurred while generating the PDF. Please try again."); } } // --- EVENT LISTENERS --- // Tab link clicks pap_tabLinks.forEach((link, index) => { link.addEventListener('click', () => pap_switchTab(index)); }); // Next/Prev button clicks if (pap_prevButton) { pap_prevButton.addEventListener('click', () => { if (pap_currentTab > 0) pap_switchTab(pap_currentTab - 1); }); } if (pap_nextButton) { pap_nextButton.addEventListener('click', () => { // If on the last tab (Config tab, index 1) if (pap_currentTab === pap_tabLinks.length - 1) { // Act as Submit: Save data and switch to Dashboard (index 0) pap_updateDataFromConfig(); pap_switchTab(0); } else { // Otherwise, just go to the next tab if (pap_currentTab < pap_tabLinks.length - 1) pap_switchTab(pap_currentTab + 1); } }); } // PDF download if (pap_downloadPdfButton) { pap_downloadPdfButton.addEventListener('click', pap_downloadPDF); } // --- Config Tab Listeners --- if (pap_addBudgetItemButton) { pap_addBudgetItemButton.addEventListener('click', () => { const newItem = pap_createBudgetItemInput(); const container = pap_budgetBreakdownContainer.querySelector('.space-y-2'); if(container) container.appendChild(newItem); else pap_budgetBreakdownContainer.appendChild(newItem); }); } if (pap_configTab) { // Handle remove pap_configTab.addEventListener('click', (e) => { const removeButton = e.target.closest('.pap-remove-budget-item'); if (removeButton) { removeButton.closest('.flex[data-id]').remove(); } }); } // --- INITIALIZATION --- pap_renderConfig(); // Populate config tab on load pap_renderDashboard(); // Show initial state on dashboard // Set initial tab state pap_tabPanes.forEach((pane, index) => { pane.classList.toggle('hidden', index !== 0); pane.classList.toggle('pap-active', index === 0); }); pap_tabLinks.forEach((link, index) => { TAB_CLASSES.active.forEach(cls => link.classList.remove(cls)); TAB_CLASSES.inactive.forEach(cls => link.classList.remove(cls)); if (index === 0) { TAB_CLASSES.active.forEach(cls => link.classList.add(cls)); } else { TAB_CLASSES.inactive.forEach(cls => link.classList.add(cls)); } }); });
Scroll to Top