devstok commited on
Commit
25ae121
Β·
verified Β·
1 Parent(s): 52ffea9

JUDUL: Buatkan aplikasi web Cosplayer Gallery (HTML, CSS, JavaScript) menggunakan REST API dengan AllOrigins Proxy.

Browse files

TUJUAN: Buat satu file HTML tunggal yang berisi HTML, CSS, dan JavaScript (vanilla JS) untuk membangun halaman web komunitas cosplayer yang menampilkan data dari REST API eksternal. Semua permintaan API harus menggunakan https://api.allorigins.win/get?url= sebagai proxy untuk mengatasi masalah CORS, dan harus mem-parse properti contents dari respons proxy untuk mendapatkan data JSON yang sebenarnya.
1. Konfigurasi API
| Nama Variabel | Nilai | Endpoint Penuh (Contoh) |
|---|---|---|
| API_BASE_URL | https://restapi.rizk.my.id/sfwnsfw/cosplaytelensfw | |
| API_KEY | vip | |
| PROXY_URL_BASE | https://api.allorigins.win/get?url= | |
2. Fitur & Endpoint
| Nama Fitur | Endpoint API | Parameter | Catatan |
|---|---|---|---|
| Terbaru (Latest) | /latest | ?page=1&apikey={API_KEY} | Tampilkan di halaman awal dan tombol "Terbaru". |
| Pencarian (Search) | /search | ?query={keyword}&apikey={API_KEY} | Tampilkan hasil pencarian. |
| Detail | /detail | ?url={post_url}&apikey={API_KEY} | Gunakan url dari hasil latest/search sebagai parameter. |
3. Struktur Respons API (untuk Pemetaan Data)
Semua endpoint mengembalikan struktur { "status": "success", "data": [...] } (atau { "data": {...} } untuk detail).
| Endpoint | Data Array/Objek | Properti Utama | Digunakan untuk |
|---|---|---|---|
| Latest & Search | Array data | title, url, image, excerpt | Membuat Card di Grid View. |
| Detail | Objek data | title, images (Array URL), videos (Array), details (Objek) | Menampilkan Galeri Foto di Halaman Detail. |
4. Tugas dan Persyaratan Khusus
* Satu File: Semua kode (HTML5, CSS, dan Vanilla JavaScript) harus ada dalam satu file HTML (CSS di <style>, JS di <script>).
* Proxy Logic: Buatkan fungsi pembantu fetchWithProxy(apiEndpoint) yang:
* Membuat URL lengkap dengan PROXY_URL_BASE dan encodeURIComponent(apiEndpoint).
* Memanggil fetch().
* Mendapatkan response.json() (ini adalah respons dari proxy).
* Mengambil string di response.contents dan mem-parse ulang (JSON.parse()) untuk mendapatkan data API yang asli.
* Tampilan Kartu (Grid View):
* Gunakan CSS Grid untuk menampilkan hasil latest dan search dalam bentuk kartu (card).
* Ekstrak Cosplayer dan Karakter dari properti title untuk ditampilkan secara jelas di setiap kartu. (Contoh parsing: Cari kata 'cosplay' dan '–' untuk membagi informasi).
* Tampilan Detail:
* Ketika kartu diklik, panggil fungsi loadDetail(index) yang akan mengambil properti url dari item yang disimpan.
* Panggil endpoint Detail API menggunakan URL tersebut.
* Tampilkan semua URL dari array images sebagai galeri foto yang responsive di halaman detail.
* Sediakan tombol "Kembali" untuk kembali ke tampilan daftar terakhir (currentListData).
* Initial Load: Halaman harus otomatis memanggil fungsi Terbaru (loadLatest()) saat dimuat. Note jangan kasih source web nya cosplayertele

