feat: add zoom and fullscreen controls to preview, enhance card fit options
This commit is contained in:
@@ -57,7 +57,8 @@ class YotoCoversApp {
|
|||||||
cardsPerRow: 2,
|
cardsPerRow: 2,
|
||||||
cardsPerColumn: 5,
|
cardsPerColumn: 5,
|
||||||
pageMargin: 10,
|
pageMargin: 10,
|
||||||
cardSpacing: 5
|
cardSpacing: 5,
|
||||||
|
cardFit: 0
|
||||||
};
|
};
|
||||||
this.templateSettings = {
|
this.templateSettings = {
|
||||||
title: '',
|
title: '',
|
||||||
@@ -70,6 +71,15 @@ class YotoCoversApp {
|
|||||||
footerFontSize: null
|
footerFontSize: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.previewZoom = {
|
||||||
|
level: 1.0,
|
||||||
|
min: 0.25,
|
||||||
|
max: 3.0,
|
||||||
|
step: 0.25
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isFullscreen = false;
|
||||||
|
|
||||||
this.cardVariations = [];
|
this.cardVariations = [];
|
||||||
this.activeCardIndex = -1; // Currently selected card for editing
|
this.activeCardIndex = -1; // Currently selected card for editing
|
||||||
this.init();
|
this.init();
|
||||||
@@ -103,6 +113,11 @@ class YotoCoversApp {
|
|||||||
this.generatePreview();
|
this.generatePreview();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('global-card-fit').addEventListener('change', (e) => {
|
||||||
|
this.printLayout.cardFit = parseFloat(e.target.value);
|
||||||
|
this.generatePreview();
|
||||||
|
});
|
||||||
|
|
||||||
// Template settings - REMOVED: Now per-card only
|
// Template settings - REMOVED: Now per-card only
|
||||||
// document.getElementById('template-select').addEventListener('change', (e) => {
|
// document.getElementById('template-select').addEventListener('change', (e) => {
|
||||||
// this.currentTemplate = e.target.value;
|
// this.currentTemplate = e.target.value;
|
||||||
@@ -181,6 +196,36 @@ class YotoCoversApp {
|
|||||||
document.getElementById('open-preview').addEventListener('click', () => this.openPreviewInNewTab());
|
document.getElementById('open-preview').addEventListener('click', () => this.openPreviewInNewTab());
|
||||||
document.getElementById('print-preview').addEventListener('click', () => this.printPreview());
|
document.getElementById('print-preview').addEventListener('click', () => this.printPreview());
|
||||||
|
|
||||||
|
// Preview zoom and fullscreen
|
||||||
|
document.getElementById('zoom-in').addEventListener('click', () => this.zoomIn());
|
||||||
|
document.getElementById('zoom-out').addEventListener('click', () => this.zoomOut());
|
||||||
|
document.getElementById('zoom-reset').addEventListener('click', () => this.resetZoom());
|
||||||
|
document.getElementById('fullscreen-btn').addEventListener('click', () => this.toggleFullscreen());
|
||||||
|
|
||||||
|
// ESC key to exit fullscreen
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && this.isFullscreen) {
|
||||||
|
this.toggleFullscreen();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click on preview container close button area to exit fullscreen
|
||||||
|
document.querySelector('.preview-container').addEventListener('click', (e) => {
|
||||||
|
if (this.isFullscreen) {
|
||||||
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
|
const closeButtonArea = {
|
||||||
|
left: rect.right - 200,
|
||||||
|
right: rect.right - 20,
|
||||||
|
top: rect.top + 20,
|
||||||
|
bottom: rect.top + 60
|
||||||
|
};
|
||||||
|
if (e.clientX >= closeButtonArea.left && e.clientX <= closeButtonArea.right &&
|
||||||
|
e.clientY >= closeButtonArea.top && e.clientY <= closeButtonArea.bottom) {
|
||||||
|
this.toggleFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Card management
|
// Card management
|
||||||
document.getElementById('add-card').addEventListener('click', () => this.addCardVariation());
|
document.getElementById('add-card').addEventListener('click', () => this.addCardVariation());
|
||||||
document.getElementById('active-card-select').addEventListener('change', (e) => {
|
document.getElementById('active-card-select').addEventListener('change', (e) => {
|
||||||
@@ -637,6 +682,9 @@ class YotoCoversApp {
|
|||||||
document.getElementById('card-image-panel-rotation').value = variation.imagePanel?.rotation || 0;
|
document.getElementById('card-image-panel-rotation').value = variation.imagePanel?.rotation || 0;
|
||||||
document.getElementById('card-footer-panel-border').value = variation.footerPanel?.borderWidth || 0;
|
document.getElementById('card-footer-panel-border').value = variation.footerPanel?.borderWidth || 0;
|
||||||
document.getElementById('card-footer-panel-rotation').value = variation.footerPanel?.rotation || 0;
|
document.getElementById('card-footer-panel-rotation').value = variation.footerPanel?.rotation || 0;
|
||||||
|
|
||||||
|
// Set card fit
|
||||||
|
document.getElementById('card-fit').value = variation.cardFit !== undefined ? variation.cardFit : '';
|
||||||
|
|
||||||
// Populate image options
|
// Populate image options
|
||||||
const imageSelect = document.getElementById('card-image-select');
|
const imageSelect = document.getElementById('card-image-select');
|
||||||
@@ -940,6 +988,14 @@ class YotoCoversApp {
|
|||||||
borderWidth: parseInt(document.getElementById('card-footer-panel-border').value),
|
borderWidth: parseInt(document.getElementById('card-footer-panel-border').value),
|
||||||
rotation: parseInt(document.getElementById('card-footer-panel-rotation').value)
|
rotation: parseInt(document.getElementById('card-footer-panel-rotation').value)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Update card fit
|
||||||
|
const cardFitValue = document.getElementById('card-fit').value;
|
||||||
|
if (cardFitValue === '') {
|
||||||
|
delete variation.cardFit;
|
||||||
|
} else {
|
||||||
|
variation.cardFit = parseFloat(cardFitValue);
|
||||||
|
}
|
||||||
|
|
||||||
this.updateCardList();
|
this.updateCardList();
|
||||||
this.generatePreview();
|
this.generatePreview();
|
||||||
@@ -984,6 +1040,7 @@ class YotoCoversApp {
|
|||||||
// Apply zoom after iframe loads
|
// Apply zoom after iframe loads
|
||||||
iframe.onload = () => {
|
iframe.onload = () => {
|
||||||
this.initDragFunctionality();
|
this.initDragFunctionality();
|
||||||
|
this.updateZoom();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clean up the previous blob URL
|
// Clean up the previous blob URL
|
||||||
@@ -1025,6 +1082,7 @@ class YotoCoversApp {
|
|||||||
let cardFooterFontSize, cardFooterX, cardFooterY, cardFooterDraggable;
|
let cardFooterFontSize, cardFooterX, cardFooterY, cardFooterDraggable;
|
||||||
let cardShowAccent;
|
let cardShowAccent;
|
||||||
let cardPanelMode, cardTitlePanel, cardImagePanel, cardFooterPanel;
|
let cardPanelMode, cardTitlePanel, cardImagePanel, cardFooterPanel;
|
||||||
|
let cardFit;
|
||||||
|
|
||||||
if (this.cardVariations.length > 0) {
|
if (this.cardVariations.length > 0) {
|
||||||
// Use variation settings
|
// Use variation settings
|
||||||
@@ -1059,6 +1117,7 @@ class YotoCoversApp {
|
|||||||
cardTitlePanel = variation && variation.titlePanel ? variation.titlePanel : { borderWidth: 0, rotation: 0, expand: false };
|
cardTitlePanel = variation && variation.titlePanel ? variation.titlePanel : { borderWidth: 0, rotation: 0, expand: false };
|
||||||
cardImagePanel = variation && variation.imagePanel ? variation.imagePanel : { borderWidth: 0, rotation: 0 };
|
cardImagePanel = variation && variation.imagePanel ? variation.imagePanel : { borderWidth: 0, rotation: 0 };
|
||||||
cardFooterPanel = variation && variation.footerPanel ? variation.footerPanel : { borderWidth: 0, rotation: 0 };
|
cardFooterPanel = variation && variation.footerPanel ? variation.footerPanel : { borderWidth: 0, rotation: 0 };
|
||||||
|
cardFit = variation && variation.cardFit !== undefined ? variation.cardFit : this.printLayout.cardFit;
|
||||||
} else {
|
} else {
|
||||||
// No variations - auto-assign images
|
// No variations - auto-assign images
|
||||||
imageIndex = i < this.images.length ? i : 0;
|
imageIndex = i < this.images.length ? i : 0;
|
||||||
@@ -1091,6 +1150,7 @@ class YotoCoversApp {
|
|||||||
cardTitlePanel = { borderWidth: 0, rotation: 0, expand: false };
|
cardTitlePanel = { borderWidth: 0, rotation: 0, expand: false };
|
||||||
cardImagePanel = { borderWidth: 0, rotation: 0 };
|
cardImagePanel = { borderWidth: 0, rotation: 0 };
|
||||||
cardFooterPanel = { borderWidth: 0, rotation: 0 };
|
cardFooterPanel = { borderWidth: 0, rotation: 0 };
|
||||||
|
cardFit = this.printLayout.cardFit;
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageUrl = this.images.length > 0 ? this.images[imageIndex].dataUrl : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
|
const imageUrl = this.images.length > 0 ? this.images[imageIndex].dataUrl : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
|
||||||
@@ -1125,6 +1185,7 @@ class YotoCoversApp {
|
|||||||
titlePanel: cardTitlePanel,
|
titlePanel: cardTitlePanel,
|
||||||
imagePanel: cardImagePanel,
|
imagePanel: cardImagePanel,
|
||||||
footerPanel: cardFooterPanel,
|
footerPanel: cardFooterPanel,
|
||||||
|
cardFit: cardFit,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -1155,14 +1216,17 @@ class YotoCoversApp {
|
|||||||
titlePanel: { borderWidth: 0, rotation: 0, expand: false },
|
titlePanel: { borderWidth: 0, rotation: 0, expand: false },
|
||||||
imagePanel: { borderWidth: 0, rotation: 0 },
|
imagePanel: { borderWidth: 0, rotation: 0 },
|
||||||
footerPanel: { borderWidth: 0, rotation: 0 },
|
footerPanel: { borderWidth: 0, rotation: 0 },
|
||||||
|
cardFit: this.printLayout.cardFit,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fixed card dimensions: 54mm x 85.6mm
|
// Fixed card dimensions: 54mm x 85.6mm
|
||||||
const cardWidthMm = 54;
|
const baseCardWidthMm = 54;
|
||||||
const cardHeightMm = 85.6;
|
const baseCardHeightMm = 85.6;
|
||||||
|
const cardWidthMm = baseCardWidthMm;
|
||||||
|
const cardHeightMm = baseCardHeightMm;
|
||||||
|
|
||||||
// Calculate grid layout
|
// Calculate grid layout
|
||||||
const pageWidthMm = 210; // A4 width
|
const pageWidthMm = 210; // A4 width
|
||||||
@@ -1180,23 +1244,33 @@ class YotoCoversApp {
|
|||||||
const gridCols = this.printLayout.cardsPerRow;
|
const gridCols = this.printLayout.cardsPerRow;
|
||||||
const gridRows = this.printLayout.cardsPerColumn;
|
const gridRows = this.printLayout.cardsPerColumn;
|
||||||
|
|
||||||
htmlContent += `
|
const pageBreak = page < pagesNeeded - 1 ? "always" : "auto";
|
||||||
<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;">
|
const gridColsValue = gridCols;
|
||||||
<div class="print-sheet" style="
|
const cardWidthValue = cardWidthMm;
|
||||||
display: grid;
|
const cardHeightValue = cardHeightMm;
|
||||||
grid-template-columns: repeat(${gridCols}, ${cardWidthMm}mm);
|
const spacingValue = this.printLayout.cardSpacing;
|
||||||
grid-template-rows: repeat(${gridRows}, ${cardHeightMm}mm);
|
const widthValue = gridCols * cardWidthMm + (gridCols - 1) * this.printLayout.cardSpacing;
|
||||||
gap: ${this.printLayout.cardSpacing}mm;
|
const heightValue = gridRows * cardHeightMm + (gridRows - 1) * this.printLayout.cardSpacing;
|
||||||
width: ${gridCols * cardWidthMm + (gridCols - 1) * this.printLayout.cardSpacing}mm;
|
|
||||||
height: ${gridRows * cardHeightMm + (gridRows - 1) * this.printLayout.cardSpacing}mm;
|
htmlContent += `<div class="print-page" style="page-break-after: ${pageBreak}; width: ${pageWidthMm}mm; padding: ${this.printLayout.pageMargin}mm; box-sizing: border-box;">
|
||||||
margin: 0;
|
<div class="print-sheet" style="
|
||||||
padding: 0;
|
display: grid;
|
||||||
box-sizing: border-box;
|
grid-template-columns: repeat(${gridColsValue}, ${cardWidthValue}mm);
|
||||||
">
|
grid-template-rows: repeat(${gridRows}, ${cardHeightValue}mm);
|
||||||
${pageCards.map((card, index) => `
|
gap: ${spacingValue}mm;
|
||||||
|
width: ${widthValue}mm;
|
||||||
|
height: ${heightValue}mm;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
">
|
||||||
|
${pageCards.map((card, index) => {
|
||||||
|
const cardFitFactor = card.cardFit || 0;
|
||||||
|
return `
|
||||||
<div class="card card-${startIndex + index} ${card.isVisible ? '' : 'empty-card'}"
|
<div class="card card-${startIndex + index} ${card.isVisible ? '' : 'empty-card'}"
|
||||||
data-card-index="${startIndex + index}"
|
data-card-index="${startIndex + index}"
|
||||||
data-background-style="${card.backgroundStyle}"
|
data-background-style="${card.backgroundStyle}"
|
||||||
|
data-card-fit="${cardFitFactor}"
|
||||||
onclick="if (window.parent && window.parent.selectCardFromPreview) { window.parent.selectCardFromPreview(${startIndex + index}); }"
|
onclick="if (window.parent && window.parent.selectCardFromPreview) { window.parent.selectCardFromPreview(${startIndex + index}); }"
|
||||||
style="cursor: pointer;
|
style="cursor: pointer;
|
||||||
width: ${cardWidthMm}mm;
|
width: ${cardWidthMm}mm;
|
||||||
@@ -1243,7 +1317,7 @@ class YotoCoversApp {
|
|||||||
-webkit-text-stroke: #fff calc(var(--o));
|
-webkit-text-stroke: #fff calc(var(--o));
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
mix-blend-mode: screen;
|
mix-blend-mode: screen;
|
||||||
font: clamp(1em, min(65vh, 35vw), 2em)/ 0.75 'Bebas Neue', sans-serif;
|
font: ${card.titleFontSize * 0.8}px/ 0.75 'Bebas Neue', sans-serif;
|
||||||
transform: rotate(${card.titlePanel.rotation}deg);
|
transform: rotate(${card.titlePanel.rotation}deg);
|
||||||
box-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
box-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
||||||
@supports (line-height: 1cap) { line-height: 1cap; }
|
@supports (line-height: 1cap) { line-height: 1cap; }
|
||||||
@@ -1353,7 +1427,7 @@ class YotoCoversApp {
|
|||||||
-webkit-text-stroke: #fff calc(var(--o));
|
-webkit-text-stroke: #fff calc(var(--o));
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
mix-blend-mode: screen;
|
mix-blend-mode: screen;
|
||||||
font: clamp(2em, min(65vh, 35vw), 3.9em)/ 0.75 'Bebas Neue', sans-serif;
|
font: ${card.titleFontSize}px/ 0.75 'Bebas Neue', sans-serif;
|
||||||
${card.titleDraggable ? 'cursor: move; user-select: none;' : ''}
|
${card.titleDraggable ? 'cursor: move; user-select: none;' : ''}
|
||||||
@supports (line-height: 1cap) { line-height: 1cap; }
|
@supports (line-height: 1cap) { line-height: 1cap; }
|
||||||
}
|
}
|
||||||
@@ -1394,7 +1468,7 @@ class YotoCoversApp {
|
|||||||
`}
|
`}
|
||||||
` : ''}
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
`).join('')}
|
`;}).join('')}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -1427,6 +1501,14 @@ class YotoCoversApp {
|
|||||||
.empty-card {
|
.empty-card {
|
||||||
background: #f9f9f9;
|
background: #f9f9f9;
|
||||||
}
|
}
|
||||||
|
@media print {
|
||||||
|
${cards.map((card, index) => {
|
||||||
|
const fitFactor = card.cardFit || 0;
|
||||||
|
const widthMm = 54 * (1 + fitFactor);
|
||||||
|
const heightMm = 85.6 * (1 + fitFactor);
|
||||||
|
return `.card-${index} { width: ${widthMm}mm !important; height: ${heightMm}mm !important; }`;
|
||||||
|
}).join('\n ')}
|
||||||
|
}
|
||||||
${cards.map((card, index) => `
|
${cards.map((card, index) => `
|
||||||
.card-${index} .title {
|
.card-${index} .title {
|
||||||
color: ${card.titleColor};
|
color: ${card.titleColor};
|
||||||
@@ -1624,12 +1706,69 @@ class YotoCoversApp {
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zoomIn() {
|
||||||
|
if (this.previewZoom.level < this.previewZoom.max) {
|
||||||
|
this.previewZoom.level = Math.min(this.previewZoom.max, this.previewZoom.level + this.previewZoom.step);
|
||||||
|
this.updateZoom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomOut() {
|
||||||
|
if (this.previewZoom.level > this.previewZoom.min) {
|
||||||
|
this.previewZoom.level = Math.max(this.previewZoom.min, this.previewZoom.level - this.previewZoom.step);
|
||||||
|
this.updateZoom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetZoom() {
|
||||||
|
this.previewZoom.level = 1.0;
|
||||||
|
this.updateZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateZoom() {
|
||||||
|
const iframe = document.getElementById('preview-iframe');
|
||||||
|
const zoomLevel = document.getElementById('zoom-level');
|
||||||
|
|
||||||
|
if (iframe) {
|
||||||
|
iframe.style.transform = `scale(${this.previewZoom.level})`;
|
||||||
|
iframe.style.transformOrigin = 'top left';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zoomLevel) {
|
||||||
|
zoomLevel.textContent = Math.round(this.previewZoom.level * 100) + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFullscreen() {
|
||||||
|
const container = document.querySelector('.preview-container');
|
||||||
|
const overlay = document.getElementById('fullscreen-overlay');
|
||||||
|
const btn = document.getElementById('fullscreen-btn');
|
||||||
|
|
||||||
|
if (this.isFullscreen) {
|
||||||
|
// Exit fullscreen
|
||||||
|
container.classList.remove('fullscreen');
|
||||||
|
overlay.classList.remove('active');
|
||||||
|
btn.textContent = '⛶ Full Screen';
|
||||||
|
this.isFullscreen = false;
|
||||||
|
} else {
|
||||||
|
// Enter fullscreen
|
||||||
|
container.classList.add('fullscreen');
|
||||||
|
overlay.classList.add('active');
|
||||||
|
btn.textContent = '⛶ Exit Full Screen';
|
||||||
|
this.isFullscreen = true;
|
||||||
|
|
||||||
|
// Add click handler to overlay to exit fullscreen
|
||||||
|
overlay.onclick = () => this.toggleFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateUI() {
|
updateUI() {
|
||||||
// Initialize print layout controls
|
// Initialize print layout controls
|
||||||
document.getElementById('cards-per-row').value = this.printLayout.cardsPerRow;
|
document.getElementById('cards-per-row').value = this.printLayout.cardsPerRow;
|
||||||
document.getElementById('cards-per-column').value = this.printLayout.cardsPerColumn;
|
document.getElementById('cards-per-column').value = this.printLayout.cardsPerColumn;
|
||||||
document.getElementById('page-margin').value = this.printLayout.pageMargin;
|
document.getElementById('page-margin').value = this.printLayout.pageMargin;
|
||||||
document.getElementById('card-spacing').value = this.printLayout.cardSpacing;
|
document.getElementById('card-spacing').value = this.printLayout.cardSpacing;
|
||||||
|
document.getElementById('global-card-fit').value = this.printLayout.cardFit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+147
@@ -287,6 +287,8 @@
|
|||||||
height: 600px;
|
height: 600px;
|
||||||
border: none;
|
border: none;
|
||||||
display: block;
|
display: block;
|
||||||
|
transform-origin: top left;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-placeholder {
|
.preview-placeholder {
|
||||||
@@ -301,6 +303,118 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-btn:active {
|
||||||
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-level {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-btn {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-btn:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container.fullscreen {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 9999;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container.fullscreen .preview-iframe {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container.fullscreen::before {
|
||||||
|
content: '✕ Close (ESC)';
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 10000;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container.fullscreen:hover::before {
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
z-index: 9998;
|
||||||
|
display: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-overlay.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.color-picker {
|
.color-picker {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -612,6 +726,16 @@
|
|||||||
<label for="card-spacing">Card spacing (mm)</label>
|
<label for="card-spacing">Card spacing (mm)</label>
|
||||||
<input type="number" id="card-spacing" value="5" min="0" max="20">
|
<input type="number" id="card-spacing" value="5" min="0" max="20">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="global-card-fit">Global card fit</label>
|
||||||
|
<select id="global-card-fit">
|
||||||
|
<option value="-0.02">Overfit (+2%)</option>
|
||||||
|
<option value="0" selected>Exact fit</option>
|
||||||
|
<option value="0.02">Underfit (-2%)</option>
|
||||||
|
</select>
|
||||||
|
<small style="color: #666; display: block; margin-top: 5px;">Adjust the print size for all cards (can be overridden per card)</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -644,6 +768,16 @@
|
|||||||
<div class="panel preview-panel">
|
<div class="panel preview-panel">
|
||||||
<h3 style="margin-bottom: 20px;">Preview</h3>
|
<h3 style="margin-bottom: 20px;">Preview</h3>
|
||||||
|
|
||||||
|
<div class="preview-controls">
|
||||||
|
<div class="zoom-controls">
|
||||||
|
<button class="zoom-btn" id="zoom-out" title="Zoom Out">-</button>
|
||||||
|
<span class="zoom-level" id="zoom-level">100%</span>
|
||||||
|
<button class="zoom-btn" id="zoom-in" title="Zoom In">+</button>
|
||||||
|
<button class="zoom-btn" id="zoom-reset" title="Reset Zoom">↻</button>
|
||||||
|
</div>
|
||||||
|
<button class="fullscreen-btn" id="fullscreen-btn" title="Full Screen (Press ESC to exit)">⛶ Full Screen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="preview-container">
|
<div class="preview-container">
|
||||||
<div class="preview-placeholder" id="preview-placeholder">
|
<div class="preview-placeholder" id="preview-placeholder">
|
||||||
Click "Generate Preview" to see your cover
|
Click "Generate Preview" to see your cover
|
||||||
@@ -930,6 +1064,17 @@
|
|||||||
<input type="number" id="card-footer-font-size" value="12" min="8" max="100" step="2">
|
<input type="number" id="card-footer-font-size" value="12" min="8" max="100" step="2">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="card-fit">Card print fit override</label>
|
||||||
|
<select id="card-fit">
|
||||||
|
<option value="">Use global setting</option>
|
||||||
|
<option value="-0.02">Overfit (+2%)</option>
|
||||||
|
<option value="0">Exact fit</option>
|
||||||
|
<option value="0.02">Underfit (-2%)</option>
|
||||||
|
</select>
|
||||||
|
<small style="color: #666; display: block; margin-top: 5px;">Override the global card fit setting for this card</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Footer Position</label>
|
<label>Footer Position</label>
|
||||||
<div class="position-controls">
|
<div class="position-controls">
|
||||||
@@ -988,6 +1133,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="fullscreen-overlay" id="fullscreen-overlay"></div>
|
||||||
|
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user