From 072c97858de30b762b8b3a57c0db6cd0e2b2e068 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Sun, 2 Aug 2026 09:51:16 +0100 Subject: [PATCH 01/24] Updated index.html to include my name and github user name. --- index.html | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/index.html b/index.html index 9a400d1..e7eb4b5 100644 --- a/index.html +++ b/index.html @@ -1,21 +1,23 @@ - - - - TV Show Project | My Name (My GitHub username) - - - - -
-
+ + + + TV Show Project | Toluwalase Tiamiyu (TTiamiyu) + + + - - + +
+
- - - - + + + + + + + + \ No newline at end of file From 511dde67f1533aa3885fa145434a4346bdf88220 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 3 Aug 2026 23:35:20 +0100 Subject: [PATCH 02/24] feat: display episode details on the page through JavaScript --- script.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/script.js b/script.js index 87a7de8..a54cc8c 100644 --- a/script.js +++ b/script.js @@ -1,4 +1,6 @@ //You can edit ALL of the code here +const allEpisodes = getAllEpisodes(); +console.log(allEpisodes); function setup() { const allEpisodes = getAllEpisodes(); makePageForEpisodes(allEpisodes); @@ -6,7 +8,28 @@ function setup() { function makePageForEpisodes(episodeList) { const rootElem = document.getElementById("root"); - rootElem.textContent = `Got ${episodeList.length} episode(s)`; + episodeList.forEach(function (episode) { + const card = document.createElement("article"); + const season = String(episode.season).padStart(2, "0"); + const number = String(episode.number).padStart(2, "0"); + const episodeCode = `S${season}E${number}`; + const image = document.createElement("img"); + image.src = episode.image.medium; + image.alt = episode.name; + const summary = document.createElement("div"); + summary.innerHTML = episode.summary; + + const title = document.createElement("h2"); + title.textContent = `${episode.name}-${episodeCode}`; + const link = document.createElement("a"); + link.href = episode.url; + link.textContent = "View on Tv Maze"; + card.appendChild(title); + card.appendChild(image); + card.appendChild(summary); + card.appendChild(link); + rootElem.appendChild(card); + }); } window.onload = setup; From 2bb14ed6fc4d7dc5436c36f3ef5d58e7dbae21c9 Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Thu, 6 Aug 2026 07:47:05 +0100 Subject: [PATCH 03/24] added filter and search bars --- script.js | 155 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 133 insertions(+), 22 deletions(-) diff --git a/script.js b/script.js index a54cc8c..7970c05 100644 --- a/script.js +++ b/script.js @@ -1,35 +1,146 @@ -//You can edit ALL of the code here -const allEpisodes = getAllEpisodes(); -console.log(allEpisodes); +let allEpisodes = []; + function setup() { - const allEpisodes = getAllEpisodes(); + allEpisodes = getAllEpisodes(); + createControls(); makePageForEpisodes(allEpisodes); + addTvmazeAttribution(); +} + +function createControls() { + const rootElem = document.getElementById("root"); + + const controlsContainer = document.createElement("div"); + controlsContainer.className = "controls-container"; + + const episodeSelect = document.createElement("select"); + episodeSelect.id = "episode-select"; + + const defaultOption = document.createElement("option"); + defaultOption.value = "ALL"; + defaultOption.textContent = "All Episodes"; + episodeSelect.appendChild(defaultOption); + + allEpisodes.forEach((episode) => { + const option = document.createElement("option"); + option.value = episode.id; + const code = formatEpisodeCode(episode.season, episode.number); + option.textContent = `${code} - ${episode.name}`; + episodeSelect.appendChild(option); + }); + + const searchInput = document.createElement("input"); + searchInput.type = "text"; + searchInput.id = "search-input"; + searchInput.placeholder = "Search episodes..."; + + const countDisplay = document.createElement("span"); + countDisplay.id = "search-count"; + + controlsContainer.appendChild(episodeSelect); + controlsContainer.appendChild(searchInput); + controlsContainer.appendChild(countDisplay); + + rootElem.parentNode.insertBefore(controlsContainer, rootElem); + + searchInput.addEventListener("input", handleSearch); + episodeSelect.addEventListener("change", handleSelect); +} + +function handleSelect(event) { + const selectedId = event.target.value; + const searchInput = document.getElementById("search-input"); + + if (searchInput) searchInput.value = ""; + + if (selectedId === "ALL") { + makePageForEpisodes(allEpisodes); + } else { + + const selectedEpisode = allEpisodes.filter( + (episode) => String(episode.id) === String(selectedId), + ); + makePageForEpisodes(selectedEpisode); + } +} + +function handleSearch(event) { + const searchTerm = event.target.value.toLowerCase().trim(); + const episodeSelect = document.getElementById("episode-select"); + + if (episodeSelect) episodeSelect.value = "ALL"; + + const filteredEpisodes = allEpisodes.filter((episode) => { + const nameMatches = episode.name.toLowerCase().includes(searchTerm); + const summaryMatches = (episode.summary || "") + .toLowerCase() + .includes(searchTerm); + + return nameMatches || summaryMatches; + }); + + makePageForEpisodes(filteredEpisodes); +} + +function updateSearchCount(matchCount, totalCount) { + const countDisplay = document.getElementById("search-count"); + if (countDisplay) { + countDisplay.textContent = `Displaying ${matchCount}/${totalCount} episodes`; + } +} + +function formatEpisodeCode(season, number) { + const paddedSeason = String(season).padStart(2, "0"); + const paddedNumber = String(number).padStart(2, "0"); + return `S${paddedSeason}E${paddedNumber}`; } function makePageForEpisodes(episodeList) { const rootElem = document.getElementById("root"); - episodeList.forEach(function (episode) { - const card = document.createElement("article"); - const season = String(episode.season).padStart(2, "0"); - const number = String(episode.number).padStart(2, "0"); - const episodeCode = `S${season}E${number}`; - const image = document.createElement("img"); - image.src = episode.image.medium; - image.alt = episode.name; - const summary = document.createElement("div"); - summary.innerHTML = episode.summary; - const title = document.createElement("h2"); - title.textContent = `${episode.name}-${episodeCode}`; - const link = document.createElement("a"); - link.href = episode.url; - link.textContent = "View on Tv Maze"; + rootElem.innerHTML = ""; + + const container = document.createElement("div"); + container.className = "episodes-container"; + + episodeList.forEach((episode) => { + const card = document.createElement("section"); + card.className = "episode-card"; + + const title = document.createElement("h3"); + const code = formatEpisodeCode(episode.season, episode.number); + title.textContent = `${episode.name} - ${code}`; card.appendChild(title); - card.appendChild(image); + + if (episode.image && episode.image.medium) { + const img = document.createElement("img"); + img.src = episode.image.medium; + img.alt = episode.name; + card.appendChild(img); + } + + const summary = document.createElement("div"); + summary.className = "episode-summary"; + summary.innerHTML = episode.summary || "

No summary available.

"; card.appendChild(summary); - card.appendChild(link); - rootElem.appendChild(card); + + container.appendChild(card); }); + + rootElem.appendChild(container); + + updateSearchCount(episodeList.length, allEpisodes.length); +} + +function addTvmazeAttribution() { + if (document.getElementById("tvmaze-attribution")) return; + + const footer = document.createElement("footer"); + footer.id = "tvmaze-attribution"; + footer.innerHTML = ` +

Data provided by TVMaze.com

+ `; + document.body.appendChild(footer); } window.onload = setup; From e526eda0519879377d231997f1eeb158b3510337 Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Thu, 6 Aug 2026 08:04:00 +0100 Subject: [PATCH 04/24] changing the grid layout --- style.css | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 1 deletion(-) diff --git a/style.css b/style.css index 77cb8d4..684f194 100644 --- a/style.css +++ b/style.css @@ -1,3 +1,170 @@ +/* ================================ + 1. Base & Layout Styles + ================================ */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, + Arial, sans-serif; + background-color: #f4f6f8; + color: #333; + line-height: 1.5; + padding-bottom: 60px; +} + #root { - color: red; + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* ================================ + 2. Controls Container (Top Bar) + ================================ */ +.controls-container { + position: sticky; + top: 0; + z-index: 100; + background-color: #ffffff; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 15px; + padding: 16px 24px; + margin-bottom: 24px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + border-radius: 8px; +} + +.controls-container select, +.controls-container input[type="text"] { + padding: 10px 14px; + font-size: 0.95rem; + border: 1px solid #ccc; + border-radius: 6px; + outline: none; + transition: border-color 0.2s ease, box-shadow 0.2s ease; + flex: 1 1 220px; + /* Responsive sizing */ + max-width: 350px; +} + +.controls-container select:focus, +.controls-container input[type="text"]:focus { + border-color: #0066cc; + box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.15); +} + +#search-count { + font-weight: 600; + color: #555; + font-size: 0.95rem; + white-space: nowrap; +} + +/* ================================ + 3. Episodes Grid + ================================ */ +.episodes-container { + display: grid; + /* Automatically fits as many 280px cards per row as possible */ + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; +} + +/* ================================ + 4. Episode Card + ================================ */ +.episode-card { + background-color: #ffffff; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + display: flex; + flex-direction: column; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.episode-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); +} + +.episode-card h3 { + font-size: 1.1rem; + font-weight: 700; + color: #1a1a1a; + padding: 16px; + text-align: center; + background-color: #f9f9fb; + border-bottom: 1px solid #eeeeee; + min-height: 60px; + display: flex; + align-items: center; + justify-content: center; +} + +.episode-card img { + width: 100%; + height: 200px; + object-fit: cover; + display: block; +} + +.episode-summary { + padding: 16px; + font-size: 0.9rem; + color: #4a4a4a; + flex-grow: 1; + /* Ensures equal-height cards in grid */ +} + +.episode-summary p { + margin-bottom: 8px; +} + +/* ================================ + 5. Footer (TVMaze Attribution) + ================================ */ +#tvmaze-attribution { + text-align: center; + padding: 20px; + margin-top: 40px; + font-size: 0.9rem; + color: #666; + border-top: 1px solid #e0e0e0; +} + +#tvmaze-attribution a { + color: #0066cc; + text-decoration: none; + font-weight: 600; +} + +#tvmaze-attribution a:hover { + text-decoration: underline; } + +/* ================================ + 6. Mobile Responsiveness + ================================ */ +@media (max-width: 600px) { + .controls-container { + flex-direction: column; + align-items: stretch; + } + + .controls-container select, + .controls-container input[type="text"] { + max-width: 100%; + } + + #search-count { + text-align: center; + } +} \ No newline at end of file From 5675122d045c60fa68552c65976121254d6f31d4 Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Thu, 6 Aug 2026 09:19:24 +0100 Subject: [PATCH 05/24] formated js code --- script.js | 1 - 1 file changed, 1 deletion(-) diff --git a/script.js b/script.js index 7970c05..ecb6c0e 100644 --- a/script.js +++ b/script.js @@ -56,7 +56,6 @@ function handleSelect(event) { if (selectedId === "ALL") { makePageForEpisodes(allEpisodes); } else { - const selectedEpisode = allEpisodes.filter( (episode) => String(episode.id) === String(selectedId), ); From 2db8eaadfd2ab61ddc79d435ad897d0f1e138727 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Fri, 7 Aug 2026 10:20:44 +0100 Subject: [PATCH 06/24] refactor: remove unnecessary script loading for episode retrieval --- index.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.html b/index.html index e7eb4b5..d9b6734 100644 --- a/index.html +++ b/index.html @@ -13,8 +13,7 @@
- - + From e399105bdfe11da0f19d2d9a463a4e7178f3de0e Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Fri, 7 Aug 2026 10:21:00 +0100 Subject: [PATCH 07/24] feat: implement async data fetching for episode loading with error handling --- script.js | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/script.js b/script.js index ecb6c0e..4bd2396 100644 --- a/script.js +++ b/script.js @@ -1,10 +1,47 @@ let allEpisodes = []; -function setup() { - allEpisodes = getAllEpisodes(); - createControls(); - makePageForEpisodes(allEpisodes); +async function setup() { addTvmazeAttribution(); + showLoading(); + + try { + const response = await fetch("https://api.tvmaze.com/shows/82/episodes"); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + allEpisodes = await response.json(); + + // Clear loading message and build UI + const rootElem = document.getElementById("root"); + rootElem.innerHTML = ""; + + createControls(); + makePageForEpisodes(allEpisodes); + } catch (error) { + showError("Failed to load episode data. Please try again later."); + } +} + +function showLoading() { + const rootElem = document.getElementById("root"); + rootElem.innerHTML = ` +
+
+

Loading episodes, please wait...

+
+ `; +} + +function showError(message) { + const rootElem = document.getElementById("root"); + rootElem.innerHTML = ` +
+

Something went wrong

+

${message}

