cosplayverse-showcase / index.html
devstok's picture
Update kan pas di bagian detail ada foto random (beda kontek) kalau bisa di hilangin aja dan tampilan nya di baguskan dong yang moderen gitu
b53211f verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CosplayVerse Showcase 🎭</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>
<style>
.card {
transition: all 0.3s ease;
transform: scale(1);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.card:hover {
transform: scale(1.03);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.image-container {
height: 300px;
overflow: hidden;
}
.gallery-image {
transition: transform 0.5s ease;
}
.gallery-image:hover {
transform: scale(1.05);
}
#loader {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.detail-view {
opacity: 0;
transition: opacity 0.5s ease;
}
.detail-view.active {
opacity: 1;
}
</style>
</head>
<body class="bg-gradient-to-b from-purple-900 to-indigo-900 text-white min-h-screen">
<!-- Header Section -->
<header class="bg-black bg-opacity-80 backdrop-filter backdrop-blur-lg sticky top-0 z-50">
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
<div class="flex items-center space-x-2">
<i data-feather="aperture" class="text-pink-500"></i>
<h1 class="text-2xl font-bold bg-gradient-to-r from-pink-500 to-purple-500 bg-clip-text text-transparent">CosplayVerse</h1>
</div>
<div class="relative w-1/3">
<input type="text" id="searchInput" placeholder="Search cosplayers..."
class="w-full px-4 py-2 rounded-full bg-gray-800 text-white focus:outline-none focus:ring-2 focus:ring-pink-500">
<button id="searchBtn" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-pink-500">
<i data-feather="search"></i>
</button>
</div>
<div class="flex space-x-4">
<button id="latestBtn" class="px-4 py-2 rounded-full bg-pink-600 hover:bg-pink-700 transition-colors flex items-center space-x-2">
<i data-feather="refresh-cw" class="w-4 h-4"></i>
<span>Latest</span>
</button>
<button id="toggleTheme" class="p-2 rounded-full bg-gray-800 hover:bg-gray-700 transition-colors">
<i data-feather="moon" class="w-5 h-5"></i>
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8">
<!-- Loading State -->
<div id="loading" class="flex justify-center items-center py-20">
<div id="loader" class="w-12 h-12 border-4 border-pink-500 border-t-transparent rounded-full"></div>
</div>
<!-- Grid View -->
<div id="gridView" class="hidden">
<h2 id="gridTitle" class="text-3xl font-bold mb-8 text-center bg-gradient-to-r from-pink-400 to-purple-400 bg-clip-text text-transparent">Latest Cosplays</h2>
<div id="cosplayGrid" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"></div>
<div id="pagination" class="flex justify-center mt-8 space-x-2 hidden">
<button id="prevPage" class="px-4 py-2 bg-gray-800 rounded hover:bg-gray-700 disabled:opacity-50">
<i data-feather="chevron-left" class="w-5 h-5"></i>
</button>
<span id="pageInfo" class="px-4 py-2">Page 1</span>
<button id="nextPage" class="px-4 py-2 bg-gray-800 rounded hover:bg-gray-700 disabled:opacity-50">
<i data-feather="chevron-right" class="w-5 h-5"></i>
</button>
</div>
</div>
<!-- Detail View -->
<div id="detailView" class="detail-view hidden">
<div class="flex justify-between items-center mb-6">
<button id="backBtn" class="flex items-center space-x-2 px-4 py-2 bg-gray-800 rounded-full hover:bg-gray-700 transition-colors">
<i data-feather="arrow-left" class="w-5 h-5"></i>
<span>Back</span>
</button>
</div>
<div class="bg-black bg-opacity-50 rounded-xl p-6 mb-8">
<h2 id="detailTitle" class="text-3xl font-bold mb-4"></h2>
<div id="detailInfo" class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<!-- Will be populated with details -->
</div>
<!-- Gallery -->
<div class="mb-8">
<h3 class="text-2xl font-bold mb-6 text-center bg-gradient-to-r from-pink-400 to-purple-400 bg-clip-text text-transparent">Gallery</h3>
<div id="imageGallery" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Images will be inserted here -->
</div>
</div>
<!-- Videos -->
<div class="mb-8" id="videoSection">
<h3 class="text-2xl font-bold mb-6 text-center bg-gradient-to-r from-pink-400 to-purple-400 bg-clip-text text-transparent">Videos</h3>
<div id="videoGallery" class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Videos will be inserted here -->
</div>
</div>
</div>
</div>
</main>
<!-- Footer -->
<footer class="bg-black bg-opacity-80 py-8 mt-12">
<div class="container mx-auto px-4 text-center">
<div class="flex justify-center space-x-4 mb-4">
<a href="#" class="text-gray-400 hover:text-pink-500 transition-colors">
<i data-feather="instagram"></i>
</a>
<a href="#" class="text-gray-400 hover:text-pink-500 transition-colors">
<i data-feather="twitter"></i>
</a>
<a href="#" class="text-gray-400 hover:text-pink-500 transition-colors">
<i data-feather="github"></i>
</a>
</div>
<p class="text-gray-400">© 2023 CosplayVerse Showcase. All rights reserved.</p>
</div>
</footer>
<script>
// Constants
const API_BASE_URL = "https://restapi.rizk.my.id/sfwnsfw/cosplaytelensfw";
const API_KEY = "vip";
const PROXY_URL_BASE = "https://api.allorigins.win/get?url=";
let currentPage = 1;
let currentListData = [];
let currentView = 'grid';
let currentSearchQuery = '';
// DOM Elements
const gridView = document.getElementById('gridView');
const detailView = document.getElementById('detailView');
const cosplayGrid = document.getElementById('cosplayGrid');
const loading = document.getElementById('loading');
const gridTitle = document.getElementById('gridTitle');
const pagination = document.getElementById('pagination');
const pageInfo = document.getElementById('pageInfo');
const prevPage = document.getElementById('prevPage');
const nextPage = document.getElementById('nextPage');
const searchInput = document.getElementById('searchInput');
const searchBtn = document.getElementById('searchBtn');
const latestBtn = document.getElementById('latestBtn');
const backBtn = document.getElementById('backBtn');
const detailTitle = document.getElementById('detailTitle');
const detailInfo = document.getElementById('detailInfo');
const imageGallery = document.getElementById('imageGallery');
const videoGallery = document.getElementById('videoGallery');
const toggleTheme = document.getElementById('toggleTheme');
// Initialize
document.addEventListener('DOMContentLoaded', () => {
feather.replace();
loadLatest();
setupEventListeners();
// Check for saved theme preference
if (localStorage.getItem('theme') === 'dark') {
document.documentElement.classList.add('dark');
toggleTheme.innerHTML = feather.icons['sun'].toSvg();
} else {
document.documentElement.classList.remove('dark');
toggleTheme.innerHTML = feather.icons['moon'].toSvg();
}
});
function setupEventListeners() {
// Navigation
searchBtn.addEventListener('click', () => {
currentSearchQuery = searchInput.value.trim();
if (currentSearchQuery) {
currentPage = 1;
loadSearch(currentSearchQuery);
}
});
searchInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
currentSearchQuery = searchInput.value.trim();
if (currentSearchQuery) {
currentPage = 1;
loadSearch(currentSearchQuery);
}
}
});
latestBtn.addEventListener('click', () => {
currentSearchQuery = '';
searchInput.value = '';
currentPage = 1;
loadLatest();
});
backBtn.addEventListener('click', () => {
showGridView();
});
// Pagination
prevPage.addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
if (currentSearchQuery) {
loadSearch(currentSearchQuery);
} else {
loadLatest();
}
}
});
nextPage.addEventListener('click', () => {
currentPage++;
if (currentSearchQuery) {
loadSearch(currentSearchQuery);
} else {
loadLatest();
}
});
// Theme toggle
toggleTheme.addEventListener('click', () => {
if (document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
toggleTheme.innerHTML = feather.icons['moon'].toSvg();
} else {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
toggleTheme.innerHTML = feather.icons['sun'].toSvg();
}
});
}
// API Fetch Helper
async function fetchWithProxy(apiEndpoint) {
try {
const encodedUrl = encodeURIComponent(apiEndpoint);
const proxyUrl = `${PROXY_URL_BASE}${encodedUrl}`;
const response = await fetch(proxyUrl);
const data = await response.json();
if (data.contents) {
return JSON.parse(data.contents);
}
return null;
} catch (error) {
console.error('Error fetching data:', error);
return null;
}
}
// Load Latest Cosplays
async function loadLatest() {
showLoading();
gridTitle.textContent = 'Latest Cosplays';
const endpoint = `${API_BASE_URL}/latest?page=${currentPage}&apikey=${API_KEY}`;
const data = await fetchWithProxy(endpoint);
if (data && data.status === 'success' && data.data) {
currentListData = data.data;
renderCosplayGrid();
updatePagination();
showGridView();
} else {
showError('Failed to load latest cosplays');
}
}
// Load Search Results
async function loadSearch(query) {
showLoading();
gridTitle.textContent = `Search Results for "${query}"`;
const endpoint = `${API_BASE_URL}/search?query=${encodeURIComponent(query)}&apikey=${API_KEY}`;
const data = await fetchWithProxy(endpoint);
if (data && data.status === 'success' && data.data) {
currentListData = data.data;
renderCosplayGrid();
pagination.classList.add('hidden'); // Search doesn't have pagination
showGridView();
} else {
showError('No results found');
}
}
// Load Detail View
async function loadDetail(index) {
showLoading();
const item = currentListData[index];
const endpoint = `${API_BASE_URL}/detail?url=${encodeURIComponent(item.url)}&apikey=${API_KEY}`;
const data = await fetchWithProxy(endpoint);
if (data && data.status === 'success' && data.data) {
renderDetailView(data.data);
showDetailView();
} else {
showError('Failed to load cosplay details');
}
}
// Render Grid View
function renderCosplayGrid() {
cosplayGrid.innerHTML = '';
currentListData.forEach((item, index) => {
const card = document.createElement('div');
card.className = 'card bg-gray-800 rounded-xl overflow-hidden hover:cursor-pointer transition-all duration-300';
card.addEventListener('click', () => loadDetail(index));
// Parse title to extract cosplayer and character
let cosplayer = 'Unknown';
let character = 'Unknown';
if (item.title) {
const parts = item.title.split('–');
if (parts.length >= 2) {
cosplayer = parts[0].trim();
character = parts[1].trim();
} else {
cosplayer = item.title.trim();
}
}
card.innerHTML = `
<div class="image-container">
<img src="${item.image}" alt="${item.title}" class="w-full h-full object-cover">
</div>
<div class="p-4">
<h3 class="text-xl font-bold mb-1 truncate">${character}</h3>
<p class="text-pink-400 mb-2">${cosplayer}</p>
<p class="text-gray-400 text-sm line-clamp-2">${item.excerpt || ''}</p>
</div>
`;
cosplayGrid.appendChild(card);
});
// Animate cards
anime({
targets: '.card',
opacity: [0, 1],
translateY: [20, 0],
delay: anime.stagger(50),
easing: 'easeOutExpo'
});
}
// Render Detail View
function renderDetailView(data) {
detailTitle.textContent = data.title || 'Cosplay Details';
// Clear previous content
detailInfo.innerHTML = '';
imageGallery.innerHTML = '';
videoGallery.innerHTML = '';
// Parse title to extract cosplayer and character if available
let cosplayer = 'Unknown';
let character = 'Unknown';
if (data.title) {
const parts = data.title.split('–');
if (parts.length >= 2) {
cosplayer = parts[0].trim();
character = parts[1].trim();
} else {
cosplayer = data.title.trim();
}
}
// Add basic info
detailInfo.innerHTML = `
<div class="bg-gray-800 p-4 rounded-lg">
<h4 class="text-lg font-bold text-pink-400 mb-2">Cosplayer</h4>
<p>${cosplayer}</p>
</div>
<div class="bg-gray-800 p-4 rounded-lg">
<h4 class="text-lg font-bold text-pink-400 mb-2">Character</h4>
<p>${character}</p>
</div>
<div class="bg-gray-800 p-4 rounded-lg">
<h4 class="text-lg font-bold text-pink-400 mb-2">Details</h4>
<p>${data.details?.description || 'No additional details available'}</p>
</div>
`;
// Add images
if (data.images && data.images.length > 0) {
// Filter out any non-image URLs that might be in the data
const validImages = data.images.filter(img =>
img && (img.endsWith('.jpg') || img.endsWith('.jpeg') || img.endsWith('.png') || img.endsWith('.webp'))
);
data.images.forEach((imageUrl, index) => {
const imgContainer = document.createElement('div');
imgContainer.className = 'relative overflow-hidden rounded-lg gallery-image';
const imgContainer = document.createElement('div');
imgContainer.className = 'relative overflow-hidden rounded-xl group';
const img = document.createElement('img');
img.src = imageUrl;
img.alt = `Cosplay image ${index + 1}`;
img.className = 'w-full h-72 object-cover transition-transform duration-500 group-hover:scale-105';
const overlay = document.createElement('div');
overlay.className = 'absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300';
imgContainer.appendChild(img);
imgContainer.appendChild(overlay);
imageGallery.appendChild(imgContainer);
});
} else {
imageGallery.innerHTML = `
<div class="col-span-full text-center py-12">
<i data-feather="image" class="w-12 h-12 mx-auto text-gray-400 mb-4"></i>
<p class="text-gray-400">No cosplay images available</p>
</div>
`;
feather.replace();
}
// Add videos if available
if (data.videos && data.videos.length > 0) {
const videosContainer = videoGallery.querySelector('div');
data.videos.forEach(videoUrl => {
const videoContainer = document.createElement('div');
videoContainer.className = 'relative overflow-hidden rounded-xl group';
videoContainer.innerHTML = `
<div class="aspect-w-16 aspect-h-9">
<iframe src="${videoUrl}" class="w-full h-full rounded-xl" frameborder="0" allowfullscreen></iframe>
</div>
<div class="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
`;
videosContainer.appendChild(videoContainer);
});
} else {
videoGallery.innerHTML = `
<div class="col-span-full text-center py-12">
<i data-feather="video" class="w-12 h-12 mx-auto text-gray-400 mb-4"></i>
<p class="text-gray-400">No cosplay videos available</p>
</div>
`;
feather.replace();
}
// Animate elements
anime({
targets: [detailTitle, '#detailInfo > div', '#imageGallery > div', '#videoGallery'],
opacity: [0, 1],
translateY: [20, 0],
delay: anime.stagger(50),
easing: 'easeOutExpo'
});
}
// View Management
function showLoading() {
loading.classList.remove('hidden');
gridView.classList.add('hidden');
detailView.classList.add('hidden');
}
function showGridView() {
loading.classList.add('hidden');
detailView.classList.add('hidden');
gridView.classList.remove('hidden');
currentView = 'grid';
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function showDetailView() {
loading.classList.add('hidden');
gridView.classList.add('hidden');
detailView.classList.remove('hidden');
detailView.classList.add('active');
currentView = 'detail';
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function showError(message) {
loading.classList.add('hidden');
if (currentView === 'grid') {
cosplayGrid.innerHTML = `
<div class="col-span-full text-center py-12">
<i data-feather="alert-triangle" class="w-12 h-12 mx-auto text-yellow-400 mb-4"></i>
<h3 class="text-xl font-bold">${message}</h3>
</div>
`;
feather.replace();
} else {
detailView.innerHTML = `
<div class="text-center py-12">
<i data-feather="alert-triangle" class="w-12 h-12 mx-auto text-yellow-400 mb-4"></i>
<h3 class="text-xl font-bold">${message}</h3>
<button id="backBtn" class="mt-4 px-4 py-2 bg-pink-600 rounded-full hover:bg-pink-700 transition-colors">
Go Back
</button>
</div>
`;
feather.replace();
document.getElementById('backBtn').addEventListener('click', showGridView);
}
}
function updatePagination() {
if (currentListData.length > 0) {
pagination.classList.remove('hidden');
pageInfo.textContent = `Page ${currentPage}`;
prevPage.disabled = currentPage === 1;
nextPage.disabled = currentListData.length < 20; // Assuming 20 items per page
} else {
pagination.classList.add('hidden');
}
}
// Theme management
function toggleDarkMode() {
document.documentElement.classList.toggle('dark');
const isDark = document.documentElement.classList.contains('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
toggleTheme.innerHTML = isDark ? feather.icons['sun'].toSvg() : feather.icons['moon'].toSvg();
}
</script>
</body>
</html>