cosplay-haven-hub / index.html
devstok's picture
Fitur search detail Ama ga bisa
9b9d002 verified
<!DOCTYPE html>
<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>