diff --git a/index.html b/index.html
index 9a400d1..035aeab 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
- TV Show Project | My Name (My GitHub username)
+ TV Show Project | Joanne O'Malley (joanne342)
diff --git a/script.js b/script.js
index 87a7de8..01cbdf4 100644
--- a/script.js
+++ b/script.js
@@ -1,12 +1,297 @@
-//You can edit ALL of the code here
-function setup() {
- const allEpisodes = getAllEpisodes();
- makePageForEpisodes(allEpisodes);
+// Helper to format season and episode numbers into "S01E01" format
+function formatEpisodeCode(episode) {
+ const season = String(episode.season).padStart(2, "0");
+ const number = String(episode.number).padStart(2, "0");
+ return `S${season}E${number}`;
}
-function makePageForEpisodes(episodeList) {
+// Cache episode requests so each episode URL is fetched only once
+const episodeCache = {};
+
+function getEpisodesForShow(showId) {
+ if (!episodeCache[showId]) {
+ episodeCache[showId] = fetch(
+ `https://api.tvmaze.com/shows/${showId}/episodes`
+ ).then((response) => {
+ if (!response.ok) {
+ throw new Error(`Failed to load episodes for show ID ${showId}`);
+ }
+
+ return response.json();
+ });
+ }
+
+ return episodeCache[showId];
+}
+
+// Creates or gets the .controls container
+function getControlsContainer() {
+ let controls = document.querySelector(".controls");
+
+ if (!controls) {
+ controls = document.createElement("div");
+ controls.className = "controls";
+
+ const root = document.getElementById("root");
+ root.parentNode.insertBefore(controls, root);
+ }
+
+ return controls;
+}
+
+async function setup() {
+ const controls = getControlsContainer();
+
+ // Create the controls once (Added Back Button)
+ controls.innerHTML = `
+
+
+
+
+ Loading shows...
+ `;
+
+ const backToShowsBtn = document.getElementById("back-to-shows");
+ const showSelect = document.getElementById("show-select");
+ const episodeSelect = document.getElementById("episode-select");
+ const searchInput = document.getElementById("search-input");
+ const searchCount = document.getElementById("search-count");
+
+ let currentEpisodes = [];
+ let currentShowName = "";
+ let allShows = [];
+ let currentView = "shows"; // View state tracker ("shows" | "episodes")
+
+ try {
+ // Fetch all shows
+ const showsResponse = await fetch("https://api.tvmaze.com/shows");
+
+ if (!showsResponse.ok) {
+ throw new Error("Failed to load shows");
+ }
+
+ allShows = await showsResponse.json();
+
+ // Sort alphabetically, ignoring case
+ allShows.sort((a, b) =>
+ a.name.localeCompare(b.name, undefined, {
+ sensitivity: "base",
+ })
+ );
+
+ // Populate show dropdown selector
+ showSelect.innerHTML = ``;
+ allShows.forEach((show) => {
+ const option = document.createElement("option");
+ option.value = show.id;
+ option.textContent = show.name;
+ showSelect.appendChild(option);
+ });
+
+ // Renders the main shows front-page view
+ function renderShowsView(showsToDisplay) {
+ currentView = "shows";
+ backToShowsBtn.style.display = "none";
+ episodeSelect.style.display = "none";
+ showSelect.value = "";
+ searchInput.value = "";
+ searchInput.placeholder = "Search shows by name, genre, summary...";
+
+ makePageForShows(showsToDisplay, loadShow);
+ searchCount.textContent = `Displaying ${showsToDisplay.length}/${allShows.length} shows`;
+ }
+
+ // Populate the episode dropdown for the current show
+ function populateEpisodeSelect(episodes) {
+ episodeSelect.innerHTML = ``;
+
+ episodes.forEach((episode) => {
+ const option = document.createElement("option");
+ option.value = episode.id;
+ option.textContent = `${formatEpisodeCode(episode)} - ${episode.name}`;
+ episodeSelect.appendChild(option);
+ });
+ }
+
+ // Load episodes for a show
+ async function loadShow(showId) {
+ searchCount.textContent = "Loading episodes...";
+
+ try {
+ const selectedShow = allShows.find(
+ (show) => show.id === Number(showId)
+ );
+
+ if (!selectedShow) return;
+
+ currentShowName = selectedShow.name;
+ currentEpisodes = await getEpisodesForShow(showId);
+
+ currentView = "episodes";
+ backToShowsBtn.style.display = "inline-block";
+ episodeSelect.style.display = "inline-block";
+ showSelect.value = showId;
+ searchInput.value = "";
+ searchInput.placeholder = "Search episodes...";
+
+ populateEpisodeSelect(currentEpisodes);
+ makePageForEpisodes(currentEpisodes, currentShowName);
+ searchCount.textContent = `Displaying ${currentEpisodes.length}/${currentEpisodes.length} episodes`;
+ } catch (error) {
+ searchCount.textContent = "Error loading episodes.";
+ console.error(error);
+ }
+ }
+
+ // Display shows listing on app load
+ renderShowsView(allShows);
+
+ // Navigation: Back to shows button
+ backToShowsBtn.addEventListener("click", () => {
+ renderShowsView(allShows);
+ });
+
+ // Show dropdown selector
+ showSelect.addEventListener("change", async (event) => {
+ if (event.target.value) {
+ await loadShow(event.target.value);
+ } else {
+ renderShowsView(allShows);
+ }
+ });
+
+ // Dual-purpose search (Shows or Episodes based on view)
+ searchInput.addEventListener("input", (event) => {
+ const searchTerm = event.target.value.toLowerCase().trim();
+
+ if (currentView === "shows") {
+ const filteredShows = allShows.filter((show) => {
+ const nameMatch = show.name.toLowerCase().includes(searchTerm);
+ const genreMatch = show.genres.some((g) =>
+ g.toLowerCase().includes(searchTerm)
+ );
+ const summaryMatch = show.summary
+ ? show.summary.replace(/<[^>]*>/g, "").toLowerCase().includes(searchTerm)
+ : false;
+
+ return nameMatch || genreMatch || summaryMatch;
+ });
+
+ makePageForShows(filteredShows, loadShow);
+ searchCount.textContent = `Displaying ${filteredShows.length}/${allShows.length} shows`;
+ } else {
+ episodeSelect.value = "ALL";
+
+ const filteredEpisodes = currentEpisodes.filter((episode) => {
+ const nameMatch = episode.name.toLowerCase().includes(searchTerm);
+ const summaryMatch = episode.summary
+ ? episode.summary.replace(/<[^>]*>/g, "").toLowerCase().includes(searchTerm)
+ : false;
+
+ return nameMatch || summaryMatch;
+ });
+
+ makePageForEpisodes(filteredEpisodes, currentShowName);
+ searchCount.textContent = `Displaying ${filteredEpisodes.length}/${currentEpisodes.length} episodes`;
+ }
+ });
+
+ // Episode dropdown selector
+ episodeSelect.addEventListener("change", (event) => {
+ const selectedId = event.target.value;
+ searchInput.value = "";
+
+ if (selectedId === "ALL") {
+ makePageForEpisodes(currentEpisodes, currentShowName);
+ searchCount.textContent = `Displaying ${currentEpisodes.length}/${currentEpisodes.length} episodes`;
+ return;
+ }
+
+ const selectedEpisode = currentEpisodes.find(
+ (episode) => episode.id === Number(selectedId)
+ );
+
+ if (selectedEpisode) {
+ makePageForEpisodes([selectedEpisode], currentShowName);
+ searchCount.textContent = `Displaying 1/${currentEpisodes.length} episodes`;
+ }
+ });
+ } catch (error) {
+ searchCount.textContent = "Failed to load TV shows.";
+ console.error(error);
+ }
+}
+
+// Render show cards for front page listing
+function makePageForShows(showsList, onShowClick) {
+ const rootElem = document.getElementById("root");
+ rootElem.innerHTML = "";
+
+ const container = document.createElement("div");
+ container.className = "shows-container";
+
+ showsList.forEach((show) => {
+ const card = document.createElement("article");
+ card.className = "show-card";
+
+ card.innerHTML = `
+ ${show.name}
+
+ ${show.summary || "
No summary available.
"}
+
+ `;
+
+ card.querySelector(".show-title").addEventListener("click", () => {
+ onShowClick(show.id);
+ });
+
+ container.appendChild(card);
+ });
+
+ rootElem.appendChild(container);
+}
+
+// Render episode cards
+function makePageForEpisodes(episodeList, showName) {
const rootElem = document.getElementById("root");
- rootElem.textContent = `Got ${episodeList.length} episode(s)`;
+ rootElem.innerHTML = "";
+
+ const heading = document.createElement("h1");
+ heading.textContent = `${showName} Episodes`;
+ rootElem.appendChild(heading);
+
+ const credit = document.createElement("p");
+ credit.innerHTML =
+ 'Data originally from TVMaze.com';
+ rootElem.appendChild(credit);
+
+ const container = document.createElement("div");
+ container.className = "episodes-container";
+
+ episodeList.forEach((episode) => {
+ const card = document.createElement("article");
+ card.className = "episode-card";
+
+ card.innerHTML = `
+ ${episode.name} - ${formatEpisodeCode(episode)}
+
+ ${episode.summary || ""}
+ ${
+ episode.url
+ ? `View on TVMaze`
+ : ""
+ }
+ `;
+
+ container.appendChild(card);
+ });
+
+ rootElem.appendChild(container);
}
window.onload = setup;
diff --git a/style.css b/style.css
index 77cb8d4..89451ea 100644
--- a/style.css
+++ b/style.css
@@ -1,3 +1,59 @@
+body {
+ font-family: system-ui, sans-serif;
+ background: #fafafa;
+ padding: 2rem;
+}
+
#root {
- color: red;
+ width: min(1100px, 100%);
+ margin-inline: auto;
+}
+
+.episodes-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1.5rem;
+}
+
+.episode-card {
+ flex: 1 1 300px;
+ padding: 1.25rem;
+ background: #fff;
+ border-radius: 12px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+}
+
+.episode-card img {
+ display: block;
+ aspect-ratio: 16 / 9;
+ object-fit: cover;
+ border-radius: 10px;
+}
+
+.episode-card h2 {
+ margin: 1rem 0 0.75rem;
+}
+
+.episode-summary {
+ color: #666;
+ line-height: 1.6;
+}
+
+.episode-card a {
+ display: inline-block;
+ margin-top: 0.5rem;
+ color: #145dbf;
+}
+
+.episode-card a:hover {
+ opacity: 0.75;
+}
+.controls {
+ display: flex;
+ gap: 20px;
+}
+
+.controls input,
+.controls select {
+ padding: 10px;
}