diff --git a/.env b/.env new file mode 100644 index 0000000..5390d16 --- /dev/null +++ b/.env @@ -0,0 +1,33 @@ +# === AI CONFIGURATION === + +# OpenRouter API key for text generation (DeepSeek) +AI_API_KEY=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 + +# DeepSeek model for text/keyword generation +AI_MODEL_NAME=deepseek/deepseek-chat-v3-0324:free + +# OpenRouter API URL +AI_API_URL=https://openrouter.ai/api/v1/chat/completions + +# === GROK VISION API CONFIGURATION === + +# Grok API key for vision analysis (add your Grok API key here) +GROK_API_KEY=sk-voidai-vVU2HHiq1txTNmXdZOP98LzARmi4HsptTixMqFSX4yBbw8ogvmKlJEPeKrH1hwEd6j6AnED9LsR6ztPtRMT7UzeLOyxQkasbwKow + +# Grok-2-Vision model +GROK_VISION_MODEL=grok-2-vision-1212 + +# Grok API URL +GROK_API_URL=https://api.voidai.app/v1/ + +# Optional: AI task-specific configuration +AI_TASK=keyword_generation +AI_RENAME_STRATEGY=descriptive # Options: 'timestamped', 'uuid', 'descriptive' + +# === GENERAL SETTINGS === + +# Environment type +NODE_ENV=development + +# Optional: Logging or debugging +ENABLE_LOGGING=true \ No newline at end of file diff --git a/GROK_SETUP.md b/GROK_SETUP.md new file mode 100644 index 0000000..0653e14 --- /dev/null +++ b/GROK_SETUP.md @@ -0,0 +1,75 @@ +# Grok-2-Vision Integration Setup + +## How to Configure Your Grok API Key + +The SEO Image Renamer now supports Grok-2-Vision for enhanced image analysis. Follow these steps to set up your API key: + +### Step 1: Get Your Grok API Key +1. Visit [https://console.x.ai/](https://console.x.ai/) +2. Sign up or log in to your account +3. Navigate to API Keys section +4. Create a new API key for your project +5. Copy the generated API key + +### Step 2: Update the .env File +1. Open the `.env` file in your project directory +2. Find the line that says: `GROK_API_KEY=your_grok_api_key_here` +3. Replace `your_grok_api_key_here` with your actual Grok API key +4. Save the file + +Example: +```env +GROK_API_KEY=gsk-proj-abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567 +``` + +### Step 3: Test the Integration +1. Open your SEO Image Renamer website +2. Upload some images +3. Add keywords and click "Generate SEO Keywords" +4. Check the browser console for the message: "Grok API Key configured: Yes" + +## What's New with Grok-2-Vision + +### Enhanced Image Analysis +- **Better Keyword Extraction**: Grok-2-Vision provides more accurate and contextual keywords from your images +- **SEO-Focused Analysis**: The AI is specifically prompted to identify keywords useful for SEO image naming +- **Improved Accuracy**: Better object and action recognition compared to previous vision models + +### Technical Improvements +- **Dedicated Vision API**: Separate API endpoint optimized for vision tasks +- **Enhanced Prompts**: Specialized prompts for extracting SEO-relevant keywords +- **Better Error Handling**: Improved fallback mechanisms if vision analysis fails +- **Cleaner Responses**: Better parsing and filtering of AI responses + +## Troubleshooting + +### Common Issues + +**Issue**: "Grok API Key configured: No" in console +**Solution**: Make sure you've replaced `sk-voidai-vVU2HHiq1txTNmXdZOP98LzARmi4HsptTixMqFSX4yBbw8ogvmKlJEPeKrH1hwEd6j6AnED9LsR6ztPtRMT7UzeLOyxQkasbwKow` with your actual API key in the .env file + +**Issue**: Vision analysis fails or returns generic keywords +**Solution**: +1. Check that your API key is valid and has sufficient credits +2. Ensure your API key has access to Grok-2-Vision model +3. Check browser console for specific error messages + +**Issue**: .env file not loading +**Solution**: +1. Make sure the .env file is in the same directory as index.html +2. Ensure your web server allows access to .env files (may need to serve from a local server) +3. For production, you may need to hardcode the API key in the JavaScript file + +### API Limits +- Grok-2-Vision has rate limits and usage quotas +- If you hit limits, the system will fallback to generic keywords +- Monitor your usage in the X.AI console + +## Security Note +Keep your API key secure and never commit it to public repositories. The .env file should be added to your .gitignore file. + +## Support +If you encounter issues with the Grok-2-Vision integration, check: +1. X.AI API documentation: [https://docs.x.ai/](https://docs.x.ai/) +2. Your API key permissions and quotas +3. Browser console for detailed error messages \ No newline at end of file diff --git a/config.js b/config.js new file mode 100644 index 0000000..00ed80b --- /dev/null +++ b/config.js @@ -0,0 +1,20 @@ +// Configuration file for SEO Image Renamer +// Update your API keys here + +window.APP_CONFIG = { + // OpenRouter API for text generation (DeepSeek) - already configured + OPENROUTER_API_KEY: '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', + DEEPSEEK_MODEL: 'deepseek/deepseek-chat-v3-0324:free', + OPENROUTER_API_URL: 'https://openrouter.ai/api/v1/chat/completions', + + // Grok-2-Vision API - ADD YOUR GROK API KEY HERE + GROK_API_KEY: 'sk-voidai-vVU2HHiq1txTNmXdZOP98LzARmi4HsptTixMqFSX4yBbw8ogvmKlJEPeKrH1hwEd6j6AnED9LsR6ztPtRMT7UzeLOyxQkasbwKow' + GROK_VISION_MODEL: 'grok-2-vision-1212', + GROK_API_URL: 'https://api.x.ai/v1/chat/completions' +}; + +// Instructions: +// 1. Get your Grok API key from https://console.x.ai/ +// 2. Replace 'your_grok_api_key_here' above with your actual API key +// 3. Save this file +// 4. The website will automatically use your new API key \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..98d91cd --- /dev/null +++ b/index.html @@ -0,0 +1,310 @@ + + + + + + SEO Image Renamer - AI-Powered Image SEO Tool + + + + +
+
+ + +
+ +
+
+
+ +
+
+
+
+
+
+ + AI-Powered +
+

Save time! Bulk rename your images individually for better SEO performance

+