+
+ `; } function createControls() { From 529af9687ede438a15f3f2c8245ab5c16301457c Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Fri, 7 Aug 2026 10:21:17 +0100 Subject: [PATCH 08/24] style: format CSS for readability and add loading/error state styles --- style.css | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/style.css b/style.css index 684f194..2cb6e58 100644 --- a/style.css +++ b/style.css @@ -8,8 +8,9 @@ } body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, - Arial, sans-serif; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, + sans-serif; background-color: #f4f6f8; color: #333; line-height: 1.5; @@ -48,7 +49,9 @@ body { border: 1px solid #ccc; border-radius: 6px; outline: none; - transition: border-color 0.2s ease, box-shadow 0.2s ease; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; flex: 1 1 220px; /* Responsive sizing */ max-width: 350px; @@ -87,7 +90,9 @@ body { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); display: flex; flex-direction: column; - transition: transform 0.2s ease, box-shadow 0.2s ease; + transition: + transform 0.2s ease, + box-shadow 0.2s ease; } .episode-card:hover { @@ -167,4 +172,67 @@ body { #search-count { text-align: center; } -} \ No newline at end of file +} +/* ========================================== + Loading State & Spinner + ========================================== */ +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 4rem 1rem; + color: #4a5568; +} + +.spinner { + width: 48px; + height: 48px; + border: 4px solid #e2e8f0; + border-top: 4px solid #3182ce; + border-radius: 50%; + animation: spin 0.9s linear infinite; + margin-bottom: 1rem; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.loading-container p { + font-size: 1.125rem; + font-weight: 500; + margin: 0; +} + +/* ========================================== + Error Alert Banner + ========================================== */ +.error-container { + max-width: 500px; + margin: 3rem auto; + padding: 1.5rem; + background-color: #fff5f5; + border: 1px solid #feb2b2; + border-left: 5px solid #e53e3e; + border-radius: 8px; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + text-align: center; +} + +.error-container h3 { + color: #9b2c2c; + margin: 0 0 0.5rem 0; + font-size: 1.25rem; +} + +.error-container p { + color: #742a2a; + margin: 0; + font-size: 0.95rem; +} From 1a54c0de46593dc10759f00dcffd034a4705e79e Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Fri, 7 Aug 2026 10:21:54 +0100 Subject: [PATCH 09/24] Delete unused episodes.js for Level 300 --- episodes.js | 1855 --------------------------------------------------- 1 file changed, 1855 deletions(-) delete mode 100644 episodes.js diff --git a/episodes.js b/episodes.js deleted file mode 100644 index 5ef6e9e..0000000 --- a/episodes.js +++ /dev/null @@ -1,1855 +0,0 @@ -//DO NOT EDIT THIS FILE - -//This content is from https://www.tvmaze.com/ -//specifically: https://api.tvmaze.com/shows/82/episodes - -function getOneEpisode() { - return { - id: 4952, - url: - "http://www.tvmaze.com/episodes/4952/game-of-thrones-1x01-winter-is-coming", - name: "Winter is Coming", - season: 1, - number: 1, - airdate: "2011-04-17", - airtime: "21:00", - airstamp: "2011-04-18T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2668.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2668.jpg", - }, - summary: - "

Lord Eddard Stark, ruler of the North, is summoned to court by his old friend, King Robert Baratheon, to serve as the King's Hand. Eddard reluctantly agrees after learning of a possible threat to the King's life. Eddard's bastard son Jon Snow must make a painful decision about his own future, while in the distant east Viserys Targaryen plots to reclaim his father's throne, usurped by Robert, by selling his sister in marriage.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4952", - }, - }, - }; -} - -function getAllEpisodes() { - return [ - { - id: 4952, - url: - "http://www.tvmaze.com/episodes/4952/game-of-thrones-1x01-winter-is-coming", - name: "Winter is Coming", - season: 1, - number: 1, - airdate: "2011-04-17", - airtime: "21:00", - airstamp: "2011-04-18T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2668.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2668.jpg", - }, - summary: - "

Lord Eddard Stark, ruler of the North, is summoned to court by his old friend, King Robert Baratheon, to serve as the King's Hand. Eddard reluctantly agrees after learning of a possible threat to the King's life. Eddard's bastard son Jon Snow must make a painful decision about his own future, while in the distant east Viserys Targaryen plots to reclaim his father's throne, usurped by Robert, by selling his sister in marriage.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4952", - }, - }, - }, - { - id: 4953, - url: - "http://www.tvmaze.com/episodes/4953/game-of-thrones-1x02-the-kingsroad", - name: "The Kingsroad", - season: 1, - number: 2, - airdate: "2011-04-24", - airtime: "21:00", - airstamp: "2011-04-25T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2669.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2669.jpg", - }, - summary: - "

An incident on the Kingsroad threatens Eddard and Robert's friendship. Jon and Tyrion travel to the Wall, where they discover that the reality of the Night's Watch may not match the heroic image of it.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4953", - }, - }, - }, - { - id: 4954, - url: "http://www.tvmaze.com/episodes/4954/game-of-thrones-1x03-lord-snow", - name: "Lord Snow", - season: 1, - number: 3, - airdate: "2011-05-01", - airtime: "21:00", - airstamp: "2011-05-02T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2671.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2671.jpg", - }, - summary: - "

Jon Snow attempts to find his place amongst the Night's Watch. Eddard and his daughters arrive at King's Landing.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4954", - }, - }, - }, - { - id: 4955, - url: - "http://www.tvmaze.com/episodes/4955/game-of-thrones-1x04-cripples-bastards-and-broken-things", - name: "Cripples, Bastards, and Broken Things", - season: 1, - number: 4, - airdate: "2011-05-08", - airtime: "21:00", - airstamp: "2011-05-09T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2673.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2673.jpg", - }, - summary: - "

Tyrion stops at Winterfell on his way home and gets a frosty reception from Robb Stark. Eddard's investigation into the death of his predecessor gets underway.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4955", - }, - }, - }, - { - id: 4956, - url: - "http://www.tvmaze.com/episodes/4956/game-of-thrones-1x05-the-wolf-and-the-lion", - name: "The Wolf and the Lion", - season: 1, - number: 5, - airdate: "2011-05-15", - airtime: "21:00", - airstamp: "2011-05-16T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2674.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2674.jpg", - }, - summary: - "

Catelyn's actions on the road have repercussions for Eddard. Tyrion enjoys the dubious hospitality of the Eyrie.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4956", - }, - }, - }, - { - id: 4957, - url: - "http://www.tvmaze.com/episodes/4957/game-of-thrones-1x06-a-golden-crown", - name: "A Golden Crown", - season: 1, - number: 6, - airdate: "2011-05-22", - airtime: "21:00", - airstamp: "2011-05-23T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2676.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2676.jpg", - }, - summary: - "

Viserys is increasingly frustrated by the lack of progress towards gaining his crown.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4957", - }, - }, - }, - { - id: 4958, - url: - "http://www.tvmaze.com/episodes/4958/game-of-thrones-1x07-you-win-or-you-die", - name: "You Win or You Die", - season: 1, - number: 7, - airdate: "2011-05-29", - airtime: "21:00", - airstamp: "2011-05-30T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2677.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2677.jpg", - }, - summary: - "

Eddard's investigations in King's Landing reach a climax and a dark secret is revealed.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4958", - }, - }, - }, - { - id: 4959, - url: - "http://www.tvmaze.com/episodes/4959/game-of-thrones-1x08-the-pointy-end", - name: "The Pointy End", - season: 1, - number: 8, - airdate: "2011-06-05", - airtime: "21:00", - airstamp: "2011-06-06T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2678.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2678.jpg", - }, - summary: - "

Tyrion joins his father's army with unexpected allies. Events in King's Landing take a turn for the worse as Arya's lessons are put to the test.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4959", - }, - }, - }, - { - id: 4960, - url: "http://www.tvmaze.com/episodes/4960/game-of-thrones-1x09-baelor", - name: "Baelor", - season: 1, - number: 9, - airdate: "2011-06-12", - airtime: "21:00", - airstamp: "2011-06-13T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2679.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2679.jpg", - }, - summary: - "

Catelyn must negotiate with the irascible Lord Walder Frey.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4960", - }, - }, - }, - { - id: 4961, - url: - "http://www.tvmaze.com/episodes/4961/game-of-thrones-1x10-fire-and-blood", - name: "Fire and Blood", - season: 1, - number: 10, - airdate: "2011-06-19", - airtime: "21:00", - airstamp: "2011-06-20T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2681.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2681.jpg", - }, - summary: - "

Daenerys must realize her destiny. Jaime finds himself in an unfamiliar predicament.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4961", - }, - }, - }, - { - id: 4962, - url: - "http://www.tvmaze.com/episodes/4962/game-of-thrones-2x01-the-north-remembers", - name: "The North Remembers", - season: 2, - number: 1, - airdate: "2012-04-01", - airtime: "21:00", - airstamp: "2012-04-02T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3174.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3174.jpg", - }, - summary: - "

War grips the continent of Westeros. As Tyrion Lannister tries to take his strong-willed nephew in hand in King's Landing, Stannis Baratheon launches his own campaign to take the Iron Throne with the help of a mysterious priestess. In the east, Daenerys must lead her retinue through a desolate wasteland whilst beyond the Wall the Night's Watch seeks the aid of a wildling.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4962", - }, - }, - }, - { - id: 4963, - url: - "http://www.tvmaze.com/episodes/4963/game-of-thrones-2x02-the-night-lands", - name: "The Night Lands", - season: 2, - number: 2, - airdate: "2012-04-08", - airtime: "21:00", - airstamp: "2012-04-09T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3175.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3175.jpg", - }, - summary: - "

Stannis uses Ser Davos to seek out new allies for his war with the Lannisters. On the road north, Arya confides in Gendry. Robb Stark sends Theon Greyjoy to win an alliance with his father and the fierce warriors of the Iron Islands. Cersei and Tyrion clash on how to rule in King's Landing.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4963", - }, - }, - }, - { - id: 4964, - url: - "http://www.tvmaze.com/episodes/4964/game-of-thrones-2x03-what-is-dead-may-never-die", - name: "What is Dead May Never Die", - season: 2, - number: 3, - airdate: "2012-04-15", - airtime: "21:00", - airstamp: "2012-04-16T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3176.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3176.jpg", - }, - summary: - "

Catelyn Stark treats with King Renly in the hope of winning an alliance. Tyrion undertakes a complex plan in King's Landing to expose an enemy. At Winterfell, Bran's dreams continue to trouble him.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4964", - }, - }, - }, - { - id: 4965, - url: - "http://www.tvmaze.com/episodes/4965/game-of-thrones-2x04-garden-of-bones", - name: "Garden of Bones", - season: 2, - number: 4, - airdate: "2012-04-22", - airtime: "21:00", - airstamp: "2012-04-23T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3177.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3177.jpg", - }, - summary: - "

Tyrion attempts to restrain Joffrey's cruelty. Catelyn attempts to broker a peace between Stannis and Renly. Daenerys and her followers arrive at the great city of Qarth and hope to find refuge there. Arya and Gendry arrive at Harrenhal, a great castle now under Lannister occupation.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4965", - }, - }, - }, - { - id: 4966, - url: - "http://www.tvmaze.com/episodes/4966/game-of-thrones-2x05-the-ghost-of-harrenhal", - name: "The Ghost of Harrenhal", - season: 2, - number: 5, - airdate: "2012-04-29", - airtime: "21:00", - airstamp: "2012-04-30T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3178.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3178.jpg", - }, - summary: - "

Confusion rages in the Stormlands in the wake of a devastating reversal. Catelyn must flee with a new ally, whilst Littlefinger sees an opportunity in the chaos. Theon seeks to prove himself to his father in battle. Arya receives a promise from the enigmatic Jaqen H'ghar. The Night's Watch arrives at the Fist of the First Men. Daenerys Targaryen receives a marriage proposal.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4966", - }, - }, - }, - { - id: 4967, - url: - "http://www.tvmaze.com/episodes/4967/game-of-thrones-2x06-the-old-gods-and-the-new", - name: "The Old Gods and the New", - season: 2, - number: 6, - airdate: "2012-05-06", - airtime: "21:00", - airstamp: "2012-05-07T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3180.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3180.jpg", - }, - summary: - "

Arya has a surprise visitor; Dany vows to take what is hers; Joffrey meets his subjects; Qhorin gives Jon a chance to prove himself.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4967", - }, - }, - }, - { - id: 4968, - url: - "http://www.tvmaze.com/episodes/4968/game-of-thrones-2x07-a-man-without-honor", - name: "A Man Without Honor", - season: 2, - number: 7, - airdate: "2012-05-13", - airtime: "21:00", - airstamp: "2012-05-14T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3192.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3192.jpg", - }, - summary: - "

Jaime meets a relative; Theon hunts; Dany receives an invitation.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4968", - }, - }, - }, - { - id: 4969, - url: - "http://www.tvmaze.com/episodes/4969/game-of-thrones-2x08-the-prince-of-winterfell", - name: "The Prince of Winterfell", - season: 2, - number: 8, - airdate: "2012-05-20", - airtime: "21:00", - airstamp: "2012-05-21T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3194.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3194.jpg", - }, - summary: - "

Theon holds the fort; Arya calls in her debt with Jaqen; Robb is betrayed; Stannis and Davos approach their destination.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4969", - }, - }, - }, - { - id: 4970, - url: - "http://www.tvmaze.com/episodes/4970/game-of-thrones-2x09-blackwater", - name: "Blackwater", - season: 2, - number: 9, - airdate: "2012-05-27", - airtime: "21:00", - airstamp: "2012-05-28T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3196.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3196.jpg", - }, - summary: - "

A massive battle rages for control of King's Landing and the Iron Throne.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4970", - }, - }, - }, - { - id: 4971, - url: - "http://www.tvmaze.com/episodes/4971/game-of-thrones-2x10-valar-morghulis", - name: "Valar Morghulis", - season: 2, - number: 10, - airdate: "2012-06-03", - airtime: "21:00", - airstamp: "2012-06-04T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3197.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3197.jpg", - }, - summary: - "

Tyrion awakens to a changed situation. King Joffrey doles out rewards to his subjects. As Theon stirs his men to action, Luwin offers some final advice. Brienne silences Jaime; Arya receives a gift from Jaqen; Dany goes to a strange place; Jon proves himself to Qhorin.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4971", - }, - }, - }, - { - id: 4972, - url: - "http://www.tvmaze.com/episodes/4972/game-of-thrones-3x01-valar-dohaeris", - name: "Valar Dohaeris", - season: 3, - number: 1, - airdate: "2013-03-31", - airtime: "21:00", - airstamp: "2013-04-01T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2628.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2628.jpg", - }, - summary: - "

Jon is brought before Mance Rayder, the King Beyond the Wall, while the Night's Watch survivors retreat south. In King's Landing, Tyrion asks for his reward. Littlefinger offers Sansa a way out. Cersei hosts a dinner for the royal family. Daenerys sails into Slaver's Bay.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4972", - }, - }, - }, - { - id: 4973, - url: - "http://www.tvmaze.com/episodes/4973/game-of-thrones-3x02-dark-wings-dark-words", - name: "Dark Wings, Dark Words", - season: 3, - number: 2, - airdate: "2013-04-07", - airtime: "21:00", - airstamp: "2013-04-08T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2618.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2618.jpg", - }, - summary: - "

Sansa says too much. Shae asks Tyrion for a favor. Jaime finds a way to pass the time. Arya runs into the Brotherhood Without Banners.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4973", - }, - }, - }, - { - id: 4974, - url: - "http://www.tvmaze.com/episodes/4974/game-of-thrones-3x03-walk-of-punishment", - name: "Walk of Punishment", - season: 3, - number: 3, - airdate: "2013-04-14", - airtime: "21:00", - airstamp: "2013-04-15T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2616.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2616.jpg", - }, - summary: - "

Tyrion shoulders new responsibilities. Jon is taken to the Fist of the First Men. Daenerys meets with the slavers. Jaime strikes a deal with his captors.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4974", - }, - }, - }, - { - id: 4975, - url: - "http://www.tvmaze.com/episodes/4975/game-of-thrones-3x04-and-now-his-watch-is-ended", - name: "And Now His Watch is Ended", - season: 3, - number: 4, - airdate: "2013-04-21", - airtime: "21:00", - airstamp: "2013-04-22T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2615.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2615.jpg", - }, - summary: - "

The Night's Watch takes stock. Varys meets his better. Arya is taken to the commander of the Brotherhood. Daenerys exchanges a chain for a Whip.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4975", - }, - }, - }, - { - id: 4976, - url: - "http://www.tvmaze.com/episodes/4976/game-of-thrones-3x05-kissed-by-fire", - name: "Kissed by Fire", - season: 3, - number: 5, - airdate: "2013-04-28", - airtime: "21:00", - airstamp: "2013-04-29T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2614.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2614.jpg", - }, - summary: - "

The Hound is judged by the gods; Jaime is judged by men. Jon proves himself; Robb is betrayed. Tyrion learns the cost of weddings.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4976", - }, - }, - }, - { - id: 4977, - url: "http://www.tvmaze.com/episodes/4977/game-of-thrones-3x06-the-climb", - name: "The Climb", - season: 3, - number: 6, - airdate: "2013-05-05", - airtime: "21:00", - airstamp: "2013-05-06T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2612.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2612.jpg", - }, - summary: - "

Tywin plans strategic unions for the Lannisters. Melisandre visits the Riverlands. Robb weighs a compromise to repair his alliance with House Frey. Roose Bolton decides what to do with Jaime Lannister. Jon, Ygritte and the Wildlings face a daunting climb.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4977", - }, - }, - }, - { - id: 4978, - url: - "http://www.tvmaze.com/episodes/4978/game-of-thrones-3x07-the-bear-and-the-maiden-fair", - name: "The Bear and the Maiden Fair", - season: 3, - number: 7, - airdate: "2013-05-12", - airtime: "21:00", - airstamp: "2013-05-13T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2611.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2611.jpg", - }, - summary: - "

Daenerys exchanges gifts with a slave lord outside Yunkai. As Sansa frets about her prospects, Shae chafes at Tyrion's new situation. Tywin counsels the king, and Melisandre reveals a secret to Gendry. Brienne faces a formidable foe in Harrenhal.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4978", - }, - }, - }, - { - id: 4979, - url: - "http://www.tvmaze.com/episodes/4979/game-of-thrones-3x08-second-sons", - name: "Second Sons", - season: 3, - number: 8, - airdate: "2013-05-19", - airtime: "21:00", - airstamp: "2013-05-20T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2599.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2599.jpg", - }, - summary: - "

King's Landing hosts a wedding, and Tyrion and Sansa spend the night together. Daenerys meets the Titan's Bastard. Davos demands proof from Melisandre. Sam and Gilly meet an older Gentleman.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4979", - }, - }, - }, - { - id: 4980, - url: - "http://www.tvmaze.com/episodes/4980/game-of-thrones-3x09-the-rains-of-castamere", - name: "The Rains of Castamere", - season: 3, - number: 9, - airdate: "2013-06-02", - airtime: "21:00", - airstamp: "2013-06-03T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2598.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2598.jpg", - }, - summary: - "

Robb presents himself to Walder Frey, and Edmure meets his bride. Jon faces his harshest test yet. Bran discovers a new gift. Daario and Jorah debate how to take Yunkai. House Frey joins with House Tully.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4980", - }, - }, - }, - { - id: 4981, - url: "http://www.tvmaze.com/episodes/4981/game-of-thrones-3x10-mhysa", - name: "Mhysa", - season: 3, - number: 10, - airdate: "2013-06-09", - airtime: "21:00", - airstamp: "2013-06-10T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2597.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2597.jpg", - }, - summary: - "

Joffrey challenges Tywin. Bran tells a ghost story. In Dragonstone, mercy comes from strange quarters. Daenerys waits to see if she is a conqueror or a liberator.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4981", - }, - }, - }, - { - id: 4982, - url: - "http://www.tvmaze.com/episodes/4982/game-of-thrones-4x01-two-swords", - name: "Two Swords", - season: 4, - number: 1, - airdate: "2014-04-06", - airtime: "21:00", - airstamp: "2014-04-07T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2583.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2583.jpg", - }, - summary: - "

Tyrion welcomes a guest to King's Landing. At Castle Black, Jon Snow finds himself unwelcome. Dany is pointed to Meereen, the mother of all slave cities. Arya runs into an old friend.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4982", - }, - }, - }, - { - id: 4983, - url: - "http://www.tvmaze.com/episodes/4983/game-of-thrones-4x02-the-lion-and-the-rose", - name: "The Lion and the Rose", - season: 4, - number: 2, - airdate: "2014-04-13", - airtime: "21:00", - airstamp: "2014-04-14T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2584.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2584.jpg", - }, - summary: - "

Tyrion lends Jaime a hand. Joffrey and Margaery host a breakfast. At Dragonstone, Stannis loses patience with Davos. Ramsay finds a purpose for his pet. North of the Wall, Bran sees where they must go.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4983", - }, - }, - }, - { - id: 4984, - url: - "http://www.tvmaze.com/episodes/4984/game-of-thrones-4x03-breaker-of-chains", - name: "Breaker of Chains", - season: 4, - number: 3, - airdate: "2014-04-20", - airtime: "21:00", - airstamp: "2014-04-21T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2585.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2585.jpg", - }, - summary: - "

Tyrion ponders his options. Tywin extends an olive branch. Sam realizes Castle Black isn't safe, and Jon proposes a bold plan. The Hound teaches Arya the way things are. Dany chooses her Champion.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4984", - }, - }, - }, - { - id: 4985, - url: - "http://www.tvmaze.com/episodes/4985/game-of-thrones-4x04-oathkeeper", - name: "Oathkeeper", - season: 4, - number: 4, - airdate: "2014-04-27", - airtime: "21:00", - airstamp: "2014-04-28T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2586.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2586.jpg", - }, - summary: - "

Dany balances justice and mercy. Jaime tasks Brienne with his honor. Jon secures volunteers while Bran, Jojen, Meera and Hodor stumble on shelter.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4985", - }, - }, - }, - { - id: 4986, - url: - "http://www.tvmaze.com/episodes/4986/game-of-thrones-4x05-first-of-his-name", - name: "First of His Name", - season: 4, - number: 5, - airdate: "2014-05-04", - airtime: "21:00", - airstamp: "2014-05-05T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2587.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2587.jpg", - }, - summary: - "

Cersei and Tywin plot the Crown's next move. Dany discusses future plans. Jon embarks on a new mission.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4986", - }, - }, - }, - { - id: 4987, - url: - "http://www.tvmaze.com/episodes/4987/game-of-thrones-4x06-the-laws-of-gods-and-men", - name: "The Laws of Gods and Men", - season: 4, - number: 6, - airdate: "2014-05-11", - airtime: "21:00", - airstamp: "2014-05-12T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2588.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2588.jpg", - }, - summary: - "

Stannis and Davos set sail with a new strategy. Dany meets with supplicants. Tyrion faces down his father in the throne room.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4987", - }, - }, - }, - { - id: 4988, - url: - "http://www.tvmaze.com/episodes/4988/game-of-thrones-4x07-mockingbird", - name: "Mockingbird", - season: 4, - number: 7, - airdate: "2014-05-18", - airtime: "21:00", - airstamp: "2014-05-19T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2589.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2589.jpg", - }, - summary: - "

Tyrion enlists an unlikely ally. Daario entreats Dany to allow him to do what he does best. Jon's warnings about the Wall's vulnerability fall on deaf ears. Brienne follows a new lead on the road with Pod.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4988", - }, - }, - }, - { - id: 4989, - url: - "http://www.tvmaze.com/episodes/4989/game-of-thrones-4x08-the-mountain-and-the-viper", - name: "The Mountain and the Viper", - season: 4, - number: 8, - airdate: "2014-06-01", - airtime: "21:00", - airstamp: "2014-06-02T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2591.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2591.jpg", - }, - summary: - "

Mole's Town receives unexpected visitors. Littlefinger's motives are questioned. Ramsay attempts to prove himself to his father. Tyrion's fate is decided.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4989", - }, - }, - }, - { - id: 4990, - url: - "http://www.tvmaze.com/episodes/4990/game-of-thrones-4x09-the-watchers-on-the-wall", - name: "The Watchers on the Wall", - season: 4, - number: 9, - airdate: "2014-06-08", - airtime: "21:00", - airstamp: "2014-06-09T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2593.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2593.jpg", - }, - summary: - "

Jon Snow and the rest of the Night's Watch face the biggest challenge to the Wall yet.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4990", - }, - }, - }, - { - id: 4991, - url: - "http://www.tvmaze.com/episodes/4991/game-of-thrones-4x10-the-children", - name: "The Children", - season: 4, - number: 10, - airdate: "2014-06-15", - airtime: "21:00", - airstamp: "2014-06-16T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2594.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2594.jpg", - }, - summary: - "

An unexpected arrival north of the Wall changes circumstances. Dany is forced to face harsh realities. Bran learns more of his destiny. Tyrion sees the truth of his situation.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4991", - }, - }, - }, - { - id: 116522, - url: - "http://www.tvmaze.com/episodes/116522/game-of-thrones-5x01-the-wars-to-come", - name: "The Wars to Come", - season: 5, - number: 1, - airdate: "2015-04-12", - airtime: "21:00", - airstamp: "2015-04-13T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/10/25988.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/10/25988.jpg", - }, - summary: - "

Varys discusses a conspiracy with Tyrion; Daenerys' rule faces a new threat; Jon finds himself between two kings; and Cersei and Jaime try to move on from Tywin's demise.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/116522", - }, - }, - }, - { - id: 144328, - url: - "http://www.tvmaze.com/episodes/144328/game-of-thrones-5x02-the-house-of-black-and-white", - name: "The House of Black and White", - season: 5, - number: 2, - airdate: "2015-04-19", - airtime: "21:00", - airstamp: "2015-04-20T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/10/25989.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/10/25989.jpg", - }, - summary: - "

Arya arrives in Braavos; Brienne and Podrick find danger while traveling; Cersei worries about Myrcella in Dorne when Ellaria Sand seeks revenge for Oberyn's death; Jon is tempted by Stannis.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/144328", - }, - }, - }, - { - id: 144329, - url: - "http://www.tvmaze.com/episodes/144329/game-of-thrones-5x03-high-sparrow", - name: "High Sparrow", - season: 5, - number: 3, - airdate: "2015-04-26", - airtime: "21:00", - airstamp: "2015-04-27T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/10/25990.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/10/25990.jpg", - }, - summary: - "

Cersei meets the High Sparrow after learning of a clergyman's embarrassing tale. Meanwhile, Davos talks to Jon about the future of Winterfell, where Ramsay Snow has just learned the identity of his future bride; Arya grows impatient doing menial tasks in the House of Black and White; and Tyrion searches for more comfortable surroundings on a long trip with Varys.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/144329", - }, - }, - }, - { - id: 144330, - url: - "http://www.tvmaze.com/episodes/144330/game-of-thrones-5x04-sons-of-the-harpy", - name: "Sons of the Harpy", - season: 5, - number: 4, - airdate: "2015-05-03", - airtime: "21:00", - airstamp: "2015-05-04T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/10/26444.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/10/26444.jpg", - }, - summary: - "

Margaery seeks prudent counsel. Jaime Struggles in foreign lands. Dany answers the Harpy's call.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/144330", - }, - }, - }, - { - id: 151777, - url: - "http://www.tvmaze.com/episodes/151777/game-of-thrones-5x05-kill-the-boy", - name: "Kill the Boy", - season: 5, - number: 5, - airdate: "2015-05-10", - airtime: "21:00", - airstamp: "2015-05-11T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/10/26819.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/10/26819.jpg", - }, - summary: - "

Dany makes a difficult decision in Meereen. Jon recruits the help of an unexpected ally. Brienne searches for Sansa. Theon remains under Ramsay's control.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/151777", - }, - }, - }, - { - id: 152766, - url: - "http://www.tvmaze.com/episodes/152766/game-of-thrones-5x06-unbowed-unbent-unbroken", - name: "Unbowed, Unbent, Unbroken", - season: 5, - number: 6, - airdate: "2015-05-17", - airtime: "21:00", - airstamp: "2015-05-18T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/10/27259.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/10/27259.jpg", - }, - summary: - "

Arya trains. Jorah and Tyrion run into slavers. Trystane and Myrcella make plans. Jaime and Bronn reach their destination. The Sand Snakes attack.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/152766", - }, - }, - }, - { - id: 153327, - url: - "http://www.tvmaze.com/episodes/153327/game-of-thrones-5x07-the-gift", - name: "The Gift", - season: 5, - number: 7, - airdate: "2015-05-24", - airtime: "21:00", - airstamp: "2015-05-25T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/11/27535.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/11/27535.jpg", - }, - summary: - "

Jon prepares for conflict. Sansa tries to talk to Theon. Brienne waits for a sign. Stannis remains stubborn. Jaime attempts to reconnect with family.



", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/153327", - }, - }, - }, - { - id: 155299, - url: - "http://www.tvmaze.com/episodes/155299/game-of-thrones-5x08-hardhome", - name: "Hardhome", - season: 5, - number: 8, - airdate: "2015-05-31", - airtime: "21:00", - airstamp: "2015-06-01T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/11/28151.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/11/28151.jpg", - }, - summary: - "

Arya makes progress in her training. Sansa confronts an old friend. Cersei struggles. Jon travels.



", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/155299", - }, - }, - }, - { - id: 160040, - url: - "http://www.tvmaze.com/episodes/160040/game-of-thrones-5x09-the-dance-of-dragons", - name: "The Dance of Dragons", - season: 5, - number: 9, - airdate: "2015-06-07", - airtime: "21:00", - airstamp: "2015-06-08T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/11/29160.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/11/29160.jpg", - }, - summary: - "

Stannis confronts a troubling decision. Jon returns to The Wall. Mace visits the Iron Bank. Arya encounters someone from her past. Dany reluctantly oversees a traditional celebration of athleticism.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/160040", - }, - }, - }, - { - id: 162186, - url: - "http://www.tvmaze.com/episodes/162186/game-of-thrones-5x10-mothers-mercy", - name: "Mother's Mercy", - season: 5, - number: 10, - airdate: "2015-06-14", - airtime: "21:00", - airstamp: "2015-06-15T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/12/30012.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/12/30012.jpg", - }, - summary: - "

Cersei seeks forgiveness; Jon faces a new challenge; Arya plots to cross a name off her list; Tyrion sees a familiar face; and Daenerys finds herself surrounded by strangers.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/162186", - }, - }, - }, - { - id: 560813, - url: - "http://www.tvmaze.com/episodes/560813/game-of-thrones-6x01-the-red-woman", - name: "The Red Woman", - season: 6, - number: 1, - airdate: "2016-04-24", - airtime: "21:00", - airstamp: "2016-04-25T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/56/142371.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/56/142371.jpg", - }, - summary: - "

Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/560813", - }, - }, - }, - { - id: 664672, - url: "http://www.tvmaze.com/episodes/664672/game-of-thrones-6x02-home", - name: "Home", - season: 6, - number: 2, - airdate: "2016-05-01", - airtime: "21:00", - airstamp: "2016-05-02T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/56/142372.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/56/142372.jpg", - }, - summary: - "

Bran trains with the Three-Eyed Raven. In King's Landing, Jaime advises Tommen. Tyrion demands good news, but has to make his own. At Castle Black, the Night's Watch stands behind Thorne. Ramsay Bolton proposes a plan, and Balon Greyjoy entertains other proposals.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/664672", - }, - }, - }, - { - id: 664673, - url: - "http://www.tvmaze.com/episodes/664673/game-of-thrones-6x03-oathbreaker", - name: "Oathbreaker", - season: 6, - number: 3, - airdate: "2016-05-08", - airtime: "21:00", - airstamp: "2016-05-09T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/56/142370.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/56/142370.jpg", - }, - summary: - "

Daenerys meets her future. Bran meets the past. Tommen confronts the High Sparrow. Arya trains to be No One. Varys finds an answer. Ramsay gets a gift.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/664673", - }, - }, - }, - { - id: 664674, - url: - "http://www.tvmaze.com/episodes/664674/game-of-thrones-6x04-book-of-the-stranger", - name: "Book of the Stranger", - season: 6, - number: 4, - airdate: "2016-05-15", - airtime: "21:00", - airstamp: "2016-05-16T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/56/142273.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/56/142273.jpg", - }, - summary: - "

Tyrion strikes a deal. Jorah and Daario undertake a difficult task. Jaime and Cersei try to improve their situation.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/664674", - }, - }, - }, - { - id: 664675, - url: - "http://www.tvmaze.com/episodes/664675/game-of-thrones-6x05-the-door", - name: "The Door", - season: 6, - number: 5, - airdate: "2016-05-22", - airtime: "21:00", - airstamp: "2016-05-23T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/60/150273.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/60/150273.jpg", - }, - summary: - "

Tyrion seeks a strange ally. Bran learns a great deal. Brienne goes on a mission. Arya is given a chance to prove herself.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/664675", - }, - }, - }, - { - id: 664676, - url: - "http://www.tvmaze.com/episodes/664676/game-of-thrones-6x06-blood-of-my-blood", - name: "Blood of My Blood", - season: 6, - number: 6, - airdate: "2016-05-29", - airtime: "21:00", - airstamp: "2016-05-30T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/60/150274.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/60/150274.jpg", - }, - summary: - "

An old foe comes back into the picture. Gilly meets Sam's family. Arya faces a difficult choice. Jaime faces off against the High Sparrow.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/664676", - }, - }, - }, - { - id: 717449, - url: - "http://www.tvmaze.com/episodes/717449/game-of-thrones-6x07-the-broken-man", - name: "The Broken Man", - season: 6, - number: 7, - airdate: "2016-06-05", - airtime: "21:00", - airstamp: "2016-06-06T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/60/150275.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/60/150275.jpg", - }, - summary: - "

The High Sparrow eyes another target. Jaime confronts a hero. Arya makes a plan. The North is reminded.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/717449", - }, - }, - }, - { - id: 729573, - url: "http://www.tvmaze.com/episodes/729573/game-of-thrones-6x08-no-one", - name: "No One", - season: 6, - number: 8, - airdate: "2016-06-12", - airtime: "21:00", - airstamp: "2016-06-13T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/61/153044.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/61/153044.jpg", - }, - summary: - "

Jaime encounters a hero; the High Sparrow fixates on another prey; Arya hatches a new plan; Yara and Theon plot their next move; Olenna and Cersei discuss their families' futures.

While Jaime weighs his options, Cersei answers a request. Tyrion's plans bear fruit. Arya faces a new test.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/729573", - }, - }, - }, - { - id: 729574, - url: - "http://www.tvmaze.com/episodes/729574/game-of-thrones-6x09-battle-of-the-bastards", - name: "Battle of the Bastards", - season: 6, - number: 9, - airdate: "2016-06-19", - airtime: "21:00", - airstamp: "2016-06-20T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/62/155042.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/62/155042.jpg", - }, - summary: - "

Ramsay surprises his audience. Jon retaliates. Dany is true to her word.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/729574", - }, - }, - }, - { - id: 729575, - url: - "http://www.tvmaze.com/episodes/729575/game-of-thrones-6x10-the-winds-of-winter", - name: "The Winds of Winter", - season: 6, - number: 10, - airdate: "2016-06-26", - airtime: "21:00", - airstamp: "2016-06-27T01:00:00+00:00", - runtime: 69, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/63/157920.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/63/157920.jpg", - }, - summary: - "

Alliances are made, the High Sparrow is holding trials at King's Landing, Daenerys is sailing for the Seven Kingdoms and a new King of the North is crowned.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/729575", - }, - }, - }, - { - id: 937256, - url: - "http://www.tvmaze.com/episodes/937256/game-of-thrones-7x01-dragonstone", - name: "Dragonstone", - season: 7, - number: 1, - airdate: "2017-07-16", - airtime: "21:00", - airstamp: "2017-07-17T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/120/302038.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/120/302038.jpg", - }, - summary: - "

Jon organizes the defense of the North. Cersei tries to even the odds. Daenerys comes home.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/937256", - }, - }, - }, - { - id: 1221410, - url: - "http://www.tvmaze.com/episodes/1221410/game-of-thrones-7x02-stormborn", - name: "Stormborn", - season: 7, - number: 2, - airdate: "2017-07-23", - airtime: "21:00", - airstamp: "2017-07-24T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/120/302047.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/120/302047.jpg", - }, - summary: - "

Daenerys receives an unexpected visitor. Jon faces a revolt. Tyrion plans the conquest of Westeros.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1221410", - }, - }, - }, - { - id: 1221411, - url: - "http://www.tvmaze.com/episodes/1221411/game-of-thrones-7x03-the-queens-justice", - name: "The Queen's Justice", - season: 7, - number: 3, - airdate: "2017-07-30", - airtime: "21:00", - airstamp: "2017-07-31T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/122/306938.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/122/306938.jpg", - }, - summary: - "

Daenerys holds court. Cersei returns a gift. Jaime learns from his mistakes.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1221411", - }, - }, - }, - { - id: 1221412, - url: - "http://www.tvmaze.com/episodes/1221412/game-of-thrones-7x04-the-spoils-of-war", - name: "The Spoils of War", - season: 7, - number: 4, - airdate: "2017-08-06", - airtime: "21:00", - airstamp: "2017-08-07T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/123/307677.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/123/307677.jpg", - }, - summary: - "

Arya gets to the final destination. Daenerys takes it upon herself to strike back.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1221412", - }, - }, - }, - { - id: 1221413, - url: - "http://www.tvmaze.com/episodes/1221413/game-of-thrones-7x05-eastwatch", - name: "Eastwatch", - season: 7, - number: 5, - airdate: "2017-08-13", - airtime: "21:00", - airstamp: "2017-08-14T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/124/310839.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/124/310839.jpg", - }, - summary: - "

Daenerys demands loyalty from the surviving Lannister soldiers; Jon heeds Bran's warning about White Walkers on the move; Cersei vows to vanquish anyone or anything that stands in her way.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1221413", - }, - }, - }, - { - id: 1221414, - url: - "http://www.tvmaze.com/episodes/1221414/game-of-thrones-7x06-beyond-the-wall", - name: "Beyond the Wall", - season: 7, - number: 6, - airdate: "2017-08-20", - airtime: "21:00", - airstamp: "2017-08-21T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/125/312651.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/125/312651.jpg", - }, - summary: - "

Jon's mission continues north of the wall, but the odds against his ragged band of misfits may be greater than he imagined.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1221414", - }, - }, - }, - { - id: 1221415, - url: - "http://www.tvmaze.com/episodes/1221415/game-of-thrones-7x07-the-dragon-and-the-wolf", - name: "The Dragon and the Wolf", - season: 7, - number: 7, - airdate: "2017-08-27", - airtime: "21:00", - airstamp: "2017-08-28T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/125/314502.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/125/314502.jpg", - }, - summary: - "

Cersei sits on the Iron Throne; Daenerys sails across the Narrow Sea; Jon Snow is King in the North, and winter is finally here.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1221415", - }, - }, - }, - { - id: 1590943, - url: - "http://www.tvmaze.com/episodes/1590943/game-of-thrones-8x01-winterfell", - name: "Winterfell", - season: 8, - number: 1, - airdate: "2019-04-14", - airtime: "21:00", - airstamp: "2019-04-15T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/191/479477.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/191/479477.jpg", - }, - summary: - "

Arriving at Winterfell, Jon and Daenerys struggle to unite a divided North. Jon gets some big news.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1590943", - }, - }, - }, - { - id: 1623964, - url: - "http://www.tvmaze.com/episodes/1623964/game-of-thrones-8x02-a-knight-of-the-seven-kingdoms", - name: "A Knight of the Seven Kingdoms", - season: 8, - number: 2, - airdate: "2019-04-21", - airtime: "21:00", - airstamp: "2019-04-22T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/192/482451.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/192/482451.jpg", - }, - summary: - "

Jaime faces judgement and Winterfell prepares for the battle to come.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1623964", - }, - }, - }, - { - id: 1623965, - url: - "http://www.tvmaze.com/episodes/1623965/game-of-thrones-8x03-the-long-night", - name: "The Long Night", - season: 8, - number: 3, - airdate: "2019-04-28", - airtime: "21:00", - airstamp: "2019-04-29T01:00:00+00:00", - runtime: 90, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/192/482465.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/192/482465.jpg", - }, - summary: "

Winterfell fights the Army of the Dead.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1623965", - }, - }, - }, - { - id: 1623966, - url: - "http://www.tvmaze.com/episodes/1623966/game-of-thrones-8x04-the-last-of-the-starks", - name: "The Last of the Starks", - season: 8, - number: 4, - airdate: "2019-05-05", - airtime: "21:00", - airstamp: "2019-05-06T01:00:00+00:00", - runtime: 78, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/195/487839.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/195/487839.jpg", - }, - summary: - "

The survivors plan their next steps; Cersei makes a power move.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1623966", - }, - }, - }, - { - id: 1623967, - url: - "http://www.tvmaze.com/episodes/1623967/game-of-thrones-8x05-the-bells", - name: "The Bells", - season: 8, - number: 5, - airdate: "2019-05-12", - airtime: "21:00", - airstamp: "2019-05-13T01:00:00+00:00", - runtime: 79, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/196/491994.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/196/491994.jpg", - }, - summary: - "

Varys betrays his queen, and Daenerys brings her forces to King's Landing.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1623967", - }, - }, - }, - { - id: 1623968, - url: - "http://www.tvmaze.com/episodes/1623968/game-of-thrones-8x06-the-iron-throne", - name: "The Iron Throne", - season: 8, - number: 6, - airdate: "2019-05-19", - airtime: "21:00", - airstamp: "2019-05-20T01:00:00+00:00", - runtime: 80, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/198/495648.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/198/495648.jpg", - }, - summary: - "

In the aftermath of the devastating attack on King's Landing, Daenerys must face the survivors.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1623968", - }, - }, - }, - ]; -} From 54acb3fe8584f639cc6f4b0ee9336f5c04bc53af Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Mon, 10 Aug 2026 01:16:11 +0100 Subject: [PATCH 10/24] new --- index.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.html b/index.html index e7eb4b5..d9b6734 100644 --- a/index.html +++ b/index.html @@ -13,8 +13,7 @@
- - + From 5e50ee53d3e3e648780cf726aaaafc90d4219652 Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Mon, 10 Aug 2026 01:19:43 +0100 Subject: [PATCH 11/24] js --- script.js | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/script.js b/script.js index ecb6c0e..4bd2396 100644 --- a/script.js +++ b/script.js @@ -1,10 +1,47 @@ let allEpisodes = []; -function setup() { - allEpisodes = getAllEpisodes(); - createControls(); - makePageForEpisodes(allEpisodes); +async function setup() { addTvmazeAttribution(); + showLoading(); + + try { + const response = await fetch("https://api.tvmaze.com/shows/82/episodes"); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + allEpisodes = await response.json(); + + // Clear loading message and build UI + const rootElem = document.getElementById("root"); + rootElem.innerHTML = ""; + + createControls(); + makePageForEpisodes(allEpisodes); + } catch (error) { + showError("Failed to load episode data. Please try again later."); + } +} + +function showLoading() { + const rootElem = document.getElementById("root"); + rootElem.innerHTML = ` +
+
+

Loading episodes, please wait...

+
+ `; +} + +function showError(message) { + const rootElem = document.getElementById("root"); + rootElem.innerHTML = ` +
+

Something went wrong

+

${message}

