Scrapbook Layout Creator

Scrapbook Layout Creator

Choose a Layout Template:

1 Photo Layout Title + 1 Photo
2 Photos Side-by-Side Layout Title + 2 Photos (Side)
3 Photos Grid Layout Title + 3 Photos (Grid)
4 Photos Grid Layout Title + 4 Photos (Grid)

Page Content & Style:

Your Scrapbook Page Preview:

Generate your layout in the 'Customize Layout' tab to see it here.

`, inputs: [ { type: 'url', id: 'photo-url-1', label: 'Photo 1 URL' }, { type: 'textarea', id: 'caption-1', label: 'Caption for Photo 1' }, { type: 'url', id: 'photo-url-2', label: 'Photo 2 URL' }, { type: 'textarea', id: 'caption-2', label: 'Caption for Photo 2' }, { type: 'url', id: 'photo-url-3', label: 'Photo 3 URL' }, { type: 'textarea', id: 'caption-3', label: 'Caption for Photo 3' }, { type: 'url', id: 'photo-url-4', label: 'Photo 4 URL' }, { type: 'textarea', id: 'caption-4', label: 'Caption for Photo 4' } ] } }, // Tab Navigation openTab: function(tabId, index) { if (typeof index !== 'number') return; this.currentTabIndex = index; this.elements.tabContents.forEach(content => { content.style.display = "none"; }); this.elements.tabButtons.forEach(button => { button.classList.remove("active"); }); const activeTab = document.getElementById(tabId); if (activeTab) { activeTab.style.display = "block"; } const activeButton = document.getElementById(`sb-tab-btn-${tabId}`); if (activeButton) { activeButton.classList.add("active"); } this.updateNavButtons(); }, navigateTab: function(direction) { let newIndex = this.currentTabIndex + direction; if (newIndex < 0) newIndex = 0; if (newIndex >= this.totalTabs) newIndex = this.totalTabs - 1; if (newIndex !== this.currentTabIndex) { const tabId = this.elements.tabButtons[newIndex].id.split('-btn-')[1]; this.openTab(tabId, newIndex); } }, updateNavButtons: function() { if (this.elements.prevButton) { this.elements.prevButton.disabled = (this.currentTabIndex === 0); } if (this.elements.nextButton) { this.elements.nextButton.disabled = (this.currentTabIndex === this.totalTabs - 1); } }, // Layout Selection selectLayout: function(layoutId) { this.selectedLayout = layoutId; document.querySelectorAll('.sb-layout-option').forEach(option => { option.classList.remove('selected'); }); document.querySelector(`[data-layout="${layoutId}"]`).classList.add('selected'); this.generateDynamicInputs(); this.generatePreview(); // Update preview immediately }, // Dynamic Input Generation generateDynamicInputs: function() { const layout = this.layouts[this.selectedLayout]; if (!layout) return; let inputsHtml = ''; layout.inputs.forEach(input => { inputsHtml += `
${input.type === 'textarea' ? `` : ``}
`; }); if(this.elements.photoCaptionInputs) this.elements.photoCaptionInputs.innerHTML = inputsHtml; // Re-assign dynamic elements after they are created this.assignDynamicElements(); this.updatePreview(); // Ensure inputs are reflected in preview if already generated }, // Assign references to dynamically created inputs assignDynamicElements: function() { this.elements.photoUrls = {}; this.elements.captions = {}; const layout = this.layouts[this.selectedLayout]; if (!layout) return; layout.inputs.forEach(input => { const element = document.getElementById(input.id); if (element) { if (input.type === 'url') { this.elements.photoUrls[input.id] = element; } else if (input.type === 'textarea') { this.elements.captions[input.id] = element; } } }); }, // Generate/Update Scrapbook Preview generatePreview: function() { const layout = this.layouts[this.selectedLayout]; if (!layout) { if(this.elements.dashboardPreviewContainer) this.elements.dashboardPreviewContainer.innerHTML = "

Select a layout template first.

"; return; } // Start with the base page and apply layout-specific classes let pageHtml = `
${layout.structure}
`; if(this.elements.dashboardPreviewContainer) this.elements.dashboardPreviewContainer.innerHTML = pageHtml; this.updatePreview(); // Fill with current data }, updatePreview: function() { const previewPage = document.getElementById('preview-page'); if (!previewPage) return; // Apply background color const bgColor = this.elements.bgColor.value; previewPage.style.backgroundColor = bgColor; // Apply title const pageTitle = this.elements.pageTitle.value || "Scrapbook Page"; const previewTitleElement = previewPage.querySelector('#preview-title'); if (previewTitleElement) { previewTitleElement.textContent = pageTitle; } // Apply photos and captions const layout = this.layouts[this.selectedLayout]; if (!layout) return; layout.inputs.forEach(inputDef => { if (inputDef.type === 'url') { const imgElement = previewPage.querySelector(`#preview-${inputDef.id.replace('photo-url-', 'photo-')}`); const inputElement = this.elements.photoUrls[inputDef.id]; if (imgElement && inputElement) { imgElement.src = inputElement.value || imgElement.getAttribute('onerror').replace("this.src='", "").replace("'", ""); // Use placeholder on empty } } else if (inputDef.type === 'textarea') { const captionElement = previewPage.querySelector(`#preview-${inputDef.id}`); const inputElement = this.elements.captions[inputDef.id]; if (captionElement && inputElement) { captionElement.textContent = inputElement.value || ''; } } }); // Apply corner decorations this.addCornerDecorations(previewPage, this.elements.cornerDeco.value); }, addCornerDecorations: function(pageElement, decoClass) { // Remove existing decorations first pageElement.querySelectorAll('.sb-corner-decoration').forEach(deco => deco.remove()); if (decoClass === 'none') return; const corners = ['tl', 'tr', 'bl', 'br']; corners.forEach(corner => { const decoDiv = document.createElement('div'); decoDiv.classList.add('sb-corner-decoration', `sb-corner-${corner}`, decoClass); pageElement.appendChild(decoDiv); }); }, // PDF Download Functionality downloadPDF: function() { const { jsPDF } = window.jspdf; const html2canvas = window.html2canvas; if (!jsPDF || !html2canvas) { alert("PDF generation libraries are not loaded. Please try again."); return; } const previewPage = document.getElementById('preview-page'); if (!previewPage) { alert("Please generate a layout preview before downloading the PDF."); return; } // Clone the preview page for PDF rendering to avoid modifying live DOM const pdfRenderElement = previewPage.cloneNode(true); pdfRenderElement.style.margin = 'auto'; // Center for capture pdfRenderElement.style.border = 'none'; // Remove visible border pdfRenderElement.style.boxShadow = 'none'; // Remove shadow // Important: append to body temporarily to ensure CSS is applied for html2canvas document.body.appendChild(pdfRenderElement); html2canvas(pdfRenderElement, { scale: 3 }) // Higher scale for better quality .then(canvas => { const doc = new jsPDF({ orientation: 'p', unit: 'pt', // Using points for precise A4/Letter sizing format: 'letter' // Standard US Letter size: 612pt x 792pt }); const imgData = canvas.toDataURL('image/png'); const imgProps = doc.getImageProperties(imgData); const imgWidth = doc.internal.pageSize.getWidth(); // 612 pt const imgHeight = (imgProps.height * imgWidth) / imgProps.width; let heightLeft = imgHeight; let position = 0; doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= doc.internal.pageSize.getHeight(); while (heightLeft >= 0) { position = heightLeft - imgHeight; doc.addPage(); doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= doc.internal.pageSize.getHeight(); } doc.save('Scrapbook_Page.pdf'); }) .catch(err => { console.error("Error generating PDF:", err); alert("An error occurred while generating the PDF."); }) .finally(() => { // Clean up the temporary element if (document.body.contains(pdfRenderElement)) { document.body.removeChild(pdfRenderElement); } }); }, // Initialization init: function() { this.elements = { tabContents: document.querySelectorAll('.sb-tab-content'), tabButtons: document.querySelectorAll('.sb-tab-button'), prevButton: document.getElementById('sb-prev-btn'), nextButton: document.getElementById('sb-next-btn'), // Customization tab elements pageTitle: document.getElementById('page-title'), bgColor: document.getElementById('bg-color'), cornerDeco: document.getElementById('corner-deco'), photoCaptionInputs: document.getElementById('photo-caption-inputs'), // Dashboard tab elements dashboardPreviewContainer: document.getElementById('dashboard-preview-container') }; // Set up initial dynamic inputs and preview this.selectLayout(this.selectedLayout); // This will call generateDynamicInputs and updatePreview this.openTab('customize', 0); } }; document.addEventListener('DOMContentLoaded', function() { if (!document.getElementById('scrapbook-tool').classList.contains('sb-initialized')) { sbApp.init(); document.getElementById('scrapbook-tool').classList.add('sb-initialized'); } });