Transform your image SEO workflow with AI that analyzes content and generates perfect filenames automatically. No more manual renaming - just upload, enhance, and download.

+ +
+
+ + AI Vision Analysis +
+
+ + Smart Keyword Enhancement +
+
+ + Instant ZIP Download +
+
+ +
+
+ 10k+ + Images Processed +
+
+ 95% + Time Saved +
+
+
+ +
+
+
+
+ +
+

Drop your images here

+

or click to browse files

+ + +
+ Supports: JPG, PNG, WEBP, GIF +
+
+
+
+
+
+
+ + + +
+
+
+

Powerful Features for Better SEO

+

Everything you need to optimize your images for search engines

+
+ +
+
+
+ +
+

AI-Powered Naming

+

Advanced AI generates SEO-friendly filenames that help your images rank higher in search results.

+
+ +
+
+ +
+

Image Recognition

+

AI analyzes your images to understand content and context for more accurate naming.

+
+ +
+
+ +
+

Keyword Enhancement

+

Enhance your keywords with AI-suggested synonyms for better SEO performance.

+
+ +
+
+ +
+

Easy Download

+

Download all your renamed images in a single ZIP file for easy implementation.

+
+
+
+
+ +
+
+
+

How It Works

+

Get better SEO for your images in just three simple steps

+
+ +
+
+
1
+

Upload Images

+

Drag and drop your images or browse your files to upload them to our platform.

+
+ +
+
2
+

Add Keywords

+

Provide keywords that describe your images, or let our AI enhance them for better SEO.

+
+ +
+
3
+

Download & Implement

+

Download your renamed images as a ZIP file and use them on your website.

+
+
+
+
+ +
+
+
+

Simple, Transparent Pricing

+

Choose the plan that works best for you

+
+ +
+
+

Basic

+
$0/month
+
    +
  • 50 images per month
  • +
  • AI-powered naming
  • +
  • Keyword enhancement
  • +
  • ZIP download
  • +
+ +
+ + + +
+

Max

+
$19/month
+
    +
  • 1000 images per month
  • +
  • AI-powered naming
  • +
  • Keyword enhancement
  • +
  • ZIP download
  • +
  • Priority support
  • +
  • Advanced analytics
  • +
