Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,27 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TV Episodes — Level 400</title>
<title>TV Show Project | Level 500</title>
<link rel="stylesheet" href="style.css" />
</head>

<body>
<header>
<select id="showSelector"></select>
<span>Filtering for</span>
<input id="showSearch" type="text" />
<span id="showCount"></span>

<select id="episodeSelector"></select>
<select id="showSelector"></select>

<input id="episodeSearch" type="text" placeholder="your search term..." />
<button id="backBtn" class="hidden">← All Shows</button>

<span id="episodeCount"></span>
<select id="episodeSelector" class="hidden"></select>
<input id="episodeSearch" class="hidden" placeholder="Search episodes..." />
<span id="episodeCount" class="hidden"></span>
</header>

<div id="episodesGrid"></div>
<div id="showsView"></div>
<div id="episodesView" class="hidden"></div>

<script src="script.js"></script>
</body>
Expand Down
142 changes: 100 additions & 42 deletions script.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,95 @@
const episodeSelector = document.getElementById("episodeSelector");
const showsView = document.getElementById("showsView");
const episodesView = document.getElementById("episodesView");
const showSearch = document.getElementById("showSearch");
const episodeSearch = document.getElementById("episodeSearch");
const episodeCount = document.getElementById("episodeCount");
const showSelector = document.getElementById("showSelector");
const episodesGrid = document.getElementById("episodesGrid");
const episodeSelector = document.getElementById("episodeSelector");
const showCount = document.getElementById("showCount");
const episodeCount = document.getElementById("episodeCount");
const backBtn = document.getElementById("backBtn");

let episodes = [];
let allShows = [];
let shows = [];
let episodesCache = {};
let currentEpisodes = [];

