document.addEventListener('DOMContentLoaded', function() { // State management let currentImages = []; let currentQuery = ''; let currentPage = 1; let isLoading = false; // DOM Elements const heroSearchInput = document.getElementById('hero-search-input'); const heroSearchButton = document.getElementById('hero-search-button'); const searchResultsSection = document.getElementById('search-results'); const resultsContainer = document.getElementById('results-container'); const featuredContainer = document.getElementById('featured-container'); const resultsCount = document.getElementById('results-count'); const loadMoreBtn = document.getElementById('load-more-results'); const categories = document.querySelectorAll('.category-card'); const suggestionTags = document.querySelectorAll('.suggestion-tag'); const imageModal = document.getElementById('image-modal'); const modalClose = document.querySelector('.modal-close'); const modalImage = document.getElementById('modal-image'); const modalTitle = document.getElementById('modal-title'); const modalDescription = document.getElementById('modal-description'); const modalDownload = document.getElementById('modal-download'); const modalCopy = document.getElementById('modal-copy'); const navLinks = document.querySelectorAll('.nav-link'); // Initialize the app init(); async function checkServerStatus() { const SERVER_URL = 'https://pix-seek.com'; try { const response = await fetch(`${SERVER_URL}/health`, { method: 'GET', mode: 'cors' }); return response.ok; } catch (error) { console.warn('Server health check failed:', error); return false; } } async function init() { setupEventListeners(); updateCopyrightYear(); initSmoothScrolling(); addNotificationStyles(); // Check server status and notify users const isServerOnline = await checkServerStatus(); if (!isServerOnline) { showNotification('Demo mode active - server unavailable', 'info'); } loadFeaturedImages(); } function setupEventListeners() { // Search functionality if (heroSearchButton) { heroSearchButton.addEventListener('click', handleHeroSearch); } if (heroSearchInput) { heroSearchInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') { handleHeroSearch(); } }); } // Suggestion tags suggestionTags.forEach(tag => { tag.addEventListener('click', function() { const query = this.getAttribute('data-query'); if (heroSearchInput) { heroSearchInput.value = query; handleHeroSearch(); } }); }); // Category cards categories.forEach(category => { category.addEventListener('click', function() { const categoryName = this.getAttribute('data-category'); if (heroSearchInput) { heroSearchInput.value = categoryName; handleHeroSearch(); } }); }); // Load more button if (loadMoreBtn) { loadMoreBtn.addEventListener('click', loadMoreImages); } // Modal functionality if (modalClose) { modalClose.addEventListener('click', closeModal); } if (imageModal) { imageModal.addEventListener('click', function(e) { if (e.target === imageModal) { closeModal(); } }); } // Modal action buttons if (modalDownload) { modalDownload.addEventListener('click', downloadCurrentImage); } if (modalCopy) { modalCopy.addEventListener('click', copyImageLink); } // Navigation smooth scrolling navLinks.forEach(link => { link.addEventListener('click', function(e) { const href = this.getAttribute('href'); if (href && href.startsWith('#')) { e.preventDefault(); const target = document.querySelector(href); if (target) { target.scrollIntoView({ behavior: 'smooth' }); updateActiveNavLink(this); } } }); }); // Keyboard shortcuts document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && imageModal && imageModal.classList.contains('active')) { closeModal(); } }); } function handleHeroSearch() { if (!heroSearchInput) return; const query = heroSearchInput.value.trim(); if (query.length === 0) { showNotification('Please enter a search term', 'warning'); return; } searchImages(query); } async function searchImages(query, page = 1) { currentQuery = query; currentPage = page; isLoading = true; // Show search results section if (searchResultsSection) { searchResultsSection.classList.remove('hidden'); } // Clear results if it's a new search if (page === 1 && resultsContainer) { resultsContainer.innerHTML = createLoadingGrid(); currentImages = []; } // Scroll to results if (searchResultsSection) { searchResultsSection.scrollIntoView({ behavior: 'smooth' }); } try { const data = await fetchImages(query, page); if (data && data.images && data.images.length > 0) { if (page === 1) { currentImages = data.images; } else { currentImages = [...currentImages, ...data.images]; } if (resultsContainer) { renderImageGrid(currentImages, resultsContainer); } updateResultsCount(data.totalResults || currentImages.length); // Show notification if using backup images if (data.isBackup) { showNotification('Server unavailable - showing placeholder images', 'warning'); } if (loadMoreBtn) { loadMoreBtn.style.display = data.hasMore ? 'inline-flex' : 'none'; } } else { if (page === 1 && resultsContainer) { resultsContainer.innerHTML = `

No images found

Try searching for "${query}" with different keywords

`; updateResultsCount(0); } } } catch (error) { console.error('Error searching images:', error); if (resultsContainer) { resultsContainer.innerHTML = `

Search Error

Unable to search for images. Please try again later.

`; } } finally { isLoading = false; } } async function loadFeaturedImages() { try { const data = await fetchImages('featured', 1, 12); if (data && data.images && data.images.length > 0 && featuredContainer) { renderImageGrid(data.images, featuredContainer); // Show subtle notification if using backup images for featured section if (data.isBackup) { console.log('Featured images loaded from backup source'); } } else if (featuredContainer) { featuredContainer.innerHTML = `

Unable to load featured images

Please try refreshing the page

`; } } catch (error) { console.error('Error loading featured images:', error); if (featuredContainer) { featuredContainer.innerHTML = `

Loading Error

Unable to load featured images

`; } } } function loadMoreImages() { if (isLoading || !currentQuery) return; currentPage++; searchImages(currentQuery, currentPage); } async function fetchImages(query, page = 1, limit = 20) { const SERVER_URL = 'https://pix-seek.com'; try { let apiUrl; // Use different endpoints based on query type if (!query || query.toLowerCase() === 'featured' || query.toLowerCase() === 'random') { // For featured/random images, use the random endpoint apiUrl = `${SERVER_URL}/api/images/random?number=${limit}`; } else { // For search queries, use the search endpoint apiUrl = `${SERVER_URL}/api/images/search?q=${encodeURIComponent(query)}&num=${limit}`; } console.log('Fetching from server:', apiUrl); const response = await fetch(apiUrl, { method: 'GET', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Origin': window.location.origin }, mode: 'cors' }); if (!response.ok) { throw new Error(`Server responded with status: ${response.status}`); } const data = await response.json(); if (data && data.images && Array.isArray(data.images)) { return { images: data.images, totalResults: data.images.length * 3, // Estimate more results hasMore: page < 2, // Limit to 2 pages for now currentPage: page }; } else { console.warn('Invalid response format from server:', data); return { images: [], totalResults: 0, hasMore: false, currentPage: page }; } } catch (error) { console.error('Error fetching from server:', error); // Fallback to placeholder images if server fails console.log('Falling back to placeholder images...'); const fallbackImages = await generateMockImages(query, limit); return { images: fallbackImages, totalResults: fallbackImages.length, hasMore: false, currentPage: page, isBackup: true }; } } // Function to check if an image URL exists and returns 200 async function checkImageExists(url) { return new Promise((resolve) => { const img = new Image(); const timeout = setTimeout(() => { resolve(false); }, 1500); // Reduced timeout for faster response img.onload = () => { clearTimeout(timeout); resolve(true); }; img.onerror = () => { clearTimeout(timeout); resolve(false); }; img.src = url; }); } async function generateMockImages(query, limit) { // Use the same predefined queries as the server for consistency and variety const predefinedQueries = [ "nature", "sunset", "mountains", "ocean", "forest", "architecture", "abstract", "animals", "cityscape", "flowers", "clouds", "desert", "waterfall", "beach", "snow", "autumn", "spring", "landscape", "sky", "trees", "lake", "river", "field", "garden", "wildlife", "technology", "computer", "smartphone", "electronics", "innovation", "business", "office", "meeting", "workplace", "professional", "people", "portrait", "teamwork", "collaboration", "diversity", "art", "painting", "design", "creative", "minimalist", "modern", "vintage", "retro", "pattern", "texture", "geometric", "colorful" ]; // For random/featured searches, pick from predefined queries let actualQuery = query; if (!query || query.toLowerCase() === 'featured' || query.toLowerCase() === 'random') { actualQuery = predefinedQueries[Math.floor(Math.random() * predefinedQueries.length)]; } // Create a hash-like seed from the query for consistent results per query const queryHash = actualQuery.split('').reduce((a, b) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0); // Use the hash as seed for consistent random generation per query const seededRandom = (seed) => { const x = Math.sin(seed) * 10000; return x - Math.floor(x); }; const validImages = []; let attempts = 0; const maxAttempts = limit * 3; // Try up to 3x the requested amount to find valid images // Generate and validate images while (validImages.length < limit && attempts < maxAttempts) { const imageSeed = Math.abs(queryHash) + attempts * 1000 + Math.floor(seededRandom(queryHash + attempts) * 10000); const width = 400 + Math.floor(seededRandom(imageSeed + 1) * 600); const height = 300 + Math.floor(seededRandom(imageSeed + 2) * 500); // Generate unique image ID that's consistent per query+index const imageId = `img_${Math.abs(queryHash)}_${attempts}_${imageSeed % 100000}`; // Use Picsum with known working ID range (1-1084 are verified to exist) const picsumId = 1 + (Math.abs(imageSeed) % 1084); // Picsum has 1084 verified images const picsumUrl = `https://picsum.photos/id/${picsumId}/${width}/${height}`; const picsumThumb = `https://picsum.photos/id/${picsumId}/300/200`; // For extra reliability, we can use the /v2/list endpoint knowledge // but for now, let's validate the generated URLs const imageExists = await checkImageExists(picsumThumb); if (imageExists) { // Create descriptive titles based on the actual query const titleVariations = [ `Beautiful ${actualQuery} photography`, `Stunning ${actualQuery} image`, `Professional ${actualQuery} shot`, `High-quality ${actualQuery} photo`, `Artistic ${actualQuery} composition`, `Modern ${actualQuery} design`, `Creative ${actualQuery} artwork`, `Premium ${actualQuery} visual` ]; const titleIndex = Math.floor(seededRandom(imageSeed + 7) * titleVariations.length); const imageTitle = `${titleVariations[titleIndex]} ${validImages.length + 1}`; validImages.push({ id: imageId, title: imageTitle, link: picsumUrl, displayLink: 'picsum.photos', thumbnail: picsumThumb, width: width, height: height, source: 'Picsum Photos', description: `A high-quality ${actualQuery} photograph sourced from Picsum Photos. Perfect for creative projects, presentations, and digital content. This image features ${actualQuery} themes with professional composition and clarity.` }); } attempts++; } // If we couldn't find enough valid images, fall back to a curated list of known working IDs if (validImages.length < limit) { const knownWorkingIds = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ]; const remainingNeeded = limit - validImages.length; for (let i = 0; i < remainingNeeded && i < knownWorkingIds.length; i++) { const picId = knownWorkingIds[i]; const width = 400 + Math.floor(Math.random() * 600); const height = 300 + Math.floor(Math.random() * 500); const fallbackUrl = `https://picsum.photos/id/${picId}/${width}/${height}`; const fallbackThumb = `https://picsum.photos/id/${picId}/300/200`; validImages.push({ id: `fallback_${picId}_${Date.now()}`, title: `${actualQuery} photograph ${validImages.length + 1}`, link: fallbackUrl, displayLink: 'picsum.photos', thumbnail: fallbackThumb, width: width, height: height, source: 'Picsum Photos', description: `A curated ${actualQuery} photograph from our verified collection. High-quality and reliable for all your creative needs.` }); } } return validImages; } function renderImageGrid(images, container) { if (!container) return; container.innerHTML = ''; images.forEach((image, index) => { const imageCard = createImageCard(image, index); container.appendChild(imageCard); }); } function createImageCard(image, index) { const card = document.createElement('div'); card.className = 'image-card'; card.style.animationDelay = `${index * 0.1}s`; // Create a beautiful fallback for broken images const fallbackSvg = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(` Image Preview `); card.innerHTML = ` ${image.title}

${image.title}

${image.width}×${image.height} ${image.displayLink}
`; card.addEventListener('click', () => openImageModal(image)); card.addEventListener('mouseenter', function() { this.style.transform = 'translateY(-8px)'; }); card.addEventListener('mouseleave', function() { this.style.transform = 'translateY(0)'; }); return card; } function createLoadingGrid() { return `
${Array(12).fill().map(() => '
').join('')}
`; } function openImageModal(image) { if (!imageModal || !modalImage || !modalTitle || !modalDescription) return; modalImage.src = image.link; modalImage.alt = image.title; modalTitle.textContent = image.title; modalDescription.textContent = image.description; imageModal.dataset.imageUrl = image.link; imageModal.dataset.imageTitle = image.title; imageModal.classList.add('active'); document.body.style.overflow = 'hidden'; requestAnimationFrame(() => { imageModal.style.opacity = '1'; }); } function closeModal() { if (!imageModal) return; imageModal.classList.remove('active'); document.body.style.overflow = 'auto'; imageModal.style.opacity = '0'; } async function downloadCurrentImage() { if (!imageModal) return; const imageUrl = imageModal.dataset.imageUrl; const imageTitle = imageModal.dataset.imageTitle; if (!imageUrl) return; try { showNotification('Downloading image...', 'info'); const link = document.createElement('a'); link.href = imageUrl; link.download = `${imageTitle.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.jpg`; link.target = '_blank'; document.body.appendChild(link); link.click(); document.body.removeChild(link); showNotification('Download started!', 'success'); } catch (error) { console.error('Download error:', error); showNotification('Download failed. Please try again.', 'error'); } } async function copyImageLink() { if (!imageModal) return; const imageUrl = imageModal.dataset.imageUrl; if (!imageUrl) return; try { await navigator.clipboard.writeText(imageUrl); showNotification('Image link copied to clipboard!', 'success'); } catch (error) { console.error('Copy error:', error); const textArea = document.createElement('textarea'); textArea.value = imageUrl; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); showNotification('Image link copied!', 'success'); } } function updateResultsCount(count) { if (resultsCount) { resultsCount.textContent = `${count.toLocaleString()} images found`; } } function updateActiveNavLink(activeLink) { navLinks.forEach(link => link.classList.remove('active')); activeLink.classList.add('active'); } function initSmoothScrolling() { const sections = document.querySelectorAll('section[id]'); if (sections.length === 0) return; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const activeLink = document.querySelector(`.nav-link[href="#${entry.target.id}"]`); if (activeLink) { updateActiveNavLink(activeLink); } } }); }, { threshold: 0.3 }); sections.forEach(section => observer.observe(section)); } function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = `
${message}
`; document.body.appendChild(notification); requestAnimationFrame(() => { notification.style.transform = 'translateX(0)'; notification.style.opacity = '1'; }); setTimeout(() => { notification.style.transform = 'translateX(100%)'; notification.style.opacity = '0'; setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); }, 3000); } function getNotificationIcon(type) { switch (type) { case 'success': return 'check-circle'; case 'error': return 'exclamation-circle'; case 'warning': return 'exclamation-triangle'; default: return 'info-circle'; } } function updateCopyrightYear() { const yearElement = document.getElementById('year'); if (yearElement) { yearElement.textContent = new Date().getFullYear(); } } function addNotificationStyles() { const notificationStyles = ` .notification { position: fixed; top: 20px; right: 20px; z-index: 10000; background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); transform: translateX(100%); opacity: 0; transition: all 0.3s ease; max-width: 350px; } .notification-content { display: flex; align-items: center; gap: 12px; padding: 16px 20px; } .notification-success { border-left: 4px solid #00d4aa; } .notification-error { border-left: 4px solid #ff3b30; } .notification-warning { border-left: 4px solid #ff9500; } .notification-info { border-left: 4px solid #667eea; } .notification i { font-size: 18px; opacity: 0.8; } .notification-success i { color: #00d4aa; } .notification-error i { color: #ff3b30; } .notification-warning i { color: #ff9500; } .notification-info i { color: #667eea; } .no-results, .error-message { grid-column: 1 / -1; text-align: center; padding: 60px 20px; color: #64748b; } .no-results i, .error-message i { font-size: 48px; margin-bottom: 20px; opacity: 0.5; } .no-results h3, .error-message h3 { font-size: 24px; margin-bottom: 12px; color: #334155; } .no-results p, .error-message p { font-size: 16px; line-height: 1.6; } .image-error { filter: grayscale(1) opacity(0.7); transition: all 0.3s ease; } .image-error:hover { filter: grayscale(0.5) opacity(0.9); } `; const styleSheet = document.createElement('style'); styleSheet.textContent = notificationStyles; document.head.appendChild(styleSheet); } });