feat: enhance card customization options and UI layout in the editor
This commit is contained in:
@@ -14,7 +14,7 @@ A standalone web application for creating and customizing Yoto card covers. This
|
||||
- **Multiple Templates**: Classic, Frozen, Minimal, and Vintage cover styles
|
||||
- **Title Styles**: Classic, Large, Small, Folded, and Condensed title effects
|
||||
- **Color Customization**: Customize accent colors and title colors
|
||||
- **Full Bleed Option**: Choose whether images cover the full card background
|
||||
- **Full Cover Image (Full Bleed) Option**: Choose whether images cover the full card background
|
||||
- **Title Shadows**: Add depth with optional title shadows
|
||||
|
||||
### Card Variations
|
||||
|
||||
@@ -36,6 +36,15 @@ class YotoCoversApp {
|
||||
fontFamily: 'Comic Sans MS',
|
||||
fontWeight: 'bold',
|
||||
footerColor: '#ff6b6b'
|
||||
},
|
||||
polaroid: {
|
||||
backgroundStyle: 'polaroid',
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'normal',
|
||||
footerColor: '#666666',
|
||||
fullBleed: false,
|
||||
showFooter: false,
|
||||
imagePosition: 'center'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -58,7 +67,6 @@ class YotoCoversApp {
|
||||
|
||||
this.cardVariations = [];
|
||||
this.activeCardIndex = -1; // Currently selected card for editing
|
||||
this.globalTitleFontSize = 20; // Global title font size in pixels
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -208,9 +216,26 @@ class YotoCoversApp {
|
||||
this.updateActiveCard();
|
||||
});
|
||||
document.getElementById('card-full-bleed').addEventListener('change', () => this.updateActiveCard());
|
||||
document.getElementById('card-show-accent').addEventListener('change', (e) => {
|
||||
const accentGroup = document.getElementById('accent-color-group');
|
||||
accentGroup.style.display = e.target.checked ? 'block' : 'none';
|
||||
this.updateActiveCard();
|
||||
});
|
||||
document.getElementById('card-title-shadow').addEventListener('change', () => this.updateActiveCard());
|
||||
document.getElementById('card-show-title').addEventListener('change', () => this.updateActiveCard());
|
||||
document.getElementById('card-show-footer').addEventListener('change', () => this.updateActiveCard());
|
||||
document.getElementById('card-show-title').addEventListener('change', () => {
|
||||
this.updateActiveCard();
|
||||
this.updateSectionVisibility();
|
||||
});
|
||||
document.getElementById('card-show-footer').addEventListener('change', () => {
|
||||
this.updateActiveCard();
|
||||
this.updateSectionVisibility();
|
||||
});
|
||||
document.getElementById('card-title-font-size').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-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());
|
||||
|
||||
// Title style selection auto-update
|
||||
@@ -223,10 +248,10 @@ class YotoCoversApp {
|
||||
});
|
||||
|
||||
// Global title font size
|
||||
document.getElementById('global-title-font-size').addEventListener('input', (e) => {
|
||||
this.globalTitleFontSize = parseInt(e.target.value);
|
||||
this.generatePreview();
|
||||
});
|
||||
// document.getElementById('global-title-font-size').addEventListener('input', (e) => {
|
||||
// this.globalTitleFontSize = parseInt(e.target.value);
|
||||
// this.generatePreview();
|
||||
// });
|
||||
|
||||
// Mobile toggles
|
||||
document.getElementById('toggle-settings').addEventListener('click', () => {
|
||||
@@ -238,6 +263,14 @@ class YotoCoversApp {
|
||||
const panel = document.querySelector('.edit-panel');
|
||||
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
|
||||
});
|
||||
|
||||
// Section header click handlers for collapsing/expanding
|
||||
document.querySelectorAll('.config-section h4').forEach(header => {
|
||||
header.addEventListener('click', () => {
|
||||
const section = header.parentElement;
|
||||
section.classList.toggle('collapsed');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async handleImageUpload(files) {
|
||||
@@ -329,6 +362,17 @@ class YotoCoversApp {
|
||||
document.getElementById('card-footer-color').value = preset.footerColor;
|
||||
document.getElementById('card-footer-color-text').value = preset.footerColor;
|
||||
|
||||
// Set additional properties if defined in preset
|
||||
if (preset.fullBleed !== undefined) {
|
||||
document.getElementById('card-full-bleed').checked = preset.fullBleed;
|
||||
}
|
||||
if (preset.showFooter !== undefined) {
|
||||
document.getElementById('card-show-footer').checked = preset.showFooter;
|
||||
}
|
||||
if (preset.imagePosition !== undefined) {
|
||||
document.getElementById('card-image-position').value = preset.imagePosition;
|
||||
}
|
||||
|
||||
this.updateActiveCard();
|
||||
}
|
||||
|
||||
@@ -341,13 +385,16 @@ class YotoCoversApp {
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'normal',
|
||||
footerColor: '#111111',
|
||||
titleStyle: 'folded',
|
||||
titleStyle: 'classic',
|
||||
accentColor: '#f1c40f',
|
||||
showAccent: false,
|
||||
titleColor: '#111111',
|
||||
fullBleed: true,
|
||||
titleShadow: false,
|
||||
showTitle: true,
|
||||
showFooter: true
|
||||
showFooter: true,
|
||||
imageFit: 'cover',
|
||||
imagePosition: 'center'
|
||||
};
|
||||
this.cardVariations.push(variation);
|
||||
this.updateCardList();
|
||||
@@ -451,10 +498,17 @@ class YotoCoversApp {
|
||||
// Set template preset (detect which preset matches current features)
|
||||
let matchingPreset = '';
|
||||
for (const [presetName, preset] of Object.entries(this.templates)) {
|
||||
if (variation.backgroundStyle === preset.backgroundStyle &&
|
||||
let matches = variation.backgroundStyle === preset.backgroundStyle &&
|
||||
variation.fontFamily === preset.fontFamily &&
|
||||
variation.fontWeight === preset.fontWeight &&
|
||||
variation.footerColor === preset.footerColor) {
|
||||
variation.footerColor === preset.footerColor;
|
||||
|
||||
// Check additional properties if defined in preset
|
||||
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 (matches) {
|
||||
matchingPreset = presetName;
|
||||
break;
|
||||
}
|
||||
@@ -476,12 +530,15 @@ class YotoCoversApp {
|
||||
|
||||
// Set title style
|
||||
document.querySelectorAll('.title-style-option').forEach(option => {
|
||||
option.classList.toggle('selected', option.dataset.style === (variation.titleStyle || 'folded'));
|
||||
option.classList.toggle('selected', option.dataset.style === (variation.titleStyle || 'classic'));
|
||||
});
|
||||
|
||||
// Set colors
|
||||
document.getElementById('card-accent-color').value = variation.accentColor || '#f1c40f';
|
||||
document.getElementById('card-accent-color-text').value = variation.accentColor || '#f1c40f';
|
||||
document.getElementById('card-show-accent').checked = variation.showAccent === true;
|
||||
const accentGroup = document.getElementById('accent-color-group');
|
||||
accentGroup.style.display = variation.showAccent ? 'block' : 'none';
|
||||
document.getElementById('card-title-color').value = variation.titleColor || '#111111';
|
||||
document.getElementById('card-title-color-text').value = variation.titleColor || '#111111';
|
||||
|
||||
@@ -491,6 +548,16 @@ class YotoCoversApp {
|
||||
document.getElementById('card-show-title').checked = variation.showTitle !== false;
|
||||
document.getElementById('card-show-footer').checked = variation.showFooter !== false;
|
||||
|
||||
// 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 : 20;
|
||||
document.getElementById('card-title-draggable').checked = variation.titleDraggable === true;
|
||||
|
||||
// Set image options
|
||||
document.getElementById('card-image-fit').value = variation.imageFit || 'cover';
|
||||
document.getElementById('card-image-position').value = variation.imagePosition || 'center';
|
||||
|
||||
// Populate image options
|
||||
const imageSelect = document.getElementById('card-image-select');
|
||||
imageSelect.innerHTML = '<option value="-1">Use default image</option>';
|
||||
@@ -503,6 +570,158 @@ class YotoCoversApp {
|
||||
});
|
||||
|
||||
editor.style.display = 'block';
|
||||
|
||||
// Update section visibility based on show/hide toggles
|
||||
this.updateSectionVisibility();
|
||||
}
|
||||
|
||||
updateSectionVisibility() {
|
||||
const showTitle = document.getElementById('card-show-title').checked;
|
||||
const showFooter = document.getElementById('card-show-footer').checked;
|
||||
|
||||
const titleSection = document.getElementById('title-section');
|
||||
const footerSection = document.getElementById('footer-section');
|
||||
|
||||
if (showTitle) {
|
||||
titleSection.classList.remove('collapsed');
|
||||
} else {
|
||||
titleSection.classList.add('collapsed');
|
||||
}
|
||||
|
||||
if (showFooter) {
|
||||
footerSection.classList.remove('collapsed');
|
||||
} else {
|
||||
footerSection.classList.add('collapsed');
|
||||
}
|
||||
}
|
||||
|
||||
initDragFunctionality() {
|
||||
console.log('initDragFunctionality called');
|
||||
// This will be called when the preview iframe loads
|
||||
const iframe = document.getElementById('preview-iframe');
|
||||
if (!iframe) {
|
||||
console.log('No iframe found');
|
||||
return;
|
||||
}
|
||||
|
||||
const initializeDrag = () => {
|
||||
console.log('initializeDrag called');
|
||||
try {
|
||||
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
if (!iframeDoc) {
|
||||
console.log('No iframe document');
|
||||
return;
|
||||
}
|
||||
const titles = iframeDoc.querySelectorAll('.title');
|
||||
console.log('Found titles:', titles.length);
|
||||
|
||||
titles.forEach((title, index) => {
|
||||
console.log(`Title ${index} cursor:`, title.style.cursor);
|
||||
// Only make draggable if cursor is set to 'move'
|
||||
if (title.style.cursor !== 'move') {
|
||||
console.log(`Title ${index} not draggable, skipping`);
|
||||
return;
|
||||
}
|
||||
|
||||
let isDragging = false;
|
||||
let startX, startY, initialLeft, initialTop;
|
||||
|
||||
title.addEventListener('mousedown', (e) => {
|
||||
console.log('Title mousedown fired');
|
||||
if (title.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(title.style.left) || 50;
|
||||
initialTop = parseFloat(title.style.top) || 20;
|
||||
|
||||
title.style.zIndex = '1000';
|
||||
title.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 = title.closest('.card');
|
||||
const cardRect = card.getBoundingClientRect();
|
||||
|
||||
// Convert card position to be relative to iframe
|
||||
const cardLeftRelative = cardRect.left - iframeRect.left;
|
||||
const cardTopRelative = cardRect.top - iframeRect.top;
|
||||
|
||||
// Calculate mouse position relative to the card (within iframe)
|
||||
const relativeMouseX = mouseX - cardLeftRelative;
|
||||
const relativeMouseY = mouseY - cardTopRelative;
|
||||
|
||||
// Convert to percentage
|
||||
const newLeft = Math.max(0, Math.min(100, (relativeMouseX / cardRect.width) * 100));
|
||||
const newTop = Math.max(0, Math.min(100, (relativeMouseY / cardRect.height) * 100));
|
||||
|
||||
console.log('Drag debug:', { mouseX, mouseY, iframeRect, cardRect, cardLeftRelative, cardTopRelative, relativeMouseX, relativeMouseY, newLeft, newTop });
|
||||
|
||||
title.style.left = `${newLeft}%`;
|
||||
title.style.top = `${newTop}%`;
|
||||
|
||||
console.log('Title style set to:', title.style.left, title.style.top);
|
||||
|
||||
// Update the form values if this card is currently selected
|
||||
const cardIndex = parseInt(card.getAttribute('data-card-index'));
|
||||
if (cardIndex === this.activeCardIndex && this.cardVariations[cardIndex]) {
|
||||
document.getElementById('card-title-x').value = Math.round(newLeft);
|
||||
document.getElementById('card-title-y').value = Math.round(newTop);
|
||||
|
||||
// Update the variation data
|
||||
this.cardVariations[cardIndex].titleX = Math.round(newLeft);
|
||||
this.cardVariations[cardIndex].titleY = Math.round(newTop);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (isDragging) {
|
||||
isDragging = false;
|
||||
title.style.zIndex = '';
|
||||
title.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);
|
||||
}
|
||||
};
|
||||
|
||||
// Try to initialize immediately if the iframe is already loaded
|
||||
if (iframe.contentDocument && (iframe.contentDocument.readyState === 'complete' || iframe.contentDocument.readyState === 'interactive')) {
|
||||
console.log('Iframe already loaded, initializing immediately');
|
||||
initializeDrag();
|
||||
} else {
|
||||
console.log('Setting iframe onload');
|
||||
iframe.onload = () => {
|
||||
console.log('iframe onload fired');
|
||||
initializeDrag();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
updateActiveCard() {
|
||||
@@ -515,13 +734,20 @@ class YotoCoversApp {
|
||||
variation.footerColor = document.getElementById('card-footer-color').value;
|
||||
variation.title = document.getElementById('card-title-input').value;
|
||||
variation.footer = document.getElementById('card-footer-input').value;
|
||||
variation.titleStyle = document.querySelector('.title-style-option.selected')?.dataset.style || 'folded';
|
||||
variation.titleStyle = document.querySelector('.title-style-option.selected')?.dataset.style || 'classic';
|
||||
variation.accentColor = document.getElementById('card-accent-color').value;
|
||||
variation.showAccent = document.getElementById('card-show-accent').checked;
|
||||
variation.titleColor = document.getElementById('card-title-color').value;
|
||||
variation.fullBleed = document.getElementById('card-full-bleed').checked;
|
||||
variation.titleShadow = document.getElementById('card-title-shadow').checked;
|
||||
variation.showTitle = document.getElementById('card-show-title').checked;
|
||||
variation.showFooter = document.getElementById('card-show-footer').checked;
|
||||
variation.titleFontSize = parseInt(document.getElementById('card-title-font-size').value);
|
||||
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.imageFit = document.getElementById('card-image-fit').value;
|
||||
variation.imagePosition = document.getElementById('card-image-position').value;
|
||||
variation.imageIndex = parseInt(document.getElementById('card-image-select').value);
|
||||
|
||||
this.updateCardList();
|
||||
@@ -566,7 +792,7 @@ class YotoCoversApp {
|
||||
|
||||
// Apply zoom after iframe loads
|
||||
iframe.onload = () => {
|
||||
this.applyZoomToPreview();
|
||||
this.initDragFunctionality();
|
||||
};
|
||||
|
||||
// Clean up the previous blob URL
|
||||
@@ -604,6 +830,8 @@ class YotoCoversApp {
|
||||
if (i < totalCards) {
|
||||
let imageIndex, cardTitle, cardFooter, cardTitleStyle, cardAccentColor, cardTitleColor, cardFullBleed, cardTitleShadow;
|
||||
let cardBackgroundStyle, cardFontFamily, cardFontWeight, cardFooterColor, cardShowTitle, cardShowFooter;
|
||||
let cardImageFit, cardImagePosition, cardTitleFontSize, cardTitleX, cardTitleY, cardTitleDraggable;
|
||||
let cardShowAccent;
|
||||
|
||||
if (this.cardVariations.length > 0) {
|
||||
// Use variation settings
|
||||
@@ -611,8 +839,9 @@ class YotoCoversApp {
|
||||
imageIndex = variation && variation.imageIndex >= 0 ? variation.imageIndex : 0;
|
||||
cardTitle = (variation && variation.title) || `Card ${i + 1}`;
|
||||
cardFooter = (variation && variation.footer) || '';
|
||||
cardTitleStyle = (variation && variation.titleStyle) || 'folded';
|
||||
cardTitleStyle = (variation && variation.titleStyle) || 'classic';
|
||||
cardAccentColor = (variation && variation.accentColor) || '#f1c40f';
|
||||
cardShowAccent = variation && variation.showAccent !== undefined ? variation.showAccent : false;
|
||||
cardTitleColor = (variation && variation.titleColor) || '#111111';
|
||||
cardFullBleed = variation && variation.fullBleed !== undefined ? variation.fullBleed : true;
|
||||
cardTitleShadow = variation && variation.titleShadow !== undefined ? variation.titleShadow : false;
|
||||
@@ -623,13 +852,20 @@ class YotoCoversApp {
|
||||
cardFooterColor = (variation && variation.footerColor) || '#111111';
|
||||
cardShowTitle = variation && variation.showTitle !== undefined ? variation.showTitle : true;
|
||||
cardShowFooter = variation && variation.showFooter !== undefined ? variation.showFooter : true;
|
||||
cardImageFit = (variation && variation.imageFit) || 'cover';
|
||||
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 : 20;
|
||||
cardTitleDraggable = variation && variation.titleDraggable !== undefined ? variation.titleDraggable : false;
|
||||
} else {
|
||||
// No variations - auto-assign images
|
||||
imageIndex = i < this.images.length ? i : 0;
|
||||
cardTitle = `Card ${i + 1}`;
|
||||
cardFooter = '';
|
||||
cardTitleStyle = 'folded';
|
||||
cardTitleStyle = 'classic';
|
||||
cardAccentColor = '#f1c40f';
|
||||
cardShowAccent = false;
|
||||
cardTitleColor = '#111111';
|
||||
cardFullBleed = true;
|
||||
cardTitleShadow = false;
|
||||
@@ -640,6 +876,12 @@ class YotoCoversApp {
|
||||
cardFooterColor = '#111111';
|
||||
cardShowTitle = true;
|
||||
cardShowFooter = true;
|
||||
cardImageFit = 'cover';
|
||||
cardImagePosition = 'center';
|
||||
cardTitleFontSize = 20;
|
||||
cardTitleX = 50;
|
||||
cardTitleY = 20;
|
||||
cardTitleDraggable = false;
|
||||
}
|
||||
|
||||
const imageUrl = this.images.length > 0 ? this.images[imageIndex].dataUrl : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
|
||||
@@ -654,11 +896,18 @@ class YotoCoversApp {
|
||||
footerColor: cardFooterColor,
|
||||
titleStyle: cardTitleStyle,
|
||||
accentColor: cardAccentColor,
|
||||
showAccent: cardShowAccent,
|
||||
titleColor: cardTitleColor,
|
||||
fullBleed: cardFullBleed,
|
||||
titleShadow: cardTitleShadow,
|
||||
showTitle: cardShowTitle,
|
||||
showFooter: cardShowFooter,
|
||||
imageFit: cardImageFit,
|
||||
imagePosition: cardImagePosition,
|
||||
titleFontSize: cardTitleFontSize,
|
||||
titleX: cardTitleX,
|
||||
titleY: cardTitleY,
|
||||
titleDraggable: cardTitleDraggable,
|
||||
isVisible: true
|
||||
});
|
||||
} else {
|
||||
@@ -673,11 +922,18 @@ class YotoCoversApp {
|
||||
footerColor: '#111111',
|
||||
titleStyle: 'folded',
|
||||
accentColor: '#f1c40f',
|
||||
showAccent: false,
|
||||
titleColor: '#111111',
|
||||
fullBleed: true,
|
||||
titleShadow: false,
|
||||
showTitle: false,
|
||||
showFooter: false,
|
||||
imageFit: 'cover',
|
||||
imagePosition: 'center',
|
||||
titleFontSize: 20,
|
||||
titleX: 50,
|
||||
titleY: 20,
|
||||
titleDraggable: false,
|
||||
isVisible: false
|
||||
});
|
||||
}
|
||||
@@ -719,6 +975,7 @@ class YotoCoversApp {
|
||||
${pageCards.map((card, index) => `
|
||||
<div class="card card-${startIndex + index} ${card.isVisible ? '' : 'empty-card'}"
|
||||
data-card-index="${startIndex + index}"
|
||||
data-background-style="${card.backgroundStyle}"
|
||||
onclick="if (window.parent && window.parent.selectCardFromPreview) { window.parent.selectCardFromPreview(${startIndex + index}); }"
|
||||
style="cursor: pointer;
|
||||
width: ${cardWidthMm}mm;
|
||||
@@ -727,9 +984,11 @@ class YotoCoversApp {
|
||||
font-family: 'DejaVuSans', serif;
|
||||
${card.isVisible && card.backgroundStyle === 'gradient' ? 'background: linear-gradient(135deg, #e8f4fd 0%, #d1e8f0 100%);' : ''}
|
||||
${card.isVisible && card.backgroundStyle === 'pattern' ? 'background: linear-gradient(45deg, #f5f5dc 0%, #deb887 100%);' : ''}
|
||||
${card.isVisible && card.backgroundStyle === 'comic' ? 'background: radial-gradient(circle at 30% 40%, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #ffeaa7);' : ''}
|
||||
${card.isVisible && card.backgroundStyle === 'comic' ? 'background: repeating-linear-gradient(45deg, #fff 0px, #fff 2px, #000 2px, #000 4px), radial-gradient(circle at 20% 30%, #ff4444, #ff8844, #ffff44, #44ff44, #4444ff, #8844ff); box-shadow: inset 0 0 20px rgba(0,0,0,0.3); border: 3px solid #000;' : ''}
|
||||
${card.isVisible && card.backgroundStyle === 'polaroid' ? 'border: 8px solid #fff; border-bottom: 24px solid #fff; box-shadow: 0 4px 12px rgba(0,0,0,0.3); position: relative;' : ''}
|
||||
${card.isVisible && card.backgroundStyle === 'polaroid' && !card.fullBleed ? 'background: #f8f9fa;' : ''}
|
||||
${card.isVisible && card.backgroundStyle === 'solid' ? 'background: #f8f9fa;' : ''}
|
||||
${card.isVisible && card.fullBleed ? `background: url('${card.imageUrl}') no-repeat center/cover;` : ''}
|
||||
${card.isVisible && card.fullBleed ? `background: url('${card.imageUrl}') no-repeat ${card.imagePosition}/${card.imageFit};` : ''}
|
||||
position: relative;
|
||||
border: ${card.isVisible ? 'none' : '1px dashed #ccc'};
|
||||
overflow: hidden;
|
||||
@@ -739,11 +998,16 @@ class YotoCoversApp {
|
||||
color: ${card.titleColor};
|
||||
font-weight: ${card.fontWeight === 'bold' ? '700' : '400'};
|
||||
font-family: '${card.fontFamily}', serif;
|
||||
font-size: calc(${this.globalTitleFontSize}px + 0.5rem);
|
||||
font-size: ${card.titleFontSize}px;
|
||||
${card.titleShadow ? 'text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);' : ''}
|
||||
position: absolute;
|
||||
left: ${card.titleX}%;
|
||||
top: ${card.titleY}%;
|
||||
transform: translate(-50%, -50%);
|
||||
${card.titleDraggable ? 'cursor: move; user-select: none;' : ''}
|
||||
">${this.escapeHtml(card.title)}</div>` : ''}
|
||||
${!card.fullBleed ? `<div class="hero"><img src="${card.imageUrl}" alt="cover"/></div>` : ''}
|
||||
<div class="overlay" style="background: ${card.accentColor}40;"></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.showFooter ? `<div class="footer" style="
|
||||
color: ${card.footerColor};
|
||||
font-family: '${card.fontFamily}', serif;
|
||||
@@ -810,34 +1074,17 @@ class YotoCoversApp {
|
||||
.title.classic {
|
||||
/* Default classic style - already defined above */
|
||||
}
|
||||
.title.large {
|
||||
font-size: calc(120px + 0.5rem);
|
||||
font-weight: 900;
|
||||
}
|
||||
.title.small {
|
||||
font-size: calc(80px + 0.5rem);
|
||||
font-weight: 700;
|
||||
}
|
||||
.title.condensed {
|
||||
font-size: calc(96px + 0.5rem);
|
||||
font-weight: 900;
|
||||
letter-spacing: -2px;
|
||||
font-stretch: condensed;
|
||||
}
|
||||
.title.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.title.bold {
|
||||
font-weight: 900;
|
||||
}
|
||||
.title.outlined {
|
||||
-webkit-text-stroke: 2px white;
|
||||
text-stroke: 2px white;
|
||||
color: transparent;
|
||||
}
|
||||
.title.shadow {
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.7);
|
||||
}
|
||||
.title.folded {
|
||||
position: absolute;
|
||||
left: 6%;
|
||||
@@ -885,6 +1132,14 @@ class YotoCoversApp {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
${cards.some(card => card.backgroundStyle === 'polaroid') ? `
|
||||
.card[data-background-style="polaroid"] .hero {
|
||||
top: 12%;
|
||||
left: 8%;
|
||||
right: 8%;
|
||||
bottom: 25%;
|
||||
}
|
||||
` : ''}
|
||||
.hero img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -901,6 +1156,14 @@ class YotoCoversApp {
|
||||
pointer-events: none;
|
||||
background: rgba(0,0,0,0);
|
||||
}
|
||||
${cards.some(card => card.backgroundStyle === 'polaroid') ? `
|
||||
.card[data-background-style="polaroid"] .overlay {
|
||||
top: 12%;
|
||||
left: 8%;
|
||||
right: 8%;
|
||||
bottom: 25%;
|
||||
}
|
||||
` : ''}
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 4%;
|
||||
@@ -988,9 +1251,6 @@ class YotoCoversApp {
|
||||
document.getElementById('cards-per-column').value = this.printLayout.cardsPerColumn;
|
||||
document.getElementById('page-margin').value = this.printLayout.pageMargin;
|
||||
document.getElementById('card-spacing').value = this.printLayout.cardSpacing;
|
||||
|
||||
// Initialize global title font size
|
||||
document.getElementById('global-title-font-size').value = this.globalTitleFontSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+241
-108
@@ -445,10 +445,69 @@
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.status-info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
.config-section {
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.config-section h4 {
|
||||
margin: 0;
|
||||
padding: 12px 15px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.config-section h4:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.config-section h4::after {
|
||||
content: "▼";
|
||||
font-size: 12px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.config-section.collapsed h4::after {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.section-controls {
|
||||
padding: 10px 15px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.position-controls {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.form-group.inline {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-group.inline label {
|
||||
font-size: 12px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.form-group.inline input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading {
|
||||
@@ -607,127 +666,201 @@
|
||||
<div class="section">
|
||||
<h3 id="card-editor-title">Edit Card</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="card-template-preset">Template Preset</label>
|
||||
<select id="card-template-preset">
|
||||
<option value="">Custom</option>
|
||||
<option value="classic">Classic</option>
|
||||
<option value="frozen">Frozen</option>
|
||||
<option value="minimal">Minimal</option>
|
||||
<option value="vintage">Vintage</option>
|
||||
<option value="comic">Comic</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- Background/Style Section -->
|
||||
<div class="config-section">
|
||||
<h4>Style</h4>
|
||||
<div class="section-content">
|
||||
<div class="form-group">
|
||||
<label for="card-template-preset">Template Preset</label>
|
||||
<select id="card-template-preset">
|
||||
<option value="">Custom</option>
|
||||
<option value="classic">Classic</option>
|
||||
<option value="frozen">Frozen</option>
|
||||
<option value="minimal">Minimal</option>
|
||||
<option value="vintage">Vintage</option>
|
||||
<option value="comic">Comic</option>
|
||||
<option value="polaroid">Polaroid</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="card-background-style">Background Style</label>
|
||||
<select id="card-background-style">
|
||||
<option value="solid">Solid</option>
|
||||
<option value="gradient">Gradient</option>
|
||||
<option value="pattern">Pattern</option>
|
||||
<option value="comic">Comic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="card-background-style">Background Style</label>
|
||||
<select id="card-background-style">
|
||||
<option value="solid">Solid</option>
|
||||
<option value="gradient">Gradient</option>
|
||||
<option value="pattern">Pattern</option>
|
||||
<option value="comic">Comic</option>
|
||||
<option value="polaroid">Polaroid</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="card-font-family">Font Family</label>
|
||||
<select id="card-font-family">
|
||||
<option value="Arial">Arial</option>
|
||||
<option value="Georgia">Georgia</option>
|
||||
<option value="Times New Roman">Times New Roman</option>
|
||||
<option value="Comic Sans MS">Comic Sans MS</option>
|
||||
<option value="Impact">Impact</option>
|
||||
<option value="Courier New">Courier New</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="card-font-family">Font Family</label>
|
||||
<select id="card-font-family">
|
||||
<option value="Arial">Arial</option>
|
||||
<option value="Georgia">Georgia</option>
|
||||
<option value="Times New Roman">Times New Roman</option>
|
||||
<option value="Comic Sans MS">Comic Sans MS</option>
|
||||
<option value="Impact">Impact</option>
|
||||
<option value="Courier New">Courier New</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="card-font-weight">Font Weight</label>
|
||||
<select id="card-font-weight">
|
||||
<option value="normal">Normal</option>
|
||||
<option value="bold">Bold</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="card-footer-color">Footer Color</label>
|
||||
<div class="color-picker">
|
||||
<input type="color" id="card-footer-color" value="#111111" class="color-input">
|
||||
<input type="text" id="card-footer-color-text" value="#111111" class="color-value" readonly>
|
||||
<div class="form-group">
|
||||
<label for="card-font-weight">Font Weight</label>
|
||||
<select id="card-font-weight">
|
||||
<option value="normal">Normal</option>
|
||||
<option value="bold">Bold</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="card-title-input">Title</label>
|
||||
<input type="text" id="card-title-input" placeholder="Enter card title">
|
||||
<div class="checkbox-group inline">
|
||||
<input type="checkbox" id="card-show-title" checked>
|
||||
<label for="card-show-title">Show title</label>
|
||||
<!-- Title Section -->
|
||||
<div class="config-section" id="title-section">
|
||||
<h4>Title</h4>
|
||||
<div class="section-controls">
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="card-show-title" checked>
|
||||
<label for="card-show-title">Show title</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-content">
|
||||
<div class="form-group">
|
||||
<label for="card-title-input">Title Text</label>
|
||||
<input type="text" id="card-title-input" placeholder="Enter card title">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Title Style</label>
|
||||
<div class="title-style-grid">
|
||||
<div class="title-style-option" data-style="folded">Folded</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 selected" data-style="classic">Classic</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="card-title-color">Title Color</label>
|
||||
<div class="color-picker">
|
||||
<input type="color" id="card-title-color" value="#111111" class="color-input">
|
||||
<input type="text" id="card-title-color-text" value="#111111" class="color-value" readonly>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="card-title-shadow">
|
||||
<label for="card-title-shadow">Title shadow</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="card-title-font-size">Title Font Size (px)</label>
|
||||
<input type="number" id="card-title-font-size" value="20" min="10" max="200" step="5">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Title Position</label>
|
||||
<div class="position-controls">
|
||||
<div class="form-group inline">
|
||||
<label for="card-title-x">X (%)</label>
|
||||
<input type="number" id="card-title-x" value="50" min="0" max="100" step="1">
|
||||
</div>
|
||||
<div class="form-group inline">
|
||||
<label for="card-title-y">Y (%)</label>
|
||||
<input type="number" id="card-title-y" value="20" min="0" max="100" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="card-title-draggable">
|
||||
<label for="card-title-draggable">Enable drag to reposition</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="card-footer-input">Footer</label>
|
||||
<input type="text" id="card-footer-input" placeholder="Enter footer text">
|
||||
<div class="checkbox-group inline">
|
||||
<input type="checkbox" id="card-show-footer" checked>
|
||||
<label for="card-show-footer">Show footer</label>
|
||||
<!-- Image Section -->
|
||||
<div class="config-section" id="image-section">
|
||||
<h4>Image</h4>
|
||||
<div class="section-content">
|
||||
<div class="form-group">
|
||||
<label>Image</label>
|
||||
<select id="card-image-select">
|
||||
<option value="-1">Use default image</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="card-image-fit">Image Fit</label>
|
||||
<select id="card-image-fit">
|
||||
<option value="cover">Cover (fill area)</option>
|
||||
<option value="contain">Contain (show all)</option>
|
||||
<option value="fill">Fill (stretch)</option>
|
||||
<option value="none">None (original size)</option>
|
||||
<option value="scale-down">Scale Down</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="card-image-position">Image Position</label>
|
||||
<select id="card-image-position">
|
||||
<option value="center">Center</option>
|
||||
<option value="top">Top</option>
|
||||
<option value="bottom">Bottom</option>
|
||||
<option value="left">Left</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="top left">Top Left</option>
|
||||
<option value="top right">Top Right</option>
|
||||
<option value="bottom left">Bottom Left</option>
|
||||
<option value="bottom right">Bottom Right</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="card-full-bleed" checked>
|
||||
<label for="card-full-bleed">Full cover image (full bleed)</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="card-show-accent">
|
||||
<label for="card-show-accent">Show accent overlay</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="accent-color-group" style="display: none;">
|
||||
<label for="card-accent-color">Accent Color</label>
|
||||
<div class="color-picker">
|
||||
<input type="color" id="card-accent-color" value="#f1c40f" class="color-input">
|
||||
<input type="text" id="card-accent-color-text" value="#f1c40f" class="color-value" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Title Style</label>
|
||||
<div class="title-style-grid">
|
||||
<div class="title-style-option" data-style="classic">Classic</div>
|
||||
<div class="title-style-option" data-style="large">Large</div>
|
||||
<div class="title-style-option" data-style="small">Small</div>
|
||||
<div class="title-style-option selected" data-style="folded">Folded</div>
|
||||
<div class="title-style-option" data-style="condensed">Condensed</div>
|
||||
<div class="title-style-option" data-style="italic">Italic</div>
|
||||
<div class="title-style-option" data-style="bold">Bold</div>
|
||||
<div class="title-style-option" data-style="outlined">Outlined</div>
|
||||
<div class="title-style-option" data-style="shadow">Shadow</div>
|
||||
<!-- Footer Section -->
|
||||
<div class="config-section" id="footer-section">
|
||||
<h4>Footer</h4>
|
||||
<div class="section-controls">
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="card-show-footer" checked>
|
||||
<label for="card-show-footer">Show footer</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-content">
|
||||
<div class="form-group">
|
||||
<label for="card-footer-input">Footer Text</label>
|
||||
<input type="text" id="card-footer-input" placeholder="Enter footer text">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="global-title-font-size">Title Font Size (px)</label>
|
||||
<input type="number" id="global-title-font-size" value="20" min="10" max="200" step="5">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="card-accent-color">Accent Color</label>
|
||||
<div class="color-picker">
|
||||
<input type="color" id="card-accent-color" value="#f1c40f" class="color-input">
|
||||
<input type="text" id="card-accent-color-text" value="#f1c40f" class="color-value" readonly>
|
||||
<div class="form-group">
|
||||
<label for="card-footer-color">Footer Color</label>
|
||||
<div class="color-picker">
|
||||
<input type="color" id="card-footer-color" value="#111111" class="color-input">
|
||||
<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-title-color">Title Color</label>
|
||||
<div class="color-picker">
|
||||
<input type="color" id="card-title-color" value="#111111" class="color-input">
|
||||
<input type="text" id="card-title-color-text" value="#111111" class="color-value" readonly>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="card-full-bleed" checked>
|
||||
<label for="card-full-bleed">Full bleed background</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="card-title-shadow">
|
||||
<label for="card-title-shadow">Title shadow</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Image</label>
|
||||
<select id="card-image-select">
|
||||
<option value="-1">Use default image</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="btn-group" style="margin-top: 15px;">
|
||||
<button class="btn btn-primary" id="update-card">Update Card</button>
|
||||
<button class="btn btn-danger" id="delete-card">Delete Card</button>
|
||||
|
||||
Reference in New Issue
Block a user