link2flyer-si / index.html
SpectrumInfinite's picture
Add 2 files
0172607 verified
Raw History Blame Contribute Delete
50 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FlyerForge - Create Beautiful Product Flyers</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.color-swatch {
width: 30px;
height: 30px;
border-radius: 50%;
cursor: pointer;
transition: transform 0.2s;
}
.color-swatch:hover {
transform: scale(1.1);
}
.color-swatch.selected {
border: 3px solid #333;
transform: scale(1.1);
}
.product-placeholder {
background-color: #f3f4f6;
border: 2px dashed #d1d5db;
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
min-height: 150px;
}
.flyer-preview {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
border: 1px solid #e5e7eb;
min-height: 600px;
}
.tab-button {
position: relative;
padding: 0.75rem 1.5rem;
font-weight: 500;
color: #6b7280;
}
.tab-button.active {
color: #3b82f6;
}
.tab-button.active:after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
right: 0;
height: 2px;
background-color: #3b82f6;
}
.slide-enter-active, .slide-leave-active {
transition: all 0.3s ease;
}
.slide-enter-from, .slide-leave-to {
opacity: 0;
transform: translateX(20px);
}
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.flyer-header, .flyer-footer {
background-color: var(--theme-color, #3b82f6);
color: white;
}
.product-card {
transition: all 0.3s ease;
}
.product-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.status-badge {
position: absolute;
top: 0.5rem;
right: 0.5rem;
z-index: 10;
}
.editor-toolbar {
position: absolute;
top: -10px;
left: 50%;
transform: translateX(-50%);
background: white;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
display: flex;
gap: 0.25rem;
z-index: 20;
}
.editable {
cursor: text;
outline: 1px dashed rgba(59, 130, 246, 0.3);
padding: 2px;
min-height: 1em;
}
.editable:hover {
outline: 1px dashed rgba(59, 130, 246, 0.7);
background-color: rgba(59, 130, 246, 0.05);
}
.editable-image {
position: relative;
cursor: pointer;
}
.editable-image:hover::after {
content: 'Click to change';
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
color: white;
text-align: center;
padding: 2px;
font-size: 12px;
}
.company-name {
font-size: 1.5rem;
font-weight: bold;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-6xl" id="app">
<!-- Header -->
<header class="flex justify-between items-center mb-8">
<div class="flex items-center">
<i class="fas fa-bullhorn text-blue-500 text-2xl mr-2"></i>
<h1 class="text-2xl font-bold text-gray-800">FlyerForge</h1>
</div>
<div class="flex items-center space-x-4">
<div class="text-sm text-gray-600">
<span id="product-count">0</span>/<span id="max-products">5</span> products
</div>
<button class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition">
Upgrade Plan
</button>
</div>
</header>
<!-- Progress Steps -->
<div class="flex justify-center mb-8">
<div class="flex space-x-8">
<div class="tab-button active" data-step="1">
<span class="flex items-center justify-center w-8 h-8 bg-blue-500 text-white rounded-full mr-2">1</span>
Add Products
</div>
<div class="tab-button" data-step="2">
<span class="flex items-center justify-center w-8 h-8 bg-gray-200 text-gray-600 rounded-full mr-2">2</span>
Branding & Preview
</div>
<div class="tab-button" data-step="3">
<span class="flex items-center justify-center w-8 h-8 bg-gray-200 text-gray-600 rounded-full mr-2">3</span>
Final Preview & Export
</div>
</div>
</div>
<!-- Step 1: Add Products -->
<div id="step-1" class="step-content">
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold mb-4">Add Product URLs</h2>
<p class="text-gray-600 mb-6">You can share up to <span class="font-medium">5</span> product links</p>
<div id="url-inputs" class="space-y-4">
<!-- URL inputs will be added here -->
</div>
<div class="mt-6 flex justify-end">
<button id="next-step-1" class="px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition disabled:opacity-50 disabled:cursor-not-allowed" disabled>
Next <i class="fas fa-arrow-right ml-2"></i>
</button>
</div>
</div>
</div>
<!-- Step 2: Branding & Preview -->
<div id="step-2" class="step-content hidden">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Left Column: Branding Form -->
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold mb-4">Brand Your Flyer</h2>
<!-- Company Name -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Company Name</label>
<input type="text" id="company-name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Your Company Name">
</div>
<!-- Logo Upload -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Company Logo</label>
<div class="flex items-center">
<div id="logo-preview" class="w-16 h-16 bg-gray-100 rounded-md flex items-center justify-center overflow-hidden mr-4">
<i class="fas fa-image text-gray-400 text-xl"></i>
</div>
<div>
<input type="file" id="logo-upload" class="hidden" accept="image/png,image/jpeg,image/svg+xml">
<button id="upload-logo-btn" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition">
Upload Logo
</button>
<button id="remove-logo-btn" class="px-4 py-2 bg-red-100 text-red-600 rounded-md hover:bg-red-200 transition ml-2 hidden">
Remove
</button>
<p class="text-xs text-gray-500 mt-1">SVG, PNG or JPG (max 1MB)</p>
</div>
</div>
</div>
<!-- Contact Information -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Contact Information</label>
<div class="space-y-3">
<div>
<input type="text" id="contact-phone" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Phone Number">
</div>
<div>
<input type="email" id="contact-email" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Email Address">
</div>
<div>
<input type="text" id="contact-address" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Physical Address (optional)">
</div>
<div>
<input type="url" id="contact-website" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Website URL">
</div>
</div>
</div>
<!-- Contact Position -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Contact Position</label>
<div class="flex space-x-4">
<label class="inline-flex items-center">
<input type="radio" name="contact-position" value="header" class="h-4 w-4 text-blue-600 focus:ring-blue-500" checked>
<span class="ml-2 text-gray-700">Header</span>
</label>
<label class="inline-flex items-center">
<input type="radio" name="contact-position" value="footer" class="h-4 w-4 text-blue-600 focus:ring-blue-500">
<span class="ml-2 text-gray-700">Footer</span>
</label>
<label class="inline-flex items-center">
<input type="radio" name="contact-position" value="both" class="h-4 w-4 text-blue-600 focus:ring-blue-500">
<span class="ml-2 text-gray-700">Both</span>
</label>
</div>
</div>
<!-- Color Theme -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Color Theme</label>
<div class="flex items-center space-x-2 mb-3">
<div class="color-swatch bg-blue-500 selected" data-color="#3b82f6"></div>
<div class="color-swatch bg-purple-500" data-color="#8b5cf6"></div>
<div class="color-swatch bg-green-500" data-color="#10b981"></div>
<div class="color-swatch bg-red-500" data-color="#ef4444"></div>
<div class="color-swatch bg-yellow-500" data-color="#f59e0b"></div>
<div class="color-swatch bg-pink-500" data-color="#ec4899"></div>
</div>
<div class="flex items-center">
<span class="mr-2 text-gray-500">#</span>
<input type="text" id="custom-color" class="w-24 px-2 py-1 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="3b82f6" maxlength="6">
<div id="custom-color-preview" class="ml-2 w-6 h-6 rounded border border-gray-300"></div>
</div>
</div>
</div>
<!-- Right Column: Live Preview -->
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold mb-4">Live Flyer Preview</h2>
<div id="flyer-preview-container" class="flyer-preview bg-white p-4 rounded-md">
<!-- Preview will be rendered here -->
<div id="flyer-preview-loading" class="flex flex-col items-center justify-center h-full">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mb-4"></div>
<p class="text-gray-600">Processing your products...</p>
</div>
</div>
<div class="mt-6 flex justify-between">
<button id="back-step-2" class="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">
<i class="fas fa-arrow-left mr-2"></i> Back
</button>
<div>
<button id="edit-flyer-btn" class="px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition mr-2">
<i class="fas fa-edit mr-2"></i> Edit Flyer
</button>
<button id="next-step-2" class="px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition">
Next <i class="fas fa-arrow-right ml-2"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Step 3: Final Preview & Export -->
<div id="step-3" class="step-content hidden">
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold mb-4">Final Preview & Export</h2>
<div id="final-preview-container" class="flyer-preview bg-white p-4 rounded-md mb-6">
<!-- Final preview will be rendered here -->
</div>
<div class="flex flex-wrap gap-3 mb-6">
<button class="export-option px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition flex items-center">
<i class="fas fa-file-image mr-2"></i> PNG
</button>
<button class="export-option px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition flex items-center">
<i class="fas fa-file-image mr-2"></i> JPG
</button>
<button class="export-option px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition flex items-center">
<i class="fas fa-file-pdf mr-2"></i> PDF
</button>
<button class="export-option px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition flex items-center">
<i class="fas fa-file-code mr-2"></i> HTML
</button>
<button class="export-option px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition flex items-center">
<i class="fas fa-share-alt mr-2"></i> Share Link
</button>
</div>
<div class="flex justify-between">
<button id="back-step-3" class="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">
<i class="fas fa-arrow-left mr-2"></i> Back to Editor
</button>
<button class="px-6 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 transition">
<i class="fas fa-save mr-2"></i> Save Flyer
</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// App state
const state = {
currentStep: 1,
maxProducts: 5,
products: [],
branding: {
companyName: 'Your Company',
logo: null,
contact: {
phone: '',
email: '',
address: '',
website: ''
},
contactPosition: 'header',
themeColor: '#3b82f6'
},
flyerHtml: '',
isEditing: false,
currentlyEditing: null
};
// DOM elements
const stepContents = document.querySelectorAll('.step-content');
const tabButtons = document.querySelectorAll('.tab-button');
const urlInputsContainer = document.getElementById('url-inputs');
const nextStep1Btn = document.getElementById('next-step-1');
const backStep2Btn = document.getElementById('back-step-2');
const nextStep2Btn = document.getElementById('next-step-2');
const backStep3Btn = document.getElementById('back-step-3');
const productCountEl = document.getElementById('product-count');
const maxProductsEl = document.getElementById('max-products');
const logoUploadBtn = document.getElementById('upload-logo-btn');
const logoUploadInput = document.getElementById('logo-upload');
const logoPreview = document.getElementById('logo-preview');
const removeLogoBtn = document.getElementById('remove-logo-btn');
const flyerPreviewContainer = document.getElementById('flyer-preview-container');
const flyerPreviewLoading = document.getElementById('flyer-preview-loading');
const editFlyerBtn = document.getElementById('edit-flyer-btn');
const finalPreviewContainer = document.getElementById('final-preview-container');
const exportOptions = document.querySelectorAll('.export-option');
const colorSwatches = document.querySelectorAll('.color-swatch');
const customColorInput = document.getElementById('custom-color');
const customColorPreview = document.getElementById('custom-color-preview');
const companyNameInput = document.getElementById('company-name');
// Initialize
maxProductsEl.textContent = state.maxProducts;
createUrlInputs();
setupEventListeners();
// Functions
function setupEventListeners() {
// Navigation
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const step = parseInt(button.dataset.step);
goToStep(step);
});
});
nextStep1Btn.addEventListener('click', () => {
collectProductUrls();
goToStep(2);
simulateScraping();
});
backStep2Btn.addEventListener('click', () => goToStep(1));
nextStep2Btn.addEventListener('click', () => {
if (confirm('Proceed to final preview and export?')) {
goToStep(3);
}
});
backStep3Btn.addEventListener('click', () => goToStep(2));
// Logo upload
logoUploadBtn.addEventListener('click', () => logoUploadInput.click());
logoUploadInput.addEventListener('change', handleLogoUpload);
removeLogoBtn.addEventListener('click', removeLogo);
// Branding inputs
companyNameInput.addEventListener('input', updateBranding);
document.getElementById('contact-phone').addEventListener('input', updateBranding);
document.getElementById('contact-email').addEventListener('input', updateBranding);
document.getElementById('contact-address').addEventListener('input', updateBranding);
document.getElementById('contact-website').addEventListener('input', updateBranding);
document.querySelectorAll('input[name="contact-position"]').forEach(radio => {
radio.addEventListener('change', updateBranding);
});
// Color theme
colorSwatches.forEach(swatch => {
swatch.addEventListener('click', () => {
colorSwatches.forEach(s => s.classList.remove('selected'));
swatch.classList.add('selected');
const color = swatch.dataset.color;
state.branding.themeColor = color;
customColorInput.value = color.substring(1);
customColorPreview.style.backgroundColor = color;
updateBranding();
});
});
customColorInput.addEventListener('input', () => {
const color = '#' + customColorInput.value;
if (/^#[0-9A-F]{6}$/i.test(color)) {
state.branding.themeColor = color;
customColorPreview.style.backgroundColor = color;
colorSwatches.forEach(s => s.classList.remove('selected'));
updateBranding();
}
});
// Export options
exportOptions.forEach(option => {
option.addEventListener('click', () => {
const format = option.textContent.trim().toLowerCase();
alert(`Exporting as ${format}...`);
});
});
// Edit flyer button
editFlyerBtn.addEventListener('click', toggleEditMode);
}
function createUrlInputs() {
urlInputsContainer.innerHTML = '';
for (let i = 0; i < state.maxProducts; i++) {
const div = document.createElement('div');
div.className = 'flex items-center';
div.innerHTML = `
<div class="product-placeholder w-16 h-16 mr-4">
<i class="fas fa-image text-gray-400 text-xl"></i>
</div>
<div class="flex-1">
<input type="url" class="product-url w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="https://example.com/product">
</div>
`;
urlInputsContainer.appendChild(div);
}
// Add event listeners to URL inputs
document.querySelectorAll('.product-url').forEach(input => {
input.addEventListener('input', validateStep1);
});
}
function validateStep1() {
const urls = Array.from(document.querySelectorAll('.product-url'))
.map(input => input.value.trim())
.filter(url => url !== '');
state.products = urls.map(url => ({ url, status: 'pending' }));
productCountEl.textContent = urls.length;
nextStep1Btn.disabled = urls.length === 0;
}
function collectProductUrls() {
state.products = Array.from(document.querySelectorAll('.product-url'))
.map(input => ({
url: input.value.trim(),
status: 'pending',
productName: '',
productNumber: `SKU-${Math.floor(Math.random() * 10000)}`,
description: '',
price: '',
heroImage: '',
availableColors: ['Red', 'Blue', 'Green'].slice(0, Math.floor(Math.random() * 3) + 1),
pricingTableHTML: Math.random() > 0.5 ? '<table><tr><th>Qty</th><th>Price</th></tr><tr><td>1-9</td><td>$10.99</td></tr><tr><td>10+</td><td>$8.99</td></tr></table>' : null
}))
.filter(product => product.url !== '');
}
function simulateScraping() {
flyerPreviewLoading.classList.remove('hidden');
flyerPreviewContainer.innerHTML = '';
flyerPreviewContainer.appendChild(flyerPreviewLoading);
// Simulate scraping delay
setTimeout(() => {
// Generate mock product data
state.products = state.products.map((product, index) => ({
...product,
status: 'success',
productName: `Premium Product ${index + 1}`,
description: `This is a detailed description for Premium Product ${index + 1}. It features high-quality materials and excellent craftsmanship that customers love.`,
price: {
min: Math.random() * 100 + 10,
currency: 'USD',
minimumQuantity: Math.random() > 0.7 ? Math.floor(Math.random() * 10) + 1 : undefined
},
heroImage: `https://picsum.photos/400/300?random=${index}`,
benefits: [
`Saves time with efficient design`,
`Reduces costs with durable materials`,
`Improves productivity by ${Math.floor(Math.random() * 50) + 10}%`
]
}));
renderFlyerPreview();
}, 2000);
}
function renderFlyerPreview() {
flyerPreviewLoading.classList.add('hidden');
const { companyName, logo, contact, contactPosition, themeColor } = state.branding;
let html = `
<div class="flyer-preview-content" style="--theme-color: ${themeColor}">
${contactPosition === 'header' || contactPosition === 'both' ? renderContactHeader() : ''}
<div class="p-6">
<h1 class="text-3xl font-bold mb-6 text-center" style="color: ${themeColor}">Our Featured Products</h1>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
`;
state.products.forEach((product, index) => {
const priceStr = typeof product.price === 'object'
? `$${product.price.min.toFixed(2)}${product.price.minimumQuantity ? ` (Min. ${product.price.minimumQuantity})` : ''}`
: product.price || 'Price not available';
html += `
<div class="product-card relative border rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-md transition-shadow duration-300">
${renderStatusBadge(product.status)}
${state.isEditing ? `
<div class="editor-toolbar">
<button class="p-1 text-blue-500 hover:text-blue-700" title="Edit Text" data-action="edit-text" data-product="${index}">
<i class="fas fa-font text-xs"></i>
</button>
<button class="p-1 text-blue-500 hover:text-blue-700" title="Replace Image" data-action="replace-image" data-product="${index}">
<i class="fas fa-image text-xs"></i>
</button>
<button class="p-1 text-blue-500 hover:text-blue-700" title="Change Layout" data-action="change-layout" data-product="${index}">
<i class="fas fa-th text-xs"></i>
</button>
</div>
` : ''}
<div class="h-48 bg-gray-100 flex items-center justify-center overflow-hidden relative">
${product.heroImage ?
`<img src="${product.heroImage}" alt="${product.productName}" class="w-full h-full object-cover editable-image" data-product="${index}">` :
`<i class="fas fa-image text-gray-400 text-4xl"></i>`}
</div>
<div class="p-4">
<div class="flex justify-between items-start">
<h3 class="text-xl font-semibold mb-2 editable" data-field="productName" data-product="${index}">${product.productName}</h3>
<span class="text-sm bg-gray-100 px-2 py-1 rounded editable" data-field="productNumber" data-product="${index}">${product.productNumber}</span>
</div>
<p class="text-gray-600 mb-3 editable" data-field="description" data-product="${index}">${product.description}</p>
${product.benefits && product.benefits.length > 0 ? `
<div class="mb-3">
<h4 class="text-sm font-medium mb-1" style="color: ${themeColor}">Key Benefits:</h4>
<ul class="text-sm space-y-1 editable" data-field="benefits" data-product="${index}">
${product.benefits.map(benefit => `<li class="flex items-start">
<i class="fas fa-check-circle text-green-500 mt-1 mr-2 text-xs"></i>
<span>${benefit}</span>
</li>`).join('')}
</ul>
</div>
` : ''}
<div class="font-bold text-lg mb-2 editable" data-field="price" data-product="${index}" style="color: ${themeColor}">${priceStr}</div>
${product.availableColors && product.availableColors.length > 0 ? `
<div class="flex items-center text-sm text-gray-600 editable" data-field="colors" data-product="${index}">
<span class="mr-2">Colors:</span>
<div class="flex space-x-1">
${product.availableColors.map(color => `
<span class="inline-block w-4 h-4 rounded-full border border-gray-300" style="background-color: ${color.toLowerCase()}"></span>
`).join('')}
</div>
</div>
` : ''}
${product.pricingTableHTML ? `
<div class="mt-3 text-xs">
<details>
<summary class="text-blue-500 cursor-pointer">View quantity pricing</summary>
<div class="mt-2 overflow-x-auto">
${product.pricingTableHTML}
</div>
</details>
</div>
` : ''}
</div>
</div>
`;
});
html += `
</div>
</div>
${contactPosition === 'footer' || contactPosition === 'both' ? renderContactFooter() : ''}
</div>
`;
flyerPreviewContainer.innerHTML = html;
if (state.isEditing) {
setupEditor();
}
}
function renderStatusBadge(status) {
const statusMap = {
'pending': { text: 'Pending', color: 'bg-gray-500' },
'scraping': { text: 'Scraping...', color: 'bg-blue-500' },
'success': { text: 'Ready', color: 'bg-green-500' },
'error': { text: 'Error', color: 'bg-red-500' },
'image_upload_required': { text: 'Needs Image', color: 'bg-yellow-500' }
};
const statusInfo = statusMap[status] || statusMap.pending;
return `
<div class="status-badge px-2 py-1 text-xs text-white rounded ${statusInfo.color}">
${statusInfo.text}
</div>
`;
}
function renderContactHeader() {
const { companyName, logo, contact } = state.branding;
return `
<div class="flyer-header p-4 flex justify-between items-center">
<div class="flex items-center">
${logo ?
`<img src="${logo}" alt="Company Logo" class="h-12 editable-image" data-field="logo">` :
`<div class="h-12 w-32 bg-white rounded flex items-center justify-center editable" data-field="logo-placeholder">
<span class="font-bold" style="color: var(--theme-color)">Your Logo</span>
</div>`}
<div class="company-name ml-4 editable" data-field="companyName">${companyName}</div>
</div>
<div class="text-right text-sm">
${contact.phone ? `<div class="editable" data-field="phone"><i class="fas fa-phone mr-1"></i> ${contact.phone}</div>` :
`<div class="editable" data-field="phone-placeholder"><i class="fas fa-phone mr-1"></i> Phone</div>`}
${contact.email ? `<div class="editable" data-field="email"><i class="fas fa-envelope mr-1"></i> ${contact.email}</div>` :
`<div class="editable" data-field="email-placeholder"><i class="fas fa-envelope mr-1"></i> Email</div>`}
${contact.website ? `<div class="editable" data-field="website"><i class="fas fa-globe mr-1"></i> ${contact.website}</div>` :
`<div class="editable" data-field="website-placeholder"><i class="fas fa-globe mr-1"></i> Website</div>`}
</div>
</div>
`;
}
function renderContactFooter() {
const { companyName, contact } = state.branding;
return `
<div class="flyer-footer p-4">
<div class="flex justify-between items-center">
<div class="company-name editable" data-field="companyName">${companyName}</div>
<div class="text-sm">
${contact.phone || contact.email || contact.website || contact.address ? `
<div class="flex flex-wrap justify-center gap-4">
${contact.phone ? `<div class="editable" data-field="phone"><i class="fas fa-phone mr-1"></i> ${contact.phone}</div>` : ''}
${contact.email ? `<div class="editable" data-field="email"><i class="fas fa-envelope mr-1"></i> ${contact.email}</div>` : ''}
${contact.website ? `<div class="editable" data-field="website"><i class="fas fa-globe mr-1"></i> ${contact.website}</div>` : ''}
${contact.address ? `<div class="editable" data-field="address"><i class="fas fa-map-marker-alt mr-1"></i> ${contact.address}</div>` : ''}
</div>
` : `
<div class="editable" data-field="contact-placeholder">Contact Information</div>
`}
</div>
</div>
</div>
`;
}
function renderFinalPreview() {
finalPreviewContainer.innerHTML = flyerPreviewContainer.innerHTML;
}
function handleLogoUpload(e) {
const file = e.target.files[0];
if (!file) return;
// Check file size (1MB max)
if (file.size > 1024 * 1024) {
alert('Logo must be 1MB or smaller');
return;
}
// Check file type
const validTypes = ['image/jpeg', 'image/png', 'image/svg+xml'];
if (!validTypes.includes(file.type)) {
alert('Only JPG, PNG, or SVG files are allowed');
return;
}
// Create preview
const reader = new FileReader();
reader.onload = function(e) {
logoPreview.innerHTML = `<img src="${e.target.result}" alt="Logo Preview" class="w-full h-full object-contain">`;
removeLogoBtn.classList.remove('hidden');
state.branding.logo = e.target.result;
updateBranding();
};
reader.readAsDataURL(file);
}
function removeLogo() {
logoPreview.innerHTML = '<i class="fas fa-image text-gray-400 text-xl"></i>';
removeLogoBtn.classList.add('hidden');
logoUploadInput.value = '';
state.branding.logo = null;
updateBranding();
}
function updateBranding() {
state.branding.companyName = companyNameInput.value || 'Your Company';
state.branding.contact = {
phone: document.getElementById('contact-phone').value,
email: document.getElementById('contact-email').value,
address: document.getElementById('contact-address').value,
website: document.getElementById('contact-website').value
};
state.branding.contactPosition = document.querySelector('input[name="contact-position"]:checked').value;
if (state.currentStep === 2 && state.products.length > 0) {
renderFlyerPreview();
}
}
function toggleEditMode() {
state.isEditing = !state.isEditing;
editFlyerBtn.innerHTML = state.isEditing
? '<i class="fas fa-check mr-2"></i> Finish Editing'
: '<i class="fas fa-edit mr-2"></i> Edit Flyer';
renderFlyerPreview();
}
function setupEditor() {
// Make text editable
document.querySelectorAll('.editable').forEach(el => {
el.addEventListener('click', handleEditableClick);
});
// Make images editable
document.querySelectorAll('.editable-image').forEach(el => {
el.addEventListener('click', handleImageEditClick);
});
// Setup editor toolbar buttons
document.querySelectorAll('[data-action]').forEach(button => {
button.addEventListener('click', handleEditorAction);
});
}
function handleEditableClick(e) {
if (!state.isEditing) return;
const element = e.currentTarget;
const field = element.dataset.field;
const productIndex = element.dataset.product;
// Store the current editing element
state.currentlyEditing = { element, field, productIndex };
// Create an input field
const input = document.createElement('input');
input.type = 'text';
input.className = 'w-full px-2 py-1 border border-blue-500 rounded';
input.value = element.textContent.trim();
// Replace the element with input
element.replaceWith(input);
input.focus();
// Handle input completion
input.addEventListener('blur', () => completeEditing(input));
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') completeEditing(input);
});
}
function handleImageEditClick(e) {
if (!state.isEditing) return;
const element = e.currentTarget;
const field = element.dataset.field;
const productIndex = element.dataset.product;
// Create file input
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.className = 'hidden';
// Add to DOM and trigger click
document.body.appendChild(fileInput);
fileInput.click();
// Handle file selection
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
if (productIndex !== undefined) {
// Update product image
state.products[productIndex].heroImage = e.target.result;
} else if (field === 'logo') {
// Update logo
state.branding.logo = e.target.result;
logoPreview.innerHTML = `<img src="${e.target.result}" alt="Logo Preview" class="w-full h-full object-contain">`;
removeLogoBtn.classList.remove('hidden');
}
renderFlyerPreview();
};
reader.readAsDataURL(file);
// Clean up
document.body.removeChild(fileInput);
});
}
function handleEditorAction(e) {
const action = e.currentTarget.dataset.action;
const productIndex = e.currentTarget.dataset.product;
switch (action) {
case 'edit-text':
alert(`Editing text for product ${parseInt(productIndex) + 1}`);
break;
case 'replace-image':
handleImageEditClick({ currentTarget: document.querySelector(`.editable-image[data-product="${productIndex}"]`) });
break;
case 'change-layout':
alert(`Changing layout for product ${parseInt(productIndex) + 1}`);
break;
}
}
function completeEditing(input) {
if (!state.currentlyEditing) return;
const { element, field, productIndex } = state.currentlyEditing;
const newValue = input.value.trim();
if (productIndex !== undefined) {
// Update product field
if (field === 'productName') {
state.products[productIndex].productName = newValue;
} else if (field === 'productNumber') {
state.products[productIndex].productNumber = newValue;
} else if (field === 'description') {
state.products[productIndex].description = newValue;
}
} else {
// Update branding field
if (field === 'companyName') {
state.branding.companyName = newValue;
companyNameInput.value = newValue;
} else if (field === 'phone') {
state.branding.contact.phone = newValue;
document.getElementById('contact-phone').value = newValue;
} else if (field === 'email') {
state.branding.contact.email = newValue;
document.getElementById('contact-email').value = newValue;
} else if (field === 'website') {
state.branding.contact.website = newValue;
document.getElementById('contact-website').value = newValue;
} else if (field === 'address') {
state.branding.contact.address = newValue;
document.getElementById('contact-address').value = newValue;
}
}
// Re-render to update all instances
renderFlyerPreview();
// Clean up
document.body.removeChild(input);
state.currentlyEditing = null;
}
function goToStep(step) {
if (step < 1 || step > 3) return;
// Update UI
stepContents.forEach(content => content.classList.add('hidden'));
document.getElementById(`step-${step}`).classList.remove('hidden');
tabButtons.forEach(button => {
button.classList.remove('active');
const buttonStep = parseInt(button.dataset.step);
if (buttonStep === step) {
button.classList.add('active');
} else {
const numberSpan = button.querySelector('span');
if (buttonStep < step) {
numberSpan.className = 'flex items-center justify-center w-8 h-8 bg-green-500 text-white rounded-full mr-2';
numberSpan.innerHTML = '<i class="fas fa-check text-xs"></i>';
} else {
numberSpan.className = 'flex items-center justify-center w-8 h-8 bg-gray-200 text-gray-600 rounded-full mr-2';
numberSpan.textContent = buttonStep;
}
}
});
state.currentStep = step;
// Special handling for step 3
if (step === 3) {
renderFinalPreview();
}
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=SpectrumInfinite/link2flyer-si" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>