Skip to content

Commit 5faa1ac

Browse files
feat: implement item pages
1 parent 1987bb2 commit 5faa1ac

17 files changed

Lines changed: 1425 additions & 16 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ Information from YouTube API is also added to the database for videos.
1010

1111
- [x] Build with Jekyll
1212
- [ ] Revamp index page
13-
- [ ] Provide an item page for each API item
13+
- [x] Provide an item page for each API item
1414
- [ ] Add unit tests
1515
- [ ] Add code coverage

gh-pages-template/_config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
site-js: [] # disable crowdin for this site
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<!-- Image Gallery Modal -->
2+
<div class="modal fade" id="imageModal" tabindex="-1" aria-labelledby="imageModalLabel" aria-hidden="true">
3+
<div class="modal-dialog modal-fullscreen">
4+
<div class="modal-content bg-dark">
5+
<div class="modal-header border-0">
6+
<h5 class="modal-title text-white" id="imageModalLabel">Image Gallery</h5>
7+
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
8+
</div>
9+
<div class="modal-body d-flex align-items-center justify-content-center position-relative p-0">
10+
<button type="button" class="btn btn-light position-absolute start-0 ms-3 rounded-circle" id="modal-prev-btn" style="z-index: 1050; width: 50px; height: 50px;">
11+
<span class="material-symbols-outlined">chevron_left</span>
12+
</button>
13+
<img id="modal-image" src="" alt="" class="img-fluid" style="max-height: 90vh; max-width: 100%;">
14+
<button type="button" class="btn btn-light position-absolute end-0 me-3 rounded-circle" id="modal-next-btn" style="z-index: 1050; width: 50px; height: 50px;">
15+
<span class="material-symbols-outlined">chevron_right</span>
16+
</button>
17+
</div>
18+
<div class="modal-footer border-0 justify-content-center">
19+
<span class="text-white" id="modal-counter">1 / 1</span>
20+
</div>
21+
</div>
22+
</div>
23+
</div>
24+
25+
<script>
26+
// Image modal functionality
27+
(function() {
28+
let currentImages = [];
29+
let currentIndex = 0;
30+
31+
const modal = document.getElementById('imageModal');
32+
const modalImage = document.getElementById('modal-image');
33+
const modalCounter = document.getElementById('modal-counter');
34+
const prevBtn = document.getElementById('modal-prev-btn');
35+
const nextBtn = document.getElementById('modal-next-btn');
36+
37+
function updateImage() {
38+
if (currentImages.length === 0) return;
39+
modalImage.src = currentImages[currentIndex];
40+
modalCounter.textContent = `${currentIndex + 1} / ${currentImages.length}`;
41+
}
42+
43+
function showPrev() {
44+
currentIndex = (currentIndex - 1 + currentImages.length) % currentImages.length;
45+
updateImage();
46+
}
47+
48+
function showNext() {
49+
currentIndex = (currentIndex + 1) % currentImages.length;
50+
updateImage();
51+
}
52+
53+
// Event listeners
54+
prevBtn.addEventListener('click', showPrev);
55+
nextBtn.addEventListener('click', showNext);
56+
57+
// Keyboard navigation
58+
document.addEventListener('keydown', function(e) {
59+
if (modal.classList.contains('show')) {
60+
if (e.key === 'ArrowLeft') showPrev();
61+
else if (e.key === 'ArrowRight') showNext();
62+
}
63+
});
64+
65+
// Global function to open modal
66+
window.openImageModal = function(images, index) {
67+
currentImages = images;
68+
currentIndex = index;
69+
updateImage();
70+
const bsModal = new bootstrap.Modal(modal);
71+
bsModal.show();
72+
};
73+
})();
74+
</script>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* character_detail.js
3+
* Renders a single character detail page.
4+
* Depends on item_detail.js being loaded first.
5+
*/
6+
7+
function renderCharacter(data) {
8+
document.title = (data.name || "Character") + " – GameDB";
9+
document.getElementById("character-name").textContent = data.name || "Unknown Character";
10+
11+
// Mug shot
12+
const mugEl = document.getElementById("character-mug");
13+
const mugPlaceholder = document.getElementById("character-mug-placeholder");
14+
const mugUrl = data.mug_shot && data.mug_shot.url
15+
? igdbImageUrl(data.mug_shot.url, "t_screenshot_big")
16+
: null;
17+
if (mugUrl) {
18+
mugEl.src = mugUrl;
19+
mugEl.alt = data.name || "";
20+
mugEl.style.display = "";
21+
} else {
22+
mugPlaceholder.style.display = null;
23+
mugPlaceholder.style.removeProperty("display");
24+
}
25+
26+
// Badges / meta
27+
const badgesEl = document.getElementById("character-badges");
28+
29+
if (data.character_gender && data.character_gender.name) {
30+
badgesEl.appendChild(makeBadge(data.character_gender.name, "bg-info text-dark"));
31+
}
32+
if (data.character_species && data.character_species.name) {
33+
badgesEl.appendChild(makeBadge(data.character_species.name, "bg-secondary"));
34+
}
35+
36+
// Games
37+
if (data.games && data.games.length > 0) {
38+
const section = document.getElementById("character-games-section");
39+
section.classList.remove("d-none");
40+
renderGameList(document.getElementById("character-games"), data.games);
41+
}
42+
}
43+
44+
document.addEventListener("DOMContentLoaded", () => {
45+
loadItemDetail("characters", renderCharacter);
46+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* collection_detail.js
3+
* Renders a single collection (series) detail page.
4+
* Depends on item_detail.js being loaded first.
5+
*/
6+
7+
function renderCollection(data) {
8+
document.title = (data.name || "Series") + " – GameDB";
9+
document.getElementById("collection-name").textContent = data.name || "Unknown Series";
10+
11+
// IGDB link
12+
if (data.url) {
13+
const igdbLink = document.getElementById("collection-igdb-link");
14+
igdbLink.href = data.url;
15+
igdbLink.classList.remove("d-none");
16+
}
17+
18+
// Games
19+
if (data.games && data.games.length > 0) {
20+
const section = document.getElementById("collection-games-section");
21+
section.classList.remove("d-none");
22+
renderGameList(document.getElementById("collection-games"), data.games);
23+
}
24+
}
25+
26+
document.addEventListener("DOMContentLoaded", () => {
27+
loadItemDetail("collections", renderCollection);
28+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* franchise_detail.js
3+
* Renders a single franchise detail page.
4+
* Depends on item_detail.js being loaded first.
5+
*/
6+
7+
function renderFranchise(data) {
8+
document.title = (data.name || "Franchise") + " – GameDB";
9+
document.getElementById("franchise-name").textContent = data.name || "Unknown Franchise";
10+
11+
// IGDB link
12+
if (data.url) {
13+
const igdbLink = document.getElementById("franchise-igdb-link");
14+
igdbLink.href = data.url;
15+
igdbLink.classList.remove("d-none");
16+
}
17+
18+
// Games
19+
if (data.games && data.games.length > 0) {
20+
const section = document.getElementById("franchise-games-section");
21+
section.classList.remove("d-none");
22+
renderGameList(document.getElementById("franchise-games"), data.games);
23+
}
24+
}
25+
26+
document.addEventListener("DOMContentLoaded", () => {
27+
loadItemDetail("franchises", renderFranchise);
28+
});

0 commit comments

Comments
 (0)