+
+ `; } function createControls() { From 70c97058f4e3ed3691baa75be5aa4a974cb60d97 Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Mon, 10 Aug 2026 01:21:50 +0100 Subject: [PATCH 12/24] deleted file --- episodes.js | 1855 --------------------------------------------------- 1 file changed, 1855 deletions(-) delete mode 100644 episodes.js diff --git a/episodes.js b/episodes.js deleted file mode 100644 index 5ef6e9e..0000000 --- a/episodes.js +++ /dev/null @@ -1,1855 +0,0 @@ -//DO NOT EDIT THIS FILE - -//This content is from https://www.tvmaze.com/ -//specifically: https://api.tvmaze.com/shows/82/episodes - -function getOneEpisode() { - return { - id: 4952, - url: - "http://www.tvmaze.com/episodes/4952/game-of-thrones-1x01-winter-is-coming", - name: "Winter is Coming", - season: 1, - number: 1, - airdate: "2011-04-17", - airtime: "21:00", - airstamp: "2011-04-18T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2668.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2668.jpg", - }, - summary: - "

Lord Eddard Stark, ruler of the North, is summoned to court by his old friend, King Robert Baratheon, to serve as the King's Hand. Eddard reluctantly agrees after learning of a possible threat to the King's life. Eddard's bastard son Jon Snow must make a painful decision about his own future, while in the distant east Viserys Targaryen plots to reclaim his father's throne, usurped by Robert, by selling his sister in marriage.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4952", - }, - }, - }; -} - -function getAllEpisodes() { - return [ - { - id: 4952, - url: - "http://www.tvmaze.com/episodes/4952/game-of-thrones-1x01-winter-is-coming", - name: "Winter is Coming", - season: 1, - number: 1, - airdate: "2011-04-17", - airtime: "21:00", - airstamp: "2011-04-18T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2668.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2668.jpg", - }, - summary: - "

Lord Eddard Stark, ruler of the North, is summoned to court by his old friend, King Robert Baratheon, to serve as the King's Hand. Eddard reluctantly agrees after learning of a possible threat to the King's life. Eddard's bastard son Jon Snow must make a painful decision about his own future, while in the distant east Viserys Targaryen plots to reclaim his father's throne, usurped by Robert, by selling his sister in marriage.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4952", - }, - }, - }, - { - id: 4953, - url: - "http://www.tvmaze.com/episodes/4953/game-of-thrones-1x02-the-kingsroad", - name: "The Kingsroad", - season: 1, - number: 2, - airdate: "2011-04-24", - airtime: "21:00", - airstamp: "2011-04-25T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2669.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2669.jpg", - }, - summary: - "

An incident on the Kingsroad threatens Eddard and Robert's friendship. Jon and Tyrion travel to the Wall, where they discover that the reality of the Night's Watch may not match the heroic image of it.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4953", - }, - }, - }, - { - id: 4954, - url: "http://www.tvmaze.com/episodes/4954/game-of-thrones-1x03-lord-snow", - name: "Lord Snow", - season: 1, - number: 3, - airdate: "2011-05-01", - airtime: "21:00", - airstamp: "2011-05-02T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2671.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2671.jpg", - }, - summary: - "

Jon Snow attempts to find his place amongst the Night's Watch. Eddard and his daughters arrive at King's Landing.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4954", - }, - }, - }, - { - id: 4955, - url: - "http://www.tvmaze.com/episodes/4955/game-of-thrones-1x04-cripples-bastards-and-broken-things", - name: "Cripples, Bastards, and Broken Things", - season: 1, - number: 4, - airdate: "2011-05-08", - airtime: "21:00", - airstamp: "2011-05-09T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2673.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2673.jpg", - }, - summary: - "

Tyrion stops at Winterfell on his way home and gets a frosty reception from Robb Stark. Eddard's investigation into the death of his predecessor gets underway.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4955", - }, - }, - }, - { - id: 4956, - url: - "http://www.tvmaze.com/episodes/4956/game-of-thrones-1x05-the-wolf-and-the-lion", - name: "The Wolf and the Lion", - season: 1, - number: 5, - airdate: "2011-05-15", - airtime: "21:00", - airstamp: "2011-05-16T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2674.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2674.jpg", - }, - summary: - "

Catelyn's actions on the road have repercussions for Eddard. Tyrion enjoys the dubious hospitality of the Eyrie.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4956", - }, - }, - }, - { - id: 4957, - url: - "http://www.tvmaze.com/episodes/4957/game-of-thrones-1x06-a-golden-crown", - name: "A Golden Crown", - season: 1, - number: 6, - airdate: "2011-05-22", - airtime: "21:00", - airstamp: "2011-05-23T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2676.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2676.jpg", - }, - summary: - "

Viserys is increasingly frustrated by the lack of progress towards gaining his crown.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4957", - }, - }, - }, - { - id: 4958, - url: - "http://www.tvmaze.com/episodes/4958/game-of-thrones-1x07-you-win-or-you-die", - name: "You Win or You Die", - season: 1, - number: 7, - airdate: "2011-05-29", - airtime: "21:00", - airstamp: "2011-05-30T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2677.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2677.jpg", - }, - summary: - "

Eddard's investigations in King's Landing reach a climax and a dark secret is revealed.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4958", - }, - }, - }, - { - id: 4959, - url: - "http://www.tvmaze.com/episodes/4959/game-of-thrones-1x08-the-pointy-end", - name: "The Pointy End", - season: 1, - number: 8, - airdate: "2011-06-05", - airtime: "21:00", - airstamp: "2011-06-06T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2678.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2678.jpg", - }, - summary: - "

Tyrion joins his father's army with unexpected allies. Events in King's Landing take a turn for the worse as Arya's lessons are put to the test.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4959", - }, - }, - }, - { - id: 4960, - url: "http://www.tvmaze.com/episodes/4960/game-of-thrones-1x09-baelor", - name: "Baelor", - season: 1, - number: 9, - airdate: "2011-06-12", - airtime: "21:00", - airstamp: "2011-06-13T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2679.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2679.jpg", - }, - summary: - "

Catelyn must negotiate with the irascible Lord Walder Frey.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4960", - }, - }, - }, - { - id: 4961, - url: - "http://www.tvmaze.com/episodes/4961/game-of-thrones-1x10-fire-and-blood", - name: "Fire and Blood", - season: 1, - number: 10, - airdate: "2011-06-19", - airtime: "21:00", - airstamp: "2011-06-20T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2681.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2681.jpg", - }, - summary: - "

Daenerys must realize her destiny. Jaime finds himself in an unfamiliar predicament.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4961", - }, - }, - }, - { - id: 4962, - url: - "http://www.tvmaze.com/episodes/4962/game-of-thrones-2x01-the-north-remembers", - name: "The North Remembers", - season: 2, - number: 1, - airdate: "2012-04-01", - airtime: "21:00", - airstamp: "2012-04-02T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3174.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3174.jpg", - }, - summary: - "

War grips the continent of Westeros. As Tyrion Lannister tries to take his strong-willed nephew in hand in King's Landing, Stannis Baratheon launches his own campaign to take the Iron Throne with the help of a mysterious priestess. In the east, Daenerys must lead her retinue through a desolate wasteland whilst beyond the Wall the Night's Watch seeks the aid of a wildling.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4962", - }, - }, - }, - { - id: 4963, - url: - "http://www.tvmaze.com/episodes/4963/game-of-thrones-2x02-the-night-lands", - name: "The Night Lands", - season: 2, - number: 2, - airdate: "2012-04-08", - airtime: "21:00", - airstamp: "2012-04-09T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3175.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3175.jpg", - }, - summary: - "

Stannis uses Ser Davos to seek out new allies for his war with the Lannisters. On the road north, Arya confides in Gendry. Robb Stark sends Theon Greyjoy to win an alliance with his father and the fierce warriors of the Iron Islands. Cersei and Tyrion clash on how to rule in King's Landing.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4963", - }, - }, - }, - { - id: 4964, - url: - "http://www.tvmaze.com/episodes/4964/game-of-thrones-2x03-what-is-dead-may-never-die", - name: "What is Dead May Never Die", - season: 2, - number: 3, - airdate: "2012-04-15", - airtime: "21:00", - airstamp: "2012-04-16T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3176.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3176.jpg", - }, - summary: - "

Catelyn Stark treats with King Renly in the hope of winning an alliance. Tyrion undertakes a complex plan in King's Landing to expose an enemy. At Winterfell, Bran's dreams continue to trouble him.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4964", - }, - }, - }, - { - id: 4965, - url: - "http://www.tvmaze.com/episodes/4965/game-of-thrones-2x04-garden-of-bones", - name: "Garden of Bones", - season: 2, - number: 4, - airdate: "2012-04-22", - airtime: "21:00", - airstamp: "2012-04-23T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3177.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3177.jpg", - }, - summary: - "

Tyrion attempts to restrain Joffrey's cruelty. Catelyn attempts to broker a peace between Stannis and Renly. Daenerys and her followers arrive at the great city of Qarth and hope to find refuge there. Arya and Gendry arrive at Harrenhal, a great castle now under Lannister occupation.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4965", - }, - }, - }, - { - id: 4966, - url: - "http://www.tvmaze.com/episodes/4966/game-of-thrones-2x05-the-ghost-of-harrenhal", - name: "The Ghost of Harrenhal", - season: 2, - number: 5, - airdate: "2012-04-29", - airtime: "21:00", - airstamp: "2012-04-30T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3178.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3178.jpg", - }, - summary: - "

Confusion rages in the Stormlands in the wake of a devastating reversal. Catelyn must flee with a new ally, whilst Littlefinger sees an opportunity in the chaos. Theon seeks to prove himself to his father in battle. Arya receives a promise from the enigmatic Jaqen H'ghar. The Night's Watch arrives at the Fist of the First Men. Daenerys Targaryen receives a marriage proposal.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4966", - }, - }, - }, - { - id: 4967, - url: - "http://www.tvmaze.com/episodes/4967/game-of-thrones-2x06-the-old-gods-and-the-new", - name: "The Old Gods and the New", - season: 2, - number: 6, - airdate: "2012-05-06", - airtime: "21:00", - airstamp: "2012-05-07T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3180.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3180.jpg", - }, - summary: - "

Arya has a surprise visitor; Dany vows to take what is hers; Joffrey meets his subjects; Qhorin gives Jon a chance to prove himself.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4967", - }, - }, - }, - { - id: 4968, - url: - "http://www.tvmaze.com/episodes/4968/game-of-thrones-2x07-a-man-without-honor", - name: "A Man Without Honor", - season: 2, - number: 7, - airdate: "2012-05-13", - airtime: "21:00", - airstamp: "2012-05-14T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3192.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3192.jpg", - }, - summary: - "

Jaime meets a relative; Theon hunts; Dany receives an invitation.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4968", - }, - }, - }, - { - id: 4969, - url: - "http://www.tvmaze.com/episodes/4969/game-of-thrones-2x08-the-prince-of-winterfell", - name: "The Prince of Winterfell", - season: 2, - number: 8, - airdate: "2012-05-20", - airtime: "21:00", - airstamp: "2012-05-21T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3194.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3194.jpg", - }, - summary: - "

Theon holds the fort; Arya calls in her debt with Jaqen; Robb is betrayed; Stannis and Davos approach their destination.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4969", - }, - }, - }, - { - id: 4970, - url: - "http://www.tvmaze.com/episodes/4970/game-of-thrones-2x09-blackwater", - name: "Blackwater", - season: 2, - number: 9, - airdate: "2012-05-27", - airtime: "21:00", - airstamp: "2012-05-28T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3196.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3196.jpg", - }, - summary: - "

A massive battle rages for control of King's Landing and the Iron Throne.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4970", - }, - }, - }, - { - id: 4971, - url: - "http://www.tvmaze.com/episodes/4971/game-of-thrones-2x10-valar-morghulis", - name: "Valar Morghulis", - season: 2, - number: 10, - airdate: "2012-06-03", - airtime: "21:00", - airstamp: "2012-06-04T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/3197.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/3197.jpg", - }, - summary: - "

Tyrion awakens to a changed situation. King Joffrey doles out rewards to his subjects. As Theon stirs his men to action, Luwin offers some final advice. Brienne silences Jaime; Arya receives a gift from Jaqen; Dany goes to a strange place; Jon proves himself to Qhorin.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4971", - }, - }, - }, - { - id: 4972, - url: - "http://www.tvmaze.com/episodes/4972/game-of-thrones-3x01-valar-dohaeris", - name: "Valar Dohaeris", - season: 3, - number: 1, - airdate: "2013-03-31", - airtime: "21:00", - airstamp: "2013-04-01T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2628.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2628.jpg", - }, - summary: - "

Jon is brought before Mance Rayder, the King Beyond the Wall, while the Night's Watch survivors retreat south. In King's Landing, Tyrion asks for his reward. Littlefinger offers Sansa a way out. Cersei hosts a dinner for the royal family. Daenerys sails into Slaver's Bay.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4972", - }, - }, - }, - { - id: 4973, - url: - "http://www.tvmaze.com/episodes/4973/game-of-thrones-3x02-dark-wings-dark-words", - name: "Dark Wings, Dark Words", - season: 3, - number: 2, - airdate: "2013-04-07", - airtime: "21:00", - airstamp: "2013-04-08T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2618.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2618.jpg", - }, - summary: - "

Sansa says too much. Shae asks Tyrion for a favor. Jaime finds a way to pass the time. Arya runs into the Brotherhood Without Banners.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4973", - }, - }, - }, - { - id: 4974, - url: - "http://www.tvmaze.com/episodes/4974/game-of-thrones-3x03-walk-of-punishment", - name: "Walk of Punishment", - season: 3, - number: 3, - airdate: "2013-04-14", - airtime: "21:00", - airstamp: "2013-04-15T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2616.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2616.jpg", - }, - summary: - "

Tyrion shoulders new responsibilities. Jon is taken to the Fist of the First Men. Daenerys meets with the slavers. Jaime strikes a deal with his captors.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4974", - }, - }, - }, - { - id: 4975, - url: - "http://www.tvmaze.com/episodes/4975/game-of-thrones-3x04-and-now-his-watch-is-ended", - name: "And Now His Watch is Ended", - season: 3, - number: 4, - airdate: "2013-04-21", - airtime: "21:00", - airstamp: "2013-04-22T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2615.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2615.jpg", - }, - summary: - "

The Night's Watch takes stock. Varys meets his better. Arya is taken to the commander of the Brotherhood. Daenerys exchanges a chain for a Whip.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4975", - }, - }, - }, - { - id: 4976, - url: - "http://www.tvmaze.com/episodes/4976/game-of-thrones-3x05-kissed-by-fire", - name: "Kissed by Fire", - season: 3, - number: 5, - airdate: "2013-04-28", - airtime: "21:00", - airstamp: "2013-04-29T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2614.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2614.jpg", - }, - summary: - "

The Hound is judged by the gods; Jaime is judged by men. Jon proves himself; Robb is betrayed. Tyrion learns the cost of weddings.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4976", - }, - }, - }, - { - id: 4977, - url: "http://www.tvmaze.com/episodes/4977/game-of-thrones-3x06-the-climb", - name: "The Climb", - season: 3, - number: 6, - airdate: "2013-05-05", - airtime: "21:00", - airstamp: "2013-05-06T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2612.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2612.jpg", - }, - summary: - "

Tywin plans strategic unions for the Lannisters. Melisandre visits the Riverlands. Robb weighs a compromise to repair his alliance with House Frey. Roose Bolton decides what to do with Jaime Lannister. Jon, Ygritte and the Wildlings face a daunting climb.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4977", - }, - }, - }, - { - id: 4978, - url: - "http://www.tvmaze.com/episodes/4978/game-of-thrones-3x07-the-bear-and-the-maiden-fair", - name: "The Bear and the Maiden Fair", - season: 3, - number: 7, - airdate: "2013-05-12", - airtime: "21:00", - airstamp: "2013-05-13T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2611.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2611.jpg", - }, - summary: - "

Daenerys exchanges gifts with a slave lord outside Yunkai. As Sansa frets about her prospects, Shae chafes at Tyrion's new situation. Tywin counsels the king, and Melisandre reveals a secret to Gendry. Brienne faces a formidable foe in Harrenhal.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4978", - }, - }, - }, - { - id: 4979, - url: - "http://www.tvmaze.com/episodes/4979/game-of-thrones-3x08-second-sons", - name: "Second Sons", - season: 3, - number: 8, - airdate: "2013-05-19", - airtime: "21:00", - airstamp: "2013-05-20T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2599.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2599.jpg", - }, - summary: - "

King's Landing hosts a wedding, and Tyrion and Sansa spend the night together. Daenerys meets the Titan's Bastard. Davos demands proof from Melisandre. Sam and Gilly meet an older Gentleman.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4979", - }, - }, - }, - { - id: 4980, - url: - "http://www.tvmaze.com/episodes/4980/game-of-thrones-3x09-the-rains-of-castamere", - name: "The Rains of Castamere", - season: 3, - number: 9, - airdate: "2013-06-02", - airtime: "21:00", - airstamp: "2013-06-03T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2598.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2598.jpg", - }, - summary: - "

Robb presents himself to Walder Frey, and Edmure meets his bride. Jon faces his harshest test yet. Bran discovers a new gift. Daario and Jorah debate how to take Yunkai. House Frey joins with House Tully.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4980", - }, - }, - }, - { - id: 4981, - url: "http://www.tvmaze.com/episodes/4981/game-of-thrones-3x10-mhysa", - name: "Mhysa", - season: 3, - number: 10, - airdate: "2013-06-09", - airtime: "21:00", - airstamp: "2013-06-10T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2597.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2597.jpg", - }, - summary: - "

Joffrey challenges Tywin. Bran tells a ghost story. In Dragonstone, mercy comes from strange quarters. Daenerys waits to see if she is a conqueror or a liberator.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4981", - }, - }, - }, - { - id: 4982, - url: - "http://www.tvmaze.com/episodes/4982/game-of-thrones-4x01-two-swords", - name: "Two Swords", - season: 4, - number: 1, - airdate: "2014-04-06", - airtime: "21:00", - airstamp: "2014-04-07T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2583.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2583.jpg", - }, - summary: - "

Tyrion welcomes a guest to King's Landing. At Castle Black, Jon Snow finds himself unwelcome. Dany is pointed to Meereen, the mother of all slave cities. Arya runs into an old friend.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4982", - }, - }, - }, - { - id: 4983, - url: - "http://www.tvmaze.com/episodes/4983/game-of-thrones-4x02-the-lion-and-the-rose", - name: "The Lion and the Rose", - season: 4, - number: 2, - airdate: "2014-04-13", - airtime: "21:00", - airstamp: "2014-04-14T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2584.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2584.jpg", - }, - summary: - "

Tyrion lends Jaime a hand. Joffrey and Margaery host a breakfast. At Dragonstone, Stannis loses patience with Davos. Ramsay finds a purpose for his pet. North of the Wall, Bran sees where they must go.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4983", - }, - }, - }, - { - id: 4984, - url: - "http://www.tvmaze.com/episodes/4984/game-of-thrones-4x03-breaker-of-chains", - name: "Breaker of Chains", - season: 4, - number: 3, - airdate: "2014-04-20", - airtime: "21:00", - airstamp: "2014-04-21T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2585.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2585.jpg", - }, - summary: - "

Tyrion ponders his options. Tywin extends an olive branch. Sam realizes Castle Black isn't safe, and Jon proposes a bold plan. The Hound teaches Arya the way things are. Dany chooses her Champion.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4984", - }, - }, - }, - { - id: 4985, - url: - "http://www.tvmaze.com/episodes/4985/game-of-thrones-4x04-oathkeeper", - name: "Oathkeeper", - season: 4, - number: 4, - airdate: "2014-04-27", - airtime: "21:00", - airstamp: "2014-04-28T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2586.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2586.jpg", - }, - summary: - "

Dany balances justice and mercy. Jaime tasks Brienne with his honor. Jon secures volunteers while Bran, Jojen, Meera and Hodor stumble on shelter.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4985", - }, - }, - }, - { - id: 4986, - url: - "http://www.tvmaze.com/episodes/4986/game-of-thrones-4x05-first-of-his-name", - name: "First of His Name", - season: 4, - number: 5, - airdate: "2014-05-04", - airtime: "21:00", - airstamp: "2014-05-05T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2587.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2587.jpg", - }, - summary: - "

Cersei and Tywin plot the Crown's next move. Dany discusses future plans. Jon embarks on a new mission.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4986", - }, - }, - }, - { - id: 4987, - url: - "http://www.tvmaze.com/episodes/4987/game-of-thrones-4x06-the-laws-of-gods-and-men", - name: "The Laws of Gods and Men", - season: 4, - number: 6, - airdate: "2014-05-11", - airtime: "21:00", - airstamp: "2014-05-12T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2588.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2588.jpg", - }, - summary: - "

Stannis and Davos set sail with a new strategy. Dany meets with supplicants. Tyrion faces down his father in the throne room.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4987", - }, - }, - }, - { - id: 4988, - url: - "http://www.tvmaze.com/episodes/4988/game-of-thrones-4x07-mockingbird", - name: "Mockingbird", - season: 4, - number: 7, - airdate: "2014-05-18", - airtime: "21:00", - airstamp: "2014-05-19T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2589.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2589.jpg", - }, - summary: - "

Tyrion enlists an unlikely ally. Daario entreats Dany to allow him to do what he does best. Jon's warnings about the Wall's vulnerability fall on deaf ears. Brienne follows a new lead on the road with Pod.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4988", - }, - }, - }, - { - id: 4989, - url: - "http://www.tvmaze.com/episodes/4989/game-of-thrones-4x08-the-mountain-and-the-viper", - name: "The Mountain and the Viper", - season: 4, - number: 8, - airdate: "2014-06-01", - airtime: "21:00", - airstamp: "2014-06-02T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2591.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2591.jpg", - }, - summary: - "

Mole's Town receives unexpected visitors. Littlefinger's motives are questioned. Ramsay attempts to prove himself to his father. Tyrion's fate is decided.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4989", - }, - }, - }, - { - id: 4990, - url: - "http://www.tvmaze.com/episodes/4990/game-of-thrones-4x09-the-watchers-on-the-wall", - name: "The Watchers on the Wall", - season: 4, - number: 9, - airdate: "2014-06-08", - airtime: "21:00", - airstamp: "2014-06-09T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2593.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2593.jpg", - }, - summary: - "

Jon Snow and the rest of the Night's Watch face the biggest challenge to the Wall yet.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4990", - }, - }, - }, - { - id: 4991, - url: - "http://www.tvmaze.com/episodes/4991/game-of-thrones-4x10-the-children", - name: "The Children", - season: 4, - number: 10, - airdate: "2014-06-15", - airtime: "21:00", - airstamp: "2014-06-16T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/1/2594.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/1/2594.jpg", - }, - summary: - "

An unexpected arrival north of the Wall changes circumstances. Dany is forced to face harsh realities. Bran learns more of his destiny. Tyrion sees the truth of his situation.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/4991", - }, - }, - }, - { - id: 116522, - url: - "http://www.tvmaze.com/episodes/116522/game-of-thrones-5x01-the-wars-to-come", - name: "The Wars to Come", - season: 5, - number: 1, - airdate: "2015-04-12", - airtime: "21:00", - airstamp: "2015-04-13T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/10/25988.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/10/25988.jpg", - }, - summary: - "

Varys discusses a conspiracy with Tyrion; Daenerys' rule faces a new threat; Jon finds himself between two kings; and Cersei and Jaime try to move on from Tywin's demise.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/116522", - }, - }, - }, - { - id: 144328, - url: - "http://www.tvmaze.com/episodes/144328/game-of-thrones-5x02-the-house-of-black-and-white", - name: "The House of Black and White", - season: 5, - number: 2, - airdate: "2015-04-19", - airtime: "21:00", - airstamp: "2015-04-20T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/10/25989.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/10/25989.jpg", - }, - summary: - "

Arya arrives in Braavos; Brienne and Podrick find danger while traveling; Cersei worries about Myrcella in Dorne when Ellaria Sand seeks revenge for Oberyn's death; Jon is tempted by Stannis.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/144328", - }, - }, - }, - { - id: 144329, - url: - "http://www.tvmaze.com/episodes/144329/game-of-thrones-5x03-high-sparrow", - name: "High Sparrow", - season: 5, - number: 3, - airdate: "2015-04-26", - airtime: "21:00", - airstamp: "2015-04-27T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/10/25990.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/10/25990.jpg", - }, - summary: - "

Cersei meets the High Sparrow after learning of a clergyman's embarrassing tale. Meanwhile, Davos talks to Jon about the future of Winterfell, where Ramsay Snow has just learned the identity of his future bride; Arya grows impatient doing menial tasks in the House of Black and White; and Tyrion searches for more comfortable surroundings on a long trip with Varys.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/144329", - }, - }, - }, - { - id: 144330, - url: - "http://www.tvmaze.com/episodes/144330/game-of-thrones-5x04-sons-of-the-harpy", - name: "Sons of the Harpy", - season: 5, - number: 4, - airdate: "2015-05-03", - airtime: "21:00", - airstamp: "2015-05-04T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/10/26444.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/10/26444.jpg", - }, - summary: - "

Margaery seeks prudent counsel. Jaime Struggles in foreign lands. Dany answers the Harpy's call.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/144330", - }, - }, - }, - { - id: 151777, - url: - "http://www.tvmaze.com/episodes/151777/game-of-thrones-5x05-kill-the-boy", - name: "Kill the Boy", - season: 5, - number: 5, - airdate: "2015-05-10", - airtime: "21:00", - airstamp: "2015-05-11T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/10/26819.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/10/26819.jpg", - }, - summary: - "

Dany makes a difficult decision in Meereen. Jon recruits the help of an unexpected ally. Brienne searches for Sansa. Theon remains under Ramsay's control.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/151777", - }, - }, - }, - { - id: 152766, - url: - "http://www.tvmaze.com/episodes/152766/game-of-thrones-5x06-unbowed-unbent-unbroken", - name: "Unbowed, Unbent, Unbroken", - season: 5, - number: 6, - airdate: "2015-05-17", - airtime: "21:00", - airstamp: "2015-05-18T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/10/27259.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/10/27259.jpg", - }, - summary: - "

Arya trains. Jorah and Tyrion run into slavers. Trystane and Myrcella make plans. Jaime and Bronn reach their destination. The Sand Snakes attack.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/152766", - }, - }, - }, - { - id: 153327, - url: - "http://www.tvmaze.com/episodes/153327/game-of-thrones-5x07-the-gift", - name: "The Gift", - season: 5, - number: 7, - airdate: "2015-05-24", - airtime: "21:00", - airstamp: "2015-05-25T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/11/27535.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/11/27535.jpg", - }, - summary: - "

Jon prepares for conflict. Sansa tries to talk to Theon. Brienne waits for a sign. Stannis remains stubborn. Jaime attempts to reconnect with family.



", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/153327", - }, - }, - }, - { - id: 155299, - url: - "http://www.tvmaze.com/episodes/155299/game-of-thrones-5x08-hardhome", - name: "Hardhome", - season: 5, - number: 8, - airdate: "2015-05-31", - airtime: "21:00", - airstamp: "2015-06-01T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/11/28151.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/11/28151.jpg", - }, - summary: - "

Arya makes progress in her training. Sansa confronts an old friend. Cersei struggles. Jon travels.



", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/155299", - }, - }, - }, - { - id: 160040, - url: - "http://www.tvmaze.com/episodes/160040/game-of-thrones-5x09-the-dance-of-dragons", - name: "The Dance of Dragons", - season: 5, - number: 9, - airdate: "2015-06-07", - airtime: "21:00", - airstamp: "2015-06-08T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/11/29160.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/11/29160.jpg", - }, - summary: - "

Stannis confronts a troubling decision. Jon returns to The Wall. Mace visits the Iron Bank. Arya encounters someone from her past. Dany reluctantly oversees a traditional celebration of athleticism.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/160040", - }, - }, - }, - { - id: 162186, - url: - "http://www.tvmaze.com/episodes/162186/game-of-thrones-5x10-mothers-mercy", - name: "Mother's Mercy", - season: 5, - number: 10, - airdate: "2015-06-14", - airtime: "21:00", - airstamp: "2015-06-15T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/12/30012.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/12/30012.jpg", - }, - summary: - "

Cersei seeks forgiveness; Jon faces a new challenge; Arya plots to cross a name off her list; Tyrion sees a familiar face; and Daenerys finds herself surrounded by strangers.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/162186", - }, - }, - }, - { - id: 560813, - url: - "http://www.tvmaze.com/episodes/560813/game-of-thrones-6x01-the-red-woman", - name: "The Red Woman", - season: 6, - number: 1, - airdate: "2016-04-24", - airtime: "21:00", - airstamp: "2016-04-25T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/56/142371.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/56/142371.jpg", - }, - summary: - "

Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/560813", - }, - }, - }, - { - id: 664672, - url: "http://www.tvmaze.com/episodes/664672/game-of-thrones-6x02-home", - name: "Home", - season: 6, - number: 2, - airdate: "2016-05-01", - airtime: "21:00", - airstamp: "2016-05-02T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/56/142372.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/56/142372.jpg", - }, - summary: - "

Bran trains with the Three-Eyed Raven. In King's Landing, Jaime advises Tommen. Tyrion demands good news, but has to make his own. At Castle Black, the Night's Watch stands behind Thorne. Ramsay Bolton proposes a plan, and Balon Greyjoy entertains other proposals.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/664672", - }, - }, - }, - { - id: 664673, - url: - "http://www.tvmaze.com/episodes/664673/game-of-thrones-6x03-oathbreaker", - name: "Oathbreaker", - season: 6, - number: 3, - airdate: "2016-05-08", - airtime: "21:00", - airstamp: "2016-05-09T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/56/142370.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/56/142370.jpg", - }, - summary: - "

Daenerys meets her future. Bran meets the past. Tommen confronts the High Sparrow. Arya trains to be No One. Varys finds an answer. Ramsay gets a gift.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/664673", - }, - }, - }, - { - id: 664674, - url: - "http://www.tvmaze.com/episodes/664674/game-of-thrones-6x04-book-of-the-stranger", - name: "Book of the Stranger", - season: 6, - number: 4, - airdate: "2016-05-15", - airtime: "21:00", - airstamp: "2016-05-16T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/56/142273.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/56/142273.jpg", - }, - summary: - "

Tyrion strikes a deal. Jorah and Daario undertake a difficult task. Jaime and Cersei try to improve their situation.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/664674", - }, - }, - }, - { - id: 664675, - url: - "http://www.tvmaze.com/episodes/664675/game-of-thrones-6x05-the-door", - name: "The Door", - season: 6, - number: 5, - airdate: "2016-05-22", - airtime: "21:00", - airstamp: "2016-05-23T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/60/150273.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/60/150273.jpg", - }, - summary: - "

Tyrion seeks a strange ally. Bran learns a great deal. Brienne goes on a mission. Arya is given a chance to prove herself.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/664675", - }, - }, - }, - { - id: 664676, - url: - "http://www.tvmaze.com/episodes/664676/game-of-thrones-6x06-blood-of-my-blood", - name: "Blood of My Blood", - season: 6, - number: 6, - airdate: "2016-05-29", - airtime: "21:00", - airstamp: "2016-05-30T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/60/150274.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/60/150274.jpg", - }, - summary: - "

An old foe comes back into the picture. Gilly meets Sam's family. Arya faces a difficult choice. Jaime faces off against the High Sparrow.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/664676", - }, - }, - }, - { - id: 717449, - url: - "http://www.tvmaze.com/episodes/717449/game-of-thrones-6x07-the-broken-man", - name: "The Broken Man", - season: 6, - number: 7, - airdate: "2016-06-05", - airtime: "21:00", - airstamp: "2016-06-06T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/60/150275.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/60/150275.jpg", - }, - summary: - "

The High Sparrow eyes another target. Jaime confronts a hero. Arya makes a plan. The North is reminded.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/717449", - }, - }, - }, - { - id: 729573, - url: "http://www.tvmaze.com/episodes/729573/game-of-thrones-6x08-no-one", - name: "No One", - season: 6, - number: 8, - airdate: "2016-06-12", - airtime: "21:00", - airstamp: "2016-06-13T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/61/153044.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/61/153044.jpg", - }, - summary: - "

Jaime encounters a hero; the High Sparrow fixates on another prey; Arya hatches a new plan; Yara and Theon plot their next move; Olenna and Cersei discuss their families' futures.

While Jaime weighs his options, Cersei answers a request. Tyrion's plans bear fruit. Arya faces a new test.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/729573", - }, - }, - }, - { - id: 729574, - url: - "http://www.tvmaze.com/episodes/729574/game-of-thrones-6x09-battle-of-the-bastards", - name: "Battle of the Bastards", - season: 6, - number: 9, - airdate: "2016-06-19", - airtime: "21:00", - airstamp: "2016-06-20T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/62/155042.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/62/155042.jpg", - }, - summary: - "

Ramsay surprises his audience. Jon retaliates. Dany is true to her word.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/729574", - }, - }, - }, - { - id: 729575, - url: - "http://www.tvmaze.com/episodes/729575/game-of-thrones-6x10-the-winds-of-winter", - name: "The Winds of Winter", - season: 6, - number: 10, - airdate: "2016-06-26", - airtime: "21:00", - airstamp: "2016-06-27T01:00:00+00:00", - runtime: 69, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/63/157920.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/63/157920.jpg", - }, - summary: - "

Alliances are made, the High Sparrow is holding trials at King's Landing, Daenerys is sailing for the Seven Kingdoms and a new King of the North is crowned.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/729575", - }, - }, - }, - { - id: 937256, - url: - "http://www.tvmaze.com/episodes/937256/game-of-thrones-7x01-dragonstone", - name: "Dragonstone", - season: 7, - number: 1, - airdate: "2017-07-16", - airtime: "21:00", - airstamp: "2017-07-17T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/120/302038.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/120/302038.jpg", - }, - summary: - "

Jon organizes the defense of the North. Cersei tries to even the odds. Daenerys comes home.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/937256", - }, - }, - }, - { - id: 1221410, - url: - "http://www.tvmaze.com/episodes/1221410/game-of-thrones-7x02-stormborn", - name: "Stormborn", - season: 7, - number: 2, - airdate: "2017-07-23", - airtime: "21:00", - airstamp: "2017-07-24T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/120/302047.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/120/302047.jpg", - }, - summary: - "

Daenerys receives an unexpected visitor. Jon faces a revolt. Tyrion plans the conquest of Westeros.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1221410", - }, - }, - }, - { - id: 1221411, - url: - "http://www.tvmaze.com/episodes/1221411/game-of-thrones-7x03-the-queens-justice", - name: "The Queen's Justice", - season: 7, - number: 3, - airdate: "2017-07-30", - airtime: "21:00", - airstamp: "2017-07-31T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/122/306938.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/122/306938.jpg", - }, - summary: - "

Daenerys holds court. Cersei returns a gift. Jaime learns from his mistakes.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1221411", - }, - }, - }, - { - id: 1221412, - url: - "http://www.tvmaze.com/episodes/1221412/game-of-thrones-7x04-the-spoils-of-war", - name: "The Spoils of War", - season: 7, - number: 4, - airdate: "2017-08-06", - airtime: "21:00", - airstamp: "2017-08-07T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/123/307677.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/123/307677.jpg", - }, - summary: - "

Arya gets to the final destination. Daenerys takes it upon herself to strike back.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1221412", - }, - }, - }, - { - id: 1221413, - url: - "http://www.tvmaze.com/episodes/1221413/game-of-thrones-7x05-eastwatch", - name: "Eastwatch", - season: 7, - number: 5, - airdate: "2017-08-13", - airtime: "21:00", - airstamp: "2017-08-14T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/124/310839.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/124/310839.jpg", - }, - summary: - "

Daenerys demands loyalty from the surviving Lannister soldiers; Jon heeds Bran's warning about White Walkers on the move; Cersei vows to vanquish anyone or anything that stands in her way.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1221413", - }, - }, - }, - { - id: 1221414, - url: - "http://www.tvmaze.com/episodes/1221414/game-of-thrones-7x06-beyond-the-wall", - name: "Beyond the Wall", - season: 7, - number: 6, - airdate: "2017-08-20", - airtime: "21:00", - airstamp: "2017-08-21T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/125/312651.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/125/312651.jpg", - }, - summary: - "

Jon's mission continues north of the wall, but the odds against his ragged band of misfits may be greater than he imagined.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1221414", - }, - }, - }, - { - id: 1221415, - url: - "http://www.tvmaze.com/episodes/1221415/game-of-thrones-7x07-the-dragon-and-the-wolf", - name: "The Dragon and the Wolf", - season: 7, - number: 7, - airdate: "2017-08-27", - airtime: "21:00", - airstamp: "2017-08-28T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/125/314502.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/125/314502.jpg", - }, - summary: - "

Cersei sits on the Iron Throne; Daenerys sails across the Narrow Sea; Jon Snow is King in the North, and winter is finally here.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1221415", - }, - }, - }, - { - id: 1590943, - url: - "http://www.tvmaze.com/episodes/1590943/game-of-thrones-8x01-winterfell", - name: "Winterfell", - season: 8, - number: 1, - airdate: "2019-04-14", - airtime: "21:00", - airstamp: "2019-04-15T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/191/479477.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/191/479477.jpg", - }, - summary: - "

Arriving at Winterfell, Jon and Daenerys struggle to unite a divided North. Jon gets some big news.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1590943", - }, - }, - }, - { - id: 1623964, - url: - "http://www.tvmaze.com/episodes/1623964/game-of-thrones-8x02-a-knight-of-the-seven-kingdoms", - name: "A Knight of the Seven Kingdoms", - season: 8, - number: 2, - airdate: "2019-04-21", - airtime: "21:00", - airstamp: "2019-04-22T01:00:00+00:00", - runtime: 60, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/192/482451.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/192/482451.jpg", - }, - summary: - "

Jaime faces judgement and Winterfell prepares for the battle to come.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1623964", - }, - }, - }, - { - id: 1623965, - url: - "http://www.tvmaze.com/episodes/1623965/game-of-thrones-8x03-the-long-night", - name: "The Long Night", - season: 8, - number: 3, - airdate: "2019-04-28", - airtime: "21:00", - airstamp: "2019-04-29T01:00:00+00:00", - runtime: 90, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/192/482465.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/192/482465.jpg", - }, - summary: "

Winterfell fights the Army of the Dead.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1623965", - }, - }, - }, - { - id: 1623966, - url: - "http://www.tvmaze.com/episodes/1623966/game-of-thrones-8x04-the-last-of-the-starks", - name: "The Last of the Starks", - season: 8, - number: 4, - airdate: "2019-05-05", - airtime: "21:00", - airstamp: "2019-05-06T01:00:00+00:00", - runtime: 78, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/195/487839.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/195/487839.jpg", - }, - summary: - "

The survivors plan their next steps; Cersei makes a power move.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1623966", - }, - }, - }, - { - id: 1623967, - url: - "http://www.tvmaze.com/episodes/1623967/game-of-thrones-8x05-the-bells", - name: "The Bells", - season: 8, - number: 5, - airdate: "2019-05-12", - airtime: "21:00", - airstamp: "2019-05-13T01:00:00+00:00", - runtime: 79, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/196/491994.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/196/491994.jpg", - }, - summary: - "

Varys betrays his queen, and Daenerys brings her forces to King's Landing.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1623967", - }, - }, - }, - { - id: 1623968, - url: - "http://www.tvmaze.com/episodes/1623968/game-of-thrones-8x06-the-iron-throne", - name: "The Iron Throne", - season: 8, - number: 6, - airdate: "2019-05-19", - airtime: "21:00", - airstamp: "2019-05-20T01:00:00+00:00", - runtime: 80, - image: { - medium: - "http://static.tvmaze.com/uploads/images/medium_landscape/198/495648.jpg", - original: - "http://static.tvmaze.com/uploads/images/original_untouched/198/495648.jpg", - }, - summary: - "

In the aftermath of the devastating attack on King's Landing, Daenerys must face the survivors.

", - _links: { - self: { - href: "http://api.tvmaze.com/episodes/1623968", - }, - }, - }, - ]; -} From bba38d45343e3a59ff6776b8e25dfe07d7d35939 Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Mon, 10 Aug 2026 07:41:45 +0100 Subject: [PATCH 13/24] the 400 commit.html --- index.html | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index d9b6734..fdfe85e 100644 --- a/index.html +++ b/index.html @@ -4,18 +4,25 @@ - TV Show Project | Toluwalase Tiamiyu (TTiamiyu) + TV Show Project | Ogbemi Mene (meneogbemi42-bit) -
-
+
+ + + + +
+
- - From 762756725e5635b42c11dacd72e7edf78d7dfef2 Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Mon, 10 Aug 2026 07:42:13 +0100 Subject: [PATCH 14/24] modify Script.js --- script.js | 197 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 123 insertions(+), 74 deletions(-) diff --git a/script.js b/script.js index 4bd2396..a6e07e7 100644 --- a/script.js +++ b/script.js @@ -1,123 +1,172 @@ +// Global State & Cache +let allShows = []; let allEpisodes = []; +const showsCache = {}; // Cache episodes by show ID to enforce the single-fetch rule async function setup() { - addTvmazeAttribution(); - showLoading(); + const rootElem = document.getElementById("root"); + rootElem.innerHTML = `

