diff --git a/app.js b/app.js index 57850a8..92a4ef0 100644 --- a/app.js +++ b/app.js @@ -36,7 +36,11 @@ class YotoCoversApp { fontFamily: 'Comic Sans MS', fontWeight: 'bold', footerColor: '#ff6b6b', - fullBleed: false + fullBleed: false, + panelMode: true, + titlePanel: { borderWidth: 2, rotation: -2, expand: true }, + imagePanel: { borderWidth: 3, rotation: 1 }, + footerPanel: { borderWidth: 2, rotation: 3 } }, polaroid: { backgroundStyle: 'polaroid', @@ -235,10 +239,27 @@ class YotoCoversApp { document.getElementById('card-title-x').addEventListener('input', () => this.updateActiveCard()); document.getElementById('card-title-y').addEventListener('input', () => this.updateActiveCard()); document.getElementById('card-title-draggable').addEventListener('change', () => this.updateActiveCard()); + document.getElementById('card-footer-font-size').addEventListener('input', () => this.updateActiveCard()); + document.getElementById('card-footer-x').addEventListener('input', () => this.updateActiveCard()); + document.getElementById('card-footer-y').addEventListener('input', () => this.updateActiveCard()); + document.getElementById('card-footer-draggable').addEventListener('change', () => this.updateActiveCard()); document.getElementById('card-image-fit').addEventListener('change', () => this.updateActiveCard()); document.getElementById('card-image-position').addEventListener('change', () => this.updateActiveCard()); document.getElementById('card-image-select').addEventListener('change', () => this.updateActiveCard()); + // Panel settings + document.getElementById('card-panel-mode').addEventListener('change', () => { + this.updateActiveCard(); + this.updateSectionVisibility(); + }); + document.getElementById('card-title-panel-border').addEventListener('input', () => this.updateActiveCard()); + document.getElementById('card-title-panel-rotation').addEventListener('input', () => this.updateActiveCard()); + document.getElementById('card-title-panel-expand').addEventListener('change', () => this.updateActiveCard()); + document.getElementById('card-image-panel-border').addEventListener('input', () => this.updateActiveCard()); + document.getElementById('card-image-panel-rotation').addEventListener('input', () => this.updateActiveCard()); + document.getElementById('card-footer-panel-border').addEventListener('input', () => this.updateActiveCard()); + document.getElementById('card-footer-panel-rotation').addEventListener('input', () => this.updateActiveCard()); + // Title style selection auto-update document.querySelectorAll('.title-style-option').forEach(option => { option.addEventListener('click', () => { @@ -374,6 +395,22 @@ class YotoCoversApp { document.getElementById('card-image-position').value = preset.imagePosition; } + // Set panel properties if defined in preset + document.getElementById('card-panel-mode').checked = preset.panelMode === true; + if (preset.titlePanel !== undefined) { + document.getElementById('card-title-panel-border').value = preset.titlePanel.borderWidth; + document.getElementById('card-title-panel-rotation').value = preset.titlePanel.rotation; + document.getElementById('card-title-panel-expand').checked = preset.titlePanel.expand; + } + if (preset.imagePanel !== undefined) { + document.getElementById('card-image-panel-border').value = preset.imagePanel.borderWidth; + document.getElementById('card-image-panel-rotation').value = preset.imagePanel.rotation; + } + if (preset.footerPanel !== undefined) { + document.getElementById('card-footer-panel-border').value = preset.footerPanel.borderWidth; + document.getElementById('card-footer-panel-rotation').value = preset.footerPanel.rotation; + } + this.updateActiveCard(); } @@ -395,7 +432,19 @@ class YotoCoversApp { showTitle: true, showFooter: true, imageFit: 'cover', - imagePosition: 'center' + imagePosition: 'center', + titleFontSize: 20, + footerFontSize: 12, + titleX: 50, + titleY: 20, + footerX: 50, + footerY: 90, + titleDraggable: false, + footerDraggable: false, + panelMode: false, + titlePanel: { borderWidth: 0, rotation: 0, expand: false }, + imagePanel: { borderWidth: 0, rotation: 0 }, + footerPanel: { borderWidth: 0, rotation: 0 } }; this.cardVariations.push(variation); this.updateCardList(); @@ -508,6 +557,20 @@ class YotoCoversApp { if (preset.fullBleed !== undefined && variation.fullBleed !== preset.fullBleed) matches = false; if (preset.showFooter !== undefined && variation.showFooter !== preset.showFooter) matches = false; if (preset.imagePosition !== undefined && variation.imagePosition !== preset.imagePosition) matches = false; + if (preset.panelMode !== undefined && variation.panelMode !== preset.panelMode) matches = false; + if (preset.titlePanel !== undefined) { + if (variation.titlePanel?.borderWidth !== preset.titlePanel.borderWidth || + variation.titlePanel?.rotation !== preset.titlePanel.rotation || + variation.titlePanel?.expand !== preset.titlePanel.expand) matches = false; + } + if (preset.imagePanel !== undefined) { + if (variation.imagePanel?.borderWidth !== preset.imagePanel.borderWidth || + variation.imagePanel?.rotation !== preset.imagePanel.rotation) matches = false; + } + if (preset.footerPanel !== undefined) { + if (variation.footerPanel?.borderWidth !== preset.footerPanel.borderWidth || + variation.footerPanel?.rotation !== preset.footerPanel.rotation) matches = false; + } if (matches) { matchingPreset = presetName; @@ -552,13 +615,29 @@ class YotoCoversApp { // Set title options document.getElementById('card-title-font-size').value = variation.titleFontSize || 20; document.getElementById('card-title-x').value = variation.titleX !== undefined ? variation.titleX : 50; - document.getElementById('card-title-y').value = variation.titleY !== undefined ? variation.titleY : 10; + document.getElementById('card-title-y').value = variation.titleY !== undefined ? variation.titleY : 20; document.getElementById('card-title-draggable').checked = variation.titleDraggable === true; + // Set footer options + document.getElementById('card-footer-font-size').value = variation.footerFontSize || 12; + document.getElementById('card-footer-x').value = variation.footerX !== undefined ? variation.footerX : 50; + document.getElementById('card-footer-y').value = variation.footerY !== undefined ? variation.footerY : 90; + document.getElementById('card-footer-draggable').checked = variation.footerDraggable === true; + // Set image options document.getElementById('card-image-fit').value = variation.imageFit || 'cover'; document.getElementById('card-image-position').value = variation.imagePosition || 'center'; + // Set panel options + document.getElementById('card-panel-mode').checked = variation.panelMode === true; + document.getElementById('card-title-panel-border').value = variation.titlePanel?.borderWidth || 0; + document.getElementById('card-title-panel-rotation').value = variation.titlePanel?.rotation || 0; + document.getElementById('card-title-panel-expand').checked = variation.titlePanel?.expand === true; + document.getElementById('card-image-panel-border').value = variation.imagePanel?.borderWidth || 0; + document.getElementById('card-image-panel-rotation').value = variation.imagePanel?.rotation || 0; + document.getElementById('card-footer-panel-border').value = variation.footerPanel?.borderWidth || 0; + document.getElementById('card-footer-panel-rotation').value = variation.footerPanel?.rotation || 0; + // Populate image options const imageSelect = document.getElementById('card-image-select'); imageSelect.innerHTML = ''; @@ -579,9 +658,11 @@ class YotoCoversApp { updateSectionVisibility() { const showTitle = document.getElementById('card-show-title').checked; const showFooter = document.getElementById('card-show-footer').checked; + const panelMode = document.getElementById('card-panel-mode').checked; const titleSection = document.getElementById('title-section'); const footerSection = document.getElementById('footer-section'); + const panelSection = document.getElementById('panel-section'); if (showTitle) { titleSection.classList.remove('collapsed'); @@ -594,6 +675,12 @@ class YotoCoversApp { } else { footerSection.classList.add('collapsed'); } + + if (panelMode) { + panelSection.classList.remove('collapsed'); + } else { + panelSection.classList.add('collapsed'); + } } initDragFunctionality() { @@ -706,6 +793,89 @@ class YotoCoversApp { document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); }); + + // Initialize footer drag functionality + const footers = iframeDoc.querySelectorAll('.footer, .comic-footer-panel'); + console.log('Found footers:', footers.length); + + footers.forEach((footer, index) => { + console.log(`Footer ${index} cursor:`, footer.style.cursor); + // Only make draggable if cursor is set to 'move' and not a comic panel + if (footer.style.cursor !== 'move' || footer.classList.contains('comic-footer-panel')) { + console.log(`Footer ${index} not draggable, skipping`); + return; + } + + let isDragging = false; + let startX, startY, initialLeft, initialBottom; + + footer.addEventListener('mousedown', (e) => { + console.log('Footer mousedown fired'); + if (footer.style.cursor !== 'move') return; + + isDragging = true; + + // Get iframe position and convert mouse coordinates to iframe-relative + const iframeRect = iframe.getBoundingClientRect(); + startX = e.clientX - iframeRect.left; + startY = e.clientY - iframeRect.top; + + // Store initial position + initialLeft = parseFloat(footer.style.left) || 50; + initialBottom = parseFloat(footer.style.bottom) || 10; + + footer.style.zIndex = '1000'; + footer.style.userSelect = 'none'; + e.preventDefault(); + }); + + const handleMouseMove = (e) => { + if (!isDragging) return; + + // Get iframe position in main document + const iframeRect = iframe.getBoundingClientRect(); + + // Convert mouse coordinates to be relative to iframe viewport + const mouseX = e.clientX - iframeRect.left; + const mouseY = e.clientY - iframeRect.top; + + const card = footer.closest('.card'); + const cardRect = card.getBoundingClientRect(); + + // Calculate new position as percentage + const deltaX = mouseX - startX; + const deltaY = mouseY - startY; + + const newLeftPercent = Math.max(0, Math.min(100, initialLeft + (deltaX / cardRect.width) * 100)); + const newBottomPercent = Math.max(0, Math.min(100, initialBottom - (deltaY / cardRect.height) * 100)); + + footer.style.left = `${newLeftPercent}%`; + footer.style.bottom = `${newBottomPercent}%`; + + // Update the card data + const cardIndex = Array.from(card.parentElement.children).indexOf(card); + if (this.cardVariations[cardIndex]) { + this.cardVariations[cardIndex].footerX = Math.round(newLeftPercent); + this.cardVariations[cardIndex].footerY = Math.round(100 - newBottomPercent); + this.updateUI(); + } + }; + + const handleMouseUp = () => { + if (isDragging) { + isDragging = false; + footer.style.zIndex = ''; + footer.style.userSelect = ''; + } + }; + + // Add event listeners to both iframe document and main document + // to handle mouse events that might occur outside the iframe + iframeDoc.addEventListener('mousemove', handleMouseMove); + iframeDoc.addEventListener('mouseup', handleMouseUp); + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }); } catch (e) { // Ignore cross-origin errors console.log('Drag functionality initialization failed:', e); @@ -747,10 +917,30 @@ class YotoCoversApp { variation.titleX = parseInt(document.getElementById('card-title-x').value); variation.titleY = parseInt(document.getElementById('card-title-y').value); variation.titleDraggable = document.getElementById('card-title-draggable').checked; + variation.footerFontSize = parseInt(document.getElementById('card-footer-font-size').value); + variation.footerX = parseInt(document.getElementById('card-footer-x').value); + variation.footerY = parseInt(document.getElementById('card-footer-y').value); + variation.footerDraggable = document.getElementById('card-footer-draggable').checked; variation.imageFit = document.getElementById('card-image-fit').value; variation.imagePosition = document.getElementById('card-image-position').value; variation.imageIndex = parseInt(document.getElementById('card-image-select').value); + // Panel settings + variation.panelMode = document.getElementById('card-panel-mode').checked; + variation.titlePanel = { + borderWidth: parseInt(document.getElementById('card-title-panel-border').value), + rotation: parseInt(document.getElementById('card-title-panel-rotation').value), + expand: document.getElementById('card-title-panel-expand').checked + }; + variation.imagePanel = { + borderWidth: parseInt(document.getElementById('card-image-panel-border').value), + rotation: parseInt(document.getElementById('card-image-panel-rotation').value) + }; + variation.footerPanel = { + borderWidth: parseInt(document.getElementById('card-footer-panel-border').value), + rotation: parseInt(document.getElementById('card-footer-panel-rotation').value) + }; + this.updateCardList(); this.generatePreview(); } @@ -832,7 +1022,9 @@ class YotoCoversApp { let imageIndex, cardTitle, cardFooter, cardTitleStyle, cardAccentColor, cardTitleColor, cardFullBleed, cardTitleShadow; let cardBackgroundStyle, cardFontFamily, cardFontWeight, cardFooterColor, cardShowTitle, cardShowFooter; let cardImageFit, cardImagePosition, cardTitleFontSize, cardTitleX, cardTitleY, cardTitleDraggable; + let cardFooterFontSize, cardFooterX, cardFooterY, cardFooterDraggable; let cardShowAccent; + let cardPanelMode, cardTitlePanel, cardImagePanel, cardFooterPanel; if (this.cardVariations.length > 0) { // Use variation settings @@ -857,8 +1049,16 @@ class YotoCoversApp { cardImagePosition = (variation && variation.imagePosition) || 'center'; cardTitleFontSize = (variation && variation.titleFontSize) || 20; cardTitleX = variation && variation.titleX !== undefined ? variation.titleX : 50; - cardTitleY = variation && variation.titleY !== undefined ? variation.titleY : 10; + cardTitleY = variation && variation.titleY !== undefined ? variation.titleY : 20; cardTitleDraggable = variation && variation.titleDraggable !== undefined ? variation.titleDraggable : false; + cardFooterFontSize = (variation && variation.footerFontSize) || 12; + cardFooterX = variation && variation.footerX !== undefined ? variation.footerX : 50; + cardFooterY = variation && variation.footerY !== undefined ? variation.footerY : 90; + cardFooterDraggable = variation && variation.footerDraggable !== undefined ? variation.footerDraggable : false; + cardPanelMode = variation && variation.panelMode !== undefined ? variation.panelMode : false; + cardTitlePanel = variation && variation.titlePanel ? variation.titlePanel : { borderWidth: 0, rotation: 0, expand: false }; + cardImagePanel = variation && variation.imagePanel ? variation.imagePanel : { borderWidth: 0, rotation: 0 }; + cardFooterPanel = variation && variation.footerPanel ? variation.footerPanel : { borderWidth: 0, rotation: 0 }; } else { // No variations - auto-assign images imageIndex = i < this.images.length ? i : 0; @@ -881,8 +1081,16 @@ class YotoCoversApp { cardImagePosition = 'center'; cardTitleFontSize = 20; cardTitleX = 50; - cardTitleY = 10; + cardTitleY = 20; cardTitleDraggable = false; + cardFooterFontSize = 12; + cardFooterX = 50; + cardFooterY = 90; + cardFooterDraggable = false; + cardPanelMode = false; + cardTitlePanel = { borderWidth: 0, rotation: 0, expand: false }; + cardImagePanel = { borderWidth: 0, rotation: 0 }; + cardFooterPanel = { borderWidth: 0, rotation: 0 }; } const imageUrl = this.images.length > 0 ? this.images[imageIndex].dataUrl : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=='; @@ -909,6 +1117,14 @@ class YotoCoversApp { titleX: cardTitleX, titleY: cardTitleY, titleDraggable: cardTitleDraggable, + footerFontSize: cardFooterFontSize, + footerX: cardFooterX, + footerY: cardFooterY, + footerDraggable: cardFooterDraggable, + panelMode: cardPanelMode, + titlePanel: cardTitlePanel, + imagePanel: cardImagePanel, + footerPanel: cardFooterPanel, isVisible: true }); } else { @@ -935,6 +1151,10 @@ class YotoCoversApp { titleX: 50, titleY: 10, titleDraggable: false, + panelMode: false, + titlePanel: { borderWidth: 0, rotation: 0, expand: false }, + imagePanel: { borderWidth: 0, rotation: 0 }, + footerPanel: { borderWidth: 0, rotation: 0 }, isVisible: false }); } @@ -995,29 +1215,66 @@ class YotoCoversApp { overflow: hidden; "> ${card.isVisible ? ` - ${card.backgroundStyle === 'comic' ? ` + ${card.panelMode ? `
- ${card.showTitle ? `
${this.escapeHtml(card.title)}
+ ` : `
${this.escapeHtml(card.title)}
` : ''} + ${card.titleDraggable && !card.panelMode ? 'cursor: move; user-select: none;' : ''} + " data-panel-type="title">${this.escapeHtml(card.title)}
`) : ''}
@@ -1054,7 +1311,7 @@ class YotoCoversApp { bottom: 6%; width: 35%; height: 12%; - border: 2px solid #000; + border: ${card.footerPanel.borderWidth}px solid #000; background: #fff; display: flex; align-items: center; @@ -1063,15 +1320,49 @@ class YotoCoversApp { font-weight: ${card.fontWeight}; font-family: '${card.fontFamily}', serif; color: ${card.footerColor}; - transform: rotate(3deg); + transform: rotate(${card.footerPanel.rotation}deg); box-shadow: 2px 2px 4px rgba(0,0,0,0.2); text-align: center; padding: 2px; + white-space: pre-line; ">${this.escapeHtml(card.footer)}
` : ''} ` : ` - ${card.showTitle ? `
${this.escapeHtml(card.title)}
+ ` : `
${this.escapeHtml(card.title)}
` : ''} + ">${this.escapeHtml(card.title)}`) : ''} ${!card.fullBleed ? `
cover
` : ''} ${card.showAccent ? `
` : `
`} ${card.showFooter ? `` : ''} `} ` : ''} @@ -1269,7 +1570,7 @@ class YotoCoversApp { escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; - return div.innerHTML; + return div.innerHTML.replace(/\n/g, '
'); } openPreviewInNewTab() { diff --git a/index.html b/index.html index 4f43ad0..390d962 100644 --- a/index.html +++ b/index.html @@ -496,6 +496,13 @@ align-items: end; } + .panel-controls { + display: flex; + flex-wrap: wrap; + gap: 15px; + align-items: end; + } + .form-group.inline { flex: 1; margin-bottom: 0; @@ -716,6 +723,64 @@ + +
+

Panel Settings

+
+
+ + +
+
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ +
+
+ + +
+
+ + +
+
+
+ +
+ +
+
+ + +
+
+ + +
+
+
+
+
+

Title

@@ -728,7 +793,7 @@
- +
@@ -738,6 +803,7 @@
Condensed
Outlined
Classic
+
Split
@@ -848,7 +914,7 @@
- +
@@ -858,6 +924,29 @@
+ +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+
+
+ + +
+