loreal_ecf/ATF_preview.html

1046 lines
39 KiB
HTML
Executable file

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Amazon PDP Mockup Generator</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f8f9fa;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header h1 {
color: #232f3e;
margin-bottom: 10px;
}
.header p {
color: #666;
font-size: 16px;
}
.upload-section {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 30px;
}
.upload-box {
background: white;
border: 2px dashed #ddd;
border-radius: 8px;
padding: 30px;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
}
.upload-box:hover {
border-color: #ff9900;
background: #fff8f0;
}
.upload-box.dragover {
border-color: #ff9900;
background: #fff8f0;
transform: scale(1.02);
}
.upload-icon {
font-size: 48px;
margin-bottom: 15px;
color: #666;
}
.upload-box:hover .upload-icon {
color: #ff9900;
}
.upload-text {
font-size: 18px;
font-weight: 600;
margin-bottom: 10px;
color: #333;
}
.upload-subtext {
color: #666;
font-size: 14px;
}
.file-input {
display: none;
}
.preview-section {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 20px;
margin-bottom: 30px;
}
.image-preview {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.image-preview h3 {
margin-bottom: 15px;
color: #232f3e;
}
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
gap: 10px;
}
.image-thumb {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 4px;
border: 2px solid #ddd;
cursor: pointer;
transition: all 0.3s ease;
}
.image-thumb:hover {
border-color: #ff9900;
transform: scale(1.05);
}
.image-thumb.selected {
border-color: #ff9900;
box-shadow: 0 0 0 2px rgba(255, 153, 0, 0.3);
}
.csv-preview {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.csv-preview h3 {
margin-bottom: 15px;
color: #232f3e;
}
.csv-data {
background: #f8f9fa;
border-radius: 4px;
padding: 15px;
font-family: monospace;
font-size: 14px;
max-height: 200px;
overflow-y: auto;
}
.mockup-container {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.mockup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.mockup-header h3 {
color: #232f3e;
}
.export-btn {
background: #ff9900;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
transition: background 0.3s ease;
}
.export-btn:hover {
background: #e68900;
}
.export-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
/* Amazon PDP Mockup Styles */
.amazon-mockup {
background: white;
border: 1px solid #ddd;
border-radius: 4px;
padding: 20px;
font-family: Arial, sans-serif;
max-width: 100%;
}
.amazon-header {
background: #232f3e;
color: white;
padding: 10px 20px;
margin: -20px -20px 20px -20px;
border-radius: 4px 4px 0 0;
}
.amazon-header h2 {
font-size: 18px;
font-weight: normal;
}
.product-section {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 30px;
}
.product-images {
display: flex;
flex-direction: column;
gap: 10px;
}
.hero-image {
width: 100%;
max-width: 400px;
height: 400px;
object-fit: cover;
border: 1px solid #ddd;
border-radius: 4px;
background: #f8f9fa;
display: flex;
align-items: center;
justify-content: center;
color: #666;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.hero-image:hover {
border-color: #ff9900;
background: #fff8f0;
}
.thumbnail-images {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.thumbnail {
width: 60px;
height: 60px;
object-fit: cover;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
.product-info {
padding-left: 20px;
}
.brand-name {
color: #0066c0;
font-size: 14px;
margin-bottom: 5px;
}
.product-title {
font-size: 20px;
font-weight: 400;
line-height: 1.3;
margin-bottom: 15px;
color: #0f1111;
}
.subtitle {
font-size: 14px;
color: #565959;
margin-top: -8px;
margin-bottom: 12px;
}
.rating {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.stars {
color: #ff9900;
font-size: 16px;
margin-right: 10px;
}
.price {
font-size: 24px;
color: #b12704;
margin-bottom: 20px;
}
.bullets {
margin-bottom: 20px;
}
.bullet {
margin-bottom: 8px;
font-size: 14px;
line-height: 1.4;
}
.bullet::before {
content: "• ";
color: #ff9900;
font-weight: bold;
}
.description {
margin-bottom: 30px;
font-size: 14px;
line-height: 1.5;
}
.instructions {
margin-top: 10px;
color: #565959;
font-size: 13px;
line-height: 1.45;
}
.aplus-content {
margin-top: 30px;
border-top: 1px solid #ddd;
padding-top: 20px;
}
.aplus-content h4 {
margin-bottom: 15px;
color: #232f3e;
}
.aplus-images {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 15px;
}
.aplus-image {
width: 100%;
/* Maintain perfect squares without stretching */
aspect-ratio: 1 / 1;
height: auto;
border: 1px solid #ddd;
border-radius: 4px;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
color: #666;
font-size: 12px;
overflow: hidden;
}
.empty-state {
text-align: center;
color: #666;
font-style: italic;
padding: 40px;
}
.status-message {
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
font-size: 14px;
}
.status-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
@media (max-width: 768px) {
.upload-section,
.preview-section,
.product-section {
grid-template-columns: 1fr;
}
.container {
padding: 10px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Amazon PDP Mockup Generator</h1>
<p>Upload your product images and CSV data to create realistic Amazon product page mockups</p>
</div>
<div class="upload-section">
<div class="upload-box" id="imageUpload">
<div class="upload-icon">📸</div>
<div class="upload-text">Drop JPG Images Here</div>
<div class="upload-subtext">or click to browse files</div>
<input type="file" class="file-input" id="imageInput" multiple accept="image/jpeg,image/jpg,image/png">
</div>
<div class="upload-box" id="csvUpload">
<div class="upload-icon">📊</div>
<div class="upload-text">Upload CSV Data</div>
<div class="upload-subtext">or click to browse file</div>
<input type="file" class="file-input" id="csvInput" accept=".csv">
</div>
</div>
<div id="statusMessage"></div>
<div class="preview-section">
<div class="image-preview">
<h3>Uploaded Images</h3>
<div class="image-grid" id="imageGrid">
<div class="empty-state">No images uploaded yet</div>
</div>
</div>
<div class="csv-preview">
<h3>CSV Data Preview</h3>
<div class="csv-data" id="csvData">
<div class="empty-state">No CSV data loaded yet</div>
</div>
</div>
</div>
<div class="mockup-container">
<div class="mockup-header">
<h3>Amazon Product Page Mockup</h3>
<div style="display:flex; gap:10px; align-items:center;">
<label style="display:flex; align-items:center; gap:6px; font-size:14px; color:#232f3e;">
<span>EAN</span>
<input id="eanInput" type="text" inputmode="numeric" pattern="\\d{13}" maxlength="13" placeholder="13-digit EAN" style="height:30px; padding:4px 8px; border:1px solid #ddd; border-radius:4px; min-width:180px;" />
</label>
<label style="display:flex; align-items:center; gap:6px; font-size:14px; color:#232f3e;">
<input type="checkbox" id="pinHeroToggle"> Pin hero
</label>
<button class="export-btn" id="downloadAssetsBtn" disabled>Download assets (ZIP)</button>
<button class="export-btn" id="exportBtn" disabled>Export as PNG</button>
</div>
</div>
<div class="amazon-mockup" id="amazonMockup">
<div class="amazon-header">
<h2>Amazon.com</h2>
</div>
<div class="product-section">
<div class="product-images">
<div class="hero-image" id="heroImage" onclick="document.getElementById('imageInput').click()">
Drop your main product image here
</div>
<div class="thumbnail-images" id="thumbnailImages">
<!-- Thumbnails will be added here -->
</div>
</div>
<div class="product-info">
<div class="brand-name" id="brandName">Brand Name</div>
<div class="product-title" id="productTitle">Product Title</div>
<div class="subtitle" id="subtitle" style="display:none;"></div>
<div class="rating">
<span class="stars">★★★★★</span>
<span>(4.5) 1,234 ratings</span>
</div>
<div class="price">$29.99</div>
<div class="bullets" id="bullets">
<div class="bullet">Key feature 1</div>
<div class="bullet">Key feature 2</div>
<div class="bullet">Key feature 3</div>
</div>
<div class="description" id="description">
Product description will appear here...
</div>
<div class="instructions" id="instructions" style="display:none;"></div>
</div>
</div>
<div class="aplus-content">
<h4>From the brand</h4>
<div class="aplus-images" id="aplusImages">
<div class="aplus-image">A+ Content Image 1</div>
<div class="aplus-image">A+ Content Image 2</div>
<div class="aplus-image">A+ Content Image 3</div>
</div>
</div>
</div>
</div>
</div>
<script>
class AmazonMockupGenerator {
constructor() {
this.uploadedImages = [];
this.csvData = {};
this.selectedHeroImage = null;
this.pinHero = false;
this.detectedEAN = '';
this.initializeEventListeners();
}
initializeEventListeners() {
// Image upload
const imageUpload = document.getElementById('imageUpload');
const imageInput = document.getElementById('imageInput');
imageUpload.addEventListener('click', () => imageInput.click());
imageUpload.addEventListener('dragover', this.handleDragOver.bind(this));
imageUpload.addEventListener('dragleave', this.handleDragLeave.bind(this));
imageUpload.addEventListener('drop', this.handleImageDrop.bind(this));
imageInput.addEventListener('change', this.handleImageInput.bind(this));
// CSV upload
const csvUpload = document.getElementById('csvUpload');
const csvInput = document.getElementById('csvInput');
csvUpload.addEventListener('click', () => csvInput.click());
csvUpload.addEventListener('dragover', this.handleDragOver.bind(this));
csvUpload.addEventListener('dragleave', this.handleDragLeave.bind(this));
csvUpload.addEventListener('drop', this.handleCsvDrop.bind(this));
csvInput.addEventListener('change', this.handleCsvInput.bind(this));
// Export button
document.getElementById('exportBtn').addEventListener('click', this.exportMockup.bind(this));
// Download assets button
document.getElementById('downloadAssetsBtn').addEventListener('click', this.downloadAssetsZip.bind(this));
// Pin hero toggle
document.getElementById('pinHeroToggle').addEventListener('change', (e) => {
this.pinHero = e.target.checked;
});
// Manual EAN input
document.getElementById('eanInput').addEventListener('input', (e) => {
// Keep only digits, then left-pad with zeros to 13 (but don't exceed 13)
let digits = (e.target.value || '').replace(/\D/g, '');
if (digits.length > 13) digits = digits.slice(0, 13);
const padded = digits.padStart(13, '0');
this.detectedEAN = padded;
e.target.value = padded;
});
}
handleDragOver(e) {
e.preventDefault();
e.currentTarget.classList.add('dragover');
}
handleDragLeave(e) {
e.preventDefault();
e.currentTarget.classList.remove('dragover');
}
handleImageDrop(e) {
e.preventDefault();
e.currentTarget.classList.remove('dragover');
const files = Array.from(e.dataTransfer.files).filter(file =>
file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/png'
);
this.processImages(files);
}
handleImageInput(e) {
const files = Array.from(e.target.files);
this.processImages(files);
}
handleCsvDrop(e) {
e.preventDefault();
e.currentTarget.classList.remove('dragover');
const files = Array.from(e.dataTransfer.files).filter(file =>
file.name.toLowerCase().endsWith('.csv')
);
if (files.length > 0) {
this.processCsv(files[0]);
}
}
handleCsvInput(e) {
if (e.target.files.length > 0) {
this.processCsv(e.target.files[0]);
}
}
processImages(files) {
files.forEach(file => {
if (file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/png') {
const reader = new FileReader();
reader.onload = (e) => {
const imageData = {
name: file.name,
data: e.target.result,
file: file
};
this.uploadedImages.push(imageData);
// Try detect EAN from filename (13 or 8 consecutive digits)
this.tryDetectEanFromName(file.name);
// Ensure first image becomes hero automatically
if (this.selectedHeroImage === null && this.uploadedImages.length > 0) {
this.selectedHeroImage = 0;
}
this.updateImagePreview();
this.updateMockup();
};
reader.readAsDataURL(file);
}
});
this.showStatus(`Added ${files.length} image(s)`, 'success');
}
tryDetectEanFromName(filename) {
const nameOnly = filename.split('/').pop().split('\\').pop();
const match = nameOnly.match(/(\d{13}|\d{8})/);
if (match && !this.detectedEAN) {
this.detectedEAN = match[1];
const eanInput = document.getElementById('eanInput');
if (eanInput && !eanInput.value) eanInput.value = this.detectedEAN;
}
}
processCsv(file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
const csvText = e.target.result;
this.csvData = this.parseCsv(csvText);
this.updateCsvPreview();
this.updateMockup();
this.showStatus('CSV data loaded successfully', 'success');
} catch (error) {
this.showStatus('Error parsing CSV: ' + error.message, 'error');
}
};
reader.readAsText(file);
}
parseCsv(csvText) {
// Robust CSV parser for Salsify-style: field,length,value (value may contain commas/quotes)
const lines = csvText.split(/\r?\n/);
const data = {};
for (const rawLine of lines) {
const line = rawLine.trim();
if (!line) continue;
const firstComma = line.indexOf(',');
if (firstComma === -1) continue;
const secondComma = line.indexOf(',', firstComma + 1);
if (secondComma === -1) continue;
const field = line.slice(0, firstComma).trim();
let pos = secondComma + 1; // start of value
let value = '';
if (line[pos] === '"') {
// Quoted value; find matching closing quote accounting for escaped quotes
pos += 1; // skip opening quote
let i = pos;
let closedAt = -1;
while (i < line.length) {
if (line[i] === '"') {
if (line[i + 1] === '"') { // escaped quote
i += 2;
continue;
} else {
closedAt = i;
break;
}
}
i++;
}
if (closedAt !== -1) {
value = line.slice(pos, closedAt);
} else {
value = line.slice(pos); // fallback
}
// Unescape double quotes within quoted CSV
value = value.replace(/""/g, '"');
} else {
// Unquoted value: read until next comma (3rd column only)
const thirdComma = line.indexOf(',', pos);
if (thirdComma === -1) {
value = line.slice(pos);
} else {
value = line.slice(pos, thirdComma);
}
}
value = value.trim();
if (field) data[field] = value;
}
return data;
}
updateImagePreview() {
const imageGrid = document.getElementById('imageGrid');
if (this.uploadedImages.length === 0) {
imageGrid.innerHTML = '<div class="empty-state">No images uploaded yet</div>';
return;
}
imageGrid.innerHTML = '';
this.uploadedImages.forEach((image, index) => {
const thumb = document.createElement('img');
thumb.src = image.data;
thumb.className = 'image-thumb';
thumb.title = image.name;
// Clicking a thumbnail promotes it to hero (moves to index 0)
thumb.addEventListener('click', () => this.promoteImageToHero(index));
// Enable drag & drop reordering
thumb.draggable = true;
thumb.addEventListener('dragstart', (ev) => this.handleThumbDragStart(ev, index));
thumb.addEventListener('dragover', (ev) => this.handleThumbDragOver(ev));
thumb.addEventListener('drop', (ev) => this.handleThumbDrop(ev, index));
if (this.selectedHeroImage === index) {
thumb.classList.add('selected');
}
imageGrid.appendChild(thumb);
});
}
// Clicking a thumbnail moves it to first position and sets as hero
promoteImageToHero(index) {
if (index > 0 && index < this.uploadedImages.length) {
const [moved] = this.uploadedImages.splice(index, 1);
this.uploadedImages.unshift(moved);
}
this.selectedHeroImage = 0;
this.updateImagePreview();
this.updateMockup();
}
handleThumbDragStart(event, index) {
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', String(index));
}
handleThumbDragOver(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}
handleThumbDrop(event, targetIndex) {
event.preventDefault();
const sourceIndexStr = event.dataTransfer.getData('text/plain');
const sourceIndex = parseInt(sourceIndexStr, 10);
if (Number.isNaN(sourceIndex) || sourceIndex === targetIndex) return;
// Reorder the array
const [moved] = this.uploadedImages.splice(sourceIndex, 1);
this.uploadedImages.splice(targetIndex, 0, moved);
// First image is always hero
this.selectedHeroImage = 0;
this.updateImagePreview();
this.updateMockup();
}
updateCsvPreview() {
const csvData = document.getElementById('csvData');
if (Object.keys(this.csvData).length === 0) {
csvData.innerHTML = '<div class="empty-state">No CSV data loaded yet</div>';
return;
}
let html = '';
Object.entries(this.csvData).forEach(([field, content]) => {
html += `<div><strong>${field}:</strong> ${content}</div>`;
});
csvData.innerHTML = html;
}
updateMockup() {
// Update brand name (prefer DMI_Product_type as Brand, fallback to brand_name)
const brand = this.csvData['DMI_Product_type'] || this.csvData.brand_name;
if (brand) {
document.getElementById('brandName').textContent = brand;
}
// Update product title (prefer Amazon - Product Name (Long))
const title = this.csvData['Amazon - Product Name (Long)'] || this.csvData.product_title;
if (title) {
document.getElementById('productTitle').textContent = title;
}
// Update bullets (prefer Amazon - Bullet Features 1..5)
const bulletsContainer = document.getElementById('bullets');
bulletsContainer.innerHTML = '';
const bulletFields = [
'Amazon - Bullet Features 1',
'Amazon - Bullet Features 2',
'Amazon - Bullet Features 3',
'Amazon - Bullet Features 4',
'Amazon - Bullet Features 5'
];
let hasBullets = false;
for (const key of bulletFields) {
const val = this.csvData[key];
if (val) {
const bullet = document.createElement('div');
bullet.className = 'bullet';
bullet.textContent = val;
bulletsContainer.appendChild(bullet);
hasBullets = true;
}
}
if (!hasBullets) {
for (let i = 1; i <= 5; i++) {
const bulletKey = `bullet_${i}`;
if (this.csvData[bulletKey]) {
const bullet = document.createElement('div');
bullet.className = 'bullet';
bullet.textContent = this.csvData[bulletKey];
bulletsContainer.appendChild(bullet);
}
}
}
// Update subtitle (Amazon Benefits)
const subtitleEl = document.getElementById('subtitle');
const benefits = this.csvData['Amazon Benefits'];
if (benefits) {
subtitleEl.style.display = '';
subtitleEl.textContent = benefits;
} else {
subtitleEl.style.display = 'none';
subtitleEl.textContent = '';
}
// Update description (prefer amazon_product_description_short)
const desc = this.csvData['amazon_product_description_short'] || this.csvData.description;
if (desc) {
document.getElementById('description').textContent = desc;
} else {
document.getElementById('description').textContent = 'Product description will appear here...';
}
// Update instructions (amazon_instruction_of_use)
const instructionsEl = document.getElementById('instructions');
const iou = this.csvData['amazon_instruction_of_use'];
if (iou) {
instructionsEl.style.display = '';
instructionsEl.textContent = iou;
} else {
instructionsEl.style.display = 'none';
instructionsEl.textContent = '';
}
// Update hero image
const heroImage = document.getElementById('heroImage');
if (this.selectedHeroImage !== null && this.uploadedImages[this.selectedHeroImage]) {
heroImage.innerHTML = '';
const img = document.createElement('img');
img.src = this.uploadedImages[this.selectedHeroImage].data;
img.style.width = '100%';
img.style.height = '100%';
img.style.objectFit = 'cover';
heroImage.appendChild(img);
} else {
heroImage.innerHTML = 'Drop your main product image here';
}
// Make hero image clickable to select from uploaded images
heroImage.onclick = () => {
if (this.uploadedImages.length > 0) {
// Cycle through images or show selection
const nextIndex = this.selectedHeroImage === null ? 0 :
(this.selectedHeroImage + 1) % this.uploadedImages.length;
this.selectHeroImage(nextIndex);
} else {
document.getElementById('imageInput').click();
}
};
// Update thumbnails (follow order; first is hero)
const thumbnailImages = document.getElementById('thumbnailImages');
thumbnailImages.innerHTML = '';
this.uploadedImages.forEach((image, index) => {
if (index === 0) return; // Skip hero
const thumb = document.createElement('img');
thumb.src = image.data;
thumb.className = 'thumbnail';
// Clicking a thumbnail promotes it to hero (moves to first)
thumb.addEventListener('click', () => this.promoteImageToHero(index));
thumbnailImages.appendChild(thumb);
});
// Update A+ content images (follow order; exclude hero)
const aplusImages = document.getElementById('aplusImages');
aplusImages.innerHTML = '';
this.uploadedImages.forEach((image, index) => {
if (index === 0) return; // Skip hero
const aplusImg = document.createElement('div');
aplusImg.className = 'aplus-image';
const img = document.createElement('img');
img.src = image.data;
img.style.width = '100%';
img.style.height = '100%';
img.style.objectFit = 'contain'; // avoid cropping, keep full image visible
img.style.background = '#ffffff';
aplusImg.appendChild(img);
aplusImages.appendChild(aplusImg);
});
// Enable export button if we have content
const exportBtn = document.getElementById('exportBtn');
exportBtn.disabled = this.uploadedImages.length === 0 && Object.keys(this.csvData).length === 0;
// Enable download assets button if we have images
const downloadBtn = document.getElementById('downloadAssetsBtn');
downloadBtn.disabled = this.uploadedImages.length === 0;
}
showStatus(message, type) {
const statusDiv = document.getElementById('statusMessage');
statusDiv.innerHTML = `<div class="status-message status-${type}">${message}</div>`;
setTimeout(() => {
statusDiv.innerHTML = '';
}, 3000);
}
async exportMockup() {
const mockup = document.getElementById('amazonMockup');
const exportBtn = document.getElementById('exportBtn');
exportBtn.disabled = true;
exportBtn.textContent = 'Exporting...';
try {
// Create a canvas to render the mockup
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set canvas size
canvas.width = mockup.offsetWidth;
canvas.height = mockup.offsetHeight;
// Use html2canvas if available, otherwise use a simple method
if (typeof html2canvas !== 'undefined') {
const canvas = await html2canvas(mockup, {
backgroundColor: '#ffffff',
scale: 2
});
// Build filename: EAN_ATF.jpg (fallbacks to ATF.jpg)
const ean = (this.detectedEAN || document.getElementById('eanInput').value || '').trim();
const fileName = `${ean ? ean + '_' : ''}ATF_AMZ.jpg`;
// Convert to JPEG for download
const dataUrl = canvas.toDataURL('image/jpeg', 0.92);
const link = document.createElement('a');
link.download = fileName;
link.href = dataUrl;
link.click();
} else {
// Fallback: create a simple export
this.showStatus('Export functionality requires html2canvas library', 'error');
}
} catch (error) {
this.showStatus('Export failed: ' + error.message, 'error');
} finally {
exportBtn.disabled = false;
exportBtn.textContent = 'Export as PNG';
}
}
// Build a ZIP of the original files in the current order, renamed with _AMZ_p{n}
async downloadAssetsZip() {
try {
if (!this.uploadedImages.length) return;
if (typeof JSZip === 'undefined') {
this.showStatus('ZIP download requires JSZip library', 'error');
return;
}
const zip = new JSZip();
const folder = zip.folder('assets');
// Current order: hero first (index 0), then rest
for (let i = 0; i < this.uploadedImages.length; i++) {
const img = this.uploadedImages[i];
const pos = i + 1; // p1-based index
const ext = (img.file && img.file.name.split('.').pop()) || 'jpg';
const base = (img.file && img.file.name.replace(/\.[^.]+$/, '')) || `image_${pos}`;
const newName = `XXX_${base}_AMZ_p${pos}.${ext}`;
// Use original file bytes if available; otherwise convert dataURL
let blob;
if (img.file instanceof File || img.file instanceof Blob) {
blob = img.file;
} else {
blob = await (await fetch(img.data)).blob();
}
folder.file(newName, blob);
}
const content = await zip.generateAsync({ type: 'blob' });
const link = document.createElement('a');
link.href = URL.createObjectURL(content);
const eanVal = (this.detectedEAN || document.getElementById('eanInput').value || '').trim();
link.download = `${eanVal ? eanVal + '_' : ''}AMZ.zip`;
link.click();
URL.revokeObjectURL(link.href);
} catch (err) {
this.showStatus('Failed to build ZIP: ' + err.message, 'error');
}
}
}
// Initialize the app
document.addEventListener('DOMContentLoaded', () => {
new AmazonMockupGenerator();
});
</script>
<!-- Include html2canvas for export functionality -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<!-- Include JSZip for downloading renamed assets as a ZIP -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
</body>
</html>