Loading shows, please wait...

`; - try { - const response = await fetch("https://api.tvmaze.com/shows/82/episodes"); + // Wire event listeners once on load + const showSelect = document.getElementById("show-select"); + const searchInput = document.getElementById("search-input"); + const episodeSelect = document.getElementById("episode-select"); + + if (showSelect) showSelect.addEventListener("change", handleShowSelect); + if (searchInput) searchInput.addEventListener("input", handleSearch); + if (episodeSelect) episodeSelect.addEventListener("change", handleSelect); + try { + // 1. Fetch shows list + const response = await fetch("https://api.tvmaze.com/shows"); if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + throw new Error(`Failed to load shows (${response.status})`); } - allEpisodes = await response.json(); + const rawShows = await response.json(); + + // 2. Sort shows alphabetically (case-insensitive) + allShows = rawShows.sort((a, b) => + a.name.localeCompare(b.name, undefined, { sensitivity: "accent" }), + ); - // Clear loading message and build UI - const rootElem = document.getElementById("root"); - rootElem.innerHTML = ""; + // 3. Populate Show Dropdown + populateShowDropdown(allShows); - createControls(); - makePageForEpisodes(allEpisodes); + // 4. Load initial show (e.g., Game of Thrones - ID 82, or the first show in list) + const defaultShowId = allShows.find((show) => show.id === 82) + ? 82 + : allShows[0].id; + + showSelect.value = defaultShowId; + await loadEpisodesForShow(defaultShowId); } catch (error) { - showError("Failed to load episode data. Please try again later."); + rootElem.innerHTML = ` +
+

Unable to load shows

+

Error: ${error.message}. Please check your connection and refresh.

+
+ `; } } -function showLoading() { - const rootElem = document.getElementById("root"); - rootElem.innerHTML = ` -
-
-

Loading episodes, please wait...

-
- `; -} +// Populate Show Selector +function populateShowDropdown(shows) { + const select = document.getElementById("show-select"); + if (!select) return; -function showError(message) { - const rootElem = document.getElementById("root"); - rootElem.innerHTML = ` -
-

Something went wrong

-

${message}

-
- `; + select.innerHTML = ""; // Clear options + + shows.forEach((show) => { + const option = document.createElement("option"); + option.value = show.id; + option.textContent = show.name; + select.appendChild(option); + }); } -function createControls() { +// Fetch or retrieve episodes from cache +async function loadEpisodesForShow(showId) { const rootElem = document.getElementById("root"); + rootElem.innerHTML = `

Loading episodes...

`; + + // Reset controls + const searchInput = document.getElementById("search-input"); + if (searchInput) searchInput.value = ""; - const controlsContainer = document.createElement("div"); - controlsContainer.className = "controls-container"; + try { + // Check if show episodes are already cached + if (showsCache[showId]) { + allEpisodes = showsCache[showId]; + } else { + // Fetch ONCE and cache + const response = await fetch( + `https://api.tvmaze.com/shows/${showId}/episodes`, + ); + if (!response.ok) { + throw new Error(`Failed to load episodes (${response.status})`); + } + allEpisodes = await response.json(); + showsCache[showId] = allEpisodes; // Cache in memory + } + + // Populate episode controls & display UI + populateSelectDropdown(allEpisodes); + makePageForEpisodes(allEpisodes); + updateSearchCount(allEpisodes.length, allEpisodes.length); + } catch (error) { + rootElem.innerHTML = ` +
+

Unable to load episodes

+

Error: ${error.message}. Please try selecting another show.

