diff --git a/index.html b/index.html index e0437a9..d3c9517 100644 --- a/index.html +++ b/index.html @@ -3,21 +3,27 @@ - TV Episodes — Level 400 + TV Show Project | Level 500 +
- + Filtering for + + - + - + - + + +
-
+
+ diff --git a/script.js b/script.js index 81f1a88..00e2856 100644 --- a/script.js +++ b/script.js @@ -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 = ` +
+
+

${s.name}

+

${s.summary || ""}

+
+
+ Rated: ${s.rating.average || "N/A"}
+ Genres: ${genres}
+ Status: ${s.status}
+ Runtime: ${s.runtime || "-"} +
+ `; + 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}`; @@ -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 = ` -

${ep.name} - ${code}

+

${ep.name}

+

S${String(ep.season).padStart(2, "0")}E${String(ep.number).padStart(2, "0")}

${ep.summary || ""}

`; - 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(); diff --git a/style.css b/style.css index 228711c..5b5e1c1 100644 --- a/style.css +++ b/style.css @@ -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; }