loreal_ecf/ATF_brand.html

292 lines
23 KiB
HTML
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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 Brand Layout</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; }
.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-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, .csv-preview { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.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-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; gap: 10px; flex-wrap: wrap; }
.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:disabled { background: #ccc; cursor: not-allowed; }
.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; }
.product-section { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 30px; }
.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; }
.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; }
.brand-name { color: #0066c0; font-size: 14px; margin-bottom: 5px; }
.product-title { font-size: 20px; font-weight: 400; line-height: 1.3; margin-bottom: 8px; color: #0f1111; }
.subtitle { font-size: 14px; color: #565959; margin-bottom: 12px; }
.bullets { margin-bottom: 20px; }
.bullet { margin-bottom: 8px; font-size: 14px; line-height: 1.4; }
.bullet::before { content: "• "; color: #ff9900; font-weight: bold; }
.description { 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-controls { display:flex; gap:10px; align-items:center; margin-bottom:10px; }
.aplus-images { display: grid; gap: 15px; }
.aplus-images.standard { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
.aplus-images.large { grid-template-columns: repeat(3, 1fr); }
.aplus-image { width: 100%; aspect-ratio: 1/1; height: auto; border: 1px solid #ddd; border-radius: 4px; background: #ffffff; display: flex; align-items: center; justify-content: center; position: relative; overflow: hidden; }
.aplus-image img { width: 100%; height: 100%; object-fit: contain; background: #ffffff; }
.order-badge { position: absolute; top: 6px; left: 6px; background: #232f3e; color: #fff; font-size: 12px; padding: 2px 6px; border-radius: 12px; font-weight: 600; }
.toolbar { display:flex; gap:10px; align-items:center; }
@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 — Brand Layout</h1>
<p>Variant with A+ layout selector and order badges (self-contained)</p>
</div>
<div class="upload-section">
<div class="upload-box" id="imageUpload">
<div class="upload-icon">📸</div>
<div class="upload-text">Drop JPG/PNG 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 class="toolbar">
<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>
<label style="display:flex; align-items:center; gap:6px; font-size:14px; color:#232f3e;">
<span>A+ layout</span>
<select id="aplusLayoutSelect" style="height:30px; padding:4px 8px; border:1px solid #ddd; border-radius:4px;">
<option value="standard">Standard (auto-fit)</option>
<option value="large">Large 3-up</option>
</select>
</label>
<button class="export-btn" id="downloadAssetsBtn" disabled>Download assets (ZIP)</button>
<button class="export-btn" id="exportBtn" disabled>Export as JPG</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">Drop your main product image here</div>
<div class="thumbnail-images" id="thumbnailImages"></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="bullets" id="bullets"></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">
<div class="aplus-controls"></div>
<div class="aplus-images standard" id="aplusImages"></div>
</div>
</div>
</div>
</div>
<script>
class AmazonMockupGenerator {
constructor() {
this.uploadedImages = [];
this.csvData = {};
this.selectedHeroImage = null;
this.pinHero = false;
this.detectedEAN = '';
this.aplusLayout = 'standard';
this.initializeEventListeners();
}
initializeEventListeners() {
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));
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));
document.getElementById('exportBtn').addEventListener('click', this.exportMockup.bind(this));
document.getElementById('downloadAssetsBtn').addEventListener('click', this.downloadAssetsZip.bind(this));
document.getElementById('pinHeroToggle').addEventListener('change', (e) => { this.pinHero = e.target.checked; });
document.getElementById('eanInput').addEventListener('input', (e) => {
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;
});
document.getElementById('aplusLayoutSelect').addEventListener('change', (e) => {
this.aplusLayout = e.target.value; this.updateMockup();
});
document.getElementById('heroImage').onclick = () => document.getElementById('imageInput').click();
}
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(f => ['image/jpeg','image/jpg','image/png'].includes(f.type));
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(f => f.name.toLowerCase().endsWith('.csv'));
if (files.length) this.processCsv(files[0]);
}
handleCsvInput(e){ if (e.target.files.length) this.processCsv(e.target.files[0]); }
processImages(files){
files.forEach(file => {
const ok = ['image/jpeg','image/jpg','image/png'].includes(file.type);
if (!ok) return;
const reader = new FileReader();
reader.onload = (ev) => {
const imageData = { name: file.name, data: ev.target.result, file };
this.uploadedImages.push(imageData);
this.tryDetectEanFromName(file.name);
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 m = nameOnly.match(/(\d{13}|\d{8})/); if (m && !this.detectedEAN){ this.detectedEAN = m[1]; const inp = document.getElementById('eanInput'); if (inp && !inp.value) inp.value = this.detectedEAN; } }
processCsv(file){ const reader = new FileReader(); reader.onload = (e)=>{ try { const txt = e.target.result; this.csvData = this.parseCsv(txt); this.updateCsvPreview(); this.updateMockup(); this.showStatus('CSV data loaded successfully','success'); } catch(err){ this.showStatus('Error parsing CSV: '+err.message,'error'); } }; reader.readAsText(file); }
parseCsv(csvText){
const lines = csvText.split(/\r?\n/); const data = {};
for (const raw of lines){ const line = raw.trim(); if (!line) continue;
const first = line.indexOf(','); if (first === -1) continue;
const second = line.indexOf(',', first+1); if (second === -1) continue;
const field = line.slice(0, first).trim(); let pos = second+1; let value='';
if (line[pos] === '"'){ pos++; let i=pos, end=-1; while(i<line.length){ if(line[i]==='"'){ if(line[i+1]==='"'){ i+=2; continue;} else { end=i; break; } } i++; } value = end!==-1? line.slice(pos,end): line.slice(pos); value = value.replace(/""/g,'"'); }
else { const third = line.indexOf(',', pos); value = third===-1? line.slice(pos): line.slice(pos, third); }
value = value.trim(); if (field) data[field]=value; }
return data;
}
updateImagePreview(){ const grid = document.getElementById('imageGrid'); if (!this.uploadedImages.length){ grid.innerHTML = '<div class="empty-state">No images uploaded yet</div>'; return; } grid.innerHTML=''; this.uploadedImages.forEach((img, idx)=>{ const el=document.createElement('img'); el.src=img.data; el.className='image-thumb'; el.title=img.name; el.addEventListener('click', ()=> this.promoteImageToHero(idx)); el.draggable=true; el.addEventListener('dragstart',(ev)=>this.handleThumbDragStart(ev,idx)); el.addEventListener('dragover',(ev)=>this.handleThumbDragOver(ev)); el.addEventListener('drop',(ev)=>this.handleThumbDrop(ev,idx)); if (this.selectedHeroImage===idx) el.classList.add('selected'); grid.appendChild(el); }); }
promoteImageToHero(index){ if (index>0 && index<this.uploadedImages.length){ if (!this.pinHero){ const [moved]=this.uploadedImages.splice(index,1); this.uploadedImages.unshift(moved); } } this.selectedHeroImage=0; this.updateImagePreview(); this.updateMockup(); }
handleThumbDragStart(ev,idx){ ev.dataTransfer.effectAllowed='move'; ev.dataTransfer.setData('text/plain', String(idx)); }
handleThumbDragOver(ev){ ev.preventDefault(); ev.dataTransfer.dropEffect='move'; }
handleThumbDrop(ev,targetIdx){ ev.preventDefault(); const s=ev.dataTransfer.getData('text/plain'); const src=parseInt(s,10); if (Number.isNaN(src)||src===targetIdx) return; const [moved]=this.uploadedImages.splice(src,1); this.uploadedImages.splice(targetIdx,0,moved); this.selectedHeroImage=0; this.updateImagePreview(); this.updateMockup(); }
updateCsvPreview(){ const box=document.getElementById('csvData'); if (!Object.keys(this.csvData).length){ box.innerHTML='<div class="empty-state">No CSV data loaded yet</div>'; return;} let html=''; Object.entries(this.csvData).forEach(([k,v])=>{ html += `<div><strong>${k}:</strong> ${v}</div>`; }); box.innerHTML=html; }
updateMockup(){
// Brand
const brand = this.csvData['DMI_Product_type'] || this.csvData.brand_name; if (brand) document.getElementById('brandName').textContent = brand;
// Title
const title = this.csvData['Amazon - Product Name (Long)'] || this.csvData.product_title; if (title) document.getElementById('productTitle').textContent = title;
// Bullets
const bulletsBox = document.getElementById('bullets'); bulletsBox.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 b=document.createElement('div'); b.className='bullet'; b.textContent=val; bulletsBox.appendChild(b); hasBullets=true; } }
if (!hasBullets){ for (let i=1;i<=5;i++){ const k=`bullet_${i}`; if (this.csvData[k]){ const b=document.createElement('div'); b.className='bullet'; b.textContent=this.csvData[k]; bulletsBox.appendChild(b);} } }
// Subtitle
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=''; }
// Description
const desc=this.csvData['amazon_product_description_short'] || this.csvData.description; document.getElementById('description').textContent = desc || 'Product description will appear here...';
// Instructions
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=''; }
// Hero
const heroBox=document.getElementById('heroImage'); if (this.selectedHeroImage!==null && this.uploadedImages[this.selectedHeroImage]){ heroBox.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'; heroBox.appendChild(img);} else { heroBox.innerHTML='Drop your main product image here'; }
// Thumbnails
const thumbs=document.getElementById('thumbnailImages'); thumbs.innerHTML=''; this.uploadedImages.forEach((image, index)=>{ if (index===0) return; const t=document.createElement('img'); t.src=image.data; t.className='thumbnail'; t.addEventListener('click',()=>this.promoteImageToHero(index)); thumbs.appendChild(t); });
// A+ images (include hero as p1, then all others) with badges
const aplus=document.getElementById('aplusImages'); aplus.className = `aplus-images ${this.aplusLayout}`; aplus.innerHTML='';
this.uploadedImages.forEach((image,index)=>{ const tile=document.createElement('div'); tile.className='aplus-image'; const img=document.createElement('img'); img.src=image.data; tile.appendChild(img); const pos = index+1; const badge=document.createElement('span'); badge.className='order-badge'; badge.textContent = `p${pos}`; tile.appendChild(badge); aplus.appendChild(tile); });
// Enable buttons
document.getElementById('exportBtn').disabled = this.uploadedImages.length===0 && Object.keys(this.csvData).length===0;
document.getElementById('downloadAssetsBtn').disabled = this.uploadedImages.length===0;
}
showStatus(msg,type){ const div=document.getElementById('statusMessage'); div.innerHTML=`<div class="status-message status-${type}">${msg}</div>`; setTimeout(()=>{ div.innerHTML=''; },3000); }
async exportMockup(){ const mockup=document.getElementById('amazonMockup'); const btn=document.getElementById('exportBtn'); btn.disabled=true; btn.textContent='Exporting...'; try{
if (typeof html2canvas !== 'undefined'){
const canvas = await html2canvas(mockup, { backgroundColor:'#ffffff', scale:2 });
const ean = (this.detectedEAN || document.getElementById('eanInput').value || '').trim();
const fileName = `${ean ? ean + '_' : ''}ATF_AMZ.jpg`;
const dataUrl = canvas.toDataURL('image/jpeg', 0.92);
const link=document.createElement('a'); link.download=fileName; link.href=dataUrl; link.click();
} else { this.showStatus('Export requires html2canvas','error'); }
} catch(err){ this.showStatus('Export failed: '+err.message,'error'); } finally { btn.disabled=false; btn.textContent='Export as JPG'; } }
async downloadAssetsZip(){ try{
if (!this.uploadedImages.length) return; if (typeof JSZip==='undefined'){ this.showStatus('ZIP download requires JSZip','error'); return; }
const zip = new JSZip(); const folder = zip.folder('assets');
for (let i=0;i<this.uploadedImages.length;i++){ const img=this.uploadedImages[i]; const pos=i+1; 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}`; 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'); } }
}
// Boot
document.addEventListener('DOMContentLoaded', ()=>{ new AmazonMockupGenerator(); });
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
</body>
</html>