+
+ `; + } +} + +// Show Select Handler +async function handleShowSelect(event) { + const selectedShowId = event.target.value; + if (!selectedShowId) return; + await loadEpisodesForShow(selectedShowId); +} - const episodeSelect = document.createElement("select"); - episodeSelect.id = "episode-select"; +// Populate Episode Selector +function populateSelectDropdown(episodes) { + const select = document.getElementById("episode-select"); + if (!select) return; - const defaultOption = document.createElement("option"); - defaultOption.value = "ALL"; - defaultOption.textContent = "All Episodes"; - episodeSelect.appendChild(defaultOption); + select.innerHTML = ''; - allEpisodes.forEach((episode) => { + episodes.forEach((episode) => { const option = document.createElement("option"); option.value = episode.id; const code = formatEpisodeCode(episode.season, episode.number); option.textContent = `${code} - ${episode.name}`; - episodeSelect.appendChild(option); + select.appendChild(option); }); +} - const searchInput = document.createElement("input"); - searchInput.type = "text"; - searchInput.id = "search-input"; - searchInput.placeholder = "Search episodes..."; +// Live Search Handler +function handleSearch(event) { + const searchTerm = event.target.value.toLowerCase().trim(); - const countDisplay = document.createElement("span"); - countDisplay.id = "search-count"; + const episodeSelect = document.getElementById("episode-select"); + if (episodeSelect) episodeSelect.value = "ALL"; - controlsContainer.appendChild(episodeSelect); - controlsContainer.appendChild(searchInput); - controlsContainer.appendChild(countDisplay); + const filteredEpisodes = allEpisodes.filter((episode) => { + const nameMatches = episode.name.toLowerCase().includes(searchTerm); + const summaryMatches = episode.summary + ? episode.summary.toLowerCase().includes(searchTerm) + : false; - rootElem.parentNode.insertBefore(controlsContainer, rootElem); + return nameMatches || summaryMatches; + }); - searchInput.addEventListener("input", handleSearch); - episodeSelect.addEventListener("change", handleSelect); + makePageForEpisodes(filteredEpisodes); + updateSearchCount(filteredEpisodes.length, allEpisodes.length); } +// Episode Dropdown Selector Handler function handleSelect(event) { const selectedId = event.target.value; - const searchInput = document.getElementById("search-input"); + const searchInput = document.getElementById("search-input"); if (searchInput) searchInput.value = ""; if (selectedId === "ALL") { makePageForEpisodes(allEpisodes); + updateSearchCount(allEpisodes.length, allEpisodes.length); } else { const selectedEpisode = allEpisodes.filter( (episode) => String(episode.id) === String(selectedId), ); makePageForEpisodes(selectedEpisode); + updateSearchCount(selectedEpisode.length, allEpisodes.length); } } -function handleSearch(event) { - const searchTerm = event.target.value.toLowerCase().trim(); - const episodeSelect = document.getElementById("episode-select"); - - if (episodeSelect) episodeSelect.value = "ALL"; - - const filteredEpisodes = allEpisodes.filter((episode) => { - const nameMatches = episode.name.toLowerCase().includes(searchTerm); - const summaryMatches = (episode.summary || "") - .toLowerCase() - .includes(searchTerm); - - return nameMatches || summaryMatches; - }); - - makePageForEpisodes(filteredEpisodes); -} - +// Update Search Counter function updateSearchCount(matchCount, totalCount) { const countDisplay = document.getElementById("search-count"); if (countDisplay) { @@ -125,15 +174,16 @@ function updateSearchCount(matchCount, totalCount) { } } +// Helper: Format S01E01 function formatEpisodeCode(season, number) { const paddedSeason = String(season).padStart(2, "0"); const paddedNumber = String(number).padStart(2, "0"); return `S${paddedSeason}E${paddedNumber}`; } +// Render Episodes Page function makePageForEpisodes(episodeList) { const rootElem = document.getElementById("root"); - rootElem.innerHTML = ""; const container = document.createElement("div"); @@ -164,8 +214,7 @@ function makePageForEpisodes(episodeList) { }); rootElem.appendChild(container); - - updateSearchCount(episodeList.length, allEpisodes.length); + addTvmazeAttribution(); } function addTvmazeAttribution() { From 4da9377c4071bf9eb8516e2880bb74ea8d8eed52 Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Mon, 10 Aug 2026 07:42:35 +0100 Subject: [PATCH 15/24] css styling --- style.css | 64 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/style.css b/style.css index 684f194..74b4ee7 100644 --- a/style.css +++ b/style.css @@ -23,8 +23,9 @@ body { } /* ================================ - 2. Controls Container (Top Bar) + 2. Controls Container (Top Header Bar) ================================ */ +.controls-header, .controls-container { position: sticky; top: 0; @@ -34,13 +35,15 @@ body { flex-wrap: wrap; align-items: center; justify-content: space-between; - gap: 15px; + gap: 12px; padding: 16px 24px; margin-bottom: 24px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); border-radius: 8px; } +.controls-header select, +.controls-header input[type="text"], .controls-container select, .controls-container input[type="text"] { padding: 10px 14px; @@ -48,12 +51,15 @@ body { border: 1px solid #ccc; border-radius: 6px; outline: none; + background-color: #fff; transition: border-color 0.2s ease, box-shadow 0.2s ease; - flex: 1 1 220px; - /* Responsive sizing */ - max-width: 350px; + flex: 1 1 200px; + /* Responsive flex sizing for 3 control elements */ + max-width: 280px; } +.controls-header select:focus, +.controls-header input[type="text"]:focus, .controls-container select:focus, .controls-container input[type="text"]:focus { border-color: #0066cc; @@ -68,7 +74,40 @@ body { } /* ================================ - 3. Episodes Grid + 3. Loading & Error Feedback States + ================================ */ +.loading-state { + text-align: center; + font-size: 1.2rem; + font-weight: 600; + color: #0066cc; + padding: 60px 20px; +} + +.error-banner { + background-color: #fdf2f2; + border: 1px solid #f8b4b4; + border-left: 5px solid #e53e3e; + border-radius: 8px; + padding: 20px 24px; + margin: 20px auto; + max-width: 800px; + color: #9b2c2c; +} + +.error-banner h2 { + font-size: 1.25rem; + margin-bottom: 8px; + color: #c53030; +} + +.error-banner p { + font-size: 0.95rem; + margin: 0; +} + +/* ================================ + 4. Episodes Grid ================================ */ .episodes-container { display: grid; @@ -78,7 +117,7 @@ body { } /* ================================ - 4. Episode Card + 5. Episode Card ================================ */ .episode-card { background-color: #ffffff; @@ -129,7 +168,7 @@ body { } /* ================================ - 5. Footer (TVMaze Attribution) + 6. Footer (TVMaze Attribution) ================================ */ #tvmaze-attribution { text-align: center; @@ -151,17 +190,22 @@ body { } /* ================================ - 6. Mobile Responsiveness + 7. Mobile Responsiveness ================================ */ -@media (max-width: 600px) { +@media (max-width: 768px) { + + .controls-header, .controls-container { flex-direction: column; align-items: stretch; } + .controls-header select, + .controls-header input[type="text"], .controls-container select, .controls-container input[type="text"] { max-width: 100%; + width: 100%; } #search-count { From e364e5a79666455b0c165fba008dd8fd90f6540a Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Mon, 10 Aug 2026 08:12:59 +0100 Subject: [PATCH 16/24] commited changes --- index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index fdfe85e..20e5186 100644 --- a/index.html +++ b/index.html @@ -21,7 +21,9 @@ -
+
+ +
From 36416c6cd201f79217c11edeed52aa28bee71c48 Mon Sep 17 00:00:00 2001 From: Ogbemi Mene Date: Mon, 10 Aug 2026 09:01:48 +0100 Subject: [PATCH 17/24] Save local changes before pulling --- .github/FUNDING.yml | 26 +- .github/ISSUE_TEMPLATE/config.yml | 28 +- .github/ISSUE_TEMPLATE/pd-assignment.yml | 118 ++--- .github/ISSUE_TEMPLATE/tech-ed-assignment.yml | 160 +++--- .github/pull_request_template.md | 64 +-- HOW_TO_REVIEW.md | 132 ++--- contributing.md | 50 +- index.html | 60 +-- levels/Reviewers-All-Requirements.md | 102 ++-- levels/level-0.md | 80 +-- levels/level-100.md | 50 +- levels/level-200.md | 172 +++---- levels/level-300.md | 78 +-- levels/level-400.md | 122 ++--- levels/level-500.md | 142 +++--- levels/level-999.md | 44 +- script.js | 462 +++++++++--------- style.css | 426 ++++++++-------- 18 files changed, 1158 insertions(+), 1158 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index b34cfcf..335da9a 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,13 +1,13 @@ -# These are supported funding model platforms - -github: CodeYourFuture -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry -custom: https://codeyourfuture.io/donate +# These are supported funding model platforms + +github: CodeYourFuture +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: https://codeyourfuture.io/donate diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 5fe8ffd..3e43fc2 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,14 +1,14 @@ -blank_issues_enabled: false -contact_links: - - name: CYF - url: contact@codeyourfuture.io - about: Please report serious issues here. - - name: Join CYF - url: https://codeyourfuture.io/volunteers/ - about: Join CYF here - - name: CYF Slack - url: codeyourfuture.slack.com - about: Come to #cyf-syllabus-tech and chat - - name: CYF Tech Ed - url: https://github.com/orgs/CodeYourFuture/teams/mentors - about: CYF mentors on Github +blank_issues_enabled: false +contact_links: + - name: CYF + url: contact@codeyourfuture.io + about: Please report serious issues here. + - name: Join CYF + url: https://codeyourfuture.io/volunteers/ + about: Join CYF here + - name: CYF Slack + url: codeyourfuture.slack.com + about: Come to #cyf-syllabus-tech and chat + - name: CYF Tech Ed + url: https://github.com/orgs/CodeYourFuture/teams/mentors + about: CYF mentors on Github diff --git a/.github/ISSUE_TEMPLATE/pd-assignment.yml b/.github/ISSUE_TEMPLATE/pd-assignment.yml index c8bd229..3c739f7 100644 --- a/.github/ISSUE_TEMPLATE/pd-assignment.yml +++ b/.github/ISSUE_TEMPLATE/pd-assignment.yml @@ -1,59 +1,59 @@ -name: PD Coursework -description: Assign a piece of PD coursework -title: "[PD] " -labels: [PD, 🏝 Priority Stretch, 🐇 Size Small] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to assign this coursework! - - type: input - attributes: - label: Coursework content - validations: - required: true - - type: input - attributes: - label: Estimated time in hours - description: (PD has max 4 per week total) - validations: - required: true - - type: textarea - attributes: - label: What is the purpose of this assignment? - description: Clearly explain the purpose of this assignment and how trainees can evaluate this. - validations: - required: true - - type: textarea - attributes: - label: How to submit - description: State in clear steps how a trainee can submit this assignment. - placeholder: | - Copy the Google doc to your own Google Drive - Complete the work assigned - When you are ready, move your document to your class Drive - validations: - required: true - - type: textarea - attributes: - label: Anything else? - description: | - Links? References? Anything that will give more context - - Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. - - type: markdown - attributes: - value: | - **Thank you so much.** - - Please now complete this ticket by filling in the options on the sidebar. - - 1. Update labels - - priority -- is this coursework key, mandatory, or stretch? - - size -- help trainees plan their time with rough estimation - 2. Add to project backlog - - add to the project named the same as this repo - - fill in custom fields -- priority, size, hours, week -- to match this issue - - Once your ticket is complete, you may like to check it out on the example project board attached to this repo. - This is so you understand how trainees will use your work. +name: PD Coursework +description: Assign a piece of PD coursework +title: "[PD] <title>" +labels: [PD, 🏝 Priority Stretch, 🐇 Size Small] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to assign this coursework! + - type: input + attributes: + label: Coursework content + validations: + required: true + - type: input + attributes: + label: Estimated time in hours + description: (PD has max 4 per week total) + validations: + required: true + - type: textarea + attributes: + label: What is the purpose of this assignment? + description: Clearly explain the purpose of this assignment and how trainees can evaluate this. + validations: + required: true + - type: textarea + attributes: + label: How to submit + description: State in clear steps how a trainee can submit this assignment. + placeholder: | + Copy the Google doc to your own Google Drive + Complete the work assigned + When you are ready, move your document to your class Drive + validations: + required: true + - type: textarea + attributes: + label: Anything else? + description: | + Links? References? Anything that will give more context + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + - type: markdown + attributes: + value: | + **Thank you so much.** + + Please now complete this ticket by filling in the options on the sidebar. + + 1. Update labels + - priority -- is this coursework key, mandatory, or stretch? + - size -- help trainees plan their time with rough estimation + 2. Add to project backlog + - add to the project named the same as this repo + - fill in custom fields -- priority, size, hours, week -- to match this issue + + Once your ticket is complete, you may like to check it out on the example project board attached to this repo. + This is so you understand how trainees will use your work. diff --git a/.github/ISSUE_TEMPLATE/tech-ed-assignment.yml b/.github/ISSUE_TEMPLATE/tech-ed-assignment.yml index 1bdba29..bb0a11b 100644 --- a/.github/ISSUE_TEMPLATE/tech-ed-assignment.yml +++ b/.github/ISSUE_TEMPLATE/tech-ed-assignment.yml @@ -1,80 +1,80 @@ -name: Tech Ed Coursework -description: Assign a piece of technical coursework -title: "[TECH ED] <title>" -labels: [Tech Ed, 🏕 Priority Mandatory, 🐂 Size Medium] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to assign this coursework! - - To support our trainees with planning and prioritising their own learning journey, we want our coursework assignments to be more informative. - We don't just want to tell them what to do, we want to tell them stuff like: - - why we are doing it - - what it's "for" (problem-solving, debugging, etc) - - how long they should spend on it, maximum - - how to get help - - how to review it with answers - - how to get it reviewed from mentors and peers - - type: input - attributes: - label: Link to the coursework - validations: - required: true - - type: textarea - attributes: - label: Why are we doing this? - description: Clearly explain the purpose of this assignment - validations: - required: true - - type: input - attributes: - label: Maximum time in hours - description: (Tech has max 16 per week total) - validations: - required: true - - type: textarea - attributes: - label: How to get help - description: State simply how trainees can get help with this assignment - placeholder: | - Share your blockers in your class channel - https://syllabus.codeyourfuture.io/guides/asking-questions - - type: textarea - attributes: - label: How to submit - description: State in clear steps how a trainee can submit this assignment. - placeholder: | - Fork the repo to your own GitHub account - Make regular small commits with clear messages - When you are ready, open a Pull Request to the CYF repo - Make sure you fill in the PR template provided - validations: - required: true - - type: textarea - attributes: - label: How to review - description: How to get code review and how to self-review - - type: textarea - attributes: - label: Anything else? - description: | - Links? References? Anything that will give more context - - Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. - - type: markdown - attributes: - value: | - **Thank you so much.** - - Please now complete this ticket by filling in the options on the sidebar. - - 1. Update labels - - priority -- is this coursework key, mandatory, or stretch? Pick one. - - size -- help trainees plan their time with rough estimation. Pick one - - topics -- add all that seem relevant to you. - 2. Add to milestone - - week 1,2,3,4 - - Once your ticket is complete, you may like to add it to the example project board attached to this repo. - This is so you understand how trainees will use your work. Nobody has built a board copier yet, so trainees will be doing this step themselves. +name: Tech Ed Coursework +description: Assign a piece of technical coursework +title: "[TECH ED] <title>" +labels: [Tech Ed, 🏕 Priority Mandatory, 🐂 Size Medium] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to assign this coursework! + + To support our trainees with planning and prioritising their own learning journey, we want our coursework assignments to be more informative. + We don't just want to tell them what to do, we want to tell them stuff like: + - why we are doing it + - what it's "for" (problem-solving, debugging, etc) + - how long they should spend on it, maximum + - how to get help + - how to review it with answers + - how to get it reviewed from mentors and peers + - type: input + attributes: + label: Link to the coursework + validations: + required: true + - type: textarea + attributes: + label: Why are we doing this? + description: Clearly explain the purpose of this assignment + validations: + required: true + - type: input + attributes: + label: Maximum time in hours + description: (Tech has max 16 per week total) + validations: + required: true + - type: textarea + attributes: + label: How to get help + description: State simply how trainees can get help with this assignment + placeholder: | + Share your blockers in your class channel + https://syllabus.codeyourfuture.io/guides/asking-questions + - type: textarea + attributes: + label: How to submit + description: State in clear steps how a trainee can submit this assignment. + placeholder: | + Fork the repo to your own GitHub account + Make regular small commits with clear messages + When you are ready, open a Pull Request to the CYF repo + Make sure you fill in the PR template provided + validations: + required: true + - type: textarea + attributes: + label: How to review + description: How to get code review and how to self-review + - type: textarea + attributes: + label: Anything else? + description: | + Links? References? Anything that will give more context + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + - type: markdown + attributes: + value: | + **Thank you so much.** + + Please now complete this ticket by filling in the options on the sidebar. + + 1. Update labels + - priority -- is this coursework key, mandatory, or stretch? Pick one. + - size -- help trainees plan their time with rough estimation. Pick one + - topics -- add all that seem relevant to you. + 2. Add to milestone + - week 1,2,3,4 + + Once your ticket is complete, you may like to add it to the example project board attached to this repo. + This is so you understand how trainees will use your work. Nobody has built a board copier yet, so trainees will be doing this step themselves. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e3ae612..01d0b3a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,32 +1,32 @@ -<!-- - -You must title your PR like this: - -REGION | COHORT_NAME | FIRST_NAME LAST_NAME | SPRINT NUM | PROJECT NAME - -For example, - -LONDON | ITP-May-26 | Jane Doe | Sprint 4 | Project TV Show - -Complete the task list below this message. -If your PR is rejected, check the task list. - ---> - -## Learners, PR Template - -Self checklist - -- [ ] I have committed my files one by one, on purpose, and for a reason -- [ ] I have titled my PR with REGION | COHORT_NAME | FIRST_NAME LAST_NAME | SPRINT NUM | PROJECT NAME -- [ ] I have tested my changes -- [ ] My changes follow the [style guide](https://syllabus.codeyourfuture.io/guides/code-style-guide/) -- [ ] My changes meet the [requirements](./README.md) of this task - -## Changelist - -Briefly explain your PR. - -## Questions - -Ask any questions you have for your reviewer. +<!-- + +You must title your PR like this: + +REGION | COHORT_NAME | FIRST_NAME LAST_NAME | SPRINT NUM | PROJECT NAME + +For example, + +LONDON | ITP-May-26 | Jane Doe | Sprint 4 | Project TV Show + +Complete the task list below this message. +If your PR is rejected, check the task list. + +--> + +## Learners, PR Template + +Self checklist + +- [ ] I have committed my files one by one, on purpose, and for a reason +- [ ] I have titled my PR with REGION | COHORT_NAME | FIRST_NAME LAST_NAME | SPRINT NUM | PROJECT NAME +- [ ] I have tested my changes +- [ ] My changes follow the [style guide](https://syllabus.codeyourfuture.io/guides/code-style-guide/) +- [ ] My changes meet the [requirements](./README.md) of this task + +## Changelist + +Briefly explain your PR. + +## Questions + +Ask any questions you have for your reviewer. diff --git a/HOW_TO_REVIEW.md b/HOW_TO_REVIEW.md index 1afda18..8f78199 100644 --- a/HOW_TO_REVIEW.md +++ b/HOW_TO_REVIEW.md @@ -1,66 +1,66 @@ -<!-- -Do not edit this file. -Make a change to the template and then pull changes -Repo: https://github.com/CodeYourFuture/Module-Template ---> - -# Everyone reviews code at CYF - -Mentors and trainees all review code, and collaborate on improving code quality. We are all helping each other to talk, write, and think about code more clearly. - -We are not reviewing code as if we were to merge this PR into production; we are opening a technical conversation for the purpose of insight and development. - -## Key points: - -1. Ask questions instead of making statements: - - **YES:** "Is there another element you could use to group a set of fields in a form? Why might someone use a different element in a form?" - **NO:** "Use fieldset not divs" - -2. Encourage simplicity, clarity, and precision: - - **YES** "There are 15,0000 files in this changelist. Which files should be reviewed?" - **NO** "It doesn't matter; I can try to figure out what you meant." - -3. Respect everyone's work and time: - - **YES** "I think there's some more to do here. Thanks for sharing where you're up to." - **NO** "This is rubbish. Try harder." - -## Labels - -Reviewers, please add labels (provided) to the PR once you've reviewed. This helps to focus the trainee on the areas they should work on, and gives an overview for mentors on what the whole cohort needs to work on. - -## Solutions - -### Where to find solutions? - -You can find the solutions for the module on the `solutions` branch. - -### Solutions branch - -The solutions branch typically contains: - -#### Sample solutions - -Solutions are example answers not the only correct answers. - -#### Common responses guides. - -Everyone is invited to contribute commonly encountered problems, mistakes, misunderstandings, and mental-model errors to our common responses documents. - -Use these resources to inform your code review, get unstuck, and improve your understanding. - -## Guides - -Here's a detailed checklist of the sorts of things we should check code for: - -https://syllabus.codeyourfuture.io/guides/marking-guide - -Here's a detailed style guide to help us all write clear, high quality code: - -https://syllabus.codeyourfuture.io/guides/code-style-guide - -Here's some help with giving good feedback during code review: - -https://teachertraining.codeyourfuture.io/tasks/code-review +<!-- +Do not edit this file. +Make a change to the template and then pull changes +Repo: https://github.com/CodeYourFuture/Module-Template +--> + +# Everyone reviews code at CYF + +Mentors and trainees all review code, and collaborate on improving code quality. We are all helping each other to talk, write, and think about code more clearly. + +We are not reviewing code as if we were to merge this PR into production; we are opening a technical conversation for the purpose of insight and development. + +## Key points: + +1. Ask questions instead of making statements: + + **YES:** "Is there another element you could use to group a set of fields in a form? Why might someone use a different element in a form?" + **NO:** "Use fieldset not divs" + +2. Encourage simplicity, clarity, and precision: + + **YES** "There are 15,0000 files in this changelist. Which files should be reviewed?" + **NO** "It doesn't matter; I can try to figure out what you meant." + +3. Respect everyone's work and time: + + **YES** "I think there's some more to do here. Thanks for sharing where you're up to." + **NO** "This is rubbish. Try harder." + +## Labels + +Reviewers, please add labels (provided) to the PR once you've reviewed. This helps to focus the trainee on the areas they should work on, and gives an overview for mentors on what the whole cohort needs to work on. + +## Solutions + +### Where to find solutions? + +You can find the solutions for the module on the `solutions` branch. + +### Solutions branch + +The solutions branch typically contains: + +#### Sample solutions + +Solutions are example answers not the only correct answers. + +#### Common responses guides. + +Everyone is invited to contribute commonly encountered problems, mistakes, misunderstandings, and mental-model errors to our common responses documents. + +Use these resources to inform your code review, get unstuck, and improve your understanding. + +## Guides + +Here's a detailed checklist of the sorts of things we should check code for: + +https://syllabus.codeyourfuture.io/guides/marking-guide + +Here's a detailed style guide to help us all write clear, high quality code: + +https://syllabus.codeyourfuture.io/guides/code-style-guide + +Here's some help with giving good feedback during code review: + +https://teachertraining.codeyourfuture.io/tasks/code-review diff --git a/contributing.md b/contributing.md index 6540972..a9e21c1 100644 --- a/contributing.md +++ b/contributing.md @@ -1,25 +1,25 @@ -<!-- -Do not edit this file. -Make a change to the template and then pull changes -Repo: https://github.com/CodeYourFuture/Module-Template ---> - -# How To Submit Your Coursework - -Use Git & Github to submit your coursework as a pull request. - -The Github Desktop cheatsheet will help you. - -[Github Desktop Cheatsheet](http://syllabus.codeyourfuture.io/git/cheatsheet) - -This module will help you submit your coursework. - -[Git](http://syllabus.codeyourfuture.io/git/index) - -## Questions & Help - -Contributing to a remote codebase is a necessary skill for a professional developer. Opening PRs is mandatory at CYF. It is part of the coursework. - -If you cannot submit your coursework you **must** post on Slack to get unblocked. - -[How to get help](./HOW-TO-GET-HELP.md) +<!-- +Do not edit this file. +Make a change to the template and then pull changes +Repo: https://github.com/CodeYourFuture/Module-Template +--> + +# How To Submit Your Coursework + +Use Git & Github to submit your coursework as a pull request. + +The Github Desktop cheatsheet will help you. + +[Github Desktop Cheatsheet](http://syllabus.codeyourfuture.io/git/cheatsheet) + +This module will help you submit your coursework. + +[Git](http://syllabus.codeyourfuture.io/git/index) + +## Questions & Help + +Contributing to a remote codebase is a necessary skill for a professional developer. Opening PRs is mandatory at CYF. It is part of the coursework. + +If you cannot submit your coursework you **must** post on Slack to get unblocked. + +[How to get help](./HOW-TO-GET-HELP.md) diff --git a/index.html b/index.html index 20e5186..7a0b76f 100644 --- a/index.html +++ b/index.html @@ -1,31 +1,31 @@ -<!DOCTYPE html> -<html lang="en"> - -<head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>TV Show Project | Ogbemi Mene (meneogbemi42-bit) - - - - - -
- - - - -
- -
- -
- - - - + + + + + + + TV Show Project | Toluwalase Tiamiyu (TTiamiyu) + + + + + +
+ + + + +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/levels/Reviewers-All-Requirements.md b/levels/Reviewers-All-Requirements.md index 36e72de..fa454e4 100644 --- a/levels/Reviewers-All-Requirements.md +++ b/levels/Reviewers-All-Requirements.md @@ -1,51 +1,51 @@ -There are the main requirements that a submitted project needs checked by reviewers. -Trainees should not need to refer to this document, and should focus on completing each level one at a time. - -1. It is deployed on GitHub pages or Netlify -1. The site must fetch data from an API at `TVMaze.com`, never a JSON file in the repo - 1. The page should state somewhere that the data has (originally) come from [TVMaze.com](https://tvmaze.com/), and link back to that site. - 1. During a visit to the website it should never fetch any URL more than once. (Check this using the dev tools network inspector) - 1. The site should indicate when data is loading. - 1. If an error occurred loading the data, notify the user on the page with a useful message (Not only in the console) -1. Listing Shows - When the site starts, present a listing of all shows ("shows listing") - 1. For each show, display at least: - 1. name - 1. image - 1. summary - 1. genres - 1. status - 1. rating - 1. runtime - 1. When a show name is clicked, it should: - 1. Fetch and present episodes from that show - 1. Hide the "shows listing" view - 1. Enable episode search / select (see below) - 1. Have a navigation link or button to enable the user to return to the "shows listing" - 1. When this is clicked, the episodes listing should be hidden - 1. Ensure that the search and selector controls still work correctly when you switch from shows listing to episodes listing and back -1. Listing Episodes - When a show is selected, all episodes must be on the page shown for that given show, with at least: - 1. The name of the episode - 1. The combined season number and episode number into a zero-padded episode code: `S02E07` is correct, `S2E7` is incorrect. - 1. The medium-sized image for the episode - 1. The summary text of the episode -1. Select Shows - a `select` element to your page so the user can choose a show. - 1. When the user first loads the page, use the fetched list of available shows, and add an entry to the drop-down per show. - 1. When a user selects a show, display the episodes for that show after fetching the episode list. - 1. The select must list shows in alphabetical order, case-insensitive. -1. Select Episodes - a `select` drop-down which lets the user jump quickly to a particular episode: - 1. The select options are updated whenever a new show is selected, and this select isn't used otherwise - 1. The select input should list all episodes in the format: "S01E01 - Episode Title" - 1. When the user makes a selection, they should be taken directly to that episode on the page -1. Search Shows - When a user types a search term into the search box: - 1. Only shows whose summary **OR** name contains the search term should be displayed - 1. The search should be case-**in**sensitive - 1. The display should update **immediately** after each keystroke changes the input - 1. Display how many shows match the current search - 1. If the search box is cleared, **all** shows should be shown -1. Search Episodes - When a user types a search term into the search box: - 1. Only episodes whose summary **OR** name contains the search term should be displayed - 1. The search should be case-**in**sensitive - 1. The display should update **immediately** after each keystroke changes the input - 1. Display how many episodes match the current search - 1. If the search box is cleared, **all** episodes should be shown - +There are the main requirements that a submitted project needs checked by reviewers. +Trainees should not need to refer to this document, and should focus on completing each level one at a time. + +1. It is deployed on GitHub pages or Netlify +1. The site must fetch data from an API at `TVMaze.com`, never a JSON file in the repo + 1. The page should state somewhere that the data has (originally) come from [TVMaze.com](https://tvmaze.com/), and link back to that site. + 1. During a visit to the website it should never fetch any URL more than once. (Check this using the dev tools network inspector) + 1. The site should indicate when data is loading. + 1. If an error occurred loading the data, notify the user on the page with a useful message (Not only in the console) +1. Listing Shows - When the site starts, present a listing of all shows ("shows listing") + 1. For each show, display at least: + 1. name + 1. image + 1. summary + 1. genres + 1. status + 1. rating + 1. runtime + 1. When a show name is clicked, it should: + 1. Fetch and present episodes from that show + 1. Hide the "shows listing" view + 1. Enable episode search / select (see below) + 1. Have a navigation link or button to enable the user to return to the "shows listing" + 1. When this is clicked, the episodes listing should be hidden + 1. Ensure that the search and selector controls still work correctly when you switch from shows listing to episodes listing and back +1. Listing Episodes - When a show is selected, all episodes must be on the page shown for that given show, with at least: + 1. The name of the episode + 1. The combined season number and episode number into a zero-padded episode code: `S02E07` is correct, `S2E7` is incorrect. + 1. The medium-sized image for the episode + 1. The summary text of the episode +1. Select Shows - a `select` element to your page so the user can choose a show. + 1. When the user first loads the page, use the fetched list of available shows, and add an entry to the drop-down per show. + 1. When a user selects a show, display the episodes for that show after fetching the episode list. + 1. The select must list shows in alphabetical order, case-insensitive. +1. Select Episodes - a `select` drop-down which lets the user jump quickly to a particular episode: + 1. The select options are updated whenever a new show is selected, and this select isn't used otherwise + 1. The select input should list all episodes in the format: "S01E01 - Episode Title" + 1. When the user makes a selection, they should be taken directly to that episode on the page +1. Search Shows - When a user types a search term into the search box: + 1. Only shows whose summary **OR** name contains the search term should be displayed + 1. The search should be case-**in**sensitive + 1. The display should update **immediately** after each keystroke changes the input + 1. Display how many shows match the current search + 1. If the search box is cleared, **all** shows should be shown +1. Search Episodes - When a user types a search term into the search box: + 1. Only episodes whose summary **OR** name contains the search term should be displayed + 1. The search should be case-**in**sensitive + 1. The display should update **immediately** after each keystroke changes the input + 1. Display how many episodes match the current search + 1. If the search box is cleared, **all** episodes should be shown + diff --git a/levels/level-0.md b/levels/level-0.md index 42e681e..acf9f46 100644 --- a/levels/level-0.md +++ b/levels/level-0.md @@ -1,41 +1,41 @@ -# Level 0 - -The goals of level 0 are to: -* Create a GitHub repository with the starter scaffolding in-place. -* Deploy it to **GitHub Pages**. -* Make sure whenever you push changes, your live site will be updated. - -## Fork your repository - -1. Go to https://github.com/CodeYourFuture/Project-TV-Show -2. Fork the repo to your account -3. Do not enable `Include all branches` - -## Deployment to GitHub Pages - -Follow the [CYF GitHub Pages Deployment Guide](https://curriculum.codeyourfuture.io/guides/deploying/ghpages/). - -> [!TIP] -> Deployment to **Netlify** is only required upon reaching **Level 500**. Using GitHub Pages until then helps conserve build credits during the development phase. - -## Get set up on your laptop - -1. Clone your repository. -2. Open your repository in VS Code. -3. Open the `index.html` page in Chrome. -4. Make sure in Chrome you can see the text "Got 73 episode(s)" in red. If you can't, something has gone wrong. -5. Edit `index.html` to include your name and GitHub username in the page title instead of "(My Name (My GitHub username))". - -## Push your changes - -1. Commit your changes to your `index.html` to your git repository (with a clear commit message). -2. Push your changes to your GitHub repository. -3. Check your GitHub Pages URL to ensure the changes are live. - -## Completion criteria - -You have completed level 0 when: -- [ ] You have forked the GitHub repository called `Project-TV-Show` into your account. -- [ ] The `index.html` page on your GitHub project contains your name and GitHub username. -- [ ] Your project is successfully deployed to **GitHub Pages**. +# Level 0 + +The goals of level 0 are to: +* Create a GitHub repository with the starter scaffolding in-place. +* Deploy it to **GitHub Pages**. +* Make sure whenever you push changes, your live site will be updated. + +## Fork your repository + +1. Go to https://github.com/CodeYourFuture/Project-TV-Show +2. Fork the repo to your account +3. Do not enable `Include all branches` + +## Deployment to GitHub Pages + +Follow the [CYF GitHub Pages Deployment Guide](https://curriculum.codeyourfuture.io/guides/deploying/ghpages/). + +> [!TIP] +> Deployment to **Netlify** is only required upon reaching **Level 500**. Using GitHub Pages until then helps conserve build credits during the development phase. + +## Get set up on your laptop + +1. Clone your repository. +2. Open your repository in VS Code. +3. Open the `index.html` page in Chrome. +4. Make sure in Chrome you can see the text "Got 73 episode(s)" in red. If you can't, something has gone wrong. +5. Edit `index.html` to include your name and GitHub username in the page title instead of "(My Name (My GitHub username))". + +## Push your changes + +1. Commit your changes to your `index.html` to your git repository (with a clear commit message). +2. Push your changes to your GitHub repository. +3. Check your GitHub Pages URL to ensure the changes are live. + +## Completion criteria + +You have completed level 0 when: +- [ ] You have forked the GitHub repository called `Project-TV-Show` into your account. +- [ ] The `index.html` page on your GitHub project contains your name and GitHub username. +- [ ] Your project is successfully deployed to **GitHub Pages**. - [ ] Your deployed project has your name and GitHub username in its title. \ No newline at end of file diff --git a/levels/level-100.md b/levels/level-100.md index a50ddce..4b368b8 100644 --- a/levels/level-100.md +++ b/levels/level-100.md @@ -1,25 +1,25 @@ -# Level 100 - -For level 100, we are going to display information about every episode of a single TV show. - -## Requirements - -1. All episodes must be shown -2. For each episode, _at least_ following must be displayed: - 1. The name of the episode - 2. The season number - 3. The episode number - 4. The medium-sized image for the episode - 5. The summary text of the episode -3. Combine season number and episode number into an **episode code**: - 1. Each part should be zero-padded to two digits. - 2. Example: `S02E07` would be the code for the 7th episode of the 2nd season. `S2E7` would be incorrect. -4. Your page should state somewhere that the data has (originally) come from [TVMaze.com](https://tvmaze.com/), and link back to that site (or the specific episode on that site). See [tvmaze.com/api#licensing](https://www.tvmaze.com/api#licensing). - -### Screenshot of minimal version - -Note: Provided your project meets the above requirements, it can **look** however you want. Do the simplest thing to begin with. - -Here is one example layout. - -![Screenshot of a website with cards showing per-episode data according to the requirements](example-screenshots/example-level-100.png) +# Level 100 + +For level 100, we are going to display information about every episode of a single TV show. + +## Requirements + +1. All episodes must be shown +2. For each episode, _at least_ following must be displayed: + 1. The name of the episode + 2. The season number + 3. The episode number + 4. The medium-sized image for the episode + 5. The summary text of the episode +3. Combine season number and episode number into an **episode code**: + 1. Each part should be zero-padded to two digits. + 2. Example: `S02E07` would be the code for the 7th episode of the 2nd season. `S2E7` would be incorrect. +4. Your page should state somewhere that the data has (originally) come from [TVMaze.com](https://tvmaze.com/), and link back to that site (or the specific episode on that site). See [tvmaze.com/api#licensing](https://www.tvmaze.com/api#licensing). + +### Screenshot of minimal version + +Note: Provided your project meets the above requirements, it can **look** however you want. Do the simplest thing to begin with. + +Here is one example layout. + +![Screenshot of a website with cards showing per-episode data according to the requirements](example-screenshots/example-level-100.png) diff --git a/levels/level-200.md b/levels/level-200.md index 89c28d3..32ab32e 100644 --- a/levels/level-200.md +++ b/levels/level-200.md @@ -1,86 +1,86 @@ -# Level 200 - -For level 200, you are not allowed to work on the same codebase as you worked on for level 100. - -Swap repos with a random person in your class. You may find it helpful to do this first step together: - -1. Go to _your_ copy of `Project-TV-Show` and click on the setting -2. Go to the "collaborators" section -3. Next "manage access", click "add people" -4. Type the username your partner -5. Click the green add button -6. Visit the URL for your _partner's_ project and accept the invitation to collaborate / check your email for the invite link -7. Clone your _partner's_ repository to your laptop, you will need to give it a different name, such as `Project-TV-Show-THEIR_NAME` - -Now, work individually to complete level 200: - -Look at their level 100 implementation. - -Compare their implementation to yours. Think: -1. How is it different? -2. What do you prefer about your implementation? -3. What do you prefer about their implementation? -4. What did you learn that you didn't know before? - -They should do the same with your repository. - -Have a discussion about your answers to these questions. In class, together you should give talk for 5 minutes about your conclusions. (Do this in small groups - we don't want to take all day). - -## Refactoring - -**Before implementing new features**, take some time to change the codebase you're going to build level 200 in. Change anything you think will make it easier to add more features. - -Some example ideas that you may want to think about: -1. Could any variables or functions have more clear names, to help you understand what they do? -2. Would [extracting functions](https://code.visualstudio.com/docs/editor/refactoring) help make some code easier to understand? - -Work in a new branch on your _partner's_ repo, making any changes you think are useful. -Then make a pull request to your _partner's_ main branch (take care not to PR to the CYF main branch yet). -Have them review, and when happy, merge your PR. - -![Git branch diagram for level200](branch-level200.png) - -![Select the partner repo in the dropdown when making a PR for level200](pr-level200.png) - - -## Adding new functionality - -Level 200 is all about being able to filter episodes. - -### Search - -Add a live search input which meets the following requirements: - -When a user types a search term into the search box: -1. Only episodes whose summary **OR** name contains the search term should be displayed -2. The search should be case-**in**sensitive -3. The display should update **immediately** after each keystroke changes the input -4. Display how many episodes match the current search -5. If the search box is cleared, **all** episodes should be shown - -Send a pull request to your partner's repo with this functionality. Have them review, and when happy, merge your PR. - -#### Screenshot of minimal version - -Note: Provided your project meets the above requirements, it can **look** however you want. - -Here is one example layout. - -![Screenshot of a website with a search term entered in the search box, and only matching episodes shown](example-screenshots/example-level-200-search.jpg) - -### Episode selector - -Add a `select` drop-down which lets the user jump quickly to a particular episode, with the following requirements: -1. The select input should list all episodes in the format: "S01E01 - Winter is Coming" -2. When the user makes a selection, they should be taken directly to that episode in the list -3. Bonus: if you prefer, when the select is used, ONLY show the selected episode. If you do this, be sure to provide a way for the user to see all episodes again. - -Send a pull request to your partner's repo with this functionality. Have them review, and when happy, merge your PR. - -#### Screenshot of minimal version - -Note: Provided your project meets the above requirements, it can **look** however you want. - -Here is one example layout. - -![Screenshot of a website with a drop-down listing all available episodes](example-screenshots/example-level-200-selector.jpg) +# Level 200 + +For level 200, you are not allowed to work on the same codebase as you worked on for level 100. + +Swap repos with a random person in your class. You may find it helpful to do this first step together: + +1. Go to _your_ copy of `Project-TV-Show` and click on the setting +2. Go to the "collaborators" section +3. Next "manage access", click "add people" +4. Type the username your partner +5. Click the green add button +6. Visit the URL for your _partner's_ project and accept the invitation to collaborate / check your email for the invite link +7. Clone your _partner's_ repository to your laptop, you will need to give it a different name, such as `Project-TV-Show-THEIR_NAME` + +Now, work individually to complete level 200: + +Look at their level 100 implementation. + +Compare their implementation to yours. Think: +1. How is it different? +2. What do you prefer about your implementation? +3. What do you prefer about their implementation? +4. What did you learn that you didn't know before? + +They should do the same with your repository. + +Have a discussion about your answers to these questions. In class, together you should give talk for 5 minutes about your conclusions. (Do this in small groups - we don't want to take all day). + +## Refactoring + +**Before implementing new features**, take some time to change the codebase you're going to build level 200 in. Change anything you think will make it easier to add more features. + +Some example ideas that you may want to think about: +1. Could any variables or functions have more clear names, to help you understand what they do? +2. Would [extracting functions](https://code.visualstudio.com/docs/editor/refactoring) help make some code easier to understand? + +Work in a new branch on your _partner's_ repo, making any changes you think are useful. +Then make a pull request to your _partner's_ main branch (take care not to PR to the CYF main branch yet). +Have them review, and when happy, merge your PR. + +![Git branch diagram for level200](branch-level200.png) + +![Select the partner repo in the dropdown when making a PR for level200](pr-level200.png) + + +## Adding new functionality + +Level 200 is all about being able to filter episodes. + +### Search + +Add a live search input which meets the following requirements: + +When a user types a search term into the search box: +1. Only episodes whose summary **OR** name contains the search term should be displayed +2. The search should be case-**in**sensitive +3. The display should update **immediately** after each keystroke changes the input +4. Display how many episodes match the current search +5. If the search box is cleared, **all** episodes should be shown + +Send a pull request to your partner's repo with this functionality. Have them review, and when happy, merge your PR. + +#### Screenshot of minimal version + +Note: Provided your project meets the above requirements, it can **look** however you want. + +Here is one example layout. + +![Screenshot of a website with a search term entered in the search box, and only matching episodes shown](example-screenshots/example-level-200-search.jpg) + +### Episode selector + +Add a `select` drop-down which lets the user jump quickly to a particular episode, with the following requirements: +1. The select input should list all episodes in the format: "S01E01 - Winter is Coming" +2. When the user makes a selection, they should be taken directly to that episode in the list +3. Bonus: if you prefer, when the select is used, ONLY show the selected episode. If you do this, be sure to provide a way for the user to see all episodes again. + +Send a pull request to your partner's repo with this functionality. Have them review, and when happy, merge your PR. + +#### Screenshot of minimal version + +Note: Provided your project meets the above requirements, it can **look** however you want. + +Here is one example layout. + +![Screenshot of a website with a drop-down listing all available episodes](example-screenshots/example-level-200-selector.jpg) diff --git a/levels/level-300.md b/levels/level-300.md index 9e0d9e4..437f1b8 100644 --- a/levels/level-300.md +++ b/levels/level-300.md @@ -1,39 +1,39 @@ -# Level 300 - -For level 300, you should switch back to your original codebase. - -You should have reviewed and merged your partner's level 200 changes through a PR to your own repo. - -Remember to pull the latest changes onto your local machine. - -Once again, compare their implementation to yours. Think: -1. How is it different? -2. What do you prefer about your implementation? -3. What do you prefer about their implementation? -4. What did you learn that you didn't know before? - -Have a discussion about your answers to these questions. In class, together, give a 3 minute talk about your conclusions. - -## Refactoring - -Like with level 200, feel free to change anything in your codebase which you think will make it easier to work with, or to build new features. - -Make a branch and pull request for yourself, and then have your partner review, making sure they understand the changes made. - -## Adding new functionality - -Level 300 is about getting rid of our static data from `episodes.js`, and instead using an API. - -### Requirements - -1. You must delete the `episodes.js` file from your repository. -2. Your website must still work the same, but by using a `fetch` request to https://api.tvmaze.com/shows/82/episodes. This URL should serve the exact same content as was returned by `getAllEpisodes` in `episodes.js`. -3. You must fetch this URL only _once_ per visit to your website. You should not re-fetch when someone searches, scrolls, or selects an episode from the drop-down. -4. If you don't have data yet, you should show something to tell the user to wait for the data. -5. If an error occurred loading the data, notify the user. - 1. Note: real users don't look in the console - `console.log` or `console.error` are not sufficient for this requirement. - 2. You will need to simulate an error to test this out yourself. - -### Documentation - -You can see that this endpoint has been documented here: https://www.tvmaze.com/api#show-episode-list +# Level 300 + +For level 300, you should switch back to your original codebase. + +You should have reviewed and merged your partner's level 200 changes through a PR to your own repo. + +Remember to pull the latest changes onto your local machine. + +Once again, compare their implementation to yours. Think: +1. How is it different? +2. What do you prefer about your implementation? +3. What do you prefer about their implementation? +4. What did you learn that you didn't know before? + +Have a discussion about your answers to these questions. In class, together, give a 3 minute talk about your conclusions. + +## Refactoring + +Like with level 200, feel free to change anything in your codebase which you think will make it easier to work with, or to build new features. + +Make a branch and pull request for yourself, and then have your partner review, making sure they understand the changes made. + +## Adding new functionality + +Level 300 is about getting rid of our static data from `episodes.js`, and instead using an API. + +### Requirements + +1. You must delete the `episodes.js` file from your repository. +2. Your website must still work the same, but by using a `fetch` request to https://api.tvmaze.com/shows/82/episodes. This URL should serve the exact same content as was returned by `getAllEpisodes` in `episodes.js`. +3. You must fetch this URL only _once_ per visit to your website. You should not re-fetch when someone searches, scrolls, or selects an episode from the drop-down. +4. If you don't have data yet, you should show something to tell the user to wait for the data. +5. If an error occurred loading the data, notify the user. + 1. Note: real users don't look in the console - `console.log` or `console.error` are not sufficient for this requirement. + 2. You will need to simulate an error to test this out yourself. + +### Documentation + +You can see that this endpoint has been documented here: https://www.tvmaze.com/api#show-episode-list diff --git a/levels/level-400.md b/levels/level-400.md index 60ddc9e..943d686 100644 --- a/levels/level-400.md +++ b/levels/level-400.md @@ -1,61 +1,61 @@ -# Level 400 - -For level 400, you should work in the repo of your partner from level 200. - -You do not need to re-add yourself as a collaborator. - -Remember to change to the right directory on your machine, and pull the latest changes from their main branch. - -Before writing any new code, look at their level 300 implementation. - -Compare their implementation to yours. Think: -1. How is it different? -2. What do you prefer about your implementation? -3. What do you prefer about their implementation? -4. What did you learn that you didn't know before? - -They should do the same with your repository. - -Have a discussion about your answers to these questions. In class, together you should give a 3 minute talk about your conclusions. - -## Refactoring - -Feel free to change anything in your codebase which you think will make it easier to work with, or to build new features. - -Make a branch and pull request for yourself, and then have your partner review, making sure they understand the changes made. - -## Adding new functionality - -Level 400 is about expanding beyond one TV show. - -Until now, your site has only showed information about the episode of one TV show. - -But TVmaze has information about lots of TV shows, all in the same format. - -We want to display any of them. - -### Requirements - -1. Add a `select` element to your page so the user can choose a show. -2. When the user first loads the page, make a `fetch` request to https://api.tvmaze.com/shows ([documentation](https://www.tvmaze.com/api#show-index)) to get a list of available shows, and add an entry to the drop-down per show. -3. When a user selects a show, display the episodes for that show, just like the earlier levels of this project. - - You will need to perform a `fetch` to get the episode list. -4. Make sure that your search and episode selector controls still work correctly when you change shows. -5. Your select must list shows in alphabetical order, case-insensitive. -6. During one user's visit to your website, you should never fetch any URL more than once. - -> [!NOTE] -> Be _careful_ when developing with fetch. By default, every time you make a small change to your app it will be restarted by live server - if you are fetching JSON on page load, the JSON will be downloaded again and again. These frequent HTTP requests may lead to the API permanently banning your IP address from further requests, or "throttling" it for some time. Worse, if they don't, they may cause performance issues for the API service we are using. - -Send a pull request to your partner's repo with this functionality. Have them review, and when happy, merge your PR. - -#### Screenshot of minimal version - -Note: Provided your project meets the above requirements, it can **look** however you want. - -Here is one example layout. - -![Screenshot of a website with a drop-down list with the show "Breaking Bad" selected](example-screenshots/example-level-400-1.jpg) - -![Screenshot of a website with a drop-down list showing multiple TV shows](example-screenshots/example-level-400-1.jpg) +# Level 400 + +For level 400, you should work in the repo of your partner from level 200. + +You do not need to re-add yourself as a collaborator. + +Remember to change to the right directory on your machine, and pull the latest changes from their main branch. + +Before writing any new code, look at their level 300 implementation. + +Compare their implementation to yours. Think: +1. How is it different? +2. What do you prefer about your implementation? +3. What do you prefer about their implementation? +4. What did you learn that you didn't know before? + +They should do the same with your repository. + +Have a discussion about your answers to these questions. In class, together you should give a 3 minute talk about your conclusions. + +## Refactoring + +Feel free to change anything in your codebase which you think will make it easier to work with, or to build new features. + +Make a branch and pull request for yourself, and then have your partner review, making sure they understand the changes made. + +## Adding new functionality + +Level 400 is about expanding beyond one TV show. + +Until now, your site has only showed information about the episode of one TV show. + +But TVmaze has information about lots of TV shows, all in the same format. + +We want to display any of them. + +### Requirements + +1. Add a `select` element to your page so the user can choose a show. +2. When the user first loads the page, make a `fetch` request to https://api.tvmaze.com/shows ([documentation](https://www.tvmaze.com/api#show-index)) to get a list of available shows, and add an entry to the drop-down per show. +3. When a user selects a show, display the episodes for that show, just like the earlier levels of this project. + + You will need to perform a `fetch` to get the episode list. +4. Make sure that your search and episode selector controls still work correctly when you change shows. +5. Your select must list shows in alphabetical order, case-insensitive. +6. During one user's visit to your website, you should never fetch any URL more than once. + +> [!NOTE] +> Be _careful_ when developing with fetch. By default, every time you make a small change to your app it will be restarted by live server - if you are fetching JSON on page load, the JSON will be downloaded again and again. These frequent HTTP requests may lead to the API permanently banning your IP address from further requests, or "throttling" it for some time. Worse, if they don't, they may cause performance issues for the API service we are using. + +Send a pull request to your partner's repo with this functionality. Have them review, and when happy, merge your PR. + +#### Screenshot of minimal version + +Note: Provided your project meets the above requirements, it can **look** however you want. + +Here is one example layout. + +![Screenshot of a website with a drop-down list with the show "Breaking Bad" selected](example-screenshots/example-level-400-1.jpg) + +![Screenshot of a website with a drop-down list showing multiple TV shows](example-screenshots/example-level-400-1.jpg) diff --git a/levels/level-500.md b/levels/level-500.md index 0c0e4f2..c3eadda 100644 --- a/levels/level-500.md +++ b/levels/level-500.md @@ -1,71 +1,71 @@ -# Level 500 - -For level 500, you should switch back to your original codebase. - -You should have reviewed and merged someone else's level 400 implementation. - -Once again, compare their implementation to yours. Think: - -1. How is it different? -2. What do you prefer about your implementation? -3. What do you prefer about their implementation? -4. What did you learn that you didn't know before? - -Have a discussion about your answers to these questions. In class, together you should give a 3 minute talk about your conclusions. - ---- - -### Technical Workflow - -**Best Practice: Use Feature Branches** -To maintain a clean workflow, follow these steps for Level 500: -* Once you have merged Level 400 and are satisfied with the changes, **create a new branch** (e.g., `feature/level-500`) to continue your development. -* After completing all Level 500 requirements on this branch, merge it into your main branch. -* **Important:** Test your changes thoroughly on GitHub Pages before moving to the final deployment. - ---- - -## Refactoring - -Feel free to change anything in your codebase which you think will make it easier to work with, or to build new features. - -Make a branch and pull request for yourself, and then have your partner review, making sure they understand the changes made. - ---- - -## Adding new functionality - -Level 500 is about adding a front-page which lets users select (and find) shows from your shows list. - -### Requirements - -1. When your app starts, present a listing of all shows ("shows listing") - 1. For each show, you must display at least the name, image, summary, genres, status, rating, and runtime. -2. When a show name is clicked, your app should: - 1. Fetch and present episodes from that show (enabling episode search and selection as before) - 2. Hide the "shows listing" view -3. Add a navigation link to enable the user to return to the "shows listing" - 1. When this is clicked, the episodes listing should be hidden -4. Provide a free-text show search through show names, genres, and summary texts -5. Ensure that your episode search and episode selector controls still work correctly when you switch from shows listing to the episodes listing and back -6. During one user's visit to your website, you should never fetch any URL more than once. - -#### Screenshot of minimal version - -Note: Provided your project meets the above requirements, it can **look** however you want. - -Here is one example layout. - -![Screenshot of a website with a drop-down list with the show "Breaking Bad" selected](example-screenshots/example-level-500.jpg) - -### Deployment to Netlify - -To complete the project, we will move from GitHub Pages to **Netlify** to practice using a more advanced hosting platform. - -Follow the [Official CYF Netlify Deployment Guide](https://curriculum.codeyourfuture.io/guides/deployment-netlify/) for your project. - -## Submission - -Once you have finished your level 500 it is ready to submit. - -Check the [README.md](../README.md) for this repo for instructions. +# Level 500 + +For level 500, you should switch back to your original codebase. + +You should have reviewed and merged someone else's level 400 implementation. + +Once again, compare their implementation to yours. Think: + +1. How is it different? +2. What do you prefer about your implementation? +3. What do you prefer about their implementation? +4. What did you learn that you didn't know before? + +Have a discussion about your answers to these questions. In class, together you should give a 3 minute talk about your conclusions. + +--- + +### Technical Workflow + +**Best Practice: Use Feature Branches** +To maintain a clean workflow, follow these steps for Level 500: +* Once you have merged Level 400 and are satisfied with the changes, **create a new branch** (e.g., `feature/level-500`) to continue your development. +* After completing all Level 500 requirements on this branch, merge it into your main branch. +* **Important:** Test your changes thoroughly on GitHub Pages before moving to the final deployment. + +--- + +## Refactoring + +Feel free to change anything in your codebase which you think will make it easier to work with, or to build new features. + +Make a branch and pull request for yourself, and then have your partner review, making sure they understand the changes made. + +--- + +## Adding new functionality + +Level 500 is about adding a front-page which lets users select (and find) shows from your shows list. + +### Requirements + +1. When your app starts, present a listing of all shows ("shows listing") + 1. For each show, you must display at least the name, image, summary, genres, status, rating, and runtime. +2. When a show name is clicked, your app should: + 1. Fetch and present episodes from that show (enabling episode search and selection as before) + 2. Hide the "shows listing" view +3. Add a navigation link to enable the user to return to the "shows listing" + 1. When this is clicked, the episodes listing should be hidden +4. Provide a free-text show search through show names, genres, and summary texts +5. Ensure that your episode search and episode selector controls still work correctly when you switch from shows listing to the episodes listing and back +6. During one user's visit to your website, you should never fetch any URL more than once. + +#### Screenshot of minimal version + +Note: Provided your project meets the above requirements, it can **look** however you want. + +Here is one example layout. + +![Screenshot of a website with a drop-down list with the show "Breaking Bad" selected](example-screenshots/example-level-500.jpg) + +### Deployment to Netlify + +To complete the project, we will move from GitHub Pages to **Netlify** to practice using a more advanced hosting platform. + +Follow the [Official CYF Netlify Deployment Guide](https://curriculum.codeyourfuture.io/guides/deployment-netlify/) for your project. + +## Submission + +Once you have finished your level 500 it is ready to submit. + +Check the [README.md](../README.md) for this repo for instructions. diff --git a/levels/level-999.md b/levels/level-999.md index cf3d894..e3a3509 100644 --- a/levels/level-999.md +++ b/levels/level-999.md @@ -1,22 +1,22 @@ -# Extra options - -Do not do any of these until you have completed level 500! - -Submit your Pull Request for level 500 first, and then work on these in a new branch for level 999. - -Here are some ideas for consideration for further work: -1. Add cast listing to your show listing, http://api.tvmaze.com/shows/1?embed=cast -2. Add cast listing to your episode listing, http://api.tvmaze.com/shows/1?embed=cast -3. Allow clicking a cast member to present a view of all shows the person has appeared in http://api.tvmaze.com/people/1/castcredits - - Linking those back into your episodes view of each show. -4. Truncate long summaries and provide a "... read more..." control to reveal more. -5. Allow the user to choose to have the show list sorted by rating (highest rated shows first) -6. Have your search support start-of-word search, or other search types. -7. Make the browser's back and forward buttons navigate through your changed views (see [History.pushState()](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)). -8. Allow user to mark "favourite" shows -9. Allow user to store notes on each episode -10. Use local storage to cache the above, and perhaps also to cache episode lists -11. Paginate through results with a "load more" button - don't load 100s of episodes at a time. -12. Experiment with infinite scroll -13. Responsive design -14. Style your page to fit the theme of the show (colours, typography, background images, etc). Commit to one show to do this. +# Extra options + +Do not do any of these until you have completed level 500! + +Submit your Pull Request for level 500 first, and then work on these in a new branch for level 999. + +Here are some ideas for consideration for further work: +1. Add cast listing to your show listing, http://api.tvmaze.com/shows/1?embed=cast +2. Add cast listing to your episode listing, http://api.tvmaze.com/shows/1?embed=cast +3. Allow clicking a cast member to present a view of all shows the person has appeared in http://api.tvmaze.com/people/1/castcredits + - Linking those back into your episodes view of each show. +4. Truncate long summaries and provide a "... read more..." control to reveal more. +5. Allow the user to choose to have the show list sorted by rating (highest rated shows first) +6. Have your search support start-of-word search, or other search types. +7. Make the browser's back and forward buttons navigate through your changed views (see [History.pushState()](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)). +8. Allow user to mark "favourite" shows +9. Allow user to store notes on each episode +10. Use local storage to cache the above, and perhaps also to cache episode lists +11. Paginate through results with a "load more" button - don't load 100s of episodes at a time. +12. Experiment with infinite scroll +13. Responsive design +14. Style your page to fit the theme of the show (colours, typography, background images, etc). Commit to one show to do this. diff --git a/script.js b/script.js index a6e07e7..019dc95 100644 --- a/script.js +++ b/script.js @@ -1,231 +1,231 @@ -// Global State & Cache -let allShows = []; -let allEpisodes = []; -const showsCache = {}; // Cache episodes by show ID to enforce the single-fetch rule - -async function setup() { - const rootElem = document.getElementById("root"); - rootElem.innerHTML = `

