1363 lines
60 KiB
JavaScript
1363 lines
60 KiB
JavaScript
// Yoto Up Covers Web App
|
||
class YotoCoversApp {
|
||
constructor() {
|
||
this.images = [];
|
||
this.currentTemplate = 'classic';
|
||
this.currentTitleStyle = 'folded';
|
||
|
||
// Template presets - each defines a set of feature defaults
|
||
this.templates = {
|
||
classic: {
|
||
backgroundStyle: 'solid',
|
||
fontFamily: 'Arial',
|
||
fontWeight: 'normal',
|
||
footerColor: '#666666'
|
||
},
|
||
frozen: {
|
||
backgroundStyle: 'gradient',
|
||
fontFamily: 'Arial',
|
||
fontWeight: 'normal',
|
||
footerColor: '#4a90e2'
|
||
},
|
||
minimal: {
|
||
backgroundStyle: 'solid',
|
||
fontFamily: 'Arial',
|
||
fontWeight: 'normal',
|
||
footerColor: '#999999'
|
||
},
|
||
vintage: {
|
||
backgroundStyle: 'gradient',
|
||
fontFamily: 'Georgia',
|
||
fontWeight: 'normal',
|
||
footerColor: '#654321'
|
||
},
|
||
comic: {
|
||
backgroundStyle: 'comic',
|
||
fontFamily: 'Comic Sans MS',
|
||
fontWeight: 'bold',
|
||
footerColor: '#ff6b6b',
|
||
fullBleed: false
|
||
},
|
||
polaroid: {
|
||
backgroundStyle: 'polaroid',
|
||
fontFamily: 'Arial',
|
||
fontWeight: 'normal',
|
||
footerColor: '#666666',
|
||
fullBleed: false,
|
||
showFooter: false,
|
||
imagePosition: 'center'
|
||
}
|
||
};
|
||
|
||
this.printLayout = {
|
||
cardsPerRow: 2,
|
||
cardsPerColumn: 5,
|
||
pageMargin: 10,
|
||
cardSpacing: 5
|
||
};
|
||
this.templateSettings = {
|
||
title: '',
|
||
footer: '',
|
||
accentColor: '#f1c40f',
|
||
titleColor: '#111111',
|
||
fullBleed: true,
|
||
titleShadow: false,
|
||
titleFontSize: null,
|
||
footerFontSize: null
|
||
};
|
||
|
||
this.cardVariations = [];
|
||
this.activeCardIndex = -1; // Currently selected card for editing
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
this.bindEvents();
|
||
this.updateUI();
|
||
this.updateCardList();
|
||
}
|
||
|
||
bindEvents() {
|
||
// Print layout settings
|
||
document.getElementById('cards-per-row').addEventListener('change', (e) => {
|
||
this.printLayout.cardsPerRow = parseInt(e.target.value);
|
||
this.generatePreview();
|
||
});
|
||
|
||
document.getElementById('cards-per-column').addEventListener('change', (e) => {
|
||
this.printLayout.cardsPerColumn = parseInt(e.target.value);
|
||
this.generatePreview();
|
||
});
|
||
|
||
document.getElementById('page-margin').addEventListener('input', (e) => {
|
||
this.printLayout.pageMargin = parseInt(e.target.value);
|
||
this.generatePreview();
|
||
});
|
||
|
||
document.getElementById('card-spacing').addEventListener('input', (e) => {
|
||
this.printLayout.cardSpacing = parseInt(e.target.value);
|
||
this.generatePreview();
|
||
});
|
||
|
||
// Template settings - REMOVED: Now per-card only
|
||
// document.getElementById('template-select').addEventListener('change', (e) => {
|
||
// this.currentTemplate = e.target.value;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// document.getElementById('title-input').addEventListener('input', (e) => {
|
||
// this.templateSettings.title = e.target.value;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// document.getElementById('footer-input').addEventListener('input', (e) => {
|
||
// this.templateSettings.footer = e.target.value;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// Title style selection - REMOVED: Now per-card only
|
||
// document.querySelectorAll('.title-style-option').forEach(option => {
|
||
// option.addEventListener('click', () => {
|
||
// document.querySelectorAll('.title-style-option').forEach(opt => opt.classList.remove('selected'));
|
||
// option.classList.add('selected');
|
||
// this.currentTitleStyle = option.dataset.style;
|
||
// this.generatePreview();
|
||
// });
|
||
// });
|
||
|
||
// Color pickers - REMOVED: Now per-card only
|
||
// document.getElementById('accent-color').addEventListener('input', (e) => {
|
||
// this.templateSettings.accentColor = e.target.value;
|
||
// document.getElementById('accent-color-text').value = e.target.value;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// document.getElementById('title-color').addEventListener('input', (e) => {
|
||
// this.templateSettings.titleColor = e.target.value;
|
||
// document.getElementById('title-color-text').value = e.target.value;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// Checkboxes - REMOVED: Now per-card only
|
||
// document.getElementById('full-bleed').addEventListener('change', (e) => {
|
||
// this.templateSettings.fullBleed = e.target.checked;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// document.getElementById('title-shadow').addEventListener('change', (e) => {
|
||
// this.templateSettings.titleShadow = e.target.checked;
|
||
// this.generatePreview();
|
||
// });
|
||
|
||
// Image upload
|
||
const imageUpload = document.getElementById('image-upload');
|
||
const fileInputLabel = document.querySelector('.file-input-label');
|
||
|
||
imageUpload.addEventListener('change', (e) => this.handleImageUpload(e.target.files));
|
||
imageUpload.addEventListener('dragover', (e) => {
|
||
e.preventDefault();
|
||
fileInputLabel.classList.add('dragover');
|
||
});
|
||
imageUpload.addEventListener('dragleave', (e) => {
|
||
e.preventDefault();
|
||
fileInputLabel.classList.remove('dragover');
|
||
});
|
||
imageUpload.addEventListener('drop', (e) => {
|
||
e.preventDefault();
|
||
fileInputLabel.classList.remove('dragover');
|
||
this.handleImageUpload(e.dataTransfer.files);
|
||
});
|
||
|
||
// Image management
|
||
document.getElementById('clear-images').addEventListener('click', () => this.clearAllImages());
|
||
|
||
// Actions
|
||
document.getElementById('generate-preview').addEventListener('click', () => this.generatePreview());
|
||
document.getElementById('download-html').addEventListener('click', () => this.downloadHTML());
|
||
document.getElementById('open-preview').addEventListener('click', () => this.openPreviewInNewTab());
|
||
document.getElementById('print-preview').addEventListener('click', () => this.printPreview());
|
||
|
||
// Card management
|
||
document.getElementById('add-card').addEventListener('click', () => this.addCardVariation());
|
||
document.getElementById('active-card-select').addEventListener('change', (e) => {
|
||
const index = parseInt(e.target.value);
|
||
if (index >= 0) {
|
||
this.selectCard(index);
|
||
} else {
|
||
this.activeCardIndex = -1;
|
||
this.updateCardList();
|
||
this.showCardEditor();
|
||
}
|
||
});
|
||
document.getElementById('update-card').addEventListener('click', () => this.updateActiveCard());
|
||
document.getElementById('delete-card').addEventListener('click', () => this.deleteActiveCard());
|
||
|
||
// Card editor auto-update
|
||
document.getElementById('card-title-input').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-footer-input').addEventListener('input', () => this.updateActiveCard());
|
||
document.getElementById('card-template-preset').addEventListener('change', (e) => {
|
||
const templateName = e.target.value;
|
||
if (templateName) {
|
||
this.applyTemplatePreset(templateName);
|
||
}
|
||
});
|
||
document.getElementById('card-background-style').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-font-family').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-font-weight').addEventListener('change', () => this.updateActiveCard());
|
||
document.getElementById('card-accent-color').addEventListener('input', (e) => {
|
||
document.getElementById('card-accent-color-text').value = e.target.value;
|
||
this.updateActiveCard();
|
||
});
|
||
document.getElementById('card-title-color').addEventListener('input', (e) => {
|
||
document.getElementById('card-title-color-text').value = e.target.value;
|
||
this.updateActiveCard();
|
||
});
|
||
document.getElementById('card-footer-color').addEventListener('input', (e) => {
|
||
document.getElementById('card-footer-color-text').value = e.target.value;
|
||
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();
|
||
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
|
||
document.querySelectorAll('.title-style-option').forEach(option => {
|
||
option.addEventListener('click', () => {
|
||
document.querySelectorAll('.title-style-option').forEach(opt => opt.classList.remove('selected'));
|
||
option.classList.add('selected');
|
||
this.updateActiveCard();
|
||
});
|
||
});
|
||
|
||
// Global title font size
|
||
// 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', () => {
|
||
const panel = document.querySelector('.settings-panel');
|
||
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
|
||
});
|
||
|
||
document.getElementById('toggle-editor').addEventListener('click', () => {
|
||
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) {
|
||
const initialImageCount = this.images.length;
|
||
|
||
for (const file of files) {
|
||
if (file.type.startsWith('image/')) {
|
||
try {
|
||
const imageData = await this.readFileAsDataURL(file);
|
||
const coverImage = new CoverImage(file.name, imageData);
|
||
this.images.push(coverImage);
|
||
|
||
// Automatically create a card variation for this image
|
||
const variation = {
|
||
title: '',
|
||
footer: '',
|
||
imageIndex: this.images.length - 1, // Use the index of the newly added image
|
||
template: this.currentTemplate
|
||
};
|
||
this.cardVariations.push(variation);
|
||
|
||
} catch (error) {
|
||
this.showStatusMessage(`Error loading ${file.name}: ${error.message}`, 'error');
|
||
}
|
||
}
|
||
}
|
||
|
||
// If no images were uploaded before and we now have images, remove the default single card
|
||
if (initialImageCount === 0 && this.images.length > 0 && this.cardVariations.length === 1 &&
|
||
!this.cardVariations[0].title && !this.cardVariations[0].footer && this.cardVariations[0].imageIndex === -1) {
|
||
this.cardVariations = []; // Clear the default empty variation
|
||
}
|
||
|
||
this.updateImageList();
|
||
this.updateCardList();
|
||
this.generatePreview();
|
||
}
|
||
|
||
readFileAsDataURL(file) {
|
||
return new Promise((resolve, reject) => {
|
||
const reader = new FileReader();
|
||
reader.onload = () => resolve(reader.result);
|
||
reader.onerror = () => reject(new Error('Failed to read file'));
|
||
reader.readAsDataURL(file);
|
||
});
|
||
}
|
||
|
||
updateImageList() {
|
||
const imageList = document.getElementById('image-list');
|
||
imageList.innerHTML = '';
|
||
|
||
this.images.forEach((image, index) => {
|
||
const item = document.createElement('div');
|
||
item.className = 'image-item';
|
||
item.innerHTML = `
|
||
<div class="image-info">
|
||
<div class="image-name">${image.name}</div>
|
||
<div class="image-meta">${image.width || 0} × ${image.height || 0}px</div>
|
||
</div>
|
||
<div class="image-actions">
|
||
<button class="btn btn-danger" onclick="app.removeImage(${index})">×</button>
|
||
</div>
|
||
`;
|
||
imageList.appendChild(item);
|
||
});
|
||
}
|
||
|
||
removeImage(index) {
|
||
this.images.splice(index, 1);
|
||
this.updateImageList();
|
||
this.updateCardList();
|
||
this.generatePreview();
|
||
}
|
||
|
||
clearAllImages() {
|
||
this.images = [];
|
||
this.updateImageList();
|
||
this.updateCardList();
|
||
this.generatePreview();
|
||
}
|
||
|
||
applyTemplatePreset(templateName) {
|
||
if (!this.templates[templateName]) return;
|
||
|
||
const preset = this.templates[templateName];
|
||
document.getElementById('card-background-style').value = preset.backgroundStyle;
|
||
document.getElementById('card-font-family').value = preset.fontFamily;
|
||
document.getElementById('card-font-weight').value = preset.fontWeight;
|
||
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();
|
||
}
|
||
|
||
addCardVariation() {
|
||
const variation = {
|
||
title: '',
|
||
footer: '',
|
||
imageIndex: -1,
|
||
backgroundStyle: 'solid',
|
||
fontFamily: 'Arial',
|
||
fontWeight: 'normal',
|
||
footerColor: '#111111',
|
||
titleStyle: 'classic',
|
||
accentColor: '#f1c40f',
|
||
showAccent: false,
|
||
titleColor: '#111111',
|
||
fullBleed: true,
|
||
titleShadow: false,
|
||
showTitle: true,
|
||
showFooter: true,
|
||
imageFit: 'cover',
|
||
imagePosition: 'center'
|
||
};
|
||
this.cardVariations.push(variation);
|
||
this.updateCardList();
|
||
this.generatePreview();
|
||
}
|
||
|
||
updateCardVariation(index, field, value) {
|
||
this.cardVariations[index][field] = value;
|
||
this.generatePreview();
|
||
}
|
||
|
||
removeCardVariation(index) {
|
||
this.cardVariations.splice(index, 1);
|
||
this.updateCardVariations();
|
||
this.generatePreview();
|
||
}
|
||
|
||
updateCardVariations() {
|
||
// This method is now replaced by updateCardList
|
||
this.updateCardList();
|
||
}
|
||
|
||
updateCardList() {
|
||
const container = document.getElementById('card-list');
|
||
container.innerHTML = '';
|
||
|
||
if (this.cardVariations.length === 0) {
|
||
container.innerHTML = '<p style="color: #999999; text-align: center; padding: 20px;">No cards yet. Upload images or click "Add Card".</p>';
|
||
return;
|
||
}
|
||
|
||
this.cardVariations.forEach((variation, index) => {
|
||
const cardItem = document.createElement('div');
|
||
cardItem.className = `card-list-item ${index === this.activeCardIndex ? 'active' : ''}`;
|
||
cardItem.onclick = () => this.selectCard(index);
|
||
cardItem.innerHTML = `
|
||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||
<div>
|
||
<strong>Card ${index + 1}</strong>
|
||
<span style="color: #666666; margin-left: 10px;">
|
||
${variation.title || 'No title'} •
|
||
${(() => {
|
||
for (const [presetName, preset] of Object.entries(this.templates)) {
|
||
if (variation.backgroundStyle === preset.backgroundStyle &&
|
||
variation.fontFamily === preset.fontFamily &&
|
||
variation.fontWeight === preset.fontWeight &&
|
||
variation.footerColor === preset.footerColor) {
|
||
return presetName;
|
||
}
|
||
}
|
||
return 'Custom';
|
||
})()} •
|
||
${variation.imageIndex >= 0 ? `Image ${variation.imageIndex + 1}` : 'Default image'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
container.appendChild(cardItem);
|
||
});
|
||
|
||
this.updateActiveCardSelector();
|
||
}
|
||
|
||
updateActiveCardSelector() {
|
||
const selector = document.getElementById('active-card-select');
|
||
selector.innerHTML = '<option value="-1">No card selected</option>';
|
||
|
||
this.cardVariations.forEach((variation, index) => {
|
||
const option = document.createElement('option');
|
||
option.value = index;
|
||
option.textContent = `Card ${index + 1}: ${variation.title || 'Untitled'}`;
|
||
if (index === this.activeCardIndex) option.selected = true;
|
||
selector.appendChild(option);
|
||
});
|
||
}
|
||
|
||
selectCard(index) {
|
||
this.activeCardIndex = index;
|
||
this.updateCardList();
|
||
this.showCardEditor();
|
||
}
|
||
|
||
selectCardFromPreview(index) {
|
||
// Called from iframe when clicking on a card in the preview
|
||
this.selectCard(index);
|
||
}
|
||
|
||
showCardEditor() {
|
||
const editor = document.getElementById('card-editor');
|
||
if (this.activeCardIndex === -1) {
|
||
editor.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
const variation = this.cardVariations[this.activeCardIndex];
|
||
if (!variation) return;
|
||
|
||
// Populate card settings
|
||
document.getElementById('card-editor-title').textContent = `Edit Card ${this.activeCardIndex + 1}`;
|
||
|
||
// Set template preset (detect which preset matches current features)
|
||
let matchingPreset = '';
|
||
for (const [presetName, preset] of Object.entries(this.templates)) {
|
||
let matches = variation.backgroundStyle === preset.backgroundStyle &&
|
||
variation.fontFamily === preset.fontFamily &&
|
||
variation.fontWeight === preset.fontWeight &&
|
||
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;
|
||
}
|
||
}
|
||
document.getElementById('card-template-preset').value = matchingPreset;
|
||
|
||
// Set individual features
|
||
document.getElementById('card-background-style').value = variation.backgroundStyle || 'solid';
|
||
document.getElementById('card-font-family').value = variation.fontFamily || 'Arial';
|
||
document.getElementById('card-font-weight').value = variation.fontWeight || 'normal';
|
||
document.getElementById('card-footer-color').value = variation.footerColor || '#111111';
|
||
document.getElementById('card-footer-color-text').value = variation.footerColor || '#111111';
|
||
|
||
// Set title
|
||
document.getElementById('card-title-input').value = variation.title || '';
|
||
|
||
// Set footer
|
||
document.getElementById('card-footer-input').value = variation.footer || '';
|
||
|
||
// Set title style
|
||
document.querySelectorAll('.title-style-option').forEach(option => {
|
||
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';
|
||
|
||
// Set checkboxes
|
||
document.getElementById('card-full-bleed').checked = variation.fullBleed !== false;
|
||
document.getElementById('card-title-shadow').checked = variation.titleShadow === true;
|
||
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 : 10;
|
||
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>';
|
||
this.images.forEach((img, imgIndex) => {
|
||
const option = document.createElement('option');
|
||
option.value = imgIndex;
|
||
option.textContent = img.name;
|
||
if (variation.imageIndex === imgIndex) option.selected = true;
|
||
imageSelect.appendChild(option);
|
||
});
|
||
|
||
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, .comic-title-panel');
|
||
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' and not a comic panel
|
||
if (title.style.cursor !== 'move' || title.classList.contains('comic-title-panel')) {
|
||
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() {
|
||
if (this.activeCardIndex === -1) return;
|
||
|
||
const variation = this.cardVariations[this.activeCardIndex];
|
||
variation.backgroundStyle = document.getElementById('card-background-style').value;
|
||
variation.fontFamily = document.getElementById('card-font-family').value;
|
||
variation.fontWeight = document.getElementById('card-font-weight').value;
|
||
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 || '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();
|
||
this.generatePreview();
|
||
}
|
||
|
||
deleteActiveCard() {
|
||
if (this.activeCardIndex === -1) return;
|
||
|
||
if (confirm(`Delete Card ${this.activeCardIndex + 1}?`)) {
|
||
this.cardVariations.splice(this.activeCardIndex, 1);
|
||
this.activeCardIndex = -1;
|
||
this.updateCardList();
|
||
this.showCardEditor();
|
||
this.generatePreview();
|
||
}
|
||
}
|
||
|
||
async generatePreview() {
|
||
const placeholder = document.getElementById('preview-placeholder');
|
||
const iframe = document.getElementById('preview-iframe');
|
||
|
||
if (this.images.length === 0 && !this.templateSettings.title) {
|
||
placeholder.textContent = 'Add an image or enter a title to generate preview';
|
||
placeholder.style.display = 'flex';
|
||
iframe.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
placeholder.innerHTML = '<div class="loading"></div> Generating preview...';
|
||
placeholder.style.display = 'flex';
|
||
iframe.style.display = 'none';
|
||
|
||
try {
|
||
const html = await this.generateHTML();
|
||
const blob = new Blob([html], { type: 'text/html' });
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
iframe.src = url;
|
||
iframe.style.display = 'block';
|
||
placeholder.style.display = 'none';
|
||
|
||
// Apply zoom after iframe loads
|
||
iframe.onload = () => {
|
||
this.initDragFunctionality();
|
||
};
|
||
|
||
// Clean up the previous blob URL
|
||
if (iframe.dataset.previousUrl) {
|
||
URL.revokeObjectURL(iframe.dataset.previousUrl);
|
||
}
|
||
iframe.dataset.previousUrl = url;
|
||
|
||
} catch (error) {
|
||
placeholder.textContent = `Error generating preview: ${error.message}`;
|
||
this.showStatusMessage(`Preview generation failed: ${error.message}`, 'error');
|
||
}
|
||
}
|
||
|
||
async generateHTML() {
|
||
// Determine number of cards to generate
|
||
let totalCards;
|
||
if (this.cardVariations.length > 0) {
|
||
totalCards = this.cardVariations.length;
|
||
} else if (this.images.length > 0) {
|
||
totalCards = this.images.length; // Create one card per image if no variations
|
||
} else {
|
||
totalCards = 1; // Single card if no images or variations
|
||
}
|
||
|
||
// For print sheet, arrange cards in grid, but don't exceed available space
|
||
const cardsPerPage = this.printLayout.cardsPerRow * this.printLayout.cardsPerColumn;
|
||
const pagesNeeded = Math.ceil(totalCards / cardsPerPage);
|
||
const totalCardsToGenerate = pagesNeeded * cardsPerPage; // Fill pages completely, but we'll hide empty slots
|
||
|
||
const cards = [];
|
||
|
||
// Generate cards
|
||
for (let i = 0; i < totalCardsToGenerate; i++) {
|
||
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
|
||
const variation = this.cardVariations[i];
|
||
imageIndex = variation && variation.imageIndex >= 0 ? variation.imageIndex : 0;
|
||
cardTitle = (variation && variation.title) || `Card ${i + 1}`;
|
||
cardFooter = (variation && variation.footer) || '';
|
||
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;
|
||
// Individual features
|
||
cardBackgroundStyle = (variation && variation.backgroundStyle) || 'solid';
|
||
cardFontFamily = (variation && variation.fontFamily) || 'Arial';
|
||
cardFontWeight = (variation && variation.fontWeight) || 'normal';
|
||
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 : 10;
|
||
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 = 'classic';
|
||
cardAccentColor = '#f1c40f';
|
||
cardShowAccent = false;
|
||
cardTitleColor = '#111111';
|
||
cardFullBleed = true;
|
||
cardTitleShadow = false;
|
||
// Default features
|
||
cardBackgroundStyle = 'solid';
|
||
cardFontFamily = 'Arial';
|
||
cardFontWeight = 'normal';
|
||
cardFooterColor = '#111111';
|
||
cardShowTitle = true;
|
||
cardShowFooter = true;
|
||
cardImageFit = 'cover';
|
||
cardImagePosition = 'center';
|
||
cardTitleFontSize = 20;
|
||
cardTitleX = 50;
|
||
cardTitleY = 10;
|
||
cardTitleDraggable = false;
|
||
}
|
||
|
||
const imageUrl = this.images.length > 0 ? this.images[imageIndex].dataUrl : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
|
||
|
||
cards.push({
|
||
imageUrl,
|
||
title: cardTitle,
|
||
footer: cardFooter,
|
||
backgroundStyle: cardBackgroundStyle,
|
||
fontFamily: cardFontFamily,
|
||
fontWeight: cardFontWeight,
|
||
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 {
|
||
// Empty card for filling the grid
|
||
cards.push({
|
||
imageUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
|
||
title: '',
|
||
footer: '',
|
||
backgroundStyle: 'solid',
|
||
fontFamily: 'Arial',
|
||
fontWeight: 'normal',
|
||
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: 10,
|
||
titleDraggable: false,
|
||
isVisible: false
|
||
});
|
||
}
|
||
}
|
||
|
||
// Fixed card dimensions: 54mm x 85.6mm
|
||
const cardWidthMm = 54;
|
||
const cardHeightMm = 85.6;
|
||
|
||
// Calculate grid layout
|
||
const pageWidthMm = 210; // A4 width
|
||
const pageHeightMm = 297; // A4 height
|
||
|
||
// For multiple pages, we'll create multiple print-sheet divs
|
||
let htmlContent = '';
|
||
|
||
for (let page = 0; page < pagesNeeded; page++) {
|
||
const startIndex = page * cardsPerPage;
|
||
const endIndex = Math.min(startIndex + cardsPerPage, totalCardsToGenerate);
|
||
const pageCards = cards.slice(startIndex, endIndex);
|
||
|
||
// Calculate positions for this page's grid
|
||
const gridCols = this.printLayout.cardsPerRow;
|
||
const gridRows = this.printLayout.cardsPerColumn;
|
||
|
||
htmlContent += `
|
||
<div class="print-page" style="page-break-after: ${page < pagesNeeded - 1 ? 'always' : 'auto'}; width: ${pageWidthMm}mm; padding: ${this.printLayout.pageMargin}mm; box-sizing: border-box;">
|
||
<div class="print-sheet" style="
|
||
display: grid;
|
||
grid-template-columns: repeat(${gridCols}, ${cardWidthMm}mm);
|
||
grid-template-rows: repeat(${gridRows}, ${cardHeightMm}mm);
|
||
gap: ${this.printLayout.cardSpacing}mm;
|
||
width: ${gridCols * cardWidthMm + (gridCols - 1) * this.printLayout.cardSpacing}mm;
|
||
height: ${gridRows * cardHeightMm + (gridRows - 1) * this.printLayout.cardSpacing}mm;
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
">
|
||
${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;
|
||
height: ${cardHeightMm}mm;
|
||
box-sizing: border-box;
|
||
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: #f9f9f9; border: 1px solid #ccc;' : ''}
|
||
${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 ${card.imagePosition}/${card.imageFit};` : ''}
|
||
position: relative;
|
||
border: ${card.isVisible ? 'none' : '1px dashed #ccc'};
|
||
overflow: hidden;
|
||
">
|
||
${card.isVisible ? `
|
||
${card.backgroundStyle === 'comic' ? `
|
||
<!-- Comic Style Panels -->
|
||
<div class="comic-panels">
|
||
${card.showTitle ? `<div class="comic-panel comic-title-panel" style="
|
||
position: absolute;
|
||
left: 8%;
|
||
top: 6%;
|
||
width: 40%;
|
||
height: 15%;
|
||
border: 2px solid #000;
|
||
background: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: ${card.titleFontSize * 0.8}px;
|
||
font-weight: ${card.fontWeight === 'bold' ? '700' : '400'};
|
||
font-family: '${card.fontFamily}', serif;
|
||
color: ${card.titleColor};
|
||
${card.titleShadow ? 'text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);' : ''}
|
||
transform: rotate(-2deg);
|
||
box-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
||
${card.titleDraggable && card.backgroundStyle !== 'comic' ? 'cursor: move; user-select: none;' : ''}
|
||
" data-panel-type="title">${this.escapeHtml(card.title)}</div>` : ''}
|
||
|
||
<div class="comic-panel comic-image-panel" style="
|
||
position: absolute;
|
||
left: 15%;
|
||
top: 25%;
|
||
right: 15%;
|
||
bottom: ${card.showFooter ? '35%' : '15%'};
|
||
border: 3px solid #000;
|
||
background: ${card.fullBleed ? `url('${card.imageUrl}') no-repeat ${card.imagePosition}/${card.imageFit}` : '#f8f9fa'};
|
||
transform: rotate(1deg);
|
||
box-shadow: 3px 3px 6px rgba(0,0,0,0.3);
|
||
overflow: hidden;
|
||
">
|
||
${!card.fullBleed ? `<img src="${card.imageUrl}" alt="cover" style="
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: ${card.imageFit};
|
||
object-position: ${card.imagePosition};
|
||
"/>` : ''}
|
||
${card.showAccent ? `<div class="overlay" style="
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: ${card.accentColor}40;
|
||
pointer-events: none;
|
||
"></div>` : ''}
|
||
</div>
|
||
|
||
${card.showFooter ? `<div class="comic-panel comic-footer-panel" style="
|
||
position: absolute;
|
||
right: 8%;
|
||
bottom: 6%;
|
||
width: 35%;
|
||
height: 12%;
|
||
border: 2px solid #000;
|
||
background: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 10px;
|
||
font-weight: ${card.fontWeight};
|
||
font-family: '${card.fontFamily}', serif;
|
||
color: ${card.footerColor};
|
||
transform: rotate(3deg);
|
||
box-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
||
text-align: center;
|
||
padding: 2px;
|
||
">${this.escapeHtml(card.footer)}</div>` : ''}
|
||
</div>
|
||
` : `
|
||
<!-- Standard Layout -->
|
||
${card.showTitle ? `<div class="title ${card.titleStyle}" data-heading="${this.escapeHtml(card.title)}" style="
|
||
color: ${card.titleColor};
|
||
font-weight: ${card.fontWeight === 'bold' ? '700' : '400'};
|
||
font-family: '${card.fontFamily}', serif;
|
||
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" 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;
|
||
font-weight: ${card.fontWeight};
|
||
">${this.escapeHtml(card.footer)}</div>` : ''}
|
||
`}
|
||
` : ''}
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
// Generate HTML for print sheet
|
||
const html = `
|
||
<!doctype html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>Yoto Covers Print Sheet</title>
|
||
<style>
|
||
@font-face { font-family: 'DejaVuSans'; src: local('DejaVu Sans'); }
|
||
@media screen {
|
||
html, body { margin: 0; padding: 20px; background: #f5f5f5; }
|
||
.print-page { border: 2px dashed #ccc; margin: 20px auto; background: white; page-break-after: always; width: 210mm; max-width: 100%; }
|
||
.print-page:last-child { page-break-after: auto; }
|
||
.print-sheet { max-width: 100%; height: auto !important; }
|
||
}
|
||
@media print {
|
||
html, body { margin: 0; padding: 0; background: white; }
|
||
.print-page { border: none; margin: 0; page-break-after: always; }
|
||
.print-page:last-child { page-break-after: auto; }
|
||
@page { margin: 0; size: A4; }
|
||
}
|
||
.card {
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
}
|
||
.empty-card {
|
||
background: #f9f9f9;
|
||
}
|
||
${cards.map((card, index) => `
|
||
.card-${index} .title {
|
||
color: ${card.titleColor};
|
||
font-family: '${card.fontFamily}', serif;
|
||
font-weight: ${card.fontWeight === 'bold' ? '700' : '400'};
|
||
}
|
||
.card-${index} .footer {
|
||
color: ${card.footerColor};
|
||
}
|
||
`).join('')}
|
||
.title {
|
||
position: absolute;
|
||
top: 6%;
|
||
left: 6%;
|
||
right: 6%;
|
||
text-align: center;
|
||
font-size: calc(108px + 0.5rem);
|
||
color: ${this.templateSettings.titleColor};
|
||
font-weight: 900;
|
||
text-transform: uppercase;
|
||
z-index: 10;
|
||
${this.templateSettings.titleShadow ? 'text-shadow: 1px 4px 6px rgba(0,0,0,0.3);' : ''}
|
||
}
|
||
.title.classic {
|
||
/* Default classic style - already defined above */
|
||
}
|
||
.title.condensed {
|
||
font-size: calc(96px + 0.5rem);
|
||
font-weight: 900;
|
||
letter-spacing: -2px;
|
||
font-stretch: condensed;
|
||
}
|
||
.title.outlined {
|
||
-webkit-text-stroke: 2px white;
|
||
text-stroke: 2px white;
|
||
color: transparent;
|
||
}
|
||
.title.folded {
|
||
position: absolute;
|
||
left: 6%;
|
||
right: 6%;
|
||
top: 6%;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
font-family: 'Source Code Pro', monospace;
|
||
font-weight: 900;
|
||
white-space: nowrap;
|
||
color: lch(76 39.21 9.23 / 0.5);
|
||
text-transform: uppercase;
|
||
transform: skew(10deg) rotate(-10deg);
|
||
text-shadow: 1px 4px 6px lch(90 2.22 62.5), 0 0 0 lch(28 26.21 12.27), 1px 4px 6px lch(90 2.22 62.5);
|
||
z-index: 10;
|
||
}
|
||
.title.folded::before {
|
||
content: attr(data-heading);
|
||
position: absolute;
|
||
left: 0;
|
||
top: -4.8%;
|
||
overflow: hidden;
|
||
height: 50%;
|
||
color: lch(97 2.19 62.49);
|
||
transform: translate(0.15em, 0) skew(-13deg) scale(1, 1.2);
|
||
text-shadow: 2px -1px 6px rgba(0,0,0,0.2);
|
||
}
|
||
.title.folded::after {
|
||
content: attr(data-heading);
|
||
position: absolute;
|
||
left: 0;
|
||
color: lch(83 2.26 62.51);
|
||
transform: translate(0, 0) skew(13deg) scale(1, 0.8);
|
||
clip-path: polygon(0 50%, 100% 50%, 100% 100%, 0 100%);
|
||
text-shadow: 2px -1px 6px lch(0 0 0 / 0.3);
|
||
}
|
||
.hero {
|
||
position: absolute;
|
||
top: 18%;
|
||
left: 6%;
|
||
right: 6%;
|
||
bottom: 18%;
|
||
display: flex;
|
||
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%;
|
||
object-fit: cover;
|
||
border-radius: 8px;
|
||
}
|
||
.overlay {
|
||
position: absolute;
|
||
top: 18%;
|
||
left: 6%;
|
||
right: 6%;
|
||
bottom: 18%;
|
||
border-radius: 8px;
|
||
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%;
|
||
left: 6%;
|
||
right: 6%;
|
||
height: 10%;
|
||
background: ${this.templateSettings.accentColor};
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 700;
|
||
color: #000;
|
||
border-radius: 6px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
${htmlContent}
|
||
</body>
|
||
</html>`;
|
||
|
||
return html;
|
||
}
|
||
|
||
escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
openPreviewInNewTab() {
|
||
const iframe = document.getElementById('preview-iframe');
|
||
if (iframe.src) {
|
||
window.open(iframe.src, '_blank');
|
||
} else {
|
||
this.showStatusMessage('No preview available. Generate a preview first.', 'error');
|
||
}
|
||
}
|
||
|
||
printPreview() {
|
||
const iframe = document.getElementById('preview-iframe');
|
||
if (iframe.contentWindow) {
|
||
iframe.contentWindow.print();
|
||
} else {
|
||
this.showStatusMessage('No preview available to print.', 'error');
|
||
}
|
||
}
|
||
|
||
downloadHTML() {
|
||
if (!this.templateSettings.title && this.images.length === 0) {
|
||
this.showStatusMessage('Add content before downloading.', 'error');
|
||
return;
|
||
}
|
||
|
||
this.generateHTML().then(html => {
|
||
const blob = new Blob([html], { type: 'text/html' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `yoto-cover-${Date.now()}.html`;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
this.showStatusMessage('HTML file downloaded successfully!', 'success');
|
||
}).catch(error => {
|
||
this.showStatusMessage(`Download failed: ${error.message}`, 'error');
|
||
});
|
||
}
|
||
|
||
showStatusMessage(message, type = 'info') {
|
||
const statusDiv = document.getElementById('status-message');
|
||
statusDiv.textContent = message;
|
||
statusDiv.className = `status-message status-${type}`;
|
||
statusDiv.style.display = 'block';
|
||
|
||
setTimeout(() => {
|
||
statusDiv.style.display = 'none';
|
||
}, 5000);
|
||
}
|
||
|
||
updateUI() {
|
||
// Initialize print layout controls
|
||
document.getElementById('cards-per-row').value = this.printLayout.cardsPerRow;
|
||
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;
|
||
}
|
||
}
|
||
|
||
class CoverImage {
|
||
constructor(name, dataUrl) {
|
||
this.name = name;
|
||
this.dataUrl = dataUrl;
|
||
this.width = 0;
|
||
this.height = 0;
|
||
|
||
// Get image dimensions
|
||
this.getImageDimensions();
|
||
}
|
||
|
||
async getImageDimensions() {
|
||
return new Promise((resolve) => {
|
||
const img = new Image();
|
||
img.onload = () => {
|
||
this.width = img.naturalWidth;
|
||
this.height = img.naturalHeight;
|
||
resolve();
|
||
};
|
||
img.src = this.dataUrl;
|
||
});
|
||
}
|
||
}
|
||
|
||
// Initialize the app when DOM is ready
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
window.app = new YotoCoversApp();
|
||
window.selectCardFromPreview = (index) => window.app.selectCardFromPreview(index);
|
||
}); |