|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Meme Generator - Wear.Fun</title> |
|
|
<link rel="icon" type="image/x-icon" href="https://huggingface.co/dodey917/wear-fun/resolve/main/images/KZr0gBp.png"> |
|
|
<link rel="stylesheet" href="style.css"> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script src="https://unpkg.com/feather-icons"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
|
|
</head> |
|
|
<body class="bg-black text-white"> |
|
|
<custom-header></custom-header> |
|
|
<main class="py-12 px-6"> |
|
|
<div class="max-w-4xl mx-auto"> |
|
|
<h1 class="text-3xl md:text-4xl font-bold text-center mb-2">Meme Generator</h1> |
|
|
<p class="text-gray-400 text-center mb-12">Turn your memes into wearable art</p> |
|
|
|
|
|
<div class="bg-gray-900 rounded-2xl p-6 md:p-8 border border-gray-800"> |
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> |
|
|
<div> |
|
|
<h2 class="text-xl font-bold mb-4">Upload Your Image</h2> |
|
|
<div id="dropZone" class="border-2 border-dashed border-gray-700 rounded-xl p-8 text-center mb-6 transition hover:border-red-500 cursor-pointer"> |
|
|
<i data-feather="upload" class="w-12 h-12 mx-auto text-gray-500 mb-4"></i> |
|
|
<p class="mb-2">Drag & drop your image here</p> |
|
|
<p class="text-sm text-gray-500 mb-4">or</p> |
|
|
<button type="button" id="browseBtn" class="bg-red-600 hover:bg-red-700 px-6 py-2 rounded-lg font-medium">Browse Files</button> |
|
|
<input type="file" id="fileInput" class="hidden" accept="image/*"> |
|
|
</div> |
|
|
<div id="imagePreview" class="hidden mb-6"> |
|
|
<img id="previewImg" class="w-full rounded-lg" alt="Preview"> |
|
|
</div> |
|
|
<div class="mb-6"> |
|
|
<label class="block text-gray-400 mb-2">Style Hint (Optional)</label> |
|
|
<input type="text" placeholder="e.g., crypto meme, sarcastic" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-red-500"> |
|
|
</div> |
|
|
<button id="generateBtn" class="w-full bg-red-600 hover:bg-red-700 text-white py-3 rounded-lg font-semibold transition disabled:bg-gray-600 disabled:cursor-not-allowed"> |
|
|
Generate Captions |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<h2 class="text-xl font-bold mb-4">Generated Captions</h2> |
|
|
<div id="captionsContainer" class="space-y-4 hidden"> |
|
|
|
|
|
</div> |
|
|
<div id="loadingCaptions" class="hidden text-center py-8"> |
|
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-red-600 mx-auto"></div> |
|
|
<p class="mt-4 text-gray-400">Generating hilarious captions...</p> |
|
|
</div> |
|
|
<div id="noCaptions" class="text-center py-8 text-gray-500"> |
|
|
<i data-feather="message-circle" class="w-12 h-12 mx-auto mb-4 opacity-50"></i> |
|
|
<p>Upload an image and generate captions to see them here</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-12"> |
|
|
<h2 class="text-xl font-bold mb-4">T-Shirt Preview</h2> |
|
|
<div id="mockupContainer" class="bg-gray-800 rounded-xl p-8 flex items-center justify-center min-h-[500px] border border-gray-700"> |
|
|
<div id="mockupPlaceholder" class="text-center"> |
|
|
<i data-feather="t-shirt" class="w-24 h-24 mx-auto text-gray-600 mb-4"></i> |
|
|
<p class="text-gray-500">Your t-shirt mockup will appear here</p> |
|
|
</div> |
|
|
<div id="mockupPreview" class="hidden relative"> |
|
|
|
|
|
<svg width="300" height="350" viewBox="0 0 300 350" class="relative"> |
|
|
|
|
|
<path d="M75 80 L75 40 L100 20 L120 30 L150 25 L180 30 L200 20 L225 40 L225 80 L200 100 L200 320 L100 320 L100 100 Z" |
|
|
fill="#1a1a1a" stroke="#333" stroke-width="2"/> |
|
|
|
|
|
<ellipse cx="150" cy="40" rx="25" ry="20" fill="none" stroke="#333" stroke-width="2"/> |
|
|
|
|
|
<path d="M75 80 L50 120 L40 180 L70 180 L100 100" fill="#1a1a1a" stroke="#333" stroke-width="2"/> |
|
|
<path d="M225 80 L250 120 L260 180 L230 180 L200 100" fill="#1a1a1a" stroke="#333" stroke-width="2"/> |
|
|
|
|
|
|
|
|
<defs> |
|
|
<clipPath id="designArea"> |
|
|
<rect x="90" y="100" width="120" height="120" rx="5"/> |
|
|
</clipPath> |
|
|
</defs> |
|
|
<rect x="90" y="100" width="120" height="120" rx="5" fill="white" opacity="0.1"/> |
|
|
<g clip-path="url(#designArea)"> |
|
|
<image id="tshirtImage" x="90" y="100" width="120" height="120" preserveAspectRatio="xMidYMid meet"/> |
|
|
</g> |
|
|
|
|
|
|
|
|
<text id="tshirtCaption" x="150" y="240" text-anchor="middle" fill="white" font-size="14" font-weight="bold" font-family="Arial, sans-serif"></text> |
|
|
</svg> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</main> |
|
|
|
|
|
<custom-footer></custom-footer> |
|
|
<script src="components/header.js"></script> |
|
|
<script src="components/footer.js"></script> |
|
|
<script src="script.js"></script> |
|
|
<script> |
|
|
feather.replace(); |
|
|
|
|
|
let uploadedImage = null; |
|
|
let currentCaption = ''; |
|
|
|
|
|
const dropZone = document.getElementById('dropZone'); |
|
|
const fileInput = document.getElementById('fileInput'); |
|
|
const browseBtn = document.getElementById('browseBtn'); |
|
|
const generateBtn = document.getElementById('generateBtn'); |
|
|
const imagePreview = document.getElementById('imagePreview'); |
|
|
const previewImg = document.getElementById('previewImg'); |
|
|
const captionsContainer = document.getElementById('captionsContainer'); |
|
|
const loadingCaptions = document.getElementById('loadingCaptions'); |
|
|
const noCaptions = document.getElementById('noCaptions'); |
|
|
const mockupContainer = document.getElementById('mockupContainer'); |
|
|
const mockupPlaceholder = document.getElementById('mockupPlaceholder'); |
|
|
const mockupPreview = document.getElementById('mockupPreview'); |
|
|
const mockupImg = document.getElementById('mockupImg'); |
|
|
const mockupCaption = document.getElementById('mockupCaption'); |
|
|
|
|
|
|
|
|
browseBtn.addEventListener('click', () => fileInput.click()); |
|
|
|
|
|
fileInput.addEventListener('change', (e) => { |
|
|
const file = e.target.files[0]; |
|
|
if (file && file.type.startsWith('image/')) { |
|
|
handleImageUpload(file); |
|
|
} |
|
|
}); |
|
|
|
|
|
dropZone.addEventListener('dragover', (e) => { |
|
|
e.preventDefault(); |
|
|
dropZone.classList.add('border-red-500', 'bg-red-900/10'); |
|
|
}); |
|
|
|
|
|
dropZone.addEventListener('dragleave', () => { |
|
|
dropZone.classList.remove('border-red-500', 'bg-red-900/10'); |
|
|
}); |
|
|
|
|
|
dropZone.addEventListener('drop', (e) => { |
|
|
e.preventDefault(); |
|
|
dropZone.classList.remove('border-red-500', 'bg-red-900/10'); |
|
|
|
|
|
const file = e.dataTransfer.files[0]; |
|
|
if (file && file.type.startsWith('image/')) { |
|
|
handleImageUpload(file); |
|
|
} |
|
|
}); |
|
|
function handleImageUpload(file) { |
|
|
const reader = new FileReader(); |
|
|
reader.onload = (e) => { |
|
|
uploadedImage = e.target.result; |
|
|
previewImg.src = uploadedImage; |
|
|
imagePreview.classList.remove('hidden'); |
|
|
generateBtn.disabled = false; |
|
|
resetCaptions(); |
|
|
}; |
|
|
reader.readAsDataURL(file); |
|
|
} |
|
|
|
|
|
generateBtn.addEventListener('click', async () => { |
|
|
if (!uploadedImage) return; |
|
|
|
|
|
loadingCaptions.classList.remove('hidden'); |
|
|
captionsContainer.classList.add('hidden'); |
|
|
noCaptions.classList.add('hidden'); |
|
|
generateBtn.disabled = true; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
const captions = generateMockCaptions(); |
|
|
displayCaptions(captions); |
|
|
loadingCaptions.classList.add('hidden'); |
|
|
generateBtn.disabled = false; |
|
|
}, 1500); |
|
|
}); |
|
|
function generateMockCaptions() { |
|
|
const captionTemplates = [ |
|
|
"When you finally understand that meme", |
|
|
"Send this to your group chat immediately", |
|
|
"My therapist: 'And how does that make you feel?'", |
|
|
"Me explaining why this is the funniest thing ever", |
|
|
"POV: You get the reference", |
|
|
"IYKYK (If you know, you know)", |
|
|
"This is my emotional support meme", |
|
|
"When the meme hits different at 3 AM", |
|
|
"Tell me you have internet without telling me you have internet", |
|
|
"Chef's kiss: 馃" |
|
|
]; |
|
|
|
|
|
return Array.from({length: 3}, () => |
|
|
captionTemplates[Math.floor(Math.random() * captionTemplates.length)] |
|
|
); |
|
|
} |
|
|
|
|
|
function displayCaptions(captions) { |
|
|
captionsContainer.innerHTML = ''; |
|
|
captions.forEach((caption, index) => { |
|
|
const captionDiv = document.createElement('div'); |
|
|
captionDiv.className = 'bg-gray-800 p-4 rounded-lg border border-gray-700 hover:border-red-500 transition'; |
|
|
captionDiv.innerHTML = ` |
|
|
<p class="font-medium mb-3">${caption}</p> |
|
|
<div class="flex gap-2"> |
|
|
<button class="preview-btn flex-1 bg-gray-700 hover:bg-gray-600 py-2 rounded-lg text-sm transition" data-caption="${caption}"> |
|
|
Preview Mockup |
|
|
</button> |
|
|
<button class="add-to-shirt-btn flex-1 bg-red-600 hover:bg-red-700 py-2 rounded-lg text-sm transition" data-caption="${caption}"> |
|
|
Add to T-Shirt |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
captionsContainer.appendChild(captionDiv); |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('.preview-btn').forEach(btn => { |
|
|
btn.addEventListener('click', (e) => { |
|
|
const caption = e.target.dataset.caption; |
|
|
showMockup(caption); |
|
|
}); |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('.add-to-shirt-btn').forEach(btn => { |
|
|
btn.addEventListener('click', (e) => { |
|
|
const caption = e.target.dataset.caption; |
|
|
currentCaption = caption; |
|
|
showMockup(caption); |
|
|
|
|
|
|
|
|
const notification = document.createElement('div'); |
|
|
notification.className = 'fixed top-20 right-4 bg-green-600 text-white px-6 py-3 rounded-lg shadow-lg z-50 transform translate-x-full transition-transform duration-300'; |
|
|
notification.innerHTML = ` |
|
|
<div class="flex items-center"> |
|
|
<i data-feather="check-circle" class="w-5 h-5 mr-2"></i> |
|
|
<span>Design added to T-Shirt! Scroll down to preview.</span> |
|
|
</div> |
|
|
`; |
|
|
document.body.appendChild(notification); |
|
|
feather.replace(); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
notification.style.transform = 'translateX(0)'; |
|
|
}, 100); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
notification.style.transform = 'translateX(100%)'; |
|
|
setTimeout(() => { |
|
|
document.body.removeChild(notification); |
|
|
}, 300); |
|
|
}, 3000); |
|
|
}); |
|
|
}); |
|
|
captionsContainer.classList.remove('hidden'); |
|
|
} |
|
|
async function showMockup(caption) { |
|
|
if (!uploadedImage) return; |
|
|
|
|
|
mockupPlaceholder.classList.add('hidden'); |
|
|
mockupPreview.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
mockupPreview.innerHTML = ` |
|
|
<div class="text-center py-8"> |
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-red-600 mx-auto mb-4"></div> |
|
|
<p class="text-gray-400 mb-2">Generating realistic T-shirt mockup...</p> |
|
|
<p class="text-gray-500 text-sm">Using AI to create a photorealistic design</p> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
try { |
|
|
const shirtColor = window.selectedShirtColor || 'white'; |
|
|
|
|
|
|
|
|
const response = await fetch('/api/generate-mockup', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json', |
|
|
}, |
|
|
body: JSON.stringify({ |
|
|
image: uploadedImage, |
|
|
caption: caption, |
|
|
shirtColor: shirtColor |
|
|
}) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error('Failed to generate mockup'); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
if (data.success && data.imageUrl) { |
|
|
|
|
|
mockupPreview.innerHTML = ` |
|
|
<div class="space-y-4"> |
|
|
<div class="relative rounded-lg overflow-hidden shadow-2xl"> |
|
|
<img src="${data.imageUrl}" alt="T-shirt Mockup" class="w-full h-auto"> |
|
|
<div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-4"> |
|
|
<p class="text-white text-sm font-medium">${caption}</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="flex gap-2 justify-center"> |
|
|
<button onclick="regenerateMockup('${caption}')" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg text-sm transition"> |
|
|
<i data-feather="refresh-cw" class="w-4 h-4 inline mr-1"></i> |
|
|
Regenerate |
|
|
</button> |
|
|
<button onclick="downloadMockup('${data.imageUrl}')" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded-lg text-sm transition"> |
|
|
<i data-feather="download" class="w-4 h-4 inline mr-1"></i> |
|
|
Download |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
feather.replace(); |
|
|
} else { |
|
|
throw new Error(data.error || 'Generation failed'); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Mockup generation error:', error); |
|
|
showNotification('Failed to generate realistic mockup. Using fallback preview.', 'warning'); |
|
|
|
|
|
showFallbackMockup(caption); |
|
|
} |
|
|
} |
|
|
|
|
|
function regenerateMockup(caption) { |
|
|
showMockup(caption); |
|
|
} |
|
|
|
|
|
function downloadMockup(imageUrl) { |
|
|
const link = document.createElement('a'); |
|
|
link.href = imageUrl; |
|
|
link.download = 'tshirt-mockup.png'; |
|
|
link.target = '_blank'; |
|
|
document.body.appendChild(link); |
|
|
link.click(); |
|
|
document.body.removeChild(link); |
|
|
} |
|
|
|
|
|
function showNotification(message, type = 'info') { |
|
|
const notification = document.createElement('div'); |
|
|
const bgColor = type === 'warning' ? 'bg-yellow-600' : type === 'error' ? 'bg-red-600' : 'bg-green-600'; |
|
|
notification.className = `fixed top-20 right-4 ${bgColor} text-white px-6 py-3 rounded-lg shadow-lg z-50 transform translate-x-full transition-transform duration-300`; |
|
|
notification.innerHTML = ` |
|
|
<div class="flex items-center"> |
|
|
<i data-feather="${type === 'warning' ? 'alert-triangle' : type === 'error' ? 'x-circle' : 'check-circle'}" class="w-5 h-5 mr-2"></i> |
|
|
<span>${message}</span> |
|
|
</div> |
|
|
`; |
|
|
document.body.appendChild(notification); |
|
|
feather.replace(); |
|
|
|
|
|
setTimeout(() => { |
|
|
notification.style.transform = 'translateX(0)'; |
|
|
}, 100); |
|
|
|
|
|
setTimeout(() => { |
|
|
notification.style.transform = 'translateX(100%)'; |
|
|
setTimeout(() => { |
|
|
document.body.removeChild(notification); |
|
|
}, 300); |
|
|
}, 5000); |
|
|
} |
|
|
function showFallbackMockup(caption) { |
|
|
const tshirtImage = document.getElementById('tshirtImage'); |
|
|
const tshirtCaption = document.getElementById('tshirtCaption'); |
|
|
|
|
|
|
|
|
mockupPreview.innerHTML = ` |
|
|
<svg width="300" height="350" viewBox="0 0 300 350" class="relative"> |
|
|
<!-- T-Shirt Base --> |
|
|
<path d="M75 80 L75 40 L100 20 L120 30 L150 25 L180 30 L200 20 L225 40 L225 80 L200 100 L200 320 L100 320 L100 100 Z" |
|
|
fill="#1a1a1a" stroke="#333" stroke-width="2"/> |
|
|
<!-- Neckline --> |
|
|
<ellipse cx="150" cy="40" rx="25" ry="20" fill="none" stroke="#333" stroke-width="2"/> |
|
|
<!-- Sleeves --> |
|
|
<path d="M75 80 L50 120 L40 180 L70 180 L100 100" fill="#1a1a1a" stroke="#333" stroke-width="2"/> |
|
|
<path d="M225 80 L250 120 L260 180 L230 180 L200 100" fill="#1a1a1a" stroke="#333" stroke-width="2"/> |
|
|
|
|
|
<!-- Design Area --> |
|
|
<defs> |
|
|
<clipPath id="designArea"> |
|
|
<rect x="90" y="100" width="120" height="120" rx="5"/> |
|
|
</clipPath> |
|
|
</defs> |
|
|
<rect x="90" y="100" width="120" height="120" rx="5" fill="white" opacity="0.1"/> |
|
|
<g clip-path="url(#designArea)"> |
|
|
<image id="tshirtImage" x="90" y="100" width="120" height="120" preserveAspectRatio="xMidYMid meet"/> |
|
|
</g> |
|
|
</svg> |
|
|
<div class="mt-4 p-4 bg-gray-800 rounded-lg"> |
|
|
<p class="text-white text-center font-medium">${caption}</p> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
|
|
|
const tshirtImg = document.getElementById('tshirtImage'); |
|
|
if (tshirtImg) { |
|
|
tshirtImg.setAttributeNS('http://www.w3.org/1999/xlink', 'href', uploadedImage); |
|
|
} |
|
|
} |
|
|
function resetCaptions() { |
|
|
captionsContainer.classList.add('hidden'); |
|
|
noCaptions.classList.remove('hidden'); |
|
|
mockupPlaceholder.classList.remove('hidden'); |
|
|
mockupPreview.classList.add('hidden'); |
|
|
generateBtn.disabled = !uploadedImage; |
|
|
} |
|
|
|
|
|
const styleHint = document.querySelector('input[placeholder="e.g., crypto meme, sarcastic"]'); |
|
|
if (styleHint) { |
|
|
styleHint.addEventListener('input', (e) => { |
|
|
|
|
|
console.log('Style hint:', e.target.value); |
|
|
}); |
|
|
} |
|
|
</script> |
|
|
</body> |
|
|
</html> |