Loading shows, please wait...

`; - - // Wire event listeners once on load - const showSelect = document.getElementById("show-select"); - const searchInput = document.getElementById("search-input"); - const episodeSelect = document.getElementById("episode-select"); - - if (showSelect) showSelect.addEventListener("change", handleShowSelect); - if (searchInput) searchInput.addEventListener("input", handleSearch); - if (episodeSelect) episodeSelect.addEventListener("change", handleSelect); - - try { - // 1. Fetch shows list - const response = await fetch("https://api.tvmaze.com/shows"); - if (!response.ok) { - throw new Error(`Failed to load shows (${response.status})`); - } - - const rawShows = await response.json(); - - // 2. Sort shows alphabetically (case-insensitive) - allShows = rawShows.sort((a, b) => - a.name.localeCompare(b.name, undefined, { sensitivity: "accent" }), - ); - - // 3. Populate Show Dropdown - populateShowDropdown(allShows); - - // 4. Load initial show (e.g., Game of Thrones - ID 82, or the first show in list) - const defaultShowId = allShows.find((show) => show.id === 82) - ? 82 - : allShows[0].id; - - showSelect.value = defaultShowId; - await loadEpisodesForShow(defaultShowId); - } catch (error) { - rootElem.innerHTML = ` -
-

Unable to load shows

-

Error: ${error.message}. Please check your connection and refresh.

-
- `; - } -} - -// Populate Show Selector -function populateShowDropdown(shows) { - const select = document.getElementById("show-select"); - if (!select) return; - - select.innerHTML = ""; // Clear options - - shows.forEach((show) => { - const option = document.createElement("option"); - option.value = show.id; - option.textContent = show.name; - select.appendChild(option); - }); -} - -// Fetch or retrieve episodes from cache -async function loadEpisodesForShow(showId) { - const rootElem = document.getElementById("root"); - rootElem.innerHTML = `

Loading episodes...

`; - - // Reset controls - const searchInput = document.getElementById("search-input"); - if (searchInput) searchInput.value = ""; - - try { - // Check if show episodes are already cached - if (showsCache[showId]) { - allEpisodes = showsCache[showId]; - } else { - // Fetch ONCE and cache - const response = await fetch( - `https://api.tvmaze.com/shows/${showId}/episodes`, - ); - if (!response.ok) { - throw new Error(`Failed to load episodes (${response.status})`); - } - allEpisodes = await response.json(); - showsCache[showId] = allEpisodes; // Cache in memory - } - - // Populate episode controls & display UI - populateSelectDropdown(allEpisodes); - makePageForEpisodes(allEpisodes); - updateSearchCount(allEpisodes.length, allEpisodes.length); - } catch (error) { - rootElem.innerHTML = ` -
-

Unable to load episodes

-

Error: ${error.message}. Please try selecting another show.

-
- `; - } -} - -// Show Select Handler -async function handleShowSelect(event) { - const selectedShowId = event.target.value; - if (!selectedShowId) return; - await loadEpisodesForShow(selectedShowId); -} - -// Populate Episode Selector -function populateSelectDropdown(episodes) { - const select = document.getElementById("episode-select"); - if (!select) return; - - select.innerHTML = ''; - - episodes.forEach((episode) => { - const option = document.createElement("option"); - option.value = episode.id; - const code = formatEpisodeCode(episode.season, episode.number); - option.textContent = `${code} - ${episode.name}`; - select.appendChild(option); - }); -} - -// Live Search Handler -function handleSearch(event) { - const searchTerm = event.target.value.toLowerCase().trim(); - - const episodeSelect = document.getElementById("episode-select"); - if (episodeSelect) episodeSelect.value = "ALL"; - - const filteredEpisodes = allEpisodes.filter((episode) => { - const nameMatches = episode.name.toLowerCase().includes(searchTerm); - const summaryMatches = episode.summary - ? episode.summary.toLowerCase().includes(searchTerm) - : false; - - return nameMatches || summaryMatches; - }); - - makePageForEpisodes(filteredEpisodes); - updateSearchCount(filteredEpisodes.length, allEpisodes.length); -} - -// Episode Dropdown Selector Handler -function handleSelect(event) { - const selectedId = event.target.value; - - const searchInput = document.getElementById("search-input"); - if (searchInput) searchInput.value = ""; - - if (selectedId === "ALL") { - makePageForEpisodes(allEpisodes); - updateSearchCount(allEpisodes.length, allEpisodes.length); - } else { - const selectedEpisode = allEpisodes.filter( - (episode) => String(episode.id) === String(selectedId), - ); - makePageForEpisodes(selectedEpisode); - updateSearchCount(selectedEpisode.length, allEpisodes.length); - } -} - -// Update Search Counter -function updateSearchCount(matchCount, totalCount) { - const countDisplay = document.getElementById("search-count"); - if (countDisplay) { - countDisplay.textContent = `Displaying ${matchCount}/${totalCount} episodes`; - } -} - -// Helper: Format S01E01 -function formatEpisodeCode(season, number) { - const paddedSeason = String(season).padStart(2, "0"); - const paddedNumber = String(number).padStart(2, "0"); - return `S${paddedSeason}E${paddedNumber}`; -} - -// Render Episodes Page -function makePageForEpisodes(episodeList) { - const rootElem = document.getElementById("root"); - rootElem.innerHTML = ""; - - const container = document.createElement("div"); - container.className = "episodes-container"; - - episodeList.forEach((episode) => { - const card = document.createElement("section"); - card.className = "episode-card"; - - const title = document.createElement("h3"); - const code = formatEpisodeCode(episode.season, episode.number); - title.textContent = `${episode.name} - ${code}`; - card.appendChild(title); - - if (episode.image && episode.image.medium) { - const img = document.createElement("img"); - img.src = episode.image.medium; - img.alt = episode.name; - card.appendChild(img); - } - - const summary = document.createElement("div"); - summary.className = "episode-summary"; - summary.innerHTML = episode.summary || "

No summary available.

"; - card.appendChild(summary); - - container.appendChild(card); - }); - - rootElem.appendChild(container); - addTvmazeAttribution(); -} - -function addTvmazeAttribution() { - if (document.getElementById("tvmaze-attribution")) return; - - const footer = document.createElement("footer"); - footer.id = "tvmaze-attribution"; - footer.innerHTML = ` -

Data provided by TVMaze.com

- `; - document.body.appendChild(footer); -} - -window.onload = setup; +// Global State & Cache +let allShows = []; +let allEpisodes = []; +const showsCache = {}; // Cache episodes by show ID to enforce the single-fetch rule + +async function setup() { + const rootElem = document.getElementById("root"); + rootElem.innerHTML = `

Loading shows, please wait...

`; + + // Wire event listeners once on load + const showSelect = document.getElementById("show-select"); + const searchInput = document.getElementById("search-input"); + const episodeSelect = document.getElementById("episode-select"); + + if (showSelect) showSelect.addEventListener("change", handleShowSelect); + if (searchInput) searchInput.addEventListener("input", handleSearch); + if (episodeSelect) episodeSelect.addEventListener("change", handleSelect); + + try { + // 1. Fetch shows list + const response = await fetch("https://api.tvmaze.com/shows"); + if (!response.ok) { + throw new Error(`Failed to load shows (${response.status})`); + } + + const rawShows = await response.json(); + + // 2. Sort shows alphabetically (case-insensitive) + allShows = rawShows.sort((a, b) => + a.name.localeCompare(b.name, undefined, { sensitivity: "accent" }), + ); + + // 3. Populate Show Dropdown + populateShowDropdown(allShows); + + // 4. Load initial show (e.g., Game of Thrones - ID 82, or the first show in list) + const defaultShowId = allShows.find((show) => show.id === 82) + ? 82 + : allShows[0].id; + + showSelect.value = defaultShowId; + await loadEpisodesForShow(defaultShowId); + } catch (error) { + rootElem.innerHTML = ` +
+

Unable to load shows

+

Error: ${error.message}. Please check your connection and refresh.

+
+ `; + } +} + +// Populate Show Selector +function populateShowDropdown(shows) { + const select = document.getElementById("show-select"); + if (!select) return; + + select.innerHTML = ""; // Clear options + + shows.forEach((show) => { + const option = document.createElement("option"); + option.value = show.id; + option.textContent = show.name; + select.appendChild(option); + }); +} + +// Fetch or retrieve episodes from cache +async function loadEpisodesForShow(showId) { + const rootElem = document.getElementById("root"); + rootElem.innerHTML = `

Loading episodes...

`; + + // Reset controls + const searchInput = document.getElementById("search-input"); + if (searchInput) searchInput.value = ""; + + try { + // Check if show episodes are already cached + if (showsCache[showId]) { + allEpisodes = showsCache[showId]; + } else { + // Fetch ONCE and cache + const response = await fetch( + `https://api.tvmaze.com/shows/${showId}/episodes`, + ); + if (!response.ok) { + throw new Error(`Failed to load episodes (${response.status})`); + } + allEpisodes = await response.json(); + showsCache[showId] = allEpisodes; // Cache in memory + } + + // Populate episode controls & display UI + populateSelectDropdown(allEpisodes); + makePageForEpisodes(allEpisodes); + updateSearchCount(allEpisodes.length, allEpisodes.length); + } catch (error) { + rootElem.innerHTML = ` +
+

Unable to load episodes

+

Error: ${error.message}. Please try selecting another show.

+
+ `; + } +} + +// Show Select Handler +async function handleShowSelect(event) { + const selectedShowId = event.target.value; + if (!selectedShowId) return; + await loadEpisodesForShow(selectedShowId); +} + +// Populate Episode Selector +function populateSelectDropdown(episodes) { + const select = document.getElementById("episode-select"); + if (!select) return; + + select.innerHTML = ''; + + episodes.forEach((episode) => { + const option = document.createElement("option"); + option.value = episode.id; + const code = formatEpisodeCode(episode.season, episode.number); + option.textContent = `${code} - ${episode.name}`; + select.appendChild(option); + }); +} + +// Live Search Handler +function handleSearch(event) { + const searchTerm = event.target.value.toLowerCase().trim(); + + const episodeSelect = document.getElementById("episode-select"); + if (episodeSelect) episodeSelect.value = "ALL"; + + const filteredEpisodes = allEpisodes.filter((episode) => { + const nameMatches = episode.name.toLowerCase().includes(searchTerm); + const summaryMatches = episode.summary + ? episode.summary.toLowerCase().includes(searchTerm) + : false; + + return nameMatches || summaryMatches; + }); + + makePageForEpisodes(filteredEpisodes); + updateSearchCount(filteredEpisodes.length, allEpisodes.length); +} + +// Episode Dropdown Selector Handler +function handleSelect(event) { + const selectedId = event.target.value; + + const searchInput = document.getElementById("search-input"); + if (searchInput) searchInput.value = ""; + + if (selectedId === "ALL") { + makePageForEpisodes(allEpisodes); + updateSearchCount(allEpisodes.length, allEpisodes.length); + } else { + const selectedEpisode = allEpisodes.filter( + (episode) => String(episode.id) === String(selectedId), + ); + makePageForEpisodes(selectedEpisode); + updateSearchCount(selectedEpisode.length, allEpisodes.length); + } +} + +// Update Search Counter +function updateSearchCount(matchCount, totalCount) { + const countDisplay = document.getElementById("search-count"); + if (countDisplay) { + countDisplay.textContent = `Displaying ${matchCount}/${totalCount} episodes`; + } +} + +// Helper: Format S01E01 +function formatEpisodeCode(season, number) { + const paddedSeason = String(season).padStart(2, "0"); + const paddedNumber = String(number).padStart(2, "0"); + return `S${paddedSeason}E${paddedNumber}`; +} + +// Render Episodes Page +function makePageForEpisodes(episodeList) { + const rootElem = document.getElementById("root"); + rootElem.innerHTML = ""; + + const container = document.createElement("div"); + container.className = "episodes-container"; + + episodeList.forEach((episode) => { + const card = document.createElement("section"); + card.className = "episode-card"; + + const title = document.createElement("h3"); + const code = formatEpisodeCode(episode.season, episode.number); + title.textContent = `${episode.name} - ${code}`; + card.appendChild(title); + + if (episode.image && episode.image.medium) { + const img = document.createElement("img"); + img.src = episode.image.medium; + img.alt = episode.name; + card.appendChild(img); + } + + const summary = document.createElement("div"); + summary.className = "episode-summary"; + summary.innerHTML = episode.summary || "

No summary available.

"; + card.appendChild(summary); + + container.appendChild(card); + }); + + rootElem.appendChild(container); + addTvmazeAttribution(); +} + +function addTvmazeAttribution() { + if (document.getElementById("tvmaze-attribution")) return; + + const footer = document.createElement("footer"); + footer.id = "tvmaze-attribution"; + footer.innerHTML = ` +

Data provided by TVMaze.com

