Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Cosplay Haven Hub</title> | |
| <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script> | |
| <style> | |
| .gradient-bg { | |
| background: linear-gradient(135deg, #1e1b4b 0%, #3b0764 100%); | |
| } | |
| .card-hover { | |
| transition: all 0.3s ease; | |
| } | |
| .card-hover:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| } | |
| .search-input { | |
| background: rgba(255, 255, 255, 0.1); | |
| backdrop-filter: blur(10px); | |
| } | |
| </style> | |
| </head> | |
| <body class="gradient-bg min-h-screen text-white"> | |
| <div id="vanta-globe"></div> | |
| <div class="container mx-auto px-4 py-12 relative z-10"> | |
| <!-- Header --> | |
| <header class="flex flex-col items-center mb-12"> | |
| <h1 class="text-5xl font-bold mb-4 text-center" style="text-shadow: 0 2px 10px rgba(0,0,0,0.3);"> | |
| Cosplay Haven Hub <i data-feather="heart" class="inline text-pink-500"></i> | |
| </h1> | |
| <p class="text-xl text-gray-300 max-w-2xl text-center"> | |
| Discover amazing cosplay collections from talented creators | |
| </p> | |
| </header> | |
| <!-- Search Bar --> | |
| <div class="max-w-2xl mx-auto mb-16"> | |
| <div class="relative"> | |
| <input | |
| id="searchInput" | |
| type="text" | |
| placeholder="Search cosplayers, characters..." | |
| class="w-full py-4 px-6 rounded-full search-input border border-purple-300 focus:border-purple-500 focus:ring-2 focus:ring-purple-500 outline-none transition" | |
| > | |
| <button | |
| id="searchButton" | |
| class="absolute right-2 top-1/2 transform -translate-y-1/2 bg-purple-600 hover:bg-purple-700 text-white p-3 rounded-full transition" | |
| > | |
| <i data-feather="search"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Featured Section --> | |
| <section class="mb-16"> | |
| <h2 class="text-3xl font-bold mb-8 flex items-center"> | |
| <i data-feather="star" class="mr-2 text-yellow-400"></i> Featured Cosplays | |
| </h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> | |
| <!-- Featured items will be populated by JS --> | |
| <div class="bg-gray-800 bg-opacity-50 rounded-xl p-4 card-hover animate-pulse h-96"> | |
| <div class="bg-gray-700 rounded-lg h-full"></div> | |
| </div> | |
| <div class="bg-gray-800 bg-opacity-50 rounded-xl p-4 card-hover animate-pulse h-96"> | |
| <div class="bg-gray-700 rounded-lg h-full"></div> | |
| </div> | |
| <div class="bg-gray-800 bg-opacity-50 rounded-xl p-4 card-hover animate-pulse h-96"> | |
| <div class="bg-gray-700 rounded-lg h-full"></div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Results Section --> | |
| <section id="resultsSection" class="mb-16"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-3xl font-bold flex items-center"> | |
| <i data-feather="grid" class="mr-2 text-purple-400"></i> Latest Collections | |
| </h2> | |
| <div id="loadingIndicator" class="hidden flex items-center"> | |
| <span class="mr-2">Loading</span> | |
| <div class="animate-spin"> | |
| <i data-feather="loader"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="resultsGrid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6"> | |
| <!-- Results will be populated here by JavaScript --> | |
| </div> | |
| </section> | |
| <!-- Footer --> | |
| <footer class="border-t border-gray-700 pt-8 mt-12"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <div class="mb-4 md:mb-0"> | |
| <p class="text-gray-400">© 2023 Cosplay Haven Hub. All rights reserved.</p> | |
| </div> | |
| <div class="flex space-x-6"> | |
| <a href="#" class="text-gray-400 hover:text-white transition"> | |
| <i data-feather="github"></i> | |
| </a> | |
| <a href="#" class="text-gray-400 hover:text-white transition"> | |
| <i data-feather="twitter"></i> | |
| </a> | |
| <a href="#" class="text-gray-400 hover:text-white transition"> | |
| <i data-feather="instagram"></i> | |
| </a> | |
| </div> | |
| </div> | |
| </footer> | |
| </div> | |
| <!-- Modal --> | |
| <div id="cosplayModal" class="fixed inset-0 bg-black bg-opacity-90 hidden z-50 overflow-y-auto"> | |
| <div class="container mx-auto px-4 py-12 relative"> | |
| <button id="closeModal" class="absolute top-4 right-4 text-white text-2xl hover:text-purple-300 transition"> | |
| <i data-feather="x"></i> | |
| </button> | |
| <div class="max-w-4xl mx-auto bg-gray-900 rounded-xl overflow-hidden"> | |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-0"> | |
| <div class="bg-gray-800 p-6"> | |
| <img id="modalImage" src="" alt="" class="w-full h-auto rounded-lg shadow-lg"> | |
| </div> | |
| <div class="p-6"> | |
| <h2 id="modalTitle" class="text-2xl font-bold mb-2"></h2> | |
| <p id="modalExcerpt" class="text-gray-300 mb-4"></p> | |
| <div class="flex flex-wrap gap-2 mb-6"> | |
| <span id="modalPhotos" class="bg-purple-800 text-white px-3 py-1 rounded-full text-sm"></span> | |
| <span id="modalVideos" class="bg-purple-800 text-white px-3 py-1 rounded-full text-sm"></span> | |
| </div> | |
| <a id="modalLink" href="#" target="_blank" class="inline-block bg-purple-600 hover:bg-purple-700 text-white px-6 py-3 rounded-lg font-medium transition"> | |
| View Full Collection <i data-feather="arrow-right" class="inline ml-1"></i> | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize animations and icons | |
| document.addEventListener('DOMContentLoaded', function() { | |
| feather.replace(); | |
| // Initialize Vanta.js background | |
| VANTA.GLOBE({ | |
| el: "#vanta-globe", | |
| mouseControls: true, | |
| touchControls: true, | |
| gyroControls: false, | |
| minHeight: 200.00, | |
| minWidth: 200.00, | |
| scale: 1.00, | |
| scaleMobile: 1.00, | |
| color: 0x7e22ce, | |
| backgroundColor: 0x1e1b4b, | |
| size: 1.00 | |
| }); | |
| // Load featured cosplays | |
| loadFeaturedCosplays(); | |
| // Load initial results | |
| searchCosplays(''); | |
| // Setup search functionality | |
| const searchButton = document.getElementById('searchButton'); | |
| const searchInput = document.getElementById('searchInput'); | |
| searchButton.addEventListener('click', function() { | |
| const query = searchInput.value.trim(); | |
| if (query) { | |
| searchCosplays(query); | |
| } | |
| }); | |
| searchInput.addEventListener('keypress', function(e) { | |
| if (e.key === 'Enter') { | |
| const query = searchInput.value.trim(); | |
| if (query) { | |
| searchCosplays(query); | |
| } | |
| } | |
| }); | |
| // Modal close button | |
| document.getElementById('closeModal').addEventListener('click', function() { | |
| document.getElementById('cosplayModal').classList.add('hidden'); | |
| }); | |
| }); | |
| async function loadFeaturedCosplays() { | |
| try { | |
| // Using Cosplay.com featured API | |
| const response = await fetch('https://cosplay.com/api/featured.php'); | |
| if (!response.ok) throw new Error('Failed to fetch'); | |
| const data = await response.json(); | |
| if (data.results && data.results.length > 0) { | |
| const featuredSection = document.querySelector('section.mb-16 .grid'); | |
| // Clear loading placeholders | |
| featuredSection.innerHTML = ''; | |
| // Add actual featured items (limited to 3) | |
| const featuredItems = data.results.slice(0, 3).map(item => ({ | |
| title: item.title || 'Featured Cosplay', | |
| image: item.image_url || 'http://static.photos/cosplay/640x360', | |
| excerpt: `${item.photos_count || 0} photos | ${item.videos_count || 0} videos`, | |
| detail_url: item.url || '#', | |
| photos_count: item.photos_count || 0, | |
| videos_count: item.videos_count || 0, | |
| description: item.description || 'Featured cosplay description' | |
| })); | |
| featuredItems.forEach(item => { | |
| const featuredItem = document.createElement('div'); | |
| featuredItem.className = 'bg-gray-800 bg-opacity-50 rounded-xl overflow-hidden card-hover group'; | |
| featuredItem.innerHTML = ` | |
| <div class="relative overflow-hidden h-80"> | |
| <img src="${item.image}" alt="${item.title}" class="w-full h-full object-cover transition duration-500 group-hover:scale-105" onerror="this.src='http://static.photos/cosplay/640x360'"> | |
| <div class="absolute inset-0 bg-gradient-to-t from-black to-transparent opacity-80"></div> | |
| <div class="absolute bottom-0 left-0 p-4"> | |
| <h3 class="text-xl font-bold line-clamp-2">${item.title}</h3> | |
| <div class="flex items-center mt-2 text-sm text-gray-300"> | |
| <span class="flex items-center mr-3"> | |
| <i data-feather="image" class="w-4 h-4 mr-1"></i> | |
| ${item.excerpt.match(/\d+(?=\sphotos)/i)?.[0] || '0'} photos | |
| </span> | |
| <span class="flex items-center"> | |
| <i data-feather="video" class="w-4 h-4 mr-1"></i> | |
| ${item.excerpt.match(/\d+(?=\s(videos|video))/i)?.[0] || '0'} videos | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| featuredSection.appendChild(featuredItem); | |
| // Add click event to open modal | |
| featuredItem.addEventListener('click', function() { | |
| openModal(item); | |
| }); | |
| }); | |
| feather.replace(); | |
| } | |
| } catch (error) { | |
| console.error('Error loading featured cosplays:', error); | |
| } | |
| } | |
| async function searchCosplays(query) { | |
| const resultsGrid = document.getElementById('resultsGrid'); | |
| const loadingIndicator = document.getElementById('loadingIndicator'); | |
| // Show loading indicator | |
| loadingIndicator.classList.remove('hidden'); | |
| resultsGrid.innerHTML = ''; | |
| try { | |
| // Using Cosplay.com API as a public cosplay data source | |
| const response = await fetch(`https://cosplay.com/api/search.php?term=${encodeURIComponent(query)}&type=photos`); | |
| if (!response.ok) throw new Error('Failed to fetch'); | |
| const data = await response.json(); | |
| // Transform API data to match our expected format | |
| const formattedData = data.results.map(item => ({ | |
| title: item.title || 'Untitled Cosplay', | |
| image: item.image_url || 'http://static.photos/cosplay/640x360', | |
| excerpt: `${item.photos_count || 0} photos | ${item.videos_count || 0} videos`, | |
| detail_url: item.url || '#', | |
| photos_count: item.photos_count || 0, | |
| videos_count: item.videos_count || 0, | |
| description: item.description || 'No description available' | |
| })); | |
| if (formattedData.length > 0) { | |
| formattedData.forEach(item => { | |
| const card = document.createElement('div'); | |
| card.className = 'bg-gray-800 bg-opacity-50 rounded-xl overflow-hidden card-hover group'; | |
| card.innerHTML = ` | |
| <div class="relative overflow-hidden h-64"> | |
| <img src="${item.image}" alt="${item.title}" class="w-full h-full object-cover transition duration-500 group-hover:scale-105" onerror="this.src='http://static.photos/cosplay/640x360'"> | |
| <div class="absolute inset-0 bg-gradient-to-t from-black to-transparent opacity-70"></div> | |
| <div class="absolute bottom-0 left-0 p-4 w-full"> | |
| <h3 class="text-lg font-bold line-clamp-2">${item.title}</h3> | |
| <div class="flex items-center mt-2 text-xs text-gray-300"> | |
| <span class="flex items-center mr-3"> | |
| <i data-feather="image" class="w-3 h-3 mr-1"></i> | |
| ${item.excerpt.match(/\d+(?=\sphotos)/i)?.[0] || '0'} photos | |
| </span> | |
| <span class="flex items-center"> | |
| <i data-feather="video" class="w-3 h-3 mr-1"></i> | |
| ${item.excerpt.match(/\d+(?=\s(videos|video))/i)?.[0] || '0'} videos | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| resultsGrid.appendChild(card); | |
| // Add click event to open modal | |
| card.addEventListener('click', function() { | |
| openModal(item); | |
| }); | |
| }); | |
| feather.replace(); | |
| } else { | |
| resultsGrid.innerHTML = ` | |
| <div class="col-span-full text-center py-12"> | |
| <i data-feather="frown" class="w-12 h-12 mx-auto text-gray-400 mb-4"></i> | |
| <h3 class="text-xl font-bold mb-2">No results found</h3> | |
| <p class="text-gray-400">Try searching for something else</p> | |
| </div> | |
| `; | |
| feather.replace(); | |
| } | |
| } catch (error) { | |
| console.error('Error searching cosplays:', error); | |
| resultsGrid.innerHTML = ` | |
| <div class="col-span-full text-center py-12"> | |
| <i data-feather="alert-triangle" class="w-12 h-12 mx-auto text-red-400 mb-4"></i> | |
| <h3 class="text-xl font-bold mb-2">Error loading results</h3> | |
| <p class="text-gray-400">Please try again later</p> | |
| </div> | |
| `; | |
| feather.replace(); | |
| } finally { | |
| loadingIndicator.classList.add('hidden'); | |
| } | |
| } | |
| function openModal(item) { | |
| const modal = document.getElementById('cosplayModal'); | |
| const modalImage = document.getElementById('modalImage'); | |
| const modalTitle = document.getElementById('modalTitle'); | |
| const modalExcerpt = document.getElementById('modalExcerpt'); | |
| const modalLink = document.getElementById('modalLink'); | |
| // Update modal content | |
| modalImage.src = item.image || 'http://static.photos/cosplay/640x360'; | |
| modalImage.alt = item.title || ''; | |
| modalTitle.textContent = item.title || 'No title'; | |
| modalExcerpt.textContent = item.description || 'No description available'; | |
| document.getElementById('modalPhotos').textContent = `${item.photos_count || 0} Photos`; | |
| document.getElementById('modalVideos').textContent = `${item.videos_count || 0} Videos`; | |
| if (item.detail_url !== '#') { | |
| modalLink.href = item.detail_url; | |
| modalLink.classList.remove('hidden'); | |
| } else { | |
| modalLink.classList.add('hidden'); | |
| } | |
| // Show modal with animation | |
| modal.classList.remove('hidden'); | |
| anime({ | |
| targets: modal, | |
| opacity: [0, 1], | |
| duration: 300, | |
| easing: 'easeInOutQuad' | |
| }); | |
| feather.replace(); | |
| } | |
| </script> | |
| </body> | |
| </html> | |