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(`
`);
card.innerHTML = `
${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);
}
});