+ `; + document.body.appendChild(footer); +} + +window.onload = setup; diff --git a/style.css b/style.css index 74b4ee7..5942f31 100644 --- a/style.css +++ b/style.css @@ -1,214 +1,214 @@ -/* ================================ - 1. Base & Layout Styles - ================================ */ -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, - Arial, sans-serif; - background-color: #f4f6f8; - color: #333; - line-height: 1.5; - padding-bottom: 60px; -} - -#root { - max-width: 1200px; - margin: 0 auto; - padding: 20px; -} - -/* ================================ - 2. Controls Container (Top Header Bar) - ================================ */ -.controls-header, -.controls-container { - position: sticky; - top: 0; - z-index: 100; - background-color: #ffffff; - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 16px 24px; - margin-bottom: 24px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - border-radius: 8px; -} - -.controls-header select, -.controls-header input[type="text"], -.controls-container select, -.controls-container input[type="text"] { - padding: 10px 14px; - font-size: 0.95rem; - border: 1px solid #ccc; - border-radius: 6px; - outline: none; - background-color: #fff; - transition: border-color 0.2s ease, box-shadow 0.2s ease; - flex: 1 1 200px; - /* Responsive flex sizing for 3 control elements */ - max-width: 280px; -} - -.controls-header select:focus, -.controls-header input[type="text"]:focus, -.controls-container select:focus, -.controls-container input[type="text"]:focus { - border-color: #0066cc; - box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.15); -} - -#search-count { - font-weight: 600; - color: #555; - font-size: 0.95rem; - white-space: nowrap; -} - -/* ================================ - 3. Loading & Error Feedback States - ================================ */ -.loading-state { - text-align: center; - font-size: 1.2rem; - font-weight: 600; - color: #0066cc; - padding: 60px 20px; -} - -.error-banner { - background-color: #fdf2f2; - border: 1px solid #f8b4b4; - border-left: 5px solid #e53e3e; - border-radius: 8px; - padding: 20px 24px; - margin: 20px auto; - max-width: 800px; - color: #9b2c2c; -} - -.error-banner h2 { - font-size: 1.25rem; - margin-bottom: 8px; - color: #c53030; -} - -.error-banner p { - font-size: 0.95rem; - margin: 0; -} - -/* ================================ - 4. Episodes Grid - ================================ */ -.episodes-container { - display: grid; - /* Automatically fits as many 280px cards per row as possible */ - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 24px; -} - -/* ================================ - 5. Episode Card - ================================ */ -.episode-card { - background-color: #ffffff; - border-radius: 10px; - overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); - display: flex; - flex-direction: column; - transition: transform 0.2s ease, box-shadow 0.2s ease; -} - -.episode-card:hover { - transform: translateY(-4px); - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); -} - -.episode-card h3 { - font-size: 1.1rem; - font-weight: 700; - color: #1a1a1a; - padding: 16px; - text-align: center; - background-color: #f9f9fb; - border-bottom: 1px solid #eeeeee; - min-height: 60px; - display: flex; - align-items: center; - justify-content: center; -} - -.episode-card img { - width: 100%; - height: 200px; - object-fit: cover; - display: block; -} - -.episode-summary { - padding: 16px; - font-size: 0.9rem; - color: #4a4a4a; - flex-grow: 1; - /* Ensures equal-height cards in grid */ -} - -.episode-summary p { - margin-bottom: 8px; -} - -/* ================================ - 6. Footer (TVMaze Attribution) - ================================ */ -#tvmaze-attribution { - text-align: center; - padding: 20px; - margin-top: 40px; - font-size: 0.9rem; - color: #666; - border-top: 1px solid #e0e0e0; -} - -#tvmaze-attribution a { - color: #0066cc; - text-decoration: none; - font-weight: 600; -} - -#tvmaze-attribution a:hover { - text-decoration: underline; -} - -/* ================================ - 7. Mobile Responsiveness - ================================ */ -@media (max-width: 768px) { - - .controls-header, - .controls-container { - flex-direction: column; - align-items: stretch; - } - - .controls-header select, - .controls-header input[type="text"], - .controls-container select, - .controls-container input[type="text"] { - max-width: 100%; - width: 100%; - } - - #search-count { - text-align: center; - } +/* ================================ + 1. Base & Layout Styles + ================================ */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, + Arial, sans-serif; + background-color: #f4f6f8; + color: #333; + line-height: 1.5; + padding-bottom: 60px; +} + +#root { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* ================================ + 2. Controls Container (Top Header Bar) + ================================ */ +.controls-header, +.controls-container { + position: sticky; + top: 0; + z-index: 100; + background-color: #ffffff; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 16px 24px; + margin-bottom: 24px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + border-radius: 8px; +} + +.controls-header select, +.controls-header input[type="text"], +.controls-container select, +.controls-container input[type="text"] { + padding: 10px 14px; + font-size: 0.95rem; + border: 1px solid #ccc; + border-radius: 6px; + outline: none; + background-color: #fff; + transition: border-color 0.2s ease, box-shadow 0.2s ease; + flex: 1 1 200px; + /* Responsive flex sizing for 3 control elements */ + max-width: 280px; +} + +.controls-header select:focus, +.controls-header input[type="text"]:focus, +.controls-container select:focus, +.controls-container input[type="text"]:focus { + border-color: #0066cc; + box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.15); +} + +#search-count { + font-weight: 600; + color: #555; + font-size: 0.95rem; + white-space: nowrap; +} + +/* ================================ + 3. Loading & Error Feedback States + ================================ */ +.loading-state { + text-align: center; + font-size: 1.2rem; + font-weight: 600; + color: #0066cc; + padding: 60px 20px; +} + +.error-banner { + background-color: #fdf2f2; + border: 1px solid #f8b4b4; + border-left: 5px solid #e53e3e; + border-radius: 8px; + padding: 20px 24px; + margin: 20px auto; + max-width: 800px; + color: #9b2c2c; +} + +.error-banner h2 { + font-size: 1.25rem; + margin-bottom: 8px; + color: #c53030; +} + +.error-banner p { + font-size: 0.95rem; + margin: 0; +} + +/* ================================ + 4. Episodes Grid + ================================ */ +.episodes-container { + display: grid; + /* Automatically fits as many 280px cards per row as possible */ + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; +} + +/* ================================ + 5. Episode Card + ================================ */ +.episode-card { + background-color: #ffffff; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + display: flex; + flex-direction: column; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.episode-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); +} + +.episode-card h3 { + font-size: 1.1rem; + font-weight: 700; + color: #1a1a1a; + padding: 16px; + text-align: center; + background-color: #f9f9fb; + border-bottom: 1px solid #eeeeee; + min-height: 60px; + display: flex; + align-items: center; + justify-content: center; +} + +.episode-card img { + width: 100%; + height: 200px; + object-fit: cover; + display: block; +} + +.episode-summary { + padding: 16px; + font-size: 0.9rem; + color: #4a4a4a; + flex-grow: 1; + /* Ensures equal-height cards in grid */ +} + +.episode-summary p { + margin-bottom: 8px; +} + +/* ================================ + 6. Footer (TVMaze Attribution) + ================================ */ +#tvmaze-attribution { + text-align: center; + padding: 20px; + margin-top: 40px; + font-size: 0.9rem; + color: #666; + border-top: 1px solid #e0e0e0; +} + +#tvmaze-attribution a { + color: #0066cc; + text-decoration: none; + font-weight: 600; +} + +#tvmaze-attribution a:hover { + text-decoration: underline; +} + +/* ================================ + 7. Mobile Responsiveness + ================================ */ +@media (max-width: 768px) { + + .controls-header, + .controls-container { + flex-direction: column; + align-items: stretch; + } + + .controls-header select, + .controls-header input[type="text"], + .controls-container select, + .controls-container input[type="text"] { + max-width: 100%; + width: 100%; + } + + #search-count { + text-align: center; + } } \ No newline at end of file From 4c025ea37c051b52e22763fcc10b47ad854117d5 Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Mon, 10 Aug 2026 17:38:55 +0100 Subject: [PATCH 18/24] ddd --- script.js | 69 +++++-------------------------------------------------- 1 file changed, 6 insertions(+), 63 deletions(-) diff --git a/script.js b/script.js index 019dc95..ac19e5a 100644 --- a/script.js +++ b/script.js @@ -1,72 +1,15 @@ // Global State & Cache let allShows = []; let allEpisodes = []; -const showsCache = {}; // Cache episodes by show ID to enforce the single-fetch rule -async function setup() { - const rootElem = document.getElementById("root"); - rootElem.innerHTML = `

Loading shows, please wait...

`; - - // Wire event listeners once on load - const showSelect = document.getElementById("show-select"); - const searchInput = document.getElementById("search-input"); - const episodeSelect = document.getElementById("episode-select"); - - if (showSelect) showSelect.addEventListener("change", handleShowSelect); - if (searchInput) searchInput.addEventListener("input", handleSearch); - if (episodeSelect) episodeSelect.addEventListener("change", handleSelect); - - try { - // 1. Fetch shows list - const response = await fetch("https://api.tvmaze.com/shows"); - if (!response.ok) { - throw new Error(`Failed to load shows (${response.status})`); - } - - const rawShows = await response.json(); - - // 2. Sort shows alphabetically (case-insensitive) - allShows = rawShows.sort((a, b) => - a.name.localeCompare(b.name, undefined, { sensitivity: "accent" }), - ); - - // 3. Populate Show Dropdown - populateShowDropdown(allShows); - - // 4. Load initial show (e.g., Game of Thrones - ID 82, or the first show in list) - const defaultShowId = allShows.find((show) => show.id === 82) - ? 82 - : allShows[0].id; - - showSelect.value = defaultShowId; - await loadEpisodesForShow(defaultShowId); - } catch (error) { - rootElem.innerHTML = ` -
-

Unable to load shows

-

Error: ${error.message}. Please check your connection and refresh.

-
- `; - } -} - -// Populate Show Selector -function populateShowDropdown(shows) { - const select = document.getElementById("show-select"); - if (!select) return; - - select.innerHTML = ""; // Clear options - - shows.forEach((show) => { - const option = document.createElement("option"); - option.value = show.id; - option.textContent = show.name; - select.appendChild(option); - }); +function setup() { + allEpisodes = getAllEpisodes(); + createControls(); + makePageForEpisodes(allEpisodes); + addTvmazeAttribution(); } -// Fetch or retrieve episodes from cache -async function loadEpisodesForShow(showId) { +function createControls() { const rootElem = document.getElementById("root"); rootElem.innerHTML = `

Loading episodes...

`; From d23ab956c56f51731c5396d6201ca648c284e529 Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Mon, 10 Aug 2026 17:39:04 +0100 Subject: [PATCH 19/24] sss --- style.css | 426 +++++++++++++++++++++++++++--------------------------- 1 file changed, 213 insertions(+), 213 deletions(-) diff --git a/style.css b/style.css index 5942f31..74b4ee7 100644 --- a/style.css +++ b/style.css @@ -1,214 +1,214 @@ -/* ================================ - 1. Base & Layout Styles - ================================ */ -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, - Arial, sans-serif; - background-color: #f4f6f8; - color: #333; - line-height: 1.5; - padding-bottom: 60px; -} - -#root { - max-width: 1200px; - margin: 0 auto; - padding: 20px; -} - -/* ================================ - 2. Controls Container (Top Header Bar) - ================================ */ -.controls-header, -.controls-container { - position: sticky; - top: 0; - z-index: 100; - background-color: #ffffff; - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 16px 24px; - margin-bottom: 24px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - border-radius: 8px; -} - -.controls-header select, -.controls-header input[type="text"], -.controls-container select, -.controls-container input[type="text"] { - padding: 10px 14px; - font-size: 0.95rem; - border: 1px solid #ccc; - border-radius: 6px; - outline: none; - background-color: #fff; - transition: border-color 0.2s ease, box-shadow 0.2s ease; - flex: 1 1 200px; - /* Responsive flex sizing for 3 control elements */ - max-width: 280px; -} - -.controls-header select:focus, -.controls-header input[type="text"]:focus, -.controls-container select:focus, -.controls-container input[type="text"]:focus { - border-color: #0066cc; - box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.15); -} - -#search-count { - font-weight: 600; - color: #555; - font-size: 0.95rem; - white-space: nowrap; -} - -/* ================================ - 3. Loading & Error Feedback States - ================================ */ -.loading-state { - text-align: center; - font-size: 1.2rem; - font-weight: 600; - color: #0066cc; - padding: 60px 20px; -} - -.error-banner { - background-color: #fdf2f2; - border: 1px solid #f8b4b4; - border-left: 5px solid #e53e3e; - border-radius: 8px; - padding: 20px 24px; - margin: 20px auto; - max-width: 800px; - color: #9b2c2c; -} - -.error-banner h2 { - font-size: 1.25rem; - margin-bottom: 8px; - color: #c53030; -} - -.error-banner p { - font-size: 0.95rem; - margin: 0; -} - -/* ================================ - 4. Episodes Grid - ================================ */ -.episodes-container { - display: grid; - /* Automatically fits as many 280px cards per row as possible */ - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 24px; -} - -/* ================================ - 5. Episode Card - ================================ */ -.episode-card { - background-color: #ffffff; - border-radius: 10px; - overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); - display: flex; - flex-direction: column; - transition: transform 0.2s ease, box-shadow 0.2s ease; -} - -.episode-card:hover { - transform: translateY(-4px); - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); -} - -.episode-card h3 { - font-size: 1.1rem; - font-weight: 700; - color: #1a1a1a; - padding: 16px; - text-align: center; - background-color: #f9f9fb; - border-bottom: 1px solid #eeeeee; - min-height: 60px; - display: flex; - align-items: center; - justify-content: center; -} - -.episode-card img { - width: 100%; - height: 200px; - object-fit: cover; - display: block; -} - -.episode-summary { - padding: 16px; - font-size: 0.9rem; - color: #4a4a4a; - flex-grow: 1; - /* Ensures equal-height cards in grid */ -} - -.episode-summary p { - margin-bottom: 8px; -} - -/* ================================ - 6. Footer (TVMaze Attribution) - ================================ */ -#tvmaze-attribution { - text-align: center; - padding: 20px; - margin-top: 40px; - font-size: 0.9rem; - color: #666; - border-top: 1px solid #e0e0e0; -} - -#tvmaze-attribution a { - color: #0066cc; - text-decoration: none; - font-weight: 600; -} - -#tvmaze-attribution a:hover { - text-decoration: underline; -} - -/* ================================ - 7. Mobile Responsiveness - ================================ */ -@media (max-width: 768px) { - - .controls-header, - .controls-container { - flex-direction: column; - align-items: stretch; - } - - .controls-header select, - .controls-header input[type="text"], - .controls-container select, - .controls-container input[type="text"] { - max-width: 100%; - width: 100%; - } - - #search-count { - text-align: center; - } +/* ================================ + 1. Base & Layout Styles + ================================ */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, + Arial, sans-serif; + background-color: #f4f6f8; + color: #333; + line-height: 1.5; + padding-bottom: 60px; +} + +#root { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* ================================ + 2. Controls Container (Top Header Bar) + ================================ */ +.controls-header, +.controls-container { + position: sticky; + top: 0; + z-index: 100; + background-color: #ffffff; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 16px 24px; + margin-bottom: 24px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + border-radius: 8px; +} + +.controls-header select, +.controls-header input[type="text"], +.controls-container select, +.controls-container input[type="text"] { + padding: 10px 14px; + font-size: 0.95rem; + border: 1px solid #ccc; + border-radius: 6px; + outline: none; + background-color: #fff; + transition: border-color 0.2s ease, box-shadow 0.2s ease; + flex: 1 1 200px; + /* Responsive flex sizing for 3 control elements */ + max-width: 280px; +} + +.controls-header select:focus, +.controls-header input[type="text"]:focus, +.controls-container select:focus, +.controls-container input[type="text"]:focus { + border-color: #0066cc; + box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.15); +} + +#search-count { + font-weight: 600; + color: #555; + font-size: 0.95rem; + white-space: nowrap; +} + +/* ================================ + 3. Loading & Error Feedback States + ================================ */ +.loading-state { + text-align: center; + font-size: 1.2rem; + font-weight: 600; + color: #0066cc; + padding: 60px 20px; +} + +.error-banner { + background-color: #fdf2f2; + border: 1px solid #f8b4b4; + border-left: 5px solid #e53e3e; + border-radius: 8px; + padding: 20px 24px; + margin: 20px auto; + max-width: 800px; + color: #9b2c2c; +} + +.error-banner h2 { + font-size: 1.25rem; + margin-bottom: 8px; + color: #c53030; +} + +.error-banner p { + font-size: 0.95rem; + margin: 0; +} + +/* ================================ + 4. Episodes Grid + ================================ */ +.episodes-container { + display: grid; + /* Automatically fits as many 280px cards per row as possible */ + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; +} + +/* ================================ + 5. Episode Card + ================================ */ +.episode-card { + background-color: #ffffff; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + display: flex; + flex-direction: column; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.episode-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); +} + +.episode-card h3 { + font-size: 1.1rem; + font-weight: 700; + color: #1a1a1a; + padding: 16px; + text-align: center; + background-color: #f9f9fb; + border-bottom: 1px solid #eeeeee; + min-height: 60px; + display: flex; + align-items: center; + justify-content: center; +} + +.episode-card img { + width: 100%; + height: 200px; + object-fit: cover; + display: block; +} + +.episode-summary { + padding: 16px; + font-size: 0.9rem; + color: #4a4a4a; + flex-grow: 1; + /* Ensures equal-height cards in grid */ +} + +.episode-summary p { + margin-bottom: 8px; +} + +/* ================================ + 6. Footer (TVMaze Attribution) + ================================ */ +#tvmaze-attribution { + text-align: center; + padding: 20px; + margin-top: 40px; + font-size: 0.9rem; + color: #666; + border-top: 1px solid #e0e0e0; +} + +#tvmaze-attribution a { + color: #0066cc; + text-decoration: none; + font-weight: 600; +} + +#tvmaze-attribution a:hover { + text-decoration: underline; +} + +/* ================================ + 7. Mobile Responsiveness + ================================ */ +@media (max-width: 768px) { + + .controls-header, + .controls-container { + flex-direction: column; + align-items: stretch; + } + + .controls-header select, + .controls-header input[type="text"], + .controls-container select, + .controls-container input[type="text"] { + max-width: 100%; + width: 100%; + } + + #search-count { + text-align: center; + } } \ No newline at end of file From 3306d8a13f5c46b25ceb2186272de07a49909b4a Mon Sep 17 00:00:00 2001 From: Ogbemi Mene Date: Mon, 10 Aug 2026 17:53:52 +0100 Subject: [PATCH 20/24] Update styles in style.css --- style.css | 426 +++++++++++++++++++++++++++--------------------------- 1 file changed, 213 insertions(+), 213 deletions(-) diff --git a/style.css b/style.css index 74b4ee7..5942f31 100644 --- a/style.css +++ b/style.css @@ -1,214 +1,214 @@ -/* ================================ - 1. Base & Layout Styles - ================================ */ -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, - Arial, sans-serif; - background-color: #f4f6f8; - color: #333; - line-height: 1.5; - padding-bottom: 60px; -} - -#root { - max-width: 1200px; - margin: 0 auto; - padding: 20px; -} - -/* ================================ - 2. Controls Container (Top Header Bar) - ================================ */ -.controls-header, -.controls-container { - position: sticky; - top: 0; - z-index: 100; - background-color: #ffffff; - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 16px 24px; - margin-bottom: 24px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - border-radius: 8px; -} - -.controls-header select, -.controls-header input[type="text"], -.controls-container select, -.controls-container input[type="text"] { - padding: 10px 14px; - font-size: 0.95rem; - border: 1px solid #ccc; - border-radius: 6px; - outline: none; - background-color: #fff; - transition: border-color 0.2s ease, box-shadow 0.2s ease; - flex: 1 1 200px; - /* Responsive flex sizing for 3 control elements */ - max-width: 280px; -} - -.controls-header select:focus, -.controls-header input[type="text"]:focus, -.controls-container select:focus, -.controls-container input[type="text"]:focus { - border-color: #0066cc; - box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.15); -} - -#search-count { - font-weight: 600; - color: #555; - font-size: 0.95rem; - white-space: nowrap; -} - -/* ================================ - 3. Loading & Error Feedback States - ================================ */ -.loading-state { - text-align: center; - font-size: 1.2rem; - font-weight: 600; - color: #0066cc; - padding: 60px 20px; -} - -.error-banner { - background-color: #fdf2f2; - border: 1px solid #f8b4b4; - border-left: 5px solid #e53e3e; - border-radius: 8px; - padding: 20px 24px; - margin: 20px auto; - max-width: 800px; - color: #9b2c2c; -} - -.error-banner h2 { - font-size: 1.25rem; - margin-bottom: 8px; - color: #c53030; -} - -.error-banner p { - font-size: 0.95rem; - margin: 0; -} - -/* ================================ - 4. Episodes Grid - ================================ */ -.episodes-container { - display: grid; - /* Automatically fits as many 280px cards per row as possible */ - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 24px; -} - -/* ================================ - 5. Episode Card - ================================ */ -.episode-card { - background-color: #ffffff; - border-radius: 10px; - overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); - display: flex; - flex-direction: column; - transition: transform 0.2s ease, box-shadow 0.2s ease; -} - -.episode-card:hover { - transform: translateY(-4px); - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); -} - -.episode-card h3 { - font-size: 1.1rem; - font-weight: 700; - color: #1a1a1a; - padding: 16px; - text-align: center; - background-color: #f9f9fb; - border-bottom: 1px solid #eeeeee; - min-height: 60px; - display: flex; - align-items: center; - justify-content: center; -} - -.episode-card img { - width: 100%; - height: 200px; - object-fit: cover; - display: block; -} - -.episode-summary { - padding: 16px; - font-size: 0.9rem; - color: #4a4a4a; - flex-grow: 1; - /* Ensures equal-height cards in grid */ -} - -.episode-summary p { - margin-bottom: 8px; -} - -/* ================================ - 6. Footer (TVMaze Attribution) - ================================ */ -#tvmaze-attribution { - text-align: center; - padding: 20px; - margin-top: 40px; - font-size: 0.9rem; - color: #666; - border-top: 1px solid #e0e0e0; -} - -#tvmaze-attribution a { - color: #0066cc; - text-decoration: none; - font-weight: 600; -} - -#tvmaze-attribution a:hover { - text-decoration: underline; -} - -/* ================================ - 7. Mobile Responsiveness - ================================ */ -@media (max-width: 768px) { - - .controls-header, - .controls-container { - flex-direction: column; - align-items: stretch; - } - - .controls-header select, - .controls-header input[type="text"], - .controls-container select, - .controls-container input[type="text"] { - max-width: 100%; - width: 100%; - } - - #search-count { - text-align: center; - } +/* ================================ + 1. Base & Layout Styles + ================================ */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, + Arial, sans-serif; + background-color: #f4f6f8; + color: #333; + line-height: 1.5; + padding-bottom: 60px; +} + +#root { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* ================================ + 2. Controls Container (Top Header Bar) + ================================ */ +.controls-header, +.controls-container { + position: sticky; + top: 0; + z-index: 100; + background-color: #ffffff; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 16px 24px; + margin-bottom: 24px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + border-radius: 8px; +} + +.controls-header select, +.controls-header input[type="text"], +.controls-container select, +.controls-container input[type="text"] { + padding: 10px 14px; + font-size: 0.95rem; + border: 1px solid #ccc; + border-radius: 6px; + outline: none; + background-color: #fff; + transition: border-color 0.2s ease, box-shadow 0.2s ease; + flex: 1 1 200px; + /* Responsive flex sizing for 3 control elements */ + max-width: 280px; +} + +.controls-header select:focus, +.controls-header input[type="text"]:focus, +.controls-container select:focus, +.controls-container input[type="text"]:focus { + border-color: #0066cc; + box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.15); +} + +#search-count { + font-weight: 600; + color: #555; + font-size: 0.95rem; + white-space: nowrap; +} + +/* ================================ + 3. Loading & Error Feedback States + ================================ */ +.loading-state { + text-align: center; + font-size: 1.2rem; + font-weight: 600; + color: #0066cc; + padding: 60px 20px; +} + +.error-banner { + background-color: #fdf2f2; + border: 1px solid #f8b4b4; + border-left: 5px solid #e53e3e; + border-radius: 8px; + padding: 20px 24px; + margin: 20px auto; + max-width: 800px; + color: #9b2c2c; +} + +.error-banner h2 { + font-size: 1.25rem; + margin-bottom: 8px; + color: #c53030; +} + +.error-banner p { + font-size: 0.95rem; + margin: 0; +} + +/* ================================ + 4. Episodes Grid + ================================ */ +.episodes-container { + display: grid; + /* Automatically fits as many 280px cards per row as possible */ + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 24px; +} + +/* ================================ + 5. Episode Card + ================================ */ +.episode-card { + background-color: #ffffff; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + display: flex; + flex-direction: column; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.episode-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); +} + +.episode-card h3 { + font-size: 1.1rem; + font-weight: 700; + color: #1a1a1a; + padding: 16px; + text-align: center; + background-color: #f9f9fb; + border-bottom: 1px solid #eeeeee; + min-height: 60px; + display: flex; + align-items: center; + justify-content: center; +} + +.episode-card img { + width: 100%; + height: 200px; + object-fit: cover; + display: block; +} + +.episode-summary { + padding: 16px; + font-size: 0.9rem; + color: #4a4a4a; + flex-grow: 1; + /* Ensures equal-height cards in grid */ +} + +.episode-summary p { + margin-bottom: 8px; +} + +/* ================================ + 6. Footer (TVMaze Attribution) + ================================ */ +#tvmaze-attribution { + text-align: center; + padding: 20px; + margin-top: 40px; + font-size: 0.9rem; + color: #666; + border-top: 1px solid #e0e0e0; +} + +#tvmaze-attribution a { + color: #0066cc; + text-decoration: none; + font-weight: 600; +} + +#tvmaze-attribution a:hover { + text-decoration: underline; +} + +/* ================================ + 7. Mobile Responsiveness + ================================ */ +@media (max-width: 768px) { + + .controls-header, + .controls-container { + flex-direction: column; + align-items: stretch; + } + + .controls-header select, + .controls-header input[type="text"], + .controls-container select, + .controls-container input[type="text"] { + max-width: 100%; + width: 100%; + } + + #search-count { + text-align: center; + } } \ No newline at end of file From 0c36f2344fe03ad978b5683f25010461e76b20c9 Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Mon, 10 Aug 2026 18:44:46 +0100 Subject: [PATCH 21/24] commit to the js file --- script.js | 232 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 161 insertions(+), 71 deletions(-) diff --git a/script.js b/script.js index ac19e5a..ff04859 100644 --- a/script.js +++ b/script.js @@ -1,115 +1,201 @@ -// Global State & Cache -let allShows = []; let allEpisodes = []; +let allShows = []; +const cache = {}; // Rule 6: In-memory cache to prevent duplicate fetches -function setup() { - allEpisodes = getAllEpisodes(); - createControls(); - makePageForEpisodes(allEpisodes); +async function setup() { addTvmazeAttribution(); + showLoading("Loading shows, please wait..."); + + try { + // 1. Fetch all shows on page load (cached) + allShows = await fetchWithCache("https://api.tvmaze.com/shows"); + + // Rule 5: Sort shows alphabetically, case-insensitive + allShows.sort((a, b) => + a.name.localeCompare(b.name, undefined, { sensitivity: "base" }), + ); + + // Clear root and create UI controls + const rootElem = document.getElementById("root"); + rootElem.innerHTML = ""; + createControls(); + + // Select the first show in the list by default + if (allShows.length > 0) { + const showSelect = document.getElementById("show-select"); + showSelect.value = allShows[0].id; + await loadEpisodesForShow(allShows[0].id); + } + } catch (error) { + showError("Failed to load TV shows. Please try again later."); + } } -function createControls() { - const rootElem = document.getElementById("root"); - rootElem.innerHTML = `

Loading episodes...

`; +// Helper function to handle cached fetches (Rule 6) +async function fetchWithCache(url) { + if (cache[url]) { + return cache[url]; + } - // Reset controls - const searchInput = document.getElementById("search-input"); - if (searchInput) searchInput.value = ""; + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + cache[url] = data; + return data; +} + +// Fetch and load episodes for a selected show ID +async function loadEpisodesForShow(showId) { + showLoading("Loading episodes, please wait..."); try { - // Check if show episodes are already cached - if (showsCache[showId]) { - allEpisodes = showsCache[showId]; - } else { - // Fetch ONCE and cache - const response = await fetch( - `https://api.tvmaze.com/shows/${showId}/episodes`, - ); - if (!response.ok) { - throw new Error(`Failed to load episodes (${response.status})`); - } - allEpisodes = await response.json(); - showsCache[showId] = allEpisodes; // Cache in memory - } + const url = `https://api.tvmaze.com/shows/${showId}/episodes`; + allEpisodes = await fetchWithCache(url); + + // Reset controls + populateEpisodeSelect(allEpisodes); + const searchInput = document.getElementById("search-input"); + if (searchInput) searchInput.value = ""; - // Populate episode controls & display UI - populateSelectDropdown(allEpisodes); makePageForEpisodes(allEpisodes); - updateSearchCount(allEpisodes.length, allEpisodes.length); } catch (error) { - rootElem.innerHTML = ` -
-

Unable to load episodes

-

Error: ${error.message}. Please try selecting another show.

-
- `; + showError("Failed to load episode data. Please try again later."); } } -// Show Select Handler -async function handleShowSelect(event) { - const selectedShowId = event.target.value; - if (!selectedShowId) return; - await loadEpisodesForShow(selectedShowId); +function showLoading(message = "Loading, please wait...") { + const rootElem = document.getElementById("root"); + rootElem.innerHTML = ` +
+
+

${message}

+
+ `; } -// Populate Episode Selector -function populateSelectDropdown(episodes) { - const select = document.getElementById("episode-select"); - if (!select) return; +function showError(message) { + const rootElem = document.getElementById("root"); + rootElem.innerHTML = ` +
+

Something went wrong

+

${message}

