feat: add panel settings and footer customization options in the editor

This commit is contained in:
Ross
2026-01-04 22:17:51 +00:00
parent 279ed2ff6f
commit 554075a3a9
2 changed files with 412 additions and 22 deletions
+321 -20
View File
@@ -36,7 +36,11 @@ class YotoCoversApp {
fontFamily: 'Comic Sans MS', fontFamily: 'Comic Sans MS',
fontWeight: 'bold', fontWeight: 'bold',
footerColor: '#ff6b6b', 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: { polaroid: {
backgroundStyle: 'polaroid', backgroundStyle: 'polaroid',
@@ -235,10 +239,27 @@ class YotoCoversApp {
document.getElementById('card-title-x').addEventListener('input', () => this.updateActiveCard()); document.getElementById('card-title-x').addEventListener('input', () => this.updateActiveCard());
document.getElementById('card-title-y').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-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-fit').addEventListener('change', () => this.updateActiveCard());
document.getElementById('card-image-position').addEventListener('change', () => this.updateActiveCard()); document.getElementById('card-image-position').addEventListener('change', () => this.updateActiveCard());
document.getElementById('card-image-select').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 // Title style selection auto-update
document.querySelectorAll('.title-style-option').forEach(option => { document.querySelectorAll('.title-style-option').forEach(option => {
option.addEventListener('click', () => { option.addEventListener('click', () => {
@@ -374,6 +395,22 @@ class YotoCoversApp {
document.getElementById('card-image-position').value = preset.imagePosition; 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(); this.updateActiveCard();
} }
@@ -395,7 +432,19 @@ class YotoCoversApp {
showTitle: true, showTitle: true,
showFooter: true, showFooter: true,
imageFit: 'cover', 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.cardVariations.push(variation);
this.updateCardList(); this.updateCardList();
@@ -508,6 +557,20 @@ class YotoCoversApp {
if (preset.fullBleed !== undefined && variation.fullBleed !== preset.fullBleed) matches = false; if (preset.fullBleed !== undefined && variation.fullBleed !== preset.fullBleed) matches = false;
if (preset.showFooter !== undefined && variation.showFooter !== preset.showFooter) matches = false; if (preset.showFooter !== undefined && variation.showFooter !== preset.showFooter) matches = false;
if (preset.imagePosition !== undefined && variation.imagePosition !== preset.imagePosition) 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) { if (matches) {
matchingPreset = presetName; matchingPreset = presetName;
@@ -552,13 +615,29 @@ class YotoCoversApp {
// Set title options // Set title options
document.getElementById('card-title-font-size').value = variation.titleFontSize || 20; 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-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; 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 // Set image options
document.getElementById('card-image-fit').value = variation.imageFit || 'cover'; document.getElementById('card-image-fit').value = variation.imageFit || 'cover';
document.getElementById('card-image-position').value = variation.imagePosition || 'center'; 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 // Populate image options
const imageSelect = document.getElementById('card-image-select'); const imageSelect = document.getElementById('card-image-select');
imageSelect.innerHTML = '<option value="-1">Use default image</option>'; imageSelect.innerHTML = '<option value="-1">Use default image</option>';
@@ -579,9 +658,11 @@ class YotoCoversApp {
updateSectionVisibility() { updateSectionVisibility() {
const showTitle = document.getElementById('card-show-title').checked; const showTitle = document.getElementById('card-show-title').checked;
const showFooter = document.getElementById('card-show-footer').checked; const showFooter = document.getElementById('card-show-footer').checked;
const panelMode = document.getElementById('card-panel-mode').checked;
const titleSection = document.getElementById('title-section'); const titleSection = document.getElementById('title-section');
const footerSection = document.getElementById('footer-section'); const footerSection = document.getElementById('footer-section');
const panelSection = document.getElementById('panel-section');
if (showTitle) { if (showTitle) {
titleSection.classList.remove('collapsed'); titleSection.classList.remove('collapsed');
@@ -594,6 +675,12 @@ class YotoCoversApp {
} else { } else {
footerSection.classList.add('collapsed'); footerSection.classList.add('collapsed');
} }
if (panelMode) {
panelSection.classList.remove('collapsed');
} else {
panelSection.classList.add('collapsed');
}
} }
initDragFunctionality() { initDragFunctionality() {
@@ -706,6 +793,89 @@ class YotoCoversApp {
document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp); 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) { } catch (e) {
// Ignore cross-origin errors // Ignore cross-origin errors
console.log('Drag functionality initialization failed:', e); console.log('Drag functionality initialization failed:', e);
@@ -747,10 +917,30 @@ class YotoCoversApp {
variation.titleX = parseInt(document.getElementById('card-title-x').value); variation.titleX = parseInt(document.getElementById('card-title-x').value);
variation.titleY = parseInt(document.getElementById('card-title-y').value); variation.titleY = parseInt(document.getElementById('card-title-y').value);
variation.titleDraggable = document.getElementById('card-title-draggable').checked; 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.imageFit = document.getElementById('card-image-fit').value;
variation.imagePosition = document.getElementById('card-image-position').value; variation.imagePosition = document.getElementById('card-image-position').value;
variation.imageIndex = parseInt(document.getElementById('card-image-select').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.updateCardList();
this.generatePreview(); this.generatePreview();
} }
@@ -832,7 +1022,9 @@ class YotoCoversApp {
let imageIndex, cardTitle, cardFooter, cardTitleStyle, cardAccentColor, cardTitleColor, cardFullBleed, cardTitleShadow; let imageIndex, cardTitle, cardFooter, cardTitleStyle, cardAccentColor, cardTitleColor, cardFullBleed, cardTitleShadow;
let cardBackgroundStyle, cardFontFamily, cardFontWeight, cardFooterColor, cardShowTitle, cardShowFooter; let cardBackgroundStyle, cardFontFamily, cardFontWeight, cardFooterColor, cardShowTitle, cardShowFooter;
let cardImageFit, cardImagePosition, cardTitleFontSize, cardTitleX, cardTitleY, cardTitleDraggable; let cardImageFit, cardImagePosition, cardTitleFontSize, cardTitleX, cardTitleY, cardTitleDraggable;
let cardFooterFontSize, cardFooterX, cardFooterY, cardFooterDraggable;
let cardShowAccent; let cardShowAccent;
let cardPanelMode, cardTitlePanel, cardImagePanel, cardFooterPanel;
if (this.cardVariations.length > 0) { if (this.cardVariations.length > 0) {
// Use variation settings // Use variation settings
@@ -857,8 +1049,16 @@ class YotoCoversApp {
cardImagePosition = (variation && variation.imagePosition) || 'center'; cardImagePosition = (variation && variation.imagePosition) || 'center';
cardTitleFontSize = (variation && variation.titleFontSize) || 20; cardTitleFontSize = (variation && variation.titleFontSize) || 20;
cardTitleX = variation && variation.titleX !== undefined ? variation.titleX : 50; 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; 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 { } else {
// No variations - auto-assign images // No variations - auto-assign images
imageIndex = i < this.images.length ? i : 0; imageIndex = i < this.images.length ? i : 0;
@@ -881,8 +1081,16 @@ class YotoCoversApp {
cardImagePosition = 'center'; cardImagePosition = 'center';
cardTitleFontSize = 20; cardTitleFontSize = 20;
cardTitleX = 50; cardTitleX = 50;
cardTitleY = 10; cardTitleY = 20;
cardTitleDraggable = false; 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=='; const imageUrl = this.images.length > 0 ? this.images[imageIndex].dataUrl : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
@@ -909,6 +1117,14 @@ class YotoCoversApp {
titleX: cardTitleX, titleX: cardTitleX,
titleY: cardTitleY, titleY: cardTitleY,
titleDraggable: cardTitleDraggable, titleDraggable: cardTitleDraggable,
footerFontSize: cardFooterFontSize,
footerX: cardFooterX,
footerY: cardFooterY,
footerDraggable: cardFooterDraggable,
panelMode: cardPanelMode,
titlePanel: cardTitlePanel,
imagePanel: cardImagePanel,
footerPanel: cardFooterPanel,
isVisible: true isVisible: true
}); });
} else { } else {
@@ -935,6 +1151,10 @@ class YotoCoversApp {
titleX: 50, titleX: 50,
titleY: 10, titleY: 10,
titleDraggable: false, titleDraggable: false,
panelMode: false,
titlePanel: { borderWidth: 0, rotation: 0, expand: false },
imagePanel: { borderWidth: 0, rotation: 0 },
footerPanel: { borderWidth: 0, rotation: 0 },
isVisible: false isVisible: false
}); });
} }
@@ -995,29 +1215,66 @@ class YotoCoversApp {
overflow: hidden; overflow: hidden;
"> ">
${card.isVisible ? ` ${card.isVisible ? `
${card.backgroundStyle === 'comic' ? ` ${card.panelMode ? `
<!-- Comic Style Panels --> <!-- Comic Style Panels -->
<div class="comic-panels"> <div class="comic-panels">
${card.showTitle ? `<div class="comic-panel comic-title-panel" style=" ${card.showTitle ? (card.titleStyle === 'split' ? `
<style>
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap');
.comic-panel.comic-title-panel.split {
--s: 4px;
--o: calc(0.5 * var(--s));
--sl: calc(50% - 0.5px), #0000 calc(50% + 0.5px);
position: absolute; position: absolute;
left: 8%; left: 8%;
top: 6%; top: 6%;
width: 40%; ${card.titlePanel.expand ? 'width: auto; min-width: 30%; max-width: 60%;' : 'width: 40%;'}
height: 15%; height: ${card.titlePanel.expand ? 'auto' : '15%'};
border: 2px solid #000; border: ${card.titlePanel.borderWidth}px solid #000;
place-self: center;
place-content: center;
padding: calc(0.1em) 0.05em 0 0;
background:
linear-gradient(22.5deg, #fff var(--sl)) text,
linear-gradient(22.5deg, #000 var(--sl)),
linear-gradient(${card.titleColor} 50%, #0000 0)
0 0/ 1% var(--s);
color: #0000;
-webkit-text-stroke: #fff calc(var(--o));
text-transform: uppercase;
mix-blend-mode: screen;
font: clamp(1em, min(65vh, 35vw), 2em)/ 0.75 'Bebas Neue', sans-serif;
transform: rotate(${card.titlePanel.rotation}deg);
box-shadow: 2px 2px 4px rgba(0,0,0,0.2);
@supports (line-height: 1cap) { line-height: 1cap; }
}
@media (min-width: 480px) and (min-height: 320px) {
.comic-panel.comic-title-panel.split { --s: 6px; }
}
</style>
<div class="comic-panel comic-title-panel split">${this.escapeHtml(card.title)}</div>
` : `<div class="comic-panel comic-title-panel" style="
position: absolute;
left: 8%;
top: 6%;
${card.titlePanel.expand ? 'width: auto; min-width: 30%; max-width: 60%;' : 'width: 40%;'}
height: ${card.titlePanel.expand ? 'auto' : '15%'};
border: ${card.titlePanel.borderWidth}px solid #000;
background: #fff; background: #fff;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
white-space: pre-line;
text-align: center;
font-size: ${card.titleFontSize * 0.8}px; font-size: ${card.titleFontSize * 0.8}px;
font-weight: ${card.fontWeight === 'bold' ? '700' : '400'}; font-weight: ${card.fontWeight === 'bold' ? '700' : '400'};
font-family: '${card.fontFamily}', serif; font-family: '${card.fontFamily}', serif;
color: ${card.titleColor}; color: ${card.titleColor};
${card.titleShadow ? 'text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);' : ''} ${card.titleShadow ? 'text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);' : ''}
transform: rotate(-2deg); transform: rotate(${card.titlePanel.rotation}deg);
box-shadow: 2px 2px 4px rgba(0,0,0,0.2); box-shadow: 2px 2px 4px rgba(0,0,0,0.2);
${card.titleDraggable && card.backgroundStyle !== 'comic' ? 'cursor: move; user-select: none;' : ''} ${card.titleDraggable && !card.panelMode ? 'cursor: move; user-select: none;' : ''}
" data-panel-type="title">${this.escapeHtml(card.title)}</div>` : ''} " data-panel-type="title">${this.escapeHtml(card.title)}</div>`) : ''}
<div class="comic-panel comic-image-panel" style=" <div class="comic-panel comic-image-panel" style="
position: absolute; position: absolute;
@@ -1025,9 +1282,9 @@ class YotoCoversApp {
top: 25%; top: 25%;
right: 15%; right: 15%;
bottom: ${card.showFooter ? '35%' : '15%'}; bottom: ${card.showFooter ? '35%' : '15%'};
border: 3px solid #000; border: ${card.imagePanel.borderWidth}px solid #000;
background: ${card.fullBleed ? `url('${card.imageUrl}') no-repeat ${card.imagePosition}/${card.imageFit}` : '#f8f9fa'}; background: ${card.fullBleed ? `url('${card.imageUrl}') no-repeat ${card.imagePosition}/${card.imageFit}` : '#f8f9fa'};
transform: rotate(1deg); transform: rotate(${card.imagePanel.rotation}deg);
box-shadow: 3px 3px 6px rgba(0,0,0,0.3); box-shadow: 3px 3px 6px rgba(0,0,0,0.3);
overflow: hidden; overflow: hidden;
"> ">
@@ -1054,7 +1311,7 @@ class YotoCoversApp {
bottom: 6%; bottom: 6%;
width: 35%; width: 35%;
height: 12%; height: 12%;
border: 2px solid #000; border: ${card.footerPanel.borderWidth}px solid #000;
background: #fff; background: #fff;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -1063,15 +1320,49 @@ class YotoCoversApp {
font-weight: ${card.fontWeight}; font-weight: ${card.fontWeight};
font-family: '${card.fontFamily}', serif; font-family: '${card.fontFamily}', serif;
color: ${card.footerColor}; color: ${card.footerColor};
transform: rotate(3deg); transform: rotate(${card.footerPanel.rotation}deg);
box-shadow: 2px 2px 4px rgba(0,0,0,0.2); box-shadow: 2px 2px 4px rgba(0,0,0,0.2);
text-align: center; text-align: center;
padding: 2px; padding: 2px;
white-space: pre-line;
">${this.escapeHtml(card.footer)}</div>` : ''} ">${this.escapeHtml(card.footer)}</div>` : ''}
</div> </div>
` : ` ` : `
<!-- Standard Layout --> <!-- Standard Layout -->
${card.showTitle ? `<div class="title ${card.titleStyle}" data-heading="${this.escapeHtml(card.title)}" style=" ${card.showTitle ? (card.titleStyle === 'split' ? `
<style>
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap');
.title.split {
--s: 4px;
--o: calc(0.5 * var(--s));
--sl: calc(50% - 0.5px), #0000 calc(50% + 0.5px);
position: absolute;
left: ${card.titleX}%;
top: ${card.titleY}%;
transform: translate(-50%, -50%);
place-self: center;
place-content: center;
padding: calc(0.1em) 0.05em 0 0;
height: calc(1lh - var(--o));
background:
linear-gradient(22.5deg, #fff var(--sl)) text,
linear-gradient(22.5deg, #000 var(--sl)),
linear-gradient(${card.titleColor} 50%, #0000 0)
0 0/ 1% var(--s);
color: #0000;
-webkit-text-stroke: #fff calc(var(--o));
text-transform: uppercase;
mix-blend-mode: screen;
font: clamp(2em, min(65vh, 35vw), 3.9em)/ 0.75 'Bebas Neue', sans-serif;
${card.titleDraggable ? 'cursor: move; user-select: none;' : ''}
@supports (line-height: 1cap) { line-height: 1cap; }
}
@media (min-width: 480px) and (min-height: 320px) {
.title.split { --s: 6px; }
}
</style>
<div class="title split">${this.escapeHtml(card.title)}</div>
` : `<div class="title ${card.titleStyle}" data-heading="${this.escapeHtml(card.title)}" style="
color: ${card.titleColor}; color: ${card.titleColor};
font-weight: ${card.fontWeight === 'bold' ? '700' : '400'}; font-weight: ${card.fontWeight === 'bold' ? '700' : '400'};
font-family: '${card.fontFamily}', serif; font-family: '${card.fontFamily}', serif;
@@ -1081,14 +1372,24 @@ class YotoCoversApp {
left: ${card.titleX}%; left: ${card.titleX}%;
top: ${card.titleY}%; top: ${card.titleY}%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
white-space: pre-line;
text-align: center;
${card.titleDraggable ? 'cursor: move; user-select: none;' : ''} ${card.titleDraggable ? 'cursor: move; user-select: none;' : ''}
">${this.escapeHtml(card.title)}</div>` : ''} ">${this.escapeHtml(card.title)}</div>`) : ''}
${!card.fullBleed ? `<div class="hero"><img src="${card.imageUrl}" alt="cover" style="object-fit: ${card.imageFit}; object-position: ${card.imagePosition};"/></div>` : ''} ${!card.fullBleed ? `<div class="hero"><img src="${card.imageUrl}" alt="cover" style="object-fit: ${card.imageFit}; object-position: ${card.imagePosition};"/></div>` : ''}
${card.showAccent ? `<div class="overlay" style="background: ${card.accentColor}40;"></div>` : `<div class="overlay" style="background: rgba(0,0,0,0);"></div>`} ${card.showAccent ? `<div class="overlay" style="background: ${card.accentColor}40;"></div>` : `<div class="overlay" style="background: rgba(0,0,0,0);"></div>`}
${card.showFooter ? `<div class="footer" style=" ${card.showFooter ? `<div class="footer" style="
color: ${card.footerColor}; color: ${card.footerColor};
font-family: '${card.fontFamily}', serif; font-family: '${card.fontFamily}', serif;
font-weight: ${card.fontWeight}; font-weight: ${card.fontWeight};
font-size: ${card.footerFontSize}px;
position: absolute;
left: ${card.footerX}%;
bottom: ${100 - card.footerY}%;
transform: translate(-50%, 50%);
white-space: pre-line;
text-align: center;
${card.footerDraggable ? 'cursor: move; user-select: none;' : ''}
">${this.escapeHtml(card.footer)}</div>` : ''} ">${this.escapeHtml(card.footer)}</div>` : ''}
`} `}
` : ''} ` : ''}
@@ -1269,7 +1570,7 @@ class YotoCoversApp {
escapeHtml(text) { escapeHtml(text) {
const div = document.createElement('div'); const div = document.createElement('div');
div.textContent = text; div.textContent = text;
return div.innerHTML; return div.innerHTML.replace(/\n/g, '<br>');
} }
openPreviewInNewTab() { openPreviewInNewTab() {
+91 -2
View File
@@ -496,6 +496,13 @@
align-items: end; align-items: end;
} }
.panel-controls {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: end;
}
.form-group.inline { .form-group.inline {
flex: 1; flex: 1;
margin-bottom: 0; margin-bottom: 0;
@@ -716,6 +723,64 @@
</div> </div>
</div> </div>
<!-- Panel Section -->
<div class="config-section" id="panel-section">
<h4>Panel Settings</h4>
<div class="section-controls">
<div class="checkbox-group">
<input type="checkbox" id="card-panel-mode">
<label for="card-panel-mode">Enable panel mode</label>
</div>
</div>
<div class="section-content">
<div class="form-group">
<label>Title Panel</label>
<div class="panel-controls">
<div class="form-group inline">
<label for="card-title-panel-border">Border Width</label>
<input type="number" id="card-title-panel-border" value="0" min="0" max="10" step="1">
</div>
<div class="form-group inline">
<label for="card-title-panel-rotation">Rotation (°)</label>
<input type="number" id="card-title-panel-rotation" value="0" min="-10" max="10" step="1">
</div>
<div class="checkbox-group inline">
<input type="checkbox" id="card-title-panel-expand">
<label for="card-title-panel-expand">Expandable</label>
</div>
</div>
</div>
<div class="form-group">
<label>Image Panel</label>
<div class="panel-controls">
<div class="form-group inline">
<label for="card-image-panel-border">Border Width</label>
<input type="number" id="card-image-panel-border" value="0" min="0" max="10" step="1">
</div>
<div class="form-group inline">
<label for="card-image-panel-rotation">Rotation (°)</label>
<input type="number" id="card-image-panel-rotation" value="0" min="-10" max="10" step="1">
</div>
</div>
</div>
<div class="form-group">
<label>Footer Panel</label>
<div class="panel-controls">
<div class="form-group inline">
<label for="card-footer-panel-border">Border Width</label>
<input type="number" id="card-footer-panel-border" value="0" min="0" max="10" step="1">
</div>
<div class="form-group inline">
<label for="card-footer-panel-rotation">Rotation (°)</label>
<input type="number" id="card-footer-panel-rotation" value="0" min="-10" max="10" step="1">
</div>
</div>
</div>
</div>
</div>
<!-- Title Section --> <!-- Title Section -->
<div class="config-section" id="title-section"> <div class="config-section" id="title-section">
<h4>Title</h4> <h4>Title</h4>
@@ -728,7 +793,7 @@
<div class="section-content"> <div class="section-content">
<div class="form-group"> <div class="form-group">
<label for="card-title-input">Title Text</label> <label for="card-title-input">Title Text</label>
<input type="text" id="card-title-input" placeholder="Enter card title"> <textarea id="card-title-input" placeholder="Enter card title" rows="2"></textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -738,6 +803,7 @@
<div class="title-style-option" data-style="condensed">Condensed</div> <div class="title-style-option" data-style="condensed">Condensed</div>
<div class="title-style-option" data-style="outlined">Outlined</div> <div class="title-style-option" data-style="outlined">Outlined</div>
<div class="title-style-option selected" data-style="classic">Classic</div> <div class="title-style-option selected" data-style="classic">Classic</div>
<div class="title-style-option" data-style="split">Split</div>
</div> </div>
</div> </div>
@@ -848,7 +914,7 @@
<div class="section-content"> <div class="section-content">
<div class="form-group"> <div class="form-group">
<label for="card-footer-input">Footer Text</label> <label for="card-footer-input">Footer Text</label>
<input type="text" id="card-footer-input" placeholder="Enter footer text"> <textarea id="card-footer-input" placeholder="Enter footer text" rows="2"></textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -858,6 +924,29 @@
<input type="text" id="card-footer-color-text" value="#111111" class="color-value" readonly> <input type="text" id="card-footer-color-text" value="#111111" class="color-value" readonly>
</div> </div>
</div> </div>
<div class="form-group">
<label for="card-footer-font-size">Footer Font Size (px)</label>
<input type="number" id="card-footer-font-size" value="12" min="8" max="100" step="2">
</div>
<div class="form-group">
<label>Footer Position</label>
<div class="position-controls">
<div class="form-group inline">
<label for="card-footer-x">X (%)</label>
<input type="number" id="card-footer-x" value="50" min="0" max="100" step="1">
</div>
<div class="form-group inline">
<label for="card-footer-y">Y (%)</label>
<input type="number" id="card-footer-y" value="90" min="0" max="100" step="1">
</div>
</div>
<div class="checkbox-group">
<input type="checkbox" id="card-footer-draggable">
<label for="card-footer-draggable">Enable drag to reposition</label>
</div>
</div>
</div> </div>
</div> </div>