+ +
+
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..54f1a17 --- /dev/null +++ b/script.js @@ -0,0 +1,736 @@ +// Global variables +let uploadedImages = []; +let keywords = []; +let generatedNames = []; + +// DOM elements +const dropArea = document.getElementById('drop-area'); +const fileInput = document.getElementById('file-input'); +const browseBtn = document.getElementById('browse-btn'); +const workflowSection = document.getElementById('workflow-section'); +const keywordsSection = document.getElementById('keywords-section'); +const keywordInput = document.getElementById('keyword-input'); +const enhanceBtn = document.getElementById('enhance-btn'); +const keywordsDisplay = document.getElementById('keywords-display'); +const imagesPreview = document.getElementById('images-preview'); +const imagesContainer = document.getElementById('images-container'); +const downloadBtn = document.getElementById('download-btn'); + +// AI Configuration - loaded from config.js +let AI_CONFIG = {}; + +// Initialize configuration on page load +function initializeConfig() { + // Load configuration from window.APP_CONFIG (set by config.js) + if (window.APP_CONFIG) { + AI_CONFIG = { + // OpenRouter API for text generation (DeepSeek) + API_KEY: window.APP_CONFIG.OPENROUTER_API_KEY, + DEEPSEEK_MODEL: window.APP_CONFIG.DEEPSEEK_MODEL, + API_URL: window.APP_CONFIG.OPENROUTER_API_URL, + + // Grok-2-Vision API for image analysis + GROK_API_KEY: window.APP_CONFIG.GROK_API_KEY, + GROK_VISION_MODEL: window.APP_CONFIG.GROK_VISION_MODEL, + GROK_API_URL: window.APP_CONFIG.GROK_API_URL + }; + + console.log('✅ Configuration loaded successfully from config.js'); + console.log('🔑 Grok API Key configured:', AI_CONFIG.GROK_API_KEY !== 'your_grok_api_key_here' ? '✅ Yes' : '❌ No - Please add your API key to config.js'); + + if (AI_CONFIG.GROK_API_KEY === 'your_grok_api_key_here') { + console.warn('⚠️ SETUP REQUIRED: Please add your Grok API key to config.js file'); + console.log('📖 Instructions: Open config.js and replace "your_grok_api_key_here" with your actual Grok API key from https://console.x.ai/'); + } + } else { + console.error('❌ Configuration file (config.js) not loaded properly'); + + // Fallback configuration + AI_CONFIG = { + API_KEY: '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', + DEEPSEEK_MODEL: 'deepseek/deepseek-chat-v3-0324:free', + API_URL: 'https://openrouter.ai/api/v1/chat/completions', + GROK_API_KEY: 'your_grok_api_key_here', + GROK_VISION_MODEL: 'grok-2-vision-1212', + GROK_API_URL: 'https://api.x.ai/v1/chat/completions' + }; + } +} + +// Event listeners +document.addEventListener('DOMContentLoaded', () => { + // Upload area event listeners + dropArea.addEventListener('click', () => fileInput.click()); + browseBtn.addEventListener('click', () => fileInput.click()); + fileInput.addEventListener('change', handleFileSelect); + + // Drag and drop events + ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { + dropArea.addEventListener(eventName, preventDefaults, false); + }); + + ['dragenter', 'dragover'].forEach(eventName => { + dropArea.addEventListener(eventName, highlight, false); + }); + + ['dragleave', 'drop'].forEach(eventName => { + dropArea.addEventListener(eventName, unhighlight, false); + }); + + dropArea.addEventListener('drop', handleDrop, false); + + // Keyword events + keywordInput.addEventListener('input', toggleEnhanceButton); + enhanceBtn.addEventListener('click', enhanceKeywords); + + // Download button + downloadBtn.addEventListener('click', downloadImages); +}); + +// Prevent default drag behaviors +function preventDefaults(e) { + e.preventDefault(); + e.stopPropagation(); +} + +// Highlight drop area when item is dragged over it +function highlight() { + dropArea.classList.add('dragover'); +} + +// Remove highlight when item is dragged out of drop area +function unhighlight() { + dropArea.classList.remove('dragover'); +} + +// Handle dropped files +function handleDrop(e) { + const dt = e.dataTransfer; + const files = dt.files; + handleFiles(files); +} + +// Handle file selection +function handleFileSelect(e) { + const files = e.target.files; + handleFiles(files); +} + +// Process uploaded files +function handleFiles(files) { + if (files.length === 0) return; + + // Convert FileList to Array + const filesArray = Array.from(files); + + // Filter only image files + const imageFiles = filesArray.filter(file => file.type.startsWith('image/')); + + if (imageFiles.length === 0) { + alert('Please select image files only.'); + return; + } + + // Clear previous images + uploadedImages = []; + + // Process each image file + let processedCount = 0; + imageFiles.forEach(file => { + const reader = new FileReader(); + reader.onload = (e) => { + uploadedImages.push({ + file: file, + name: file.name, + size: file.size, + type: file.type, + src: e.target.result, + newName: generateFileName(file.name), + visionKeywords: [] + }); + + processedCount++; + // Show workflow section after all files are processed + if (processedCount === imageFiles.length) { + workflowSection.style.display = 'block'; + keywordsSection.style.display = 'block'; + imagesPreview.style.display = 'block'; + updateImagesPreview(); + + // Smooth scroll to workflow section + workflowSection.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }; + reader.readAsDataURL(file); + }); +} + +// Generate a simple filename based on original name +function generateFileName(originalName) { + // Remove extension + const nameWithoutExt = originalName.substring(0, originalName.lastIndexOf('.')); + // Replace non-alphanumeric characters with spaces + const cleanName = nameWithoutExt.replace(/[^a-zA-Z0-9]/g, ' '); + // Capitalize first letter and make it SEO friendly + return cleanName.charAt(0).toUpperCase() + cleanName.slice(1); +} + +// Toggle enhance button based on keyword input +function toggleEnhanceButton() { + enhanceBtn.disabled = keywordInput.value.trim() === ''; +} + +// Enhance keywords with AI +async function enhanceKeywords() { + const keywordText = keywordInput.value.trim(); + if (keywordText === '') return; + + // Show loading state + enhanceBtn.innerHTML = ' Generating SEO Keywords...'; + enhanceBtn.disabled = true; + + try { + // Call AI API to enhance keywords + const enhancedKeywords = await callAIKeywordEnhancement(keywordText); + + // Better handling for keyphrases - split by comma primarily, preserve phrases + const newKeywords = enhancedKeywords.split(',').map(k => k.trim()).filter(k => k !== ''); + + // Add new keywords/keyphrases to the list + newKeywords.forEach(keyword => { + // Avoid duplicates by checking both exact match and similarity + const lowercaseKeyword = keyword.toLowerCase(); + const isDuplicate = keywords.some(existing => + existing.toLowerCase() === lowercaseKeyword || + existing.toLowerCase().includes(lowercaseKeyword) || + lowercaseKeyword.includes(existing.toLowerCase()) + ); + + if (!isDuplicate) { + keywords.push(keyword); + } + }); + + // Update keywords display + updateKeywordsDisplay(); + + // Clear input + keywordInput.value = ''; + + // Analyze images with vision AI and generate new filenames + await analyzeImagesAndGenerateNames(); + } catch (error) { + console.error('Error enhancing keywords:', error); + alert('An error occurred while enhancing keywords. You may have hit rate limits. Please try again in a moment.'); + + // Fallback to simple keyword enhancement + fallbackToSimpleEnhancement(keywordText); + } finally { + // Reset button + enhanceBtn.innerHTML = ' Generate SEO Keywords'; + enhanceBtn.disabled = keywordInput.value.trim() === ''; + } +} + +// Fallback to simple keyword enhancement +function fallbackToSimpleEnhancement(keywordText) { + const newKeywords = keywordText.split(/[, ]+/).filter(k => k !== ''); + newKeywords.forEach(keyword => { + if (!keywords.includes(keyword)) { + keywords.push(keyword); + } + }); + + // Add some simulated AI-enhanced keywords + const aiKeywords = [ + 'SEO optimized', + 'high quality', + 'professional', + 'digital', + 'modern' + ]; + + aiKeywords.forEach(keyword => { + if (!keywords.includes(keyword)) { + keywords.push(keyword); + } + }); + + // Update keywords display + updateKeywordsDisplay(); + + // Generate new filenames for images + generateNewFileNames(); +} + +// Detect language from provided keywords +function detectLanguage(keywords) { + const lowerKeywords = keywords.toLowerCase(); + + // Common patterns for different languages + const languagePatterns = { + dutch: /\b(de|het|en|in|van|met|bij|naar|voor|amsterdam|nederland|barbier|kapper|winkel|kapperszaak|amsterdam|rotterdam|den haag|utrecht)\b/, + german: /\b(der|die|das|und|in|von|mit|bei|nach|für|berlin|münchen|deutschland|friseur|laden|geschäft|hamburg|köln)\b/, + french: /\b(le|la|les|de|du|des|et|dans|avec|chez|vers|pour|paris|france|coiffeur|magasin|boutique|lyon|marseille)\b/, + spanish: /\b(el|la|los|las|de|del|y|en|con|para|hacia|madrid|españa|peluquero|tienda|negocio|barcelona|valencia)\b/, + italian: /\b(il|la|lo|gli|le|di|del|e|in|con|per|verso|roma|italia|parrucchiere|negozio|bottega|milano|napoli)\b/, + }; + + for (const [lang, pattern] of Object.entries(languagePatterns)) { + if (pattern.test(lowerKeywords)) { + return lang; + } + } + + return 'english'; // Default to English +} + +// Call AI API to enhance keywords +async function callAIKeywordEnhancement(keywords) { + const detectedLanguage = detectLanguage(keywords); + const languageInstruction = detectedLanguage === 'english' ? '' : `Respond in ${detectedLanguage} language. `; + + const prompt = `You are an expert SEO consultant specializing in high-performing search keywords and keyphrases that people actually search for on Google. + +${languageInstruction}Based on these seed keywords: "${keywords}" + +Generate 8-12 SEO-optimized keywords and keyphrases that: +1. Are commonly searched terms people actually use on Google +2. Include both short keywords (1-2 words) and longer keyphrases (3-5 words) +3. Focus on commercial intent and local SEO when relevant +4. Include location-specific terms if a location is mentioned +5. Consider trending and high-volume search terms +6. Think like a customer searching for these services/products + +Examples of good SEO keyphrases: +- "african barbershop amsterdam" +- "professional hair cutting services" +- "luxury wedding photography" +- "modern interior design ideas" +- "organic food delivery service" + +Return only the keywords and keyphrases separated by commas, no explanations or extra text. + +Seed keywords: ${keywords}`; + + const response = await fetch(AI_CONFIG.API_URL, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${AI_CONFIG.API_KEY}`, + 'Content-Type': 'application/json', + 'HTTP-Referer': window.location.href, + 'X-Title': 'SEO Image Renamer' + }, + body: JSON.stringify({ + model: AI_CONFIG.DEEPSEEK_MODEL, + messages: [ + { + role: "user", + content: prompt + } + ] + }) + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error('API Error Response:', errorText); + throw new Error(`API request failed with status ${response.status}: ${errorText}`); + } + + const data = await response.json(); + return data.choices[0].message.content.trim(); +} + +// Analyze images with vision AI and generate new filenames +async function analyzeImagesAndGenerateNames() { + if (keywords.length === 0) return; + + // Show loading state for each image + document.querySelectorAll('.new-name-input').forEach(input => { + input.disabled = true; + input.placeholder = 'Analyzing image and generating filename...'; + }); + + try { + // Analyze each image with vision AI and generate unique filenames + const usedNames = new Set(); + + for (let i = 0; i < uploadedImages.length; i++) { + const image = uploadedImages[i]; + + // Analyze image with vision AI + const visionKeywords = await analyzeImageWithVisionAI(image.src); + image.visionKeywords = visionKeywords; + + // Generate unique filename + let newName; + let attempts = 0; + do { + newName = await generateUniqueFilename(image, visionKeywords, usedNames); + attempts++; + } while (usedNames.has(newName.toLowerCase()) && attempts < 10); + + // Add to used names + usedNames.add(newName.toLowerCase()); + + // Update the image with the new name + const extension = image.name.substring(image.name.lastIndexOf('.')); + image.newName = `${newName.substring(0, 50)}${extension}`; + } + + // Update images preview + updateImagesPreview(); + + // Enable download button + downloadBtn.disabled = false; + } catch (error) { + console.error('Error analyzing images:', error); + alert('An error occurred while analyzing images. You may have hit rate limits or the AI service is temporarily unavailable. Please try again in a moment.'); + + // Revert to simple filename generation + fallbackToSimpleNaming(); + } finally { + // Re-enable inputs + document.querySelectorAll('.new-name-input').forEach(input => { + input.disabled = false; + input.placeholder = ''; + }); + } +} + +// Analyze image with Grok-2-Vision AI +async function analyzeImageWithVisionAI(imageSrc) { + const prompt = "Analyze this image and provide exactly 2 descriptive keywords that describe the main subject, object, or action in the image. Focus on what would be useful for SEO image naming. Return only the two keywords separated by a space, nothing else."; + + const response = await fetch(AI_CONFIG.GROK_API_URL, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${AI_CONFIG.GROK_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: AI_CONFIG.GROK_VISION_MODEL, + messages: [ + { + role: "user", + content: [ + { + type: "text", + text: prompt + }, + { + type: "image_url", + image_url: { + url: imageSrc + } + } + ] + } + ], + max_tokens: 50, + temperature: 0.3 + }) + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error('Grok Vision API Error Response:', errorText); + throw new Error(`Grok Vision API request failed with status ${response.status}: ${errorText}`); + } + + const data = await response.json(); + const visionResponse = data.choices[0].message.content.trim(); + + // Clean up response and extract keywords + const keywords = visionResponse.split(' ').filter(word => + word.length > 2 && + !word.includes('.') && + !word.includes(',') && + /^[a-zA-Z]+$/.test(word) + ).slice(0, 2); + + return keywords.length >= 2 ? keywords : ['image', 'content']; // Fallback if extraction fails +} + +// Generate unique filename +async function generateUniqueFilename(image, visionKeywords, usedNames) { + const detectedLanguage = detectLanguage(keywords.join(' ')); + const languageInstruction = detectedLanguage === 'english' ? '' : `Generate the filename in ${detectedLanguage} language. `; + + // Ensure we use ALL keywords, not just first 5 + const keywordString = keywords.join(', '); + const visionString = visionKeywords.join(' '); + + const prompt = `You are an SEO filename expert. Create an SEO-optimized filename for this image that will rank well in search engines. + +${languageInstruction}REQUIREMENTS: +1. MUST include at least 2-3 of these user keywords: ${keywordString} +2. MUST incorporate image content: ${visionString} +3. Create natural, searchable phrases people actually type into Google +4. Use 4-8 words maximum +5. Focus on commercial intent and local SEO when applicable +6. Think like someone searching for this image online + +EXAMPLES of good SEO filenames: +- "professional african barbershop amsterdam" +- "luxury wedding photography netherlands" +- "modern restaurant interior design" +- "organic food delivery service menu" +- "vintage car classic automobile show" + +Original filename reference: ${image.name} + +Create ONE natural filename that combines the keywords strategically for maximum SEO impact. +Return only the filename with spaces between words, no punctuation, no file extension.`; + + const response = await fetch(AI_CONFIG.API_URL, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${AI_CONFIG.API_KEY}`, + 'Content-Type': 'application/json', + 'HTTP-Referer': window.location.href, + 'X-Title': 'SEO Image Renamer' + }, + body: JSON.stringify({ + model: AI_CONFIG.DEEPSEEK_MODEL, + messages: [ + { + role: "user", + content: prompt + } + ] + }) + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error('Filename generation API Error Response:', errorText); + throw new Error(`Filename generation API request failed with status ${response.status}: ${errorText}`); + } + + const data = await response.json(); + return data.choices[0].message.content.trim(); +} + +// Fallback to simple naming +function fallbackToSimpleNaming() { + if (keywords.length === 0) return; + + const usedNames = new Set(); + + uploadedImages.forEach((image, index) => { + // Use more keywords for better SEO - cycle through all keywords + const keywordCount = Math.min(keywords.length, 4); // Use up to 4 keywords + const startIndex = index % Math.max(1, keywords.length - 2); // Rotate starting point + const selectedKeywords = keywords.slice(startIndex, startIndex + keywordCount); + const keywordString = selectedKeywords.join(' '); + + const nameWithoutExt = image.name.substring(0, image.name.lastIndexOf('.')); + const extension = image.name.substring(image.name.lastIndexOf('.')); + + // Create SEO-focused name - prioritize keywords over original filename + let newName = keywordString.length > 0 ? keywordString : nameWithoutExt; + newName = newName.substring(0, 45); // Leave room for counter + + // Ensure uniqueness + let counter = 1; + let uniqueName = newName; + while (usedNames.has(uniqueName.toLowerCase())) { + uniqueName = `${newName} ${counter}`; + counter++; + } + + usedNames.add(uniqueName.toLowerCase()); + image.newName = uniqueName + extension; + }); + + // Update images preview + updateImagesPreview(); + + // Enable download button + downloadBtn.disabled = false; +} + +// Fallback function to generate new filenames without AI +function generateNewFileNames() { + if (keywords.length === 0) return; + + const usedNames = new Set(); + + uploadedImages.forEach((image, index) => { + // Use more keywords for better SEO - cycle through all keywords + const keywordCount = Math.min(keywords.length, 4); // Use up to 4 keywords + const startIndex = index % Math.max(1, keywords.length - 2); // Rotate starting point + const selectedKeywords = keywords.slice(startIndex, startIndex + keywordCount); + const keywordString = selectedKeywords.join(' '); + + const nameWithoutExt = image.name.substring(0, image.name.lastIndexOf('.')); + const extension = image.name.substring(image.name.lastIndexOf('.')); + + // Create SEO-focused name - prioritize keywords over original filename + let newName = keywordString.length > 0 ? keywordString : nameWithoutExt; + newName = newName.substring(0, 45); // Leave room for counter + + // Ensure uniqueness + let counter = 1; + let uniqueName = newName; + while (usedNames.has(uniqueName.toLowerCase())) { + uniqueName = `${newName} ${counter}`; + counter++; + } + + usedNames.add(uniqueName.toLowerCase()); + image.newName = uniqueName + extension; + }); + + // Update images preview + updateImagesPreview(); + + // Enable download button + downloadBtn.disabled = false; +} + +// Highlight vision keywords in filename +function highlightVisionKeywords(filename, visionKeywords) { + if (!visionKeywords || visionKeywords.length === 0) { + return filename; + } + + let highlightedName = filename; + visionKeywords.forEach(keyword => { + if (keyword && keyword.trim()) { + const regex = new RegExp(`\\b${keyword.trim()}\\b`, 'gi'); + highlightedName = highlightedName.replace(regex, `${keyword}`); + } + }); + + return highlightedName; +} + +// Update keywords display +function updateKeywordsDisplay() { + keywordsDisplay.innerHTML = ''; + + keywords.forEach((keyword, index) => { + const keywordChip = document.createElement('div'); + keywordChip.className = 'keyword-chip'; + keywordChip.innerHTML = ` + ${keyword} + + `; + keywordsDisplay.appendChild(keywordChip); + }); + + // Add event listeners to remove buttons + document.querySelectorAll('.remove-keyword').forEach(button => { + button.addEventListener('click', (e) => { + const index = parseInt(e.target.getAttribute('data-index')); + keywords.splice(index, 1); + updateKeywordsDisplay(); + generateNewFileNames(); + }); + }); +} + +// Update images preview +function updateImagesPreview() { + imagesContainer.innerHTML = ''; + + uploadedImages.forEach((image, index) => { + const imageCard = document.createElement('div'); + imageCard.className = 'image-card'; + + // Get filename without extension for highlighting + const nameWithoutExt = image.newName.substring(0, image.newName.lastIndexOf('.')); + const extension = image.newName.substring(image.newName.lastIndexOf('.')); + const highlightedName = highlightVisionKeywords(nameWithoutExt, image.visionKeywords); + + imageCard.innerHTML = ` + ${image.name} +
+
Original: ${image.name}
+ ${image.visionKeywords && image.visionKeywords.length > 0 ? + `
Vision AI: ${image.visionKeywords.join(', ')}
` : ''} +
+ +
${highlightedName}${extension}
+ +
+
+ `; + imagesContainer.appendChild(imageCard); + }); + + // Add event listeners to name inputs + document.querySelectorAll('.new-name-input').forEach(input => { + input.addEventListener('input', (e) => { + const index = parseInt(e.target.getAttribute('data-index')); + uploadedImages[index].newName = e.target.value; + }); + }); +} + +// Download images as ZIP +async function downloadImages() { + if (uploadedImages.length === 0) return; + + // Show loading state + downloadBtn.innerHTML = ' Preparing Download...'; + downloadBtn.disabled = true; + + try { + // Create a new ZIP file + const zip = new JSZip(); + + // Add each image to the ZIP with its new name + for (const image of uploadedImages) { + // Convert data URL to blob + const blob = await fetch(image.src).then(res => res.blob()); + zip.file(image.newName, blob); + } + + // Generate the ZIP file + const content = await zip.generateAsync({type: "blob"}); + + // Create download link + const url = URL.createObjectURL(content); + const a = document.createElement('a'); + a.href = url; + a.download = 'renamed-images.zip'; + document.body.appendChild(a); + a.click(); + + // Clean up + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 100); + + // Reset button + downloadBtn.innerHTML = ' Download Renamed Images as ZIP'; + downloadBtn.disabled = false; + } catch (error) { + console.error('Error creating ZIP file:', error); + alert('An error occurred while creating the ZIP file. Please try again.'); + + // Reset button + downloadBtn.innerHTML = ' Download Renamed Images as ZIP'; + downloadBtn.disabled = false; + } +} + +// Initialize the page +function init() { + // Initialize configuration first + initializeConfig(); + + // Set up any initial state + downloadBtn.disabled = true; +} + +// Call init when page loads +init(); \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..fcfb787 --- /dev/null +++ b/styles.css @@ -0,0 +1,1196 @@ +/* Reset and base styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + /* Modern color palette */ + --primary: #6366f1; + --primary-light: #818cf8; + --primary-dark: #4f46e5; + --secondary: #06b6d4; + --success: #10b981; + --warning: #f59e0b; + --danger: #ef4444; + + /* Neutral colors */ + --gray-50: #f9fafb; + --gray-100: #f3f4f6; + --gray-200: #e5e7eb; + --gray-300: #d1d5db; + --gray-400: #9ca3af; + --gray-500: #6b7280; + --gray-600: #4b5563; + --gray-700: #374151; + --gray-800: #1f2937; + --gray-900: #111827; + + /* Typography */ + --font-sans: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif; + --font-mono: 'JetBrains Mono', 'SF Mono', Consolas, monospace; + + /* Spacing */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 1rem; + --space-lg: 1.5rem; + --space-xl: 2rem; + --space-2xl: 3rem; + --space-3xl: 4rem; + + /* Border radius */ + --radius-sm: 0.25rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + --radius-2xl: 1.5rem; + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); +} + +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); + +body { + font-family: var(--font-sans); + line-height: 1.6; + color: var(--gray-800); + background-color: white; + font-size: 16px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.container { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--space-lg); +} + +/* Button styles */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-sm); + padding: var(--space-sm) var(--space-lg); + border-radius: var(--radius-lg); + text-decoration: none; + font-weight: 500; + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + border: none; + outline: none; + position: relative; + overflow: hidden; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none !important; +} + +.btn-primary { + background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); + color: white; + box-shadow: var(--shadow-sm); +} + +.btn-primary:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: var(--shadow-lg); + background: linear-gradient(135deg, var(--primary-light) 0%, var(--primary) 100%); +} + +.btn-outline { + background: transparent; + color: var(--gray-700); + border: 2px solid var(--gray-200); +} + +.btn-outline:hover:not(:disabled) { + background: var(--gray-50); + border-color: var(--gray-300); + transform: translateY(-1px); +} + +.btn-success { + background: linear-gradient(135deg, var(--success) 0%, #059669 100%); + color: white; + box-shadow: var(--shadow-sm); +} + +.btn-success:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: var(--shadow-lg); + background: linear-gradient(135deg, #34d399 0%, var(--success) 100%); +} + +.btn-large { + padding: var(--space-md) var(--space-xl); + font-size: 1rem; + font-weight: 600; +} + +/* Header */ +header { + position: sticky; + top: 0; + z-index: 100; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-bottom: 1px solid var(--gray-100); +} + +header .container { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-lg); +} + +.logo h1 { + font-size: 1.5rem; + font-weight: 700; + color: var(--gray-900); + display: flex; + align-items: center; + gap: var(--space-sm); +} + +.logo h1 i { + color: var(--primary); +} + +nav ul { + display: flex; + align-items: center; + gap: var(--space-xl); + list-style: none; +} + +nav ul li a { + text-decoration: none; + color: var(--gray-600); + font-weight: 500; + transition: color 0.2s; +} + +nav ul li a:hover { + color: var(--primary); +} + +.mobile-menu { + display: none; + font-size: 1.5rem; + color: var(--gray-600); + cursor: pointer; +} + +/* Hero section */ +.hero { + padding: var(--space-3xl) 0; + background: linear-gradient(135deg, + rgba(99, 102, 241, 0.03) 0%, + rgba(6, 182, 212, 0.02) 50%, + rgba(16, 185, 129, 0.03) 100%); + position: relative; + overflow: hidden; +} + +.hero::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: radial-gradient(ellipse at top left, rgba(99, 102, 241, 0.1) 0%, transparent 50%), + radial-gradient(ellipse at bottom right, rgba(6, 182, 212, 0.08) 0%, transparent 50%); + pointer-events: none; +} + +.hero-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-3xl); + align-items: center; + position: relative; + z-index: 1; +} + +.hero-content { + display: flex; + flex-direction: column; + gap: var(--space-lg); +} + +.hero-badge { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); + color: white; + padding: var(--space-sm) var(--space-md); + border-radius: var(--radius-2xl); + font-size: 0.875rem; + font-weight: 600; + width: fit-content; + box-shadow: var(--shadow-md); +} + +.hero h1 { + font-size: clamp(2.5rem, 5vw, 3.5rem); + font-weight: 800; + line-height: 1.1; + color: var(--gray-900); + letter-spacing: -0.02em; +} + +.hero p { + font-size: 1.125rem; + color: var(--gray-600); + line-height: 1.7; + max-width: 500px; +} + +.hero-features { + display: flex; + flex-wrap: wrap; + gap: var(--space-lg); + margin: var(--space-md) 0; +} + +.mini-feature { + display: flex; + align-items: center; + gap: var(--space-sm); + color: var(--gray-700); + font-size: 0.875rem; + font-weight: 500; +} + +.mini-feature i { + color: var(--primary); + font-size: 1rem; + width: 20px; + text-align: center; +} + +.hero-stats { + display: flex; + gap: var(--space-xl); + margin-top: var(--space-lg); +} + +.stat { + display: flex; + flex-direction: column; + gap: var(--space-xs); +} + +.stat-number { + font-size: 2rem; + font-weight: 700; + color: var(--gray-900); + line-height: 1; +} + +.stat-label { + font-size: 0.875rem; + color: var(--gray-500); + font-weight: 500; +} + +/* Hero upload area */ +.hero-upload { + display: flex; + justify-content: center; + align-items: center; +} + +.drop-area { + width: 100%; + max-width: 480px; + min-height: 320px; + border: 2px dashed var(--gray-300); + border-radius: var(--radius-2xl); + background: white; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; + position: relative; + overflow: hidden; +} + +.drop-area::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, + rgba(99, 102, 241, 0.02) 0%, + rgba(6, 182, 212, 0.01) 100%); + opacity: 0; + transition: opacity 0.3s; +} + +.drop-area:hover::before, +.drop-area.dragover::before { + opacity: 1; +} + +.drop-area:hover, +.drop-area.dragover { + border-color: var(--primary); + transform: translateY(-4px); + box-shadow: var(--shadow-xl); +} + +.drop-area:hover .upload-btn, +.drop-area.dragover .upload-btn { + transform: scale(1.05); + box-shadow: var(--shadow-lg); +} + +.drop-area-content { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-lg); + text-align: center; + padding: var(--space-xl); + position: relative; + z-index: 1; +} + +.upload-icon { + width: 80px; + height: 80px; + background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); + border-radius: var(--radius-2xl); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: var(--space-md); + box-shadow: var(--shadow-lg); +} + +.upload-icon i { + font-size: 2rem; + color: white; +} + +.drop-area h3 { + font-size: 1.25rem; + font-weight: 600; + color: var(--gray-900); + margin-bottom: var(--space-sm); +} + +.drop-area p { + color: var(--gray-500); + font-size: 0.875rem; + margin-bottom: var(--space-lg); +} + +.upload-btn { + background: var(--gray-900); + color: white; + border: none; + padding: var(--space-md) var(--space-xl); + border-radius: var(--radius-lg); + font-weight: 600; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + align-items: center; + gap: var(--space-sm); + font-size: 0.875rem; +} + +.upload-btn:hover { + background: var(--gray-800); + transform: translateY(-2px) scale(1.05); + box-shadow: var(--shadow-lg); +} + +.supported-formats { + margin-top: var(--space-md); + padding-top: var(--space-md); + border-top: 1px solid var(--gray-200); + font-size: 0.75rem; + color: var(--gray-400); + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 500; +} + +/* Workflow section */ +.workflow-section { + padding: var(--space-3xl) 0; + background: var(--gray-50); +} + +.workflow-step { + background: white; + border-radius: var(--radius-2xl); + padding: var(--space-2xl); + margin-bottom: var(--space-xl); + box-shadow: var(--shadow-sm); + border: 1px solid var(--gray-100); +} + +.step-header { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + margin-bottom: var(--space-xl); + gap: var(--space-md); +} + +.step-header i { + width: 60px; + height: 60px; + background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); + color: white; + border-radius: var(--radius-xl); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + box-shadow: var(--shadow-md); +} + +.step-header h3 { + font-size: 1.5rem; + font-weight: 700; + color: var(--gray-900); +} + +.step-header p { + color: var(--gray-600); + font-size: 1rem; +} + +/* Keywords section */ +.keywords-input { + display: flex; + gap: var(--space-md); + max-width: 600px; + margin: 0 auto var(--space-xl); +} + +.keywords-input input { + flex: 1; + padding: var(--space-md); + border: 2px solid var(--gray-200); + border-radius: var(--radius-lg); + font-size: 1rem; + transition: all 0.2s; + background: white; +} + +.keywords-input input:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); +} + +.keywords-display { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: var(--space-md); + margin-top: var(--space-lg); +} + +.keyword-chip { + background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%); + color: white; + padding: var(--space-sm) var(--space-md); + border-radius: var(--radius-2xl); + display: flex; + align-items: center; + gap: var(--space-sm); + font-size: 0.875rem; + font-weight: 500; + box-shadow: var(--shadow-sm); + transition: all 0.2s; +} + +.keyword-chip:hover { + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.keyword-chip .remove-keyword { + background: rgba(255, 255, 255, 0.2); + border: none; + color: white; + cursor: pointer; + border-radius: 50%; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.75rem; + transition: background 0.2s; +} + +.keyword-chip .remove-keyword:hover { + background: rgba(255, 255, 255, 0.3); +} + +/* Images container */ +.images-container { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: var(--space-xl); + margin-bottom: var(--space-2xl); +} + +.image-card { + background: white; + border-radius: var(--radius-xl); + overflow: hidden; + box-shadow: var(--shadow-md); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + border: 1px solid var(--gray-100); +} + +.image-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-xl); +} + +.image-thumbnail { + width: 100%; + height: 200px; + object-fit: cover; +} + +.image-info { + padding: var(--space-lg); + display: flex; + flex-direction: column; + gap: var(--space-md); +} + +.image-info .original-name { + font-size: 0.75rem; + color: var(--gray-500); + background: var(--gray-50); + padding: var(--space-sm); + border-radius: var(--radius-md); + word-break: break-all; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Vision AI and filename highlighting styles */ +.vision-keywords { + font-size: 0.75rem; + color: var(--warning); + background: rgba(245, 158, 11, 0.1); + padding: var(--space-sm); + border-radius: var(--radius-md); + border-left: 3px solid var(--warning); + font-weight: 500; +} + +.vision-tags { + font-weight: 600; + color: #d97706; +} + +.filename-display { + font-weight: 600; + color: var(--gray-900); + word-break: break-all; + padding: var(--space-md); + background: var(--gray-50); + border-radius: var(--radius-md); + border: 2px solid var(--gray-100); + line-height: 1.4; + font-size: 0.875rem; + transition: all 0.2s; +} + +.vision-highlight { + background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); + color: #92400e; + padding: 2px 6px; + border-radius: var(--radius-sm); + font-weight: 700; + border: 1px solid #fbbf24; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + display: inline-block; +} + +.new-name-container { + display: flex; + flex-direction: column; + gap: var(--space-sm); +} + +.new-name-container label { + font-weight: 600; + color: var(--gray-700); + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.new-name-input { + width: 100%; + padding: var(--space-md); + border: 2px solid var(--gray-200); + border-radius: var(--radius-md); + font-size: 0.875rem; + transition: all 0.2s; + background: white; +} + +.new-name-input:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); +} + +.new-name-input:disabled { + background: var(--gray-50); + cursor: not-allowed; + opacity: 0.6; +} + +.actions { + text-align: center; + padding: var(--space-xl) 0; +} + +/* Section headers */ +.section-header { + text-align: center; + margin-bottom: var(--space-3xl); +} + +.section-header h2 { + font-size: clamp(2rem, 4vw, 2.5rem); + font-weight: 700; + color: var(--gray-900); + margin-bottom: var(--space-md); + letter-spacing: -0.02em; +} + +.section-header p { + font-size: 1.125rem; + color: var(--gray-600); + max-width: 600px; + margin: 0 auto; +} + +/* Features section */ +.features { + padding: var(--space-3xl) 0; + background: white; +} + +.features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--space-2xl); +} + +.feature-card { + background: white; + padding: var(--space-2xl); + border-radius: var(--radius-2xl); + text-align: center; + box-shadow: var(--shadow-sm); + border: 1px solid var(--gray-100); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.feature-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, + rgba(99, 102, 241, 0.02) 0%, + rgba(6, 182, 212, 0.01) 100%); + opacity: 0; + transition: opacity 0.3s; +} + +.feature-card:hover::before { + opacity: 1; +} + +.feature-card:hover { + transform: translateY(-8px); + box-shadow: var(--shadow-xl); + border-color: var(--gray-200); +} + +.feature-icon { + width: 80px; + height: 80px; + background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); + color: white; + border-radius: var(--radius-2xl); + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto var(--space-lg); + font-size: 2rem; + box-shadow: var(--shadow-lg); + position: relative; + z-index: 1; +} + +.feature-card h3 { + font-size: 1.25rem; + font-weight: 600; + color: var(--gray-900); + margin-bottom: var(--space-md); + position: relative; + z-index: 1; +} + +.feature-card p { + color: var(--gray-600); + line-height: 1.6; + position: relative; + z-index: 1; +} + +/* How it works section */ +.how-it-works { + padding: var(--space-3xl) 0; + background: var(--gray-50); +} + +.steps { + display: flex; + justify-content: space-between; + gap: var(--space-2xl); + flex-wrap: wrap; +} + +.step { + flex: 1; + min-width: 280px; + text-align: center; + padding: 0 var(--space-lg); + position: relative; +} + +.step:not(:last-child)::after { + content: ""; + position: absolute; + top: 40px; + right: calc(-1 * var(--space-lg)); + width: var(--space-2xl); + height: 2px; + background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); + border-radius: 1px; +} + +.step-number { + width: 80px; + height: 80px; + background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 2rem; + font-weight: 700; + margin: 0 auto var(--space-lg); + box-shadow: var(--shadow-lg); + position: relative; + z-index: 1; +} + +.step h3 { + font-size: 1.25rem; + font-weight: 600; + color: var(--gray-900); + margin-bottom: var(--space-md); +} + +.step p { + color: var(--gray-600); + line-height: 1.6; +} + +/* Pricing section */ +.pricing { + padding: var(--space-3xl) 0; + background: white; +} + +.pricing-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: var(--space-xl); + max-width: 1000px; + margin: 0 auto; +} + +.pricing-card { + background: white; + border-radius: var(--radius-2xl); + padding: var(--space-2xl); + text-align: center; + box-shadow: var(--shadow-md); + position: relative; + overflow: hidden; + border: 2px solid var(--gray-100); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.pricing-card.featured { + transform: scale(1.05); + box-shadow: var(--shadow-xl); + border-color: var(--primary); + background: linear-gradient(135deg, + rgba(99, 102, 241, 0.02) 0%, + rgba(6, 182, 212, 0.01) 100%); +} + +.featured-badge { + position: absolute; + top: var(--space-lg); + right: calc(-1 * var(--space-lg)); + background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); + color: white; + padding: var(--space-sm) var(--space-2xl); + transform: rotate(45deg); + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + box-shadow: var(--shadow-md); +} + +.pricing-card h3 { + font-size: 1.5rem; + font-weight: 600; + color: var(--gray-900); + margin-bottom: var(--space-lg); +} + +.price { + font-size: 3rem; + font-weight: 800; + color: var(--gray-900); + margin-bottom: var(--space-xl); + line-height: 1; +} + +.price span { + font-size: 1rem; + color: var(--gray-500); + font-weight: 500; +} + +.pricing-card ul { + list-style: none; + margin-bottom: var(--space-xl); + text-align: left; +} + +.pricing-card ul li { + padding: var(--space-md) 0; + border-bottom: 1px solid var(--gray-100); + display: flex; + align-items: center; + gap: var(--space-md); +} + +.pricing-card ul li::before { + content: "\f00c"; + font-family: "Font Awesome 5 Free"; + font-weight: 900; + color: var(--success); + width: 20px; +} + +.pricing-card .btn { + width: 100%; +} + +/* Footer */ +footer { + background: var(--gray-900); + color: var(--gray-300); + padding: var(--space-3xl) 0 var(--space-xl); +} + +.footer-content { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + margin-bottom: var(--space-2xl); + gap: var(--space-2xl); +} + +.footer-logo h2 { + font-size: 1.5rem; + font-weight: 700; + color: white; + margin-bottom: var(--space-md); + display: flex; + align-items: center; + gap: var(--space-sm); +} + +.footer-logo h2 i { + color: var(--primary); +} + +.footer-logo p { + color: var(--gray-400); +} + +.footer-links { + display: flex; + gap: var(--space-3xl); +} + +.footer-column h4 { + color: white; + font-weight: 600; + margin-bottom: var(--space-lg); + position: relative; + padding-bottom: var(--space-sm); +} + +.footer-column h4::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 40px; + height: 2px; + background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); +} + +.footer-column ul { + list-style: none; +} + +.footer-column ul li { + margin-bottom: var(--space-md); +} + +.footer-column ul li a { + color: var(--gray-400); + text-decoration: none; + transition: color 0.2s; +} + +.footer-column ul li a:hover { + color: var(--primary); +} + +.footer-bottom { + text-align: center; + padding-top: var(--space-xl); + border-top: 1px solid var(--gray-800); + color: var(--gray-500); + font-size: 0.875rem; +} + +/* Responsive design */ +@media (max-width: 1024px) { + .hero-grid { + grid-template-columns: 1fr; + gap: var(--space-2xl); + text-align: center; + } + + .hero-upload { + order: -1; + } + + .drop-area { + max-width: 400px; + min-height: 280px; + } + + .steps { + flex-direction: column; + align-items: center; + } + + .step:not(:last-child)::after { + display: none; + } + + .footer-content { + flex-direction: column; + gap: var(--space-xl); + } + + .footer-links { + gap: var(--space-xl); + } +} + +@media (max-width: 768px) { + :root { + --space-3xl: 3rem; + } + + .container { + padding: 0 var(--space-md); + } + + header .container { + padding: var(--space-md); + } + + nav ul { + display: none; + } + + .mobile-menu { + display: block; + } + + .hero { + padding: var(--space-2xl) 0; + } + + .hero-content { + gap: var(--space-md); + } + + .hero-features { + flex-direction: column; + gap: var(--space-md); + } + + .hero-stats { + justify-content: center; + } + + .keywords-input { + flex-direction: column; + } + + .images-container { + grid-template-columns: 1fr; + } + + .features-grid { + grid-template-columns: 1fr; + gap: var(--space-xl); + } + + .pricing-card.featured { + transform: none; + } + + .footer-links { + flex-direction: column; + gap: var(--space-lg); + } +} + +@media (max-width: 480px) { + .container { + padding: 0 var(--space-sm); + } + + .drop-area { + min-height: 240px; + } + + .drop-area-content { + padding: var(--space-lg); + } + + .upload-icon { + width: 60px; + height: 60px; + } + + .upload-icon i { + font-size: 1.5rem; + } + + .workflow-step { + padding: var(--space-lg); + } + + .feature-card { + padding: var(--space-lg); + } + + .pricing-card { + padding: var(--space-lg); + } +} + +/* Utility classes */ +.hidden { + display: none !important; +} + +.fade-in { + animation: fadeIn 0.5s ease-in-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: var(--gray-100); +} + +::-webkit-scrollbar-thumb { + background: var(--gray-300); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--gray-400); +} \ No newline at end of file