Files changed (2) hide show
  1. README.md +8 -5
  2. index.html +531 -18
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Cosplayverse Showcase
3
- emoji: πŸ“Š
4
- colorFrom: blue
5
- colorTo: indigo
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: CosplayVerse Showcase 🎭
3
+ colorFrom: green
4
+ colorTo: blue
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://deepsite.hf.co).
index.html CHANGED
@@ -1,19 +1,532 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>CosplayVerse Showcase 🎭</title>
7
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
+ <script src="https://unpkg.com/feather-icons"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script>
12
+ <style>
13
+ .card {
14
+ transition: all 0.3s ease;
15
+ transform: scale(1);
16
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
17
+ }
18
+ .card:hover {
19
+ transform: scale(1.03);
20
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
21
+ }
22
+ .image-container {
23
+ height: 300px;
24
+ overflow: hidden;
25
+ }
26
+ .gallery-image {
27
+ transition: transform 0.5s ease;
28
+ }
29
+ .gallery-image:hover {
30
+ transform: scale(1.05);
31
+ }
32
+ #loader {
33
+ animation: spin 1s linear infinite;
34
+ }
35
+ @keyframes spin {
36
+ 0% { transform: rotate(0deg); }
37
+ 100% { transform: rotate(360deg); }
38
+ }
39
+ .detail-view {
40
+ opacity: 0;
41
+ transition: opacity 0.5s ease;
42
+ }
43
+ .detail-view.active {
44
+ opacity: 1;
45
+ }
46
+ </style>
47
+ </head>
48
+ <body class="bg-gradient-to-b from-purple-900 to-indigo-900 text-white min-h-screen">
49
+ <!-- Header Section -->
50
+ <header class="bg-black bg-opacity-80 backdrop-filter backdrop-blur-lg sticky top-0 z-50">
51
+ <div class="container mx-auto px-4 py-4 flex justify-between items-center">
52
+ <div class="flex items-center space-x-2">
53
+ <i data-feather="aperture" class="text-pink-500"></i>
54
+ <h1 class="text-2xl font-bold bg-gradient-to-r from-pink-500 to-purple-500 bg-clip-text text-transparent">CosplayVerse</h1>
55
+ </div>
56
+ <div class="relative w-1/3">
57
+ <input type="text" id="searchInput" placeholder="Search cosplayers..."
58
+ class="w-full px-4 py-2 rounded-full bg-gray-800 text-white focus:outline-none focus:ring-2 focus:ring-pink-500">
59
+ <button id="searchBtn" class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-pink-500">
60
+ <i data-feather="search"></i>
61
+ </button>
62
+ </div>
63
+ <div class="flex space-x-4">
64
+ <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">
65
+ <i data-feather="refresh-cw" class="w-4 h-4"></i>
66
+ <span>Latest</span>
67
+ </button>
68
+ <button id="toggleTheme" class="p-2 rounded-full bg-gray-800 hover:bg-gray-700 transition-colors">
69
+ <i data-feather="moon" class="w-5 h-5"></i>
70
+ </button>
71
+ </div>
72
+ </div>
73
+ </header>
74
+
75
+ <!-- Main Content -->
76
+ <main class="container mx-auto px-4 py-8">
77
+ <!-- Loading State -->
78
+ <div id="loading" class="flex justify-center items-center py-20">
79
+ <div id="loader" class="w-12 h-12 border-4 border-pink-500 border-t-transparent rounded-full"></div>
80
+ </div>
81
+
82
+ <!-- Grid View -->
83
+ <div id="gridView" class="hidden">
84
+ <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>
85
+ <div id="cosplayGrid" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"></div>
86
+ <div id="pagination" class="flex justify-center mt-8 space-x-2 hidden">
87
+ <button id="prevPage" class="px-4 py-2 bg-gray-800 rounded hover:bg-gray-700 disabled:opacity-50">
88
+ <i data-feather="chevron-left" class="w-5 h-5"></i>
89
+ </button>
90
+ <span id="pageInfo" class="px-4 py-2">Page 1</span>
91
+ <button id="nextPage" class="px-4 py-2 bg-gray-800 rounded hover:bg-gray-700 disabled:opacity-50">
92
+ <i data-feather="chevron-right" class="w-5 h-5"></i>
93
+ </button>
94
+ </div>
95
+ </div>
96
+
97
+ <!-- Detail View -->
98
+ <div id="detailView" class="detail-view hidden">
99
+ <div class="flex justify-between items-center mb-6">
100
+ <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">
101
+ <i data-feather="arrow-left" class="w-5 h-5"></i>
102
+ <span>Back</span>
103
+ </button>
104
+ </div>
105
+
106
+ <div class="bg-black bg-opacity-50 rounded-xl p-6 mb-8">
107
+ <h2 id="detailTitle" class="text-3xl font-bold mb-4"></h2>
108
+ <div id="detailInfo" class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
109
+ <!-- Will be populated with details -->
110
+ </div>
111
+
112
+ <!-- Gallery -->
113
+ <div id="imageGallery" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
114
+ <!-- Images will be inserted here -->
115
+ </div>
116
+
117
+ <!-- Videos -->
118
+ <div id="videoGallery" class="mt-8">
119
+ <h3 class="text-xl font-bold mb-4">Videos</h3>
120
+ <div class="grid grid-cols-1 gap-4">
121
+ <!-- Videos will be inserted here -->
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </main>
127
+
128
+ <!-- Footer -->
129
+ <footer class="bg-black bg-opacity-80 py-8 mt-12">
130
+ <div class="container mx-auto px-4 text-center">
131
+ <div class="flex justify-center space-x-4 mb-4">
132
+ <a href="#" class="text-gray-400 hover:text-pink-500 transition-colors">
133
+ <i data-feather="instagram"></i>
134
+ </a>
135
+ <a href="#" class="text-gray-400 hover:text-pink-500 transition-colors">
136
+ <i data-feather="twitter"></i>
137
+ </a>
138
+ <a href="#" class="text-gray-400 hover:text-pink-500 transition-colors">
139
+ <i data-feather="github"></i>
140
+ </a>
141
+ </div>
142
+ <p class="text-gray-400">Β© 2023 CosplayVerse Showcase. All rights reserved.</p>
143
+ </div>
144
+ </footer>
145
+
146
+ <script>
147
+ // Constants
148
+ const API_BASE_URL = "https://restapi.rizk.my.id/sfwnsfw/cosplaytelensfw";
149
+ const API_KEY = "vip";
150
+ const PROXY_URL_BASE = "https://api.allorigins.win/get?url=";
151
+ let currentPage = 1;
152
+ let currentListData = [];
153
+ let currentView = 'grid';
154
+ let currentSearchQuery = '';
155
+
156
+ // DOM Elements
157
+ const gridView = document.getElementById('gridView');
158
+ const detailView = document.getElementById('detailView');
159
+ const cosplayGrid = document.getElementById('cosplayGrid');
160
+ const loading = document.getElementById('loading');
161
+ const gridTitle = document.getElementById('gridTitle');
162
+ const pagination = document.getElementById('pagination');
163
+ const pageInfo = document.getElementById('pageInfo');
164
+ const prevPage = document.getElementById('prevPage');
165
+ const nextPage = document.getElementById('nextPage');
166
+ const searchInput = document.getElementById('searchInput');
167
+ const searchBtn = document.getElementById('searchBtn');
168
+ const latestBtn = document.getElementById('latestBtn');
169
+ const backBtn = document.getElementById('backBtn');
170
+ const detailTitle = document.getElementById('detailTitle');
171
+ const detailInfo = document.getElementById('detailInfo');
172
+ const imageGallery = document.getElementById('imageGallery');
173
+ const videoGallery = document.getElementById('videoGallery');
174
+ const toggleTheme = document.getElementById('toggleTheme');
175
+
176
+ // Initialize
177
+ document.addEventListener('DOMContentLoaded', () => {
178
+ feather.replace();
179
+ loadLatest();
180
+ setupEventListeners();
181
+
182
+ // Check for saved theme preference
183
+ if (localStorage.getItem('theme') === 'dark') {
184
+ document.documentElement.classList.add('dark');
185
+ toggleTheme.innerHTML = feather.icons['sun'].toSvg();
186
+ } else {
187
+ document.documentElement.classList.remove('dark');
188
+ toggleTheme.innerHTML = feather.icons['moon'].toSvg();
189
+ }
190
+ });
191
+
192
+ function setupEventListeners() {
193
+ // Navigation
194
+ searchBtn.addEventListener('click', () => {
195
+ currentSearchQuery = searchInput.value.trim();
196
+ if (currentSearchQuery) {
197
+ currentPage = 1;
198
+ loadSearch(currentSearchQuery);
199
+ }
200
+ });
201
+
202
+ searchInput.addEventListener('keypress', (e) => {
203
+ if (e.key === 'Enter') {
204
+ currentSearchQuery = searchInput.value.trim();
205
+ if (currentSearchQuery) {
206
+ currentPage = 1;
207
+ loadSearch(currentSearchQuery);
208
+ }
209
+ }
210
+ });
211
+
212
+ latestBtn.addEventListener('click', () => {
213
+ currentSearchQuery = '';
214
+ searchInput.value = '';
215
+ currentPage = 1;
216
+ loadLatest();
217
+ });
218
+
219
+ backBtn.addEventListener('click', () => {
220
+ showGridView();
221
+ });
222
+
223
+ // Pagination
224
+ prevPage.addEventListener('click', () => {
225
+ if (currentPage > 1) {
226
+ currentPage--;
227
+ if (currentSearchQuery) {
228
+ loadSearch(currentSearchQuery);
229
+ } else {
230
+ loadLatest();
231
+ }
232
+ }
233
+ });
234
+
235
+ nextPage.addEventListener('click', () => {
236
+ currentPage++;
237
+ if (currentSearchQuery) {
238
+ loadSearch(currentSearchQuery);
239
+ } else {
240
+ loadLatest();
241
+ }
242
+ });
243
+
244
+ // Theme toggle
245
+ toggleTheme.addEventListener('click', () => {
246
+ if (document.documentElement.classList.contains('dark')) {
247
+ document.documentElement.classList.remove('dark');
248
+ localStorage.setItem('theme', 'light');
249
+ toggleTheme.innerHTML = feather.icons['moon'].toSvg();
250
+ } else {
251
+ document.documentElement.classList.add('dark');
252
+ localStorage.setItem('theme', 'dark');
253
+ toggleTheme.innerHTML = feather.icons['sun'].toSvg();
254
+ }
255
+ });
256
+ }
257
+
258
+ // API Fetch Helper
259
+ async function fetchWithProxy(apiEndpoint) {
260
+ try {
261
+ const encodedUrl = encodeURIComponent(apiEndpoint);
262
+ const proxyUrl = `${PROXY_URL_BASE}${encodedUrl}`;
263
+
264
+ const response = await fetch(proxyUrl);
265
+ const data = await response.json();
266
+
267
+ if (data.contents) {
268
+ return JSON.parse(data.contents);
269
+ }
270
+ return null;
271
+ } catch (error) {
272
+ console.error('Error fetching data:', error);
273
+ return null;
274
+ }
275
+ }
276
+
277
+ // Load Latest Cosplays
278
+ async function loadLatest() {
279
+ showLoading();
280
+ gridTitle.textContent = 'Latest Cosplays';
281
+
282
+ const endpoint = `${API_BASE_URL}/latest?page=${currentPage}&apikey=${API_KEY}`;
283
+ const data = await fetchWithProxy(endpoint);
284
+
285
+ if (data && data.status === 'success' && data.data) {
286
+ currentListData = data.data;
287
+ renderCosplayGrid();
288
+ updatePagination();
289
+ showGridView();
290
+ } else {
291
+ showError('Failed to load latest cosplays');
292
+ }
293
+ }
294
+
295
+ // Load Search Results
296
+ async function loadSearch(query) {
297
+ showLoading();
298
+ gridTitle.textContent = `Search Results for "${query}"`;
299
+
300
+ const endpoint = `${API_BASE_URL}/search?query=${encodeURIComponent(query)}&apikey=${API_KEY}`;
301
+ const data = await fetchWithProxy(endpoint);
302
+
303
+ if (data && data.status === 'success' && data.data) {
304
+ currentListData = data.data;
305
+ renderCosplayGrid();
306
+ pagination.classList.add('hidden'); // Search doesn't have pagination
307
+ showGridView();
308
+ } else {
309
+ showError('No results found');
310
+ }
311
+ }
312
+
313
+ // Load Detail View
314
+ async function loadDetail(index) {
315
+ showLoading();
316
+ const item = currentListData[index];
317
+
318
+ const endpoint = `${API_BASE_URL}/detail?url=${encodeURIComponent(item.url)}&apikey=${API_KEY}`;
319
+ const data = await fetchWithProxy(endpoint);
320
+
321
+ if (data && data.status === 'success' && data.data) {
322
+ renderDetailView(data.data);
323
+ showDetailView();
324
+ } else {
325
+ showError('Failed to load cosplay details');
326
+ }
327
+ }
328
+
329
+ // Render Grid View
330
+ function renderCosplayGrid() {
331
+ cosplayGrid.innerHTML = '';
332
+
333
+ currentListData.forEach((item, index) => {
334
+ const card = document.createElement('div');
335
+ card.className = 'card bg-gray-800 rounded-xl overflow-hidden hover:cursor-pointer transition-all duration-300';
336
+ card.addEventListener('click', () => loadDetail(index));
337
+
338
+ // Parse title to extract cosplayer and character
339
+ let cosplayer = 'Unknown';
340
+ let character = 'Unknown';
341
+
342
+ if (item.title) {
343
+ const parts = item.title.split('–');
344
+ if (parts.length >= 2) {
345
+ cosplayer = parts[0].trim();
346
+ character = parts[1].trim();
347
+ } else {
348
+ cosplayer = item.title.trim();
349
+ }
350
+ }
351
+
352
+ card.innerHTML = `
353
+ <div class="image-container">
354
+ <img src="${item.image}" alt="${item.title}" class="w-full h-full object-cover">
355
+ </div>
356
+ <div class="p-4">
357
+ <h3 class="text-xl font-bold mb-1 truncate">${character}</h3>
358
+ <p class="text-pink-400 mb-2">${cosplayer}</p>
359
+ <p class="text-gray-400 text-sm line-clamp-2">${item.excerpt || ''}</p>
360
+ </div>
361
+ `;
362
+
363
+ cosplayGrid.appendChild(card);
364
+ });
365
+
366
+ // Animate cards
367
+ anime({
368
+ targets: '.card',
369
+ opacity: [0, 1],
370
+ translateY: [20, 0],
371
+ delay: anime.stagger(50),
372
+ easing: 'easeOutExpo'
373
+ });
374
+ }
375
+
376
+ // Render Detail View
377
+ function renderDetailView(data) {
378
+ detailTitle.textContent = data.title || 'Cosplay Details';
379
+
380
+ // Clear previous content
381
+ detailInfo.innerHTML = '';
382
+ imageGallery.innerHTML = '';
383
+ videoGallery.innerHTML = '';
384
+
385
+ // Parse title to extract cosplayer and character if available
386
+ let cosplayer = 'Unknown';
387
+ let character = 'Unknown';
388
+
389
+ if (data.title) {
390
+ const parts = data.title.split('–');
391
+ if (parts.length >= 2) {
392
+ cosplayer = parts[0].trim();
393
+ character = parts[1].trim();
394
+ } else {
395
+ cosplayer = data.title.trim();
396
+ }
397
+ }
398
+
399
+ // Add basic info
400
+ detailInfo.innerHTML = `
401
+ <div class="bg-gray-800 p-4 rounded-lg">
402
+ <h4 class="text-lg font-bold text-pink-400 mb-2">Cosplayer</h4>
403
+ <p>${cosplayer}</p>
404
+ </div>
405
+ <div class="bg-gray-800 p-4 rounded-lg">
406
+ <h4 class="text-lg font-bold text-pink-400 mb-2">Character</h4>
407
+ <p>${character}</p>
408
+ </div>
409
+ <div class="bg-gray-800 p-4 rounded-lg">
410
+ <h4 class="text-lg font-bold text-pink-400 mb-2">Details</h4>
411
+ <p>${data.details?.description || 'No additional details available'}</p>
412
+ </div>
413
+ `;
414
+
415
+ // Add images
416
+ if (data.images && data.images.length > 0) {
417
+ data.images.forEach((imageUrl, index) => {
418
+ const imgContainer = document.createElement('div');
419
+ imgContainer.className = 'relative overflow-hidden rounded-lg gallery-image';
420
+
421
+ const img = document.createElement('img');
422
+ img.src = imageUrl;
423
+ img.alt = `Cosplay image ${index + 1}`;
424
+ img.className = 'w-full h-64 object-cover';
425
+
426
+ imgContainer.appendChild(img);
427
+ imageGallery.appendChild(imgContainer);
428
+ });
429
+ } else {
430
+ imageGallery.innerHTML = '<p class="text-gray-400">No images available</p>';
431
+ }
432
+
433
+ // Add videos if available
434
+ if (data.videos && data.videos.length > 0) {
435
+ const videosContainer = videoGallery.querySelector('div');
436
+ data.videos.forEach(videoUrl => {
437
+ const videoContainer = document.createElement('div');
438
+ videoContainer.className = 'aspect-w-16 aspect-h-9';
439
+ videoContainer.innerHTML = `
440
+ <iframe src="${videoUrl}" class="w-full h-64 rounded-lg" frameborder="0" allowfullscreen></iframe>
441
+ `;
442
+ videosContainer.appendChild(videoContainer);
443
+ });
444
+ } else {
445
+ videoGallery.innerHTML = '<p class="text-gray-400">No videos available</p>';
446
+ }
447
+
448
+ // Animate elements
449
+ anime({
450
+ targets: [detailTitle, '#detailInfo > div', '#imageGallery > div', '#videoGallery'],
451
+ opacity: [0, 1],
452
+ translateY: [20, 0],
453
+ delay: anime.stagger(50),
454
+ easing: 'easeOutExpo'
455
+ });
456
+ }
457
+
458
+ // View Management
459
+ function showLoading() {
460
+ loading.classList.remove('hidden');
461
+ gridView.classList.add('hidden');
462
+ detailView.classList.add('hidden');
463
+ }
464
+
465
+ function showGridView() {
466
+ loading.classList.add('hidden');
467
+ detailView.classList.add('hidden');
468
+ gridView.classList.remove('hidden');
469
+ currentView = 'grid';
470
+
471
+ // Scroll to top
472
+ window.scrollTo({ top: 0, behavior: 'smooth' });
473
+ }
474
+
475
+ function showDetailView() {
476
+ loading.classList.add('hidden');
477
+ gridView.classList.add('hidden');
478
+ detailView.classList.remove('hidden');
479
+ detailView.classList.add('active');
480
+ currentView = 'detail';
481
+
482
+ // Scroll to top
483
+ window.scrollTo({ top: 0, behavior: 'smooth' });
484
+ }
485
+
486
+ function showError(message) {
487
+ loading.classList.add('hidden');
488
+
489
+ if (currentView === 'grid') {
490
+ cosplayGrid.innerHTML = `
491
+ <div class="col-span-full text-center py-12">
492
+ <i data-feather="alert-triangle" class="w-12 h-12 mx-auto text-yellow-400 mb-4"></i>
493
+ <h3 class="text-xl font-bold">${message}</h3>
494
+ </div>
495
+ `;
496
+ feather.replace();
497
+ } else {
498
+ detailView.innerHTML = `
499
+ <div class="text-center py-12">
500
+ <i data-feather="alert-triangle" class="w-12 h-12 mx-auto text-yellow-400 mb-4"></i>
501
+ <h3 class="text-xl font-bold">${message}</h3>
502
+ <button id="backBtn" class="mt-4 px-4 py-2 bg-pink-600 rounded-full hover:bg-pink-700 transition-colors">
503
+ Go Back
504
+ </button>
505
+ </div>
506
+ `;
507
+ feather.replace();
508
+ document.getElementById('backBtn').addEventListener('click', showGridView);
509
+ }
510
+ }
511
+
512
+ function updatePagination() {
513
+ if (currentListData.length > 0) {
514
+ pagination.classList.remove('hidden');
515
+ pageInfo.textContent = `Page ${currentPage}`;
516
+ prevPage.disabled = currentPage === 1;
517
+ nextPage.disabled = currentListData.length < 20; // Assuming 20 items per page
518
+ } else {
519
+ pagination.classList.add('hidden');
520
+ }
521
+ }
522
+
523
+ // Theme management
524
+ function toggleDarkMode() {
525
+ document.documentElement.classList.toggle('dark');
526
+ const isDark = document.documentElement.classList.contains('dark');
527
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
528
+ toggleTheme.innerHTML = isDark ? feather.icons['sun'].toSvg() : feather.icons['moon'].toSvg();
529
+ }
530
+ </script>
531
+ </body>
532
  </html>