| |
| const artworks = [ |
| { |
| id: 1, |
| title: "Cyberpunk City Nights", |
| artist: "NeonDreamer", |
| category: "digital", |
| image: "https://static.photos/technology/640x480/1", |
| avatar: "https://static.photos/people/200x200/1", |
| likes: 2453, |
| views: "12.5K", |
| comments: 89, |
| description: "A futuristic cityscape inspired by Blade Runner and cyberpunk aesthetics. Created in Blender and Photoshop over 3 weeks.", |
| tags: ["cyberpunk", "cityscape", "digital", "scifi"] |
| }, |
| { |
| id: 2, |
| title: "Ethereal Forest Spirit", |
| artist: "MysticArt", |
| category: "digital", |
| image: "https://static.photos/nature/640x360/2", |
| avatar: "https://static.photos/people/200x200/2", |
| likes: 1892, |
| views: "8.2K", |
| comments: 56, |
| description: "Digital painting of an ancient forest guardian. Exploring light and atmosphere in fantasy environments.", |
| tags: ["fantasy", "forest", "spirit", "digitalpainting"] |
| }, |
| { |
| id: 3, |
| title: "Portrait Study #42", |
| artist: "ArtStudent_99", |
| category: "traditional", |
| image: "https://static.photos/people/640x480/3", |
| avatar: "https://static.photos/people/200x200/3", |
| likes: 567, |
| views: "2.1K", |
| comments: 23, |
| description: "Oil on canvas. 40x50cm. Working on skin tones and lighting from natural window light.", |
| tags: ["oilpainting", "portrait", "traditional", "study"] |
| }, |
| { |
| id: 4, |
| title: "Abstract Emotions", |
| artist: "ColorSplash", |
| category: "digital", |
| image: "https://static.photos/abstract/640x360/4", |
| avatar: "https://static.photos/people/200x200/4", |
| likes: 3201, |
| views: "15K", |
| comments: 134, |
| description: "Exploring the relationship between color and emotion. Digital abstract composition using custom brushes.", |
| tags: ["abstract", "colors", "emotional", "digital"] |
| }, |
| { |
| id: 5, |
| title: "Mountain Mist", |
| artist: "NatureLens", |
| category: "photography", |
| image: "https://static.photos/nature/640x480/5", |
| avatar: "https://static.photos/people/200x200/5", |
| likes: 892, |
| views: "4.5K", |
| comments: 45, |
| description: "Early morning shot in the Alps. Sony A7III with 24-70mm lens. f/8, 1/125s, ISO 100.", |
| tags: ["photography", "landscape", "mountains", "nature"] |
| }, |
| { |
| id: 6, |
| title: "Mecha Warrior", |
| artist: "RobotDesigner", |
| category: "3d", |
| image: "https://static.photos/technology/640x360/6", |
| avatar: "https://static.photos/people/200x200/6", |
| likes: 4521, |
| views: "22K", |
| comments: 201, |
| description: "Hard surface modeling practice. Rendered in Octane. Concept inspired by Gundam and Pacific Rim.", |
| tags: ["3d", "mecha", "robot", "scifi", "blender"] |
| }, |
| { |
| id: 7, |
| title: "Cherry Blossom Dreams", |
| artist: "SakuraArtist", |
| category: "anime", |
| image: "https://static.photos/nature/640x480/7", |
| avatar: "https://static.photos/people/200x200/7", |
| likes: 5678, |
| views: "28K", |
| comments: 312, |
| description: "Original character illustration. Clip Studio Paint. Focusing on soft lighting and spring atmosphere.", |
| tags: ["anime", "manga", "illustration", "sakura"] |
| }, |
| { |
| id: 8, |
| title: "Urban Decay", |
| artist: "StreetWalker", |
| category: "photography", |
| image: "https://static.photos/cityscape/640x360/8", |
| avatar: "https://static.photos/people/200x200/8", |
| likes: 1234, |
| views: "6.7K", |
| comments: 78, |
| description: "Abandoned factory exploration. High contrast black and white processing to emphasize texture.", |
| tags: ["urban", "abandoned", "blackwhite", "photography"] |
| }, |
| { |
| id: 9, |
| title: "Dragon's Lair", |
| artist: "FantasyMaster", |
| category: "digital", |
| image: "https://static.photos/abstract/640x480/9", |
| avatar: "https://static.photos/people/200x200/9", |
| likes: 3892, |
| views: "18K", |
| comments: 156, |
| description: "Epic fantasy scene. Photoshop and Wacom Cintiq. 40+ hours of work on the scales alone!", |
| tags: ["dragon", "fantasy", "epic", "creature"] |
| }, |
| { |
| id: 10, |
| title: "Minimalist Architecture", |
| artist: "CleanLines", |
| category: "photography", |
| image: "https://static.photos/minimal/640x360/10", |
| avatar: "https://static.photos/people/200x200/10", |
| likes: 2156, |
| views: "9.3K", |
| comments: 67, |
| description: "Geometry in modern architecture. Shot in Tokyo. Minimal post-processing to maintain clean lines.", |
| tags: ["architecture", "minimal", "geometry", "modern"] |
| }, |
| { |
| id: 11, |
| title: "Space Station Omega", |
| artist: "StarTraveler", |
| category: "3d", |
| image: "https://static.photos/technology/640x480/11", |
| avatar: "https://static.photos/people/200x200/11", |
| likes: 1789, |
| views: "7.8K", |
| comments: 89, |
| description: "Environment design for sci-fi game project. Blender and Unreal Engine 5. Real-time rendering.", |
| tags: ["3d", "environment", "scifi", "space"] |
| }, |
| { |
| id: 12, |
| title: "Watercolor Sunset", |
| artist: "BrushAndPaint", |
| category: "traditional", |
| image: "https://static.photos/nature/640x360/12", |
| avatar: "https://static.photos/people/200x200/12", |
| likes: 923, |
| views: "3.4K", |
| comments: 34, |
| description: "Wet-on-wet technique. Winsor & Newton watercolors. Arches cold press 300gsm.", |
| tags: ["watercolor", "traditional", "sunset", "seascape"] |
| } |
| ]; |
|
|
| let currentCategory = 'all'; |
| let currentSort = 'newest'; |
| let likedArtworks = new Set(); |
|
|
| |
| document.addEventListener('DOMContentLoaded', () => { |
| lucide.createIcons(); |
| renderGallery(); |
| setupEventListeners(); |
| checkNullUndefined(); |
| }); |
|
|
| |
| function checkNullUndefined() { |
| |
| const safeGet = (obj, path, defaultValue = null) => { |
| try { |
| return path.split('.').reduce((acc, part) => |
| acc && acc[part] !== undefined ? acc[part] : defaultValue, obj); |
| } catch (e) { |
| console.warn('Null/Undefined value encountered:', path); |
| return defaultValue; |
| } |
| }; |
|
|
| |
| const testData = { artwork: { title: "Test", stats: null } }; |
| |
| |
| const title = safeGet(testData, 'artwork.title', 'Untitled'); |
| const likes = safeGet(testData, 'artwork.stats.likes', 0); |
| |
| console.log('Null/Undefined handling demo:', { title, likes }); |
| } |
|
|
| function renderGallery() { |
| const gallery = document.getElementById('gallery'); |
| const filtered = filterArtworks(); |
| const sorted = sortArtworks(filtered); |
| |
| gallery.innerHTML = sorted.map(artwork => createArtworkCard(artwork)).join(''); |
| lucide.createIcons(); |
| |
| |
| document.getElementById('resultCount').textContent = `Showing ${sorted.length} artworks`; |
| } |
|
|
| function createArtworkCard(artwork) { |
| const isLiked = likedArtworks.has(artwork.id); |
| const likeClass = isLiked ? 'text-red-500 fill-current' : 'text-white'; |
| |
| return ` |
| <div class="group relative bg-card rounded-lg overflow-hidden cursor-pointer transition-transform hover:scale-[1.02] hover:shadow-2xl" |
| onclick="openModal(${artwork.id})"> |
| <div class="aspect-w-4 aspect-h-3 relative overflow-hidden"> |
| <img src="${artwork.image}" |
| alt="${artwork.title}" |
| class="w-full h-64 object-cover transition-transform duration-500 group-hover:scale-110" |
| loading="lazy" |
| onerror="this.src='https://static.photos/abstract/640x480/999'"> |
| |
| <!-- Hover Overlay --> |
| <div class="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300"> |
| <div class="absolute inset-0 hover-overlay"></div> |
| <div class="absolute bottom-0 left-0 right-0 p-4 transform translate-y-2 group-hover:translate-y-0 transition-transform"> |
| <h3 class="font-bold text-white text-lg mb-1 line-clamp-1">${artwork.title}</h3> |
| <p class="text-gray-300 text-sm mb-2">by ${artwork.artist}</p> |
| <div class="flex items-center justify-between"> |
| <div class="flex items-center gap-3 text-xs text-gray-300"> |
| <span class="flex items-center gap-1"> |
| <i data-lucide="heart" class="w-3 h-3"></i> |
| ${formatNumber(artwork.likes)} |
| </span> |
| <span class="flex items-center gap-1"> |
| <i data-lucide="message-circle" class="w-3 h-3"></i> |
| ${artwork.comments} |
| </span> |
| </div> |
| <button onclick="toggleLike(event, ${artwork.id})" |
| class="p-2 rounded-full bg-white/20 hover:bg-white/30 transition-colors ${likeClass}"> |
| <i data-lucide="heart" class="w-4 h-4 ${isLiked ? 'fill-current'}"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| <!-- Category Badge --> |
| <div class="absolute top-3 left-3"> |
| <span class="px-2 py-1 bg-black/60 backdrop-blur-sm rounded text-xs font-medium text-white capitalize"> |
| ${artwork.category} |
| </span> |
| </div> |
| </div> |
| </div> |
| `; |
| } |
|
|
| function filterArtworks() { |
| if (currentCategory === 'all') return artworks; |
| return artworks.filter(art => art.category === currentCategory); |
| } |
|
|
| function sortArtworks(artworks) { |
| const sorted = [...artworks]; |
| switch(currentSort) { |
| case 'popular': |
| return sorted.sort((a, b) => b.likes - a.likes); |
| case 'comments': |
| return sorted.sort((a, b) => b.comments - a.comments); |
| default: |
| return sorted.sort((a, b) => b.id - a.id); |
| } |
| } |
|
|
| function formatNumber(num) { |
| if (num >= 1000) { |
| return (num / 1000).toFixed(1) + 'K'; |
| } |
| return num; |
| } |
|
|
| function toggleLike(event, id) { |
| event.stopPropagation(); |
| |
| if (likedArtworks.has(id)) { |
| likedArtworks.delete(id); |
| showToast('Removed from favorites'); |
| } else { |
| likedArtworks.add(id); |
| showToast('Added to favorites'); |
| |
| |
| const btn = event.currentTarget; |
| btn.classList.add('like-anim'); |
| setTimeout(() => btn.classList.remove('like-anim'), 300); |
| } |
| |
| renderGallery(); |
| } |
|
|
| function openModal(id) { |
| const artwork = artworks.find(a => a.id === id); |
| if (!artwork) { |
| console.error('Artwork not found:', id); |
| return; |
| } |
| |
| document.getElementById('modalImage').src = artwork.image; |
| document.getElementById('modalTitle').textContent = artwork.title; |
| document.getElementById('modalCategory').textContent = artwork.category.charAt(0).toUpperCase() + artwork.category.slice(1); |
| document.getElementById('modalArtist').textContent = artwork.artist; |
| document.getElementById('modalAvatar').src = artwork.avatar; |
| document.getElementById('modalDescription').textContent = artwork.description; |
| document.getElementById('modalViews').textContent = artwork.views; |
| document.getElementById('modalComments').textContent = artwork.comments; |
| document.getElementById('modalLikes').textContent = artwork.likes; |
| |
| |
| const tagsContainer = document.getElementById('modalTags'); |
| tagsContainer.innerHTML = artwork.tags.map(tag => |
| `<span class="px-3 py-1 bg-gray-800 rounded-full text-xs text-gray-300 hover:bg-accent hover:text-dark cursor-pointer transition-colors">#${tag}</span>` |
| ).join(''); |
| |
| |
| const likeBtn = document.getElementById('modalLikeBtn'); |
| const isLiked = likedArtworks.has(id); |
| updateModalLikeButton(likeBtn, isLiked); |
| |
| likeBtn.onclick = () => { |
| if (likedArtworks.has(id)) { |
| likedArtworks.delete(id); |
| updateModalLikeButton(likeBtn, false); |
| showToast('Removed from favorites'); |
| } else { |
| likedArtworks.add(id); |
| updateModalLikeButton(likeBtn, true); |
| showToast('Added to favorites'); |
| } |
| renderGallery(); |
| }; |
| |
| document.getElementById('imageModal').classList.remove('hidden'); |
| document.body.style.overflow = 'hidden'; |
| lucide.createIcons(); |
| } |
|
|
| function updateModalLikeButton(btn, isLiked) { |
| const icon = btn.querySelector('i'); |
| if (isLiked) { |
| icon.classList.add('text-red-500', 'fill-current'); |
| icon.classList.remove('text-white'); |
| } else { |
| icon.classList.remove('text-red-500', 'fill-current'); |
| icon.classList.add('text-white'); |
| } |
| } |
|
|
| function closeModal() { |
| document.getElementById('imageModal').classList.add('hidden'); |
| document.body.style.overflow = ''; |
| } |
|
|
| function showToast(message) { |
| const toast = document.getElementById('toast'); |
| const toastMessage = document.getElementById('toastMessage'); |
| toastMessage.textContent = message; |
| |
| toast.classList.remove('translate-y-20', 'opacity-0'); |
| |
| setTimeout(() => { |
| toast.classList.add('translate-y-20', 'opacity-0'); |
| }, 3000); |
| } |
|
|
| function setupEventListeners() { |
| |
| document.querySelectorAll('.filter-btn').forEach(btn => { |
| btn.addEventListener('click', (e) => { |
| document.querySelectorAll('.filter-btn').forEach(b => { |
| b.classList.remove('active', 'bg-accent', 'text-dark'); |
| b.classList.add('bg-card', 'border-gray-700'); |
| }); |
| e.target.classList.remove('bg-card', 'border-gray-700'); |
| e.target.classList.add('active', 'bg-accent', 'text-dark'); |
| |
| currentCategory = e.target.dataset.category; |
| renderGallery(); |
| }); |
| }); |
| |
| |
| document.getElementById('sortSelect').addEventListener('change', (e) => { |
| currentSort = e.target.value; |
| renderGallery(); |
| }); |
| |
| |
| document.getElementById('searchInput').addEventListener('input', (e) => { |
| const query = e.target.value.toLowerCase(); |
| if (query === '') { |
| renderGallery(); |
| return; |
| } |
| |
| const filtered = artworks.filter(art => |
| art.title.toLowerCase().includes(query) || |
| art.artist.toLowerCase().includes(query) || |
| art.tags.some(tag => tag.toLowerCase().includes(query)) |
| ); |
| |
| const gallery = document.getElementById('gallery'); |
| gallery.innerHTML = filtered.map(artwork => createArtworkCard(artwork)).join(''); |
| lucide.createIcons(); |
| document.getElementById('resultCount').textContent = `Found ${filtered.length} artworks`; |
| }); |
| |
| |
| document.getElementById('closeModal').addEventListener('click', closeModal); |
| document.getElementById('imageModal').addEventListener('click', (e) => { |
| if (e.target.id === 'imageModal') closeModal(); |
| }); |
| |
| |
| document.addEventListener('keydown', (e) => { |
| if (e.key === 'Escape') closeModal(); |
| }); |
| |
| |
| document.getElementById('loadMoreBtn').addEventListener('click', () => { |
| const loadingState = document.getElementById('loadingState'); |
| const btn = document.getElementById('loadMoreBtn'); |
| |
| btn.classList.add('hidden'); |
| loadingState.classList.remove('hidden'); |
| |
| setTimeout(() => { |
| loadingState.classList.add('hidden'); |
| btn.classList.remove('hidden'); |
| showToast('More artworks loaded!'); |
| |
| |
| const newArtworks = artworks.map(art => ({ |
| ...art, |
| id: art.id + artworks.length, |
| likes: Math.floor(Math.random() * 5000) |
| })); |
| artworks.push(...newArtworks); |
| renderGallery(); |
| }, 1500); |
| }); |
| |
| |
| let isDark = true; |
| document.getElementById('themeToggle').addEventListener('click', () => { |
| isDark = !isDark; |
| const icon = document.querySelector('#themeToggle i'); |
| if (isDark) { |
| document.documentElement.classList.remove('light'); |
| icon.setAttribute('data-lucide', 'moon'); |
| } else { |
| document.documentElement.classList.add('light'); |
| icon.setAttribute('data-lucide', 'sun'); |
| } |
| lucide.createIcons(); |
| }); |
| } |
|
|
| |
| window.addEventListener('error', (e) => { |
| if (e.target.tagName === 'IMG') { |
| console.warn('Image failed to load:', e.target.src); |
| e.target.src = 'https://static.photos/abstract/640x480/999'; |
| e.target.onerror = null; |
| } |
| }, true); |