+
+ `; +} - select.innerHTML = ''; +function createControls() { + const rootElem = document.getElementById("root"); - episodes.forEach((episode) => { + // Prevent duplicate control bars on re-render + if (document.querySelector(".controls-container")) return; + + const controlsContainer = document.createElement("div"); + controlsContainer.className = "controls-container"; + + // 1. Show Select Dropdown + const showSelect = document.createElement("select"); + showSelect.id = "show-select"; + + allShows.forEach((show) => { const option = document.createElement("option"); - option.value = episode.id; - const code = formatEpisodeCode(episode.season, episode.number); - option.textContent = `${code} - ${episode.name}`; - select.appendChild(option); + option.value = show.id; + option.textContent = show.name; + showSelect.appendChild(option); }); + + // 2. Episode Select Dropdown + const episodeSelect = document.createElement("select"); + episodeSelect.id = "episode-select"; + + // 3. Search Input + const searchInput = document.createElement("input"); + searchInput.type = "text"; + searchInput.id = "search-input"; + searchInput.placeholder = "Search episodes..."; + + // 4. Count Display + const countDisplay = document.createElement("span"); + countDisplay.id = "search-count"; + + controlsContainer.appendChild(showSelect); + controlsContainer.appendChild(episodeSelect); + controlsContainer.appendChild(searchInput); + controlsContainer.appendChild(countDisplay); + + rootElem.parentNode.insertBefore(controlsContainer, rootElem); + + // Event Listeners + showSelect.addEventListener("change", handleShowChange); + episodeSelect.addEventListener("change", handleSelect); + searchInput.addEventListener("input", handleSearch); } -// Live Search Handler -function handleSearch(event) { - const searchTerm = event.target.value.toLowerCase().trim(); +// Handler when user selects a different TV show +async function handleShowChange(event) { + const showId = event.target.value; + if (showId) { + await loadEpisodesForShow(showId); + } +} +// Populates/Updates the episode dropdown options +function populateEpisodeSelect(episodes) { const episodeSelect = document.getElementById("episode-select"); - if (episodeSelect) episodeSelect.value = "ALL"; + if (!episodeSelect) return; - const filteredEpisodes = allEpisodes.filter((episode) => { - const nameMatches = episode.name.toLowerCase().includes(searchTerm); - const summaryMatches = episode.summary - ? episode.summary.toLowerCase().includes(searchTerm) - : false; + episodeSelect.innerHTML = ""; - return nameMatches || summaryMatches; - }); + const defaultOption = document.createElement("option"); + defaultOption.value = "ALL"; + defaultOption.textContent = "All Episodes"; + episodeSelect.appendChild(defaultOption); - makePageForEpisodes(filteredEpisodes); - updateSearchCount(filteredEpisodes.length, allEpisodes.length); + episodes.forEach((episode) => { + const option = document.createElement("option"); + option.value = episode.id; + const code = formatEpisodeCode(episode.season, episode.number); + option.textContent = `${code} - ${episode.name}`; + episodeSelect.appendChild(option); + }); } -// Episode Dropdown Selector Handler function handleSelect(event) { const selectedId = event.target.value; - const searchInput = document.getElementById("search-input"); + if (searchInput) searchInput.value = ""; if (selectedId === "ALL") { makePageForEpisodes(allEpisodes); - updateSearchCount(allEpisodes.length, allEpisodes.length); } else { const selectedEpisode = allEpisodes.filter( (episode) => String(episode.id) === String(selectedId), ); makePageForEpisodes(selectedEpisode); - updateSearchCount(selectedEpisode.length, allEpisodes.length); } } -// Update Search Counter +function handleSearch(event) { + const searchTerm = event.target.value.toLowerCase().trim(); + const episodeSelect = document.getElementById("episode-select"); + + if (episodeSelect) episodeSelect.value = "ALL"; + + const filteredEpisodes = allEpisodes.filter((episode) => { + const nameMatches = episode.name.toLowerCase().includes(searchTerm); + const summaryMatches = (episode.summary || "") + .toLowerCase() + .includes(searchTerm); + + return nameMatches || summaryMatches; + }); + + makePageForEpisodes(filteredEpisodes); +} + function updateSearchCount(matchCount, totalCount) { const countDisplay = document.getElementById("search-count"); if (countDisplay) { @@ -117,18 +203,22 @@ function updateSearchCount(matchCount, totalCount) { } } -// Helper: Format S01E01 function formatEpisodeCode(season, number) { const paddedSeason = String(season).padStart(2, "0"); const paddedNumber = String(number).padStart(2, "0"); return `S${paddedSeason}E${paddedNumber}`; } -// Render Episodes Page function makePageForEpisodes(episodeList) { const rootElem = document.getElementById("root"); rootElem.innerHTML = ""; + if (episodeList.length === 0) { + rootElem.innerHTML = "

No episodes found matching your criteria.

"; + updateSearchCount(0, allEpisodes.length); + return; + } + const container = document.createElement("div"); container.className = "episodes-container"; @@ -157,7 +247,7 @@ function makePageForEpisodes(episodeList) { }); rootElem.appendChild(container); - addTvmazeAttribution(); + updateSearchCount(episodeList.length, allEpisodes.length); } function addTvmazeAttribution() { From cbd5508a5b30133e6401e1f32bd53131a98640ef Mon Sep 17 00:00:00 2001 From: Ogbemi mene Date: Mon, 10 Aug 2026 18:47:13 +0100 Subject: [PATCH 22/24] html commit --- index.html | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/index.html b/index.html index fb0ec7f..746d690 100644 --- a/index.html +++ b/index.html @@ -10,23 +10,10 @@ -
- - - - -
-
-
- - + From 1fb7091a88db803e1e5fbf5e240b0b223ff6019e Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 10 Aug 2026 23:03:46 +0100 Subject: [PATCH 23/24] feat(level-500): implement shows listing, search, back navigation, and accessibility fixes --- index.html | 7 +- script.js | 204 ++++++++++++++++++++++++++++------ style.css | 317 +++++++++++++++++++++++++++++++---------------------- 3 files changed, 355 insertions(+), 173 deletions(-) diff --git a/index.html b/index.html index 746d690..87b70ac 100644 --- a/index.html +++ b/index.html @@ -10,10 +10,9 @@ -
-
- - + +
+
diff --git a/script.js b/script.js index ff04859..e74a694 100644 --- a/script.js +++ b/script.js @@ -1,6 +1,7 @@ let allEpisodes = []; let allShows = []; const cache = {}; // Rule 6: In-memory cache to prevent duplicate fetches +let currentView = "shows"; // State flag: "shows" or "episodes" async function setup() { addTvmazeAttribution(); @@ -15,17 +16,8 @@ async function setup() { a.name.localeCompare(b.name, undefined, { sensitivity: "base" }), ); - // Clear root and create UI controls - const rootElem = document.getElementById("root"); - rootElem.innerHTML = ""; createControls(); - - // Select the first show in the list by default - if (allShows.length > 0) { - const showSelect = document.getElementById("show-select"); - showSelect.value = allShows[0].id; - await loadEpisodesForShow(allShows[0].id); - } + renderShowsListing(allShows); } catch (error) { showError("Failed to load TV shows. Please try again later."); } @@ -39,7 +31,7 @@ async function fetchWithCache(url) { const response = await fetch(url); if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + throw new Error(`HTTP error status: ${response.status}`); } const data = await response.json(); @@ -55,7 +47,12 @@ async function loadEpisodesForShow(showId) { const url = `https://api.tvmaze.com/shows/${showId}/episodes`; allEpisodes = await fetchWithCache(url); - // Reset controls + currentView = "episodes"; + updateViewControls(); + + const showSelect = document.getElementById("show-select"); + if (showSelect) showSelect.value = showId; + populateEpisodeSelect(allEpisodes); const searchInput = document.getElementById("search-input"); if (searchInput) searchInput.value = ""; @@ -87,17 +84,36 @@ function showError(message) { } function createControls() { - const rootElem = document.getElementById("root"); - - // Prevent duplicate control bars on re-render if (document.querySelector(".controls-container")) return; - const controlsContainer = document.createElement("div"); + const controlsContainer = document.createElement("nav"); controlsContainer.className = "controls-container"; + controlsContainer.setAttribute("aria-label", "Search and view controls"); + + // Navigation Back Link + const backBtn = document.createElement("button"); + backBtn.id = "back-to-shows-btn"; + backBtn.className = "nav-btn hidden"; + backBtn.textContent = "◄ Back to Shows"; + backBtn.addEventListener("click", () => { + currentView = "shows"; + const searchInput = document.getElementById("search-input"); + if (searchInput) searchInput.value = ""; + const showSelect = document.getElementById("show-select"); + if (showSelect) showSelect.value = ""; + updateViewControls(); + renderShowsListing(allShows); + }); // 1. Show Select Dropdown const showSelect = document.createElement("select"); showSelect.id = "show-select"; + showSelect.setAttribute("aria-label", "Select a TV Show"); + + const defaultShowOption = document.createElement("option"); + defaultShowOption.value = ""; + defaultShowOption.textContent = "Select a Show..."; + showSelect.appendChild(defaultShowOption); allShows.forEach((show) => { const option = document.createElement("option"); @@ -109,22 +125,27 @@ function createControls() { // 2. Episode Select Dropdown const episodeSelect = document.createElement("select"); episodeSelect.id = "episode-select"; + episodeSelect.setAttribute("aria-label", "Select an Episode"); // 3. Search Input const searchInput = document.createElement("input"); searchInput.type = "text"; searchInput.id = "search-input"; - searchInput.placeholder = "Search episodes..."; + searchInput.placeholder = "Search shows..."; + searchInput.setAttribute("aria-label", "Search shows or episodes"); // 4. Count Display const countDisplay = document.createElement("span"); countDisplay.id = "search-count"; + countDisplay.setAttribute("aria-live", "polite"); + controlsContainer.appendChild(backBtn); controlsContainer.appendChild(showSelect); controlsContainer.appendChild(episodeSelect); controlsContainer.appendChild(searchInput); controlsContainer.appendChild(countDisplay); + const rootElem = document.getElementById("root"); rootElem.parentNode.insertBefore(controlsContainer, rootElem); // Event Listeners @@ -133,15 +154,105 @@ function createControls() { searchInput.addEventListener("input", handleSearch); } -// Handler when user selects a different TV show +function updateViewControls() { + const backBtn = document.getElementById("back-to-shows-btn"); + const episodeSelect = document.getElementById("episode-select"); + const searchInput = document.getElementById("search-input"); + + if (currentView === "shows") { + if (backBtn) backBtn.classList.add("hidden"); + if (episodeSelect) episodeSelect.classList.add("hidden"); + if (searchInput) + searchInput.placeholder = "Search shows (name, genre, summary)..."; + } else { + if (backBtn) backBtn.classList.remove("hidden"); + if (episodeSelect) episodeSelect.classList.remove("hidden"); + if (searchInput) searchInput.placeholder = "Search episodes..."; + } +} + +// Render Shows Page (2 Columns Grid) +function renderShowsListing(showsList) { + currentView = "shows"; + updateViewControls(); + + const rootElem = document.getElementById("root"); + rootElem.innerHTML = ""; + + if (showsList.length === 0) { + rootElem.innerHTML = "

No shows found matching your search criteria.

"; + updateSearchCount(0, allShows.length, "shows"); + return; + } + + const container = document.createElement("div"); + container.className = "shows-container"; + + showsList.forEach((show) => { + const card = document.createElement("article"); + card.className = "show-card"; + + // Header Title + const title = document.createElement("h2"); + title.textContent = show.name; + title.addEventListener("click", () => loadEpisodesForShow(show.id)); + + // Body Container (Image + Summary) + const bodyDiv = document.createElement("div"); + bodyDiv.className = "show-card-body"; + + // Media / Image Section + const mediaDiv = document.createElement("div"); + mediaDiv.className = "show-card-media"; + if (show.image && show.image.medium) { + const img = document.createElement("img"); + img.src = show.image.medium; + img.alt = show.name; + mediaDiv.appendChild(img); + } + mediaDiv.addEventListener("click", () => loadEpisodesForShow(show.id)); + + // Summary Section + const contentDiv = document.createElement("div"); + contentDiv.className = "show-card-content"; + const summary = document.createElement("div"); + summary.className = "show-card-summary"; + summary.innerHTML = show.summary || "

No summary available.

"; + contentDiv.appendChild(summary); + + bodyDiv.appendChild(mediaDiv); + bodyDiv.appendChild(contentDiv); + + // Footer Metadata Section + const metaDiv = document.createElement("div"); + metaDiv.className = "show-card-meta"; + metaDiv.innerHTML = ` +

Rated: ${show.rating && show.rating.average ? show.rating.average : "N/A"}

+

Genres: ${show.genres && show.genres.length > 0 ? show.genres.join(", ") : "N/A"}

+

Status: ${show.status || "N/A"}

+

Runtime: ${show.runtime ? show.runtime + " min" : "N/A"}

+ `; + + card.appendChild(title); + card.appendChild(bodyDiv); + card.appendChild(metaDiv); + + container.appendChild(card); + }); + + rootElem.appendChild(container); + updateSearchCount(showsList.length, allShows.length, "shows"); +} + async function handleShowChange(event) { const showId = event.target.value; if (showId) { await loadEpisodesForShow(showId); + } else { + renderShowsListing(allShows); } } -// Populates/Updates the episode dropdown options function populateEpisodeSelect(episodes) { const episodeSelect = document.getElementById("episode-select"); if (!episodeSelect) return; @@ -180,26 +291,42 @@ function handleSelect(event) { function handleSearch(event) { const searchTerm = event.target.value.toLowerCase().trim(); - const episodeSelect = document.getElementById("episode-select"); - if (episodeSelect) episodeSelect.value = "ALL"; + if (currentView === "shows") { + const filteredShows = allShows.filter((show) => { + const nameMatch = show.name.toLowerCase().includes(searchTerm); + const summaryMatch = (show.summary || "") + .toLowerCase() + .includes(searchTerm); + const genreMatch = show.genres + ? show.genres.some((g) => g.toLowerCase().includes(searchTerm)) + : false; - const filteredEpisodes = allEpisodes.filter((episode) => { - const nameMatches = episode.name.toLowerCase().includes(searchTerm); - const summaryMatches = (episode.summary || "") - .toLowerCase() - .includes(searchTerm); + return nameMatch || summaryMatch || genreMatch; + }); - return nameMatches || summaryMatches; - }); + renderShowsListing(filteredShows); + } else { + const episodeSelect = document.getElementById("episode-select"); + if (episodeSelect) episodeSelect.value = "ALL"; + + const filteredEpisodes = allEpisodes.filter((episode) => { + const nameMatches = episode.name.toLowerCase().includes(searchTerm); + const summaryMatches = (episode.summary || "") + .toLowerCase() + .includes(searchTerm); - makePageForEpisodes(filteredEpisodes); + return nameMatches || summaryMatches; + }); + + makePageForEpisodes(filteredEpisodes); + } } -function updateSearchCount(matchCount, totalCount) { +function updateSearchCount(matchCount, totalCount, type = "episodes") { const countDisplay = document.getElementById("search-count"); if (countDisplay) { - countDisplay.textContent = `Displaying ${matchCount}/${totalCount} episodes`; + countDisplay.textContent = `Displaying ${matchCount}/${totalCount} ${type}`; } } @@ -209,13 +336,14 @@ function formatEpisodeCode(season, number) { return `S${paddedSeason}E${paddedNumber}`; } +// Render Episodes Page function makePageForEpisodes(episodeList) { const rootElem = document.getElementById("root"); rootElem.innerHTML = ""; if (episodeList.length === 0) { rootElem.innerHTML = "

No episodes found matching your criteria.

"; - updateSearchCount(0, allEpisodes.length); + updateSearchCount(0, allEpisodes.length, "episodes"); return; } @@ -223,14 +351,19 @@ function makePageForEpisodes(episodeList) { container.className = "episodes-container"; episodeList.forEach((episode) => { - const card = document.createElement("section"); + const card = document.createElement("article"); card.className = "episode-card"; + // Card Header + const header = document.createElement("div"); + header.className = "episode-header"; const title = document.createElement("h3"); const code = formatEpisodeCode(episode.season, episode.number); title.textContent = `${episode.name} - ${code}`; - card.appendChild(title); + header.appendChild(title); + card.appendChild(header); + // Episode Image if (episode.image && episode.image.medium) { const img = document.createElement("img"); img.src = episode.image.medium; @@ -238,6 +371,7 @@ function makePageForEpisodes(episodeList) { card.appendChild(img); } + // Episode Summary const summary = document.createElement("div"); summary.className = "episode-summary"; summary.innerHTML = episode.summary || "

No summary available.

"; @@ -247,7 +381,7 @@ function makePageForEpisodes(episodeList) { }); rootElem.appendChild(container); - updateSearchCount(episodeList.length, allEpisodes.length); + updateSearchCount(episodeList.length, allEpisodes.length, "episodes"); } function addTvmazeAttribution() { diff --git a/style.css b/style.css index 5942f31..0aedaa4 100644 --- a/style.css +++ b/style.css @@ -1,214 +1,263 @@ -/* ================================ - 1. Base & Layout Styles - ================================ */ +/* General Layout & Fonts */ * { box-sizing: border-box; - margin: 0; - padding: 0; } body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, - Arial, sans-serif; - background-color: #f4f6f8; - color: #333; - line-height: 1.5; - padding-bottom: 60px; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, + sans-serif; + margin: 0; + padding: 0; + background-color: #f7fafc; + color: #2d3748; } -#root { - max-width: 1200px; - margin: 0 auto; - padding: 20px; +.hidden { + display: none !important; } -/* ================================ - 2. Controls Container (Top Header Bar) - ================================ */ -.controls-header, +/* Controls Container Styling */ .controls-container { - position: sticky; - top: 0; - z-index: 100; - background-color: #ffffff; display: flex; flex-wrap: wrap; align-items: center; - justify-content: space-between; gap: 12px; + background-color: #2b6cb0; padding: 16px 24px; - margin-bottom: 24px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - border-radius: 8px; + position: sticky; + top: 0; + z-index: 100; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); } -.controls-header select, -.controls-header input[type="text"], .controls-container select, .controls-container input[type="text"] { - padding: 10px 14px; - font-size: 0.95rem; - border: 1px solid #ccc; + padding: 8px 12px; + border: 1px solid #cbd5e0; border-radius: 6px; + font-size: 0.95rem; outline: none; - background-color: #fff; - transition: border-color 0.2s ease, box-shadow 0.2s ease; - flex: 1 1 200px; - /* Responsive flex sizing for 3 control elements */ - max-width: 280px; } -.controls-header select:focus, -.controls-header input[type="text"]:focus, -.controls-container select:focus, -.controls-container input[type="text"]:focus { - border-color: #0066cc; - box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.15); +.controls-container select { + max-width: 220px; + background-color: #ffffff; +} + +.controls-container input[type="text"] { + flex: 1; + min-width: 200px; +} + +.nav-btn { + padding: 8px 16px; + background-color: #3182ce; + color: #ffffff; + border: 1px solid #63b3ed; + border-radius: 6px; + cursor: pointer; + font-weight: 600; + transition: background-color 0.2s ease; +} + +.nav-btn:hover { + background-color: #2b6cb0; } #search-count { + color: #ffffff; font-weight: 600; - color: #555; - font-size: 0.95rem; - white-space: nowrap; + font-size: 0.9rem; + margin-left: auto; } -/* ================================ - 3. Loading & Error Feedback States - ================================ */ -.loading-state { +/* Loading & Error States */ +.loading-container, +.error-container { text-align: center; - font-size: 1.2rem; - font-weight: 600; - color: #0066cc; padding: 60px 20px; } -.error-banner { - background-color: #fdf2f2; - border: 1px solid #f8b4b4; - border-left: 5px solid #e53e3e; +.spinner { + width: 40px; + height: 40px; + margin: 0 auto 16px; + border: 4px solid #e2e8f0; + border-top: 4px solid #3182ce; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* Main Container Layouts */ +#root { + max-width: 1200px; + margin: 24px auto; + padding: 0 16px; +} + +/* Shows Listing Layout - 2 Side by Side */ +.shows-container { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 24px; +} + +.show-card { + display: flex; + flex-direction: column; + gap: 16px; + background-color: #ffffff; + border: 1px solid #e2e8f0; border-radius: 8px; - padding: 20px 24px; - margin: 20px auto; - max-width: 800px; - color: #9b2c2c; + padding: 20px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + transition: + transform 0.15s ease, + box-shadow 0.15s ease; } -.error-banner h2 { - font-size: 1.25rem; - margin-bottom: 8px; - color: #c53030; +.show-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); } -.error-banner p { - font-size: 0.95rem; +.show-card-body { + display: flex; + gap: 16px; + align-items: flex-start; +} + +.show-card-media { + flex-shrink: 0; + width: 120px; + cursor: pointer; +} + +.show-card-media img { + width: 100%; + border-radius: 6px; + object-fit: cover; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.show-card-content { + flex: 1; +} + +.show-card-content h2 { + margin: 0 0 8px 0; + font-size: 1.2rem; + color: #2b6cb0; + cursor: pointer; +} + +.show-card-content h2:hover { + text-decoration: underline; +} + +.show-card-summary { + font-size: 0.88rem; + line-height: 1.4; + color: #4a5568; +} + +.show-card-meta { + background-color: #f7fafc; + border-top: 1px solid #e2e8f0; + padding: 10px 12px; + border-radius: 6px; + display: flex; + flex-wrap: wrap; + gap: 12px 20px; + font-size: 0.85rem; + margin-top: auto; +} + +.show-card-meta p { margin: 0; + color: #4a5568; } -/* ================================ - 4. Episodes Grid - ================================ */ +/* Episodes Grid Layout */ .episodes-container { display: grid; - /* Automatically fits as many 280px cards per row as possible */ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 24px; } -/* ================================ - 5. Episode Card - ================================ */ .episode-card { background-color: #ffffff; - border-radius: 10px; + border: 1px solid #e2e8f0; + border-radius: 8px; overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); display: flex; flex-direction: column; - transition: transform 0.2s ease, box-shadow 0.2s ease; } -.episode-card:hover { - transform: translateY(-4px); - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); +.episode-header { + background-color: #edf2f7; + padding: 12px 16px; + text-align: center; + border-bottom: 1px solid #e2e8f0; } -.episode-card h3 { - font-size: 1.1rem; - font-weight: 700; - color: #1a1a1a; - padding: 16px; - text-align: center; - background-color: #f9f9fb; - border-bottom: 1px solid #eeeeee; - min-height: 60px; - display: flex; - align-items: center; - justify-content: center; +.episode-header h3 { + margin: 0; + font-size: 1.05rem; + color: #2b6cb0; } .episode-card img { width: 100%; - height: 200px; + height: 160px; object-fit: cover; - display: block; + background-color: #e2e8f0; } .episode-summary { padding: 16px; font-size: 0.9rem; - color: #4a4a4a; - flex-grow: 1; - /* Ensures equal-height cards in grid */ -} - -.episode-summary p { - margin-bottom: 8px; + line-height: 1.5; + color: #4a5568; + flex: 1; } -/* ================================ - 6. Footer (TVMaze Attribution) - ================================ */ +/* High Contrast TVMaze Footer */ #tvmaze-attribution { text-align: center; - padding: 20px; - margin-top: 40px; + padding: 24px 16px; + color: #2d3748; font-size: 0.9rem; - color: #666; - border-top: 1px solid #e0e0e0; + border-top: 1px solid #cbd5e0; + margin-top: 40px; } #tvmaze-attribution a { - color: #0066cc; - text-decoration: none; + color: #1a202c; font-weight: 600; -} - -#tvmaze-attribution a:hover { text-decoration: underline; } -/* ================================ - 7. Mobile Responsiveness - ================================ */ -@media (max-width: 768px) { - - .controls-header, - .controls-container { - flex-direction: column; - align-items: stretch; - } - - .controls-header select, - .controls-header input[type="text"], - .controls-container select, - .controls-container input[type="text"] { - max-width: 100%; - width: 100%; - } +#tvmaze-attribution a:hover, +#tvmaze-attribution a:focus { + color: #2b6cb0; + outline: 2px solid #2b6cb0; +} - #search-count { - text-align: center; +/* Responsive adjustment */ +@media (max-width: 820px) { + .shows-container { + grid-template-columns: 1fr; } -} \ No newline at end of file +} From 6856b06e30868e7298c350c93d655419e7f99f04 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Sat, 15 Aug 2026 14:15:32 +0100 Subject: [PATCH 24/24] feat(styles): enhance dark theme with improved accessibility and layout adjustments --- style.css | 178 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 115 insertions(+), 63 deletions(-) diff --git a/style.css b/style.css index 0aedaa4..2f51895 100644 --- a/style.css +++ b/style.css @@ -9,8 +9,8 @@ body { sans-serif; margin: 0; padding: 0; - background-color: #f7fafc; - color: #2d3748; + background-color: #000000; /* True black background */ + color: #f8fafc; /* High contrast white/slate for maximum legibility */ } .hidden { @@ -23,26 +23,35 @@ body { flex-wrap: wrap; align-items: center; gap: 12px; - background-color: #2b6cb0; + background-color: #121214; /* Deep dark surface */ + border-bottom: 1px solid #27272a; padding: 16px 24px; position: sticky; top: 0; z-index: 100; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); } .controls-container select, .controls-container input[type="text"] { - padding: 8px 12px; - border: 1px solid #cbd5e0; + padding: 10px 14px; + background-color: #18181b; + color: #ffffff; + border: 1px solid #3f3f46; border-radius: 6px; - font-size: 0.95rem; + font-size: 1rem; outline: none; } +.controls-container select:focus, +.controls-container input[type="text"]:focus { + border-color: #38bdf8; + outline: 3px solid #38bdf8; + outline-offset: 1px; +} + .controls-container select { max-width: 220px; - background-color: #ffffff; } .controls-container input[type="text"] { @@ -51,24 +60,30 @@ body { } .nav-btn { - padding: 8px 16px; - background-color: #3182ce; - color: #ffffff; - border: 1px solid #63b3ed; + padding: 10px 18px; + background-color: #1e293b; + color: #f8fafc; + border: 2px solid #38bdf8; border-radius: 6px; cursor: pointer; font-weight: 600; - transition: background-color 0.2s ease; + transition: + background-color 0.2s ease, + transform 0.15s ease; } -.nav-btn:hover { - background-color: #2b6cb0; +.nav-btn:hover, +.nav-btn:focus { + background-color: #2563eb; + color: #ffffff; + outline: 3px solid #ffffff; + outline-offset: 2px; } #search-count { - color: #ffffff; + color: #cbd5e1; font-weight: 600; - font-size: 0.9rem; + font-size: 0.95rem; margin-left: auto; } @@ -77,14 +92,15 @@ body { .error-container { text-align: center; padding: 60px 20px; + color: #f8fafc; } .spinner { width: 40px; height: 40px; margin: 0 auto 16px; - border: 4px solid #e2e8f0; - border-top: 4px solid #3182ce; + border: 4px solid #27272a; + border-top: 4px solid #38bdf8; border-radius: 50%; animation: spin 1s linear infinite; } @@ -106,48 +122,66 @@ body { } /* Shows Listing Layout - 2 Side by Side */ +/* Shows Listing Layout - 3 Side by Side */ .shows-container { display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 24px; + grid-template-columns: repeat(3, 1fr); + gap: 20px; +} + +/* Updated Responsive Breakpoints for 3-Column Layout */ +@media (max-width: 1024px) { + .shows-container { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 680px) { + .shows-container { + grid-template-columns: 1fr; + } } .show-card { display: flex; flex-direction: column; gap: 16px; - background-color: #ffffff; - border: 1px solid #e2e8f0; - border-radius: 8px; + background-color: #121214; /* Deep dark container card */ + border: 2px solid #27272a; + border-radius: 10px; padding: 20px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6); transition: transform 0.15s ease, + border-color 0.15s ease, box-shadow 0.15s ease; } -.show-card:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); +/* Enhanced focus state for TV remote D-pad navigation */ +.show-card:hover, +.show-card:focus-within { + transform: scale(1.02); + border-color: #38bdf8; + box-shadow: + 0 0 0 4px rgba(56, 189, 248, 0.3), + 0 8px 24px rgba(0, 0, 0, 0.8); } .show-card-body { - display: flex; - gap: 16px; - align-items: flex-start; + flex-direction: column; /* Stacks image on top of text inside narrow cards */ + align-items: center; } .show-card-media { - flex-shrink: 0; - width: 120px; - cursor: pointer; + width: 100%; + max-width: 180px; /* Centers the poster neatly */ } .show-card-media img { width: 100%; border-radius: 6px; object-fit: cover; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); } .show-card-content { @@ -156,36 +190,44 @@ body { .show-card-content h2 { margin: 0 0 8px 0; - font-size: 1.2rem; - color: #2b6cb0; + font-size: 1.3rem; + color: #38bdf8; /* Vibrant accessible accent color on black */ cursor: pointer; } -.show-card-content h2:hover { +.show-card-content h2:hover, +.show-card-content h2:focus { text-decoration: underline; } .show-card-summary { - font-size: 0.88rem; + font-size: 0.92rem; line-height: 1.4; - color: #4a5568; + color: #d4d4d8; /* High contrast muted text */ + + /* CSS Line Clamp */ + display: -webkit-box; + -webkit-line-clamp: 4; /* Limits description to 4 lines */ + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; } .show-card-meta { - background-color: #f7fafc; - border-top: 1px solid #e2e8f0; - padding: 10px 12px; + background-color: #18181b; + border-top: 1px solid #27272a; + padding: 12px 14px; border-radius: 6px; display: flex; flex-wrap: wrap; gap: 12px 20px; - font-size: 0.85rem; + font-size: 0.9rem; margin-top: auto; } .show-card-meta p { margin: 0; - color: #4a5568; + color: #a1a1aa; } /* Episodes Grid Layout */ @@ -196,63 +238,73 @@ body { } .episode-card { - background-color: #ffffff; - border: 1px solid #e2e8f0; - border-radius: 8px; + background-color: #121214; + border: 2px solid #27272a; + border-radius: 10px; overflow: hidden; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6); display: flex; flex-direction: column; + transition: + border-color 0.15s ease, + transform 0.15s ease; +} + +.episode-card:focus-within, +.episode-card:hover { + border-color: #38bdf8; + transform: translateY(-3px); } .episode-header { - background-color: #edf2f7; - padding: 12px 16px; + background-color: #18181b; + padding: 14px 16px; text-align: center; - border-bottom: 1px solid #e2e8f0; + border-bottom: 1px solid #27272a; } .episode-header h3 { margin: 0; - font-size: 1.05rem; - color: #2b6cb0; + font-size: 1.1rem; + color: #38bdf8; } .episode-card img { width: 100%; height: 160px; object-fit: cover; - background-color: #e2e8f0; + background-color: #27272a; } .episode-summary { padding: 16px; - font-size: 0.9rem; + font-size: 0.92rem; line-height: 1.5; - color: #4a5568; + color: #d4d4d8; flex: 1; } -/* High Contrast TVMaze Footer */ +/* TVMaze Footer */ #tvmaze-attribution { text-align: center; padding: 24px 16px; - color: #2d3748; + color: #a1a1aa; font-size: 0.9rem; - border-top: 1px solid #cbd5e0; + border-top: 1px solid #27272a; margin-top: 40px; } #tvmaze-attribution a { - color: #1a202c; + color: #38bdf8; font-weight: 600; text-decoration: underline; } #tvmaze-attribution a:hover, #tvmaze-attribution a:focus { - color: #2b6cb0; - outline: 2px solid #2b6cb0; + color: #ffffff; + outline: 3px solid #38bdf8; + outline-offset: 2px; } /* Responsive adjustment */