async function loadShows() {
const res = await fetch("https://api.tvmaze.com/shows");
allShows = await res.json();
allShows.sort((a, b) => a.name.localeCompare(b.name));

allShows.forEach((show) => {
shows = await res.json();
shows.sort((a, b) => a.name.localeCompare(b.name));
showSelector.innerHTML = "";
shows.forEach((s) => {
const op = document.createElement("option");
op.value = show.id;
op.textContent = show.name;
op.value = s.id;
op.textContent = s.name;
showSelector.appendChild(op);
});
renderShowList(shows);
updateShowCount(shows.length);
}

function renderShowList(list) {
showsView.innerHTML = "";
list.forEach((s) => {
const card = document.createElement("div");
card.className = "showCard";
card.dataset.id = s.id;
const genres = s.genres.join(" | ");
card.innerHTML = `
<div><img src="${s.image ? s.image.medium : ""}"></div>
<div>
<h2 class="showTitle">${s.name}</h2>
<p>${s.summary || ""}</p>
</div>
<div class="showInfoBox">
Rated: ${s.rating.average || "N/A"}<br>
Genres: ${genres}<br>
Status: ${s.status}<br>
Runtime: ${s.runtime || "-"}
</div>
`;
card.addEventListener("click", () => openShow(s.id));
showsView.appendChild(card);
});
}

loadEpisodes(allShows[0].id);
function updateShowCount(n) {
showCount.textContent = `found ${n} shows`;
}

async function loadEpisodes(showID) {
const res = await fetch(`https://api.tvmaze.com/shows/${showID}/episodes`);
episodes = await res.json();
function searchShows() {
const t = showSearch.value.toLowerCase();
const filtered = shows.filter(
(s) =>
s.name.toLowerCase().includes(t) ||
s.summary.toLowerCase().includes(t) ||
s.genres.join(" ").toLowerCase().includes(t)
);
renderShowList(filtered);
updateShowCount(filtered.length);
}

async function openShow(id) {
showsView.classList.add("hidden");
backBtn.classList.remove("hidden");
episodeSelector.classList.remove("hidden");
episodeSearch.classList.remove("hidden");
episodeCount.classList.remove("hidden");

if (!episodesCache[id]) {
const res = await fetch(`https://api.tvmaze.com/shows/${id}/episodes`);
episodesCache[id] = await res.json();
}

currentEpisodes = episodesCache[id];
buildEpisodeSelector();
renderEpisodes(episodes);
updateCount(episodes.length, episodes.length);
renderEpisodes(currentEpisodes);
updateEpisodeCount(currentEpisodes.length, currentEpisodes.length);
}

function buildEpisodeSelector() {
episodeSelector.innerHTML = "";
episodes.forEach((ep) => {
const code = `S${String(ep.season).padStart(2, "0")}E${String(
ep.number
).padStart(2, "0")}`;
currentEpisodes.forEach((ep) => {
const code = `S${String(ep.season).padStart(2, "0")}E${String(ep.number).padStart(2, "0")}`;
const op = document.createElement("option");
op.value = ep.id;
op.textContent = `${code} - ${ep.name}`;
Expand All @@ -44,51 +98,55 @@ function buildEpisodeSelector() {
}

function renderEpisodes(list) {
episodesGrid.innerHTML = "";
episodesView.classList.remove("hidden");
episodesView.innerHTML = "";
list.forEach((ep) => {
const code = `S${String(ep.season).padStart(2, "0")}E${String(
ep.number
).padStart(2, "0")}`;

const card = document.createElement("div");
card.className = "episodeCard";

card.innerHTML = `
<h2>${ep.name} - ${code}</h2>
<h3>${ep.name}</h3>
<p>S${String(ep.season).padStart(2, "0")}E${String(ep.number).padStart(2, "0")}</p>
<img src="${ep.image ? ep.image.medium : ""}">
<p>${ep.summary || ""}</p>
`;
episodesGrid.appendChild(card);
episodesView.appendChild(card);
});
}

function updateCount(n, total) {
episodeCount.textContent = `Displaying ${n}/${total} episodes.`;
function updateEpisodeCount(showing, total) {
episodeCount.textContent = `Displaying ${showing}/${total} episodes`;
}

function filterEpisodes() {
const txt = episodeSearch.value.toLowerCase();

const filtered = episodes.filter(
const t = episodeSearch.value.toLowerCase();
const filtered = currentEpisodes.filter(
(ep) =>
ep.name.toLowerCase().includes(txt) ||
(ep.summary && ep.summary.toLowerCase().includes(txt))
ep.name.toLowerCase().includes(t) ||
ep.summary.toLowerCase().includes(t)
);

renderEpisodes(filtered);
updateCount(filtered.length, episodes.length);
updateEpisodeCount(filtered.length, currentEpisodes.length);
}

function jumpToEpisode() {
const id = episodeSelector.value;
const index = episodes.findIndex((ep) => ep.id == id);

const card = episodesGrid.children[index];
const card = document.querySelector(`.episodeCard:nth-child(${currentEpisodes.findIndex(e => e.id == id) + 1})`);
if (card) card.scrollIntoView({ behavior: "smooth" });
}

function goBack() {
episodesView.classList.add("hidden");
backBtn.classList.add("hidden");
episodeSelector.classList.add("hidden");
episodeSearch.classList.add("hidden");
episodeCount.classList.add("hidden");
showsView.classList.remove("hidden");
episodesView.innerHTML = "";
}

showSearch.addEventListener("input", searchShows);
episodeSearch.addEventListener("input", filterEpisodes);
episodeSelector.addEventListener("change", jumpToEpisode);
showSelector.addEventListener("change", (e) => loadEpisodes(e.target.value));
backBtn.addEventListener("click", goBack);

loadShows();
83 changes: 62 additions & 21 deletions style.css
Original file line number Diff line number Diff line change
@@ -1,33 +1,74 @@
#episodesGrid {
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
background: #f4f4f4;
}

header {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 15px;
padding: 15px;
background: #eee;
border-bottom: 1px solid #ccc;
}

header input,
header select {
padding: 6px;
}

.hidden {
display: none;
}

#showsView,
#episodesView {
padding: 30px;
display: flex;
flex-direction: column;
gap: 40px;
}

.showCard {
background: white;
padding: 25px;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
justify-items: center;
gap: 30px;
grid-template-columns: 180px 1fr 200px;
gap: 25px;
}

.episodeCard {
width: 350px;
background: white;
border-radius: 12px;
padding: 20px;
border: 2px solid #ddd;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.08);
.showCard img {
width: 180px;
border-radius: 6px;
}

.episodeCard h2 {
margin: 0 0 15px 0;
text-align: center;
font-size: 20px;
.showTitle {
font-size: 32px;
margin: 0 0 10px 0;
}

.episodeCard img {
width: 100%;
.showInfoBox {
background: #fafafa;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
border: 1px solid #ddd;
font-size: 14px;
line-height: 1.6;
}

.episodeCard p {
font-size: 14px;
line-height: 1.5;
#episodesView .episodeCard {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.episodeCard img {
width: 100%;
max-width: 320px;
border-radius: 6px;
}