| <!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> |