From dd9b9dceeb7ac372124124d58e68d58930752761 Mon Sep 17 00:00:00 2001 From: Enice-Codes Date: Sat, 8 Aug 2026 14:38:37 +0200 Subject: [PATCH 01/11] used js to replicate tv show --- index.html | 2 +- script.js | 42 ++++++++++++++++++++++++++++++++++++------ style.css | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index 9a400d1..27ba131 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - TV Show Project | My Name (My GitHub username) + TV Show Project | Enice Mutanda(Enice-Codes) diff --git a/script.js b/script.js index 87a7de8..296d27a 100644 --- a/script.js +++ b/script.js @@ -1,12 +1,42 @@ //You can edit ALL of the code here -function setup() { - const allEpisodes = getAllEpisodes(); - makePageForEpisodes(allEpisodes); -} +// no getAllEpisodes() here at all — episodes.js already gives you one function makePageForEpisodes(episodeList) { const rootElem = document.getElementById("root"); - rootElem.textContent = `Got ${episodeList.length} episode(s)`; + rootElem.textContent = ""; + + console.log ("epispodelist:", episodeList.length); + + const counter = document.createElement("p"); + counter.textContent = `Got ${episodeList.length} episode(s)`; + rootElem.appendChild(counter); + + episodeList.forEach((episode) => { + const seasonCode = String(episode.season).padStart(2, "0"); + const episodeCode = String(episode.number).padStart(2, "0"); + const code = `S${seasonCode}E${episodeCode}`; + + const card = document.createElement("div"); + card.className = "episode-card"; + + let heading = document.createElement ("h2"); + heading.textContent = `${code} - ${episode.name}`; + card.appendChild(heading); +console.log(episode.image); + +let image = document.createElement ("img"); + image.setAttribute('src', episode.image.medium); + card.appendChild(image); + +let summary= document.createElement("p"); +summary.textContent = episode.summary; + card.appendChild(summary); + + rootElem.appendChild(card); + }); } -window.onload = setup; +window.onload = function () { + const allEpisodes = getAllEpisodes(); // this calls the one from episodes.js + makePageForEpisodes(allEpisodes); +}; \ No newline at end of file diff --git a/style.css b/style.css index 77cb8d4..de44e6e 100644 --- a/style.css +++ b/style.css @@ -1,3 +1,34 @@ #root { - color: red; + color: rgb(39, 38, 38); + display: flex; + flex-wrap: wrap; + gap: 20px; + padding: 20px; + justify-content: center; } + + +.episode-card { +border: 1px solid whitesmoke; + border-radius: 8px; + padding: 15px; + width: 300px; + +} + +.episode-card h2 { + text-align: center; + font-size: 16px; + margin-top: 0; +} + +.episode-card img { + width: 100%; + border-radius: 4px; + display: block; +} + +.episode-card p { + font-size: 14px; + color: #333; +} \ No newline at end of file From 86a9d5ca1f4bd1a930702d2d6faa1617e7153a89 Mon Sep 17 00:00:00 2001 From: shafiekwalker7861 Date: Tue, 11 Aug 2026 14:12:33 +0200 Subject: [PATCH 02/11] Refactor episode card rendering --- script.js | 51 ++++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/script.js b/script.js index 296d27a..051ae26 100644 --- a/script.js +++ b/script.js @@ -1,42 +1,43 @@ -//You can edit ALL of the code here -// no getAllEpisodes() here at all — episodes.js already gives you one +function createEpisodeCard(episode) { + const seasonNumber = String(episode.season).padStart(2, "0"); + const episodeNumber = String(episode.number).padStart(2, "0"); + const episodeCode = `S${seasonNumber}E${episodeNumber}`; + + const card = document.createElement("div"); + card.className = "episode-card"; + + const heading = document.createElement("h2"); + heading.textContent = `${episodeCode} - ${episode.name}`; + card.appendChild(heading); + + const image = document.createElement("img"); + image.src = episode.image.medium; + image.alt = `Image for ${episode.name}`; + card.appendChild(image); + + const summary = document.createElement("div"); + summary.innerHTML = episode.summary; + card.appendChild(summary); + + return card; +} function makePageForEpisodes(episodeList) { const rootElem = document.getElementById("root"); - rootElem.textContent = ""; - console.log ("epispodelist:", episodeList.length); + rootElem.textContent = ""; const counter = document.createElement("p"); counter.textContent = `Got ${episodeList.length} episode(s)`; rootElem.appendChild(counter); episodeList.forEach((episode) => { - const seasonCode = String(episode.season).padStart(2, "0"); - const episodeCode = String(episode.number).padStart(2, "0"); - const code = `S${seasonCode}E${episodeCode}`; - - const card = document.createElement("div"); - card.className = "episode-card"; - - let heading = document.createElement ("h2"); - heading.textContent = `${code} - ${episode.name}`; - card.appendChild(heading); -console.log(episode.image); - -let image = document.createElement ("img"); - image.setAttribute('src', episode.image.medium); - card.appendChild(image); - -let summary= document.createElement("p"); -summary.textContent = episode.summary; - card.appendChild(summary); - + const card = createEpisodeCard(episode); rootElem.appendChild(card); }); } window.onload = function () { - const allEpisodes = getAllEpisodes(); // this calls the one from episodes.js + const allEpisodes = getAllEpisodes(); makePageForEpisodes(allEpisodes); }; \ No newline at end of file From 04956cffc89af6b38739a9a44974da339350ae9e Mon Sep 17 00:00:00 2001 From: shafiekwalker7861 Date: Mon, 17 Aug 2026 00:03:56 +0200 Subject: [PATCH 03/11] Add live episode search --- script.js | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/script.js b/script.js index 051ae26..db73398 100644 --- a/script.js +++ b/script.js @@ -1,3 +1,5 @@ +let allEpisodes = []; + function createEpisodeCard(episode) { const seasonNumber = String(episode.season).padStart(2, "0"); const episodeNumber = String(episode.number).padStart(2, "0"); @@ -28,7 +30,8 @@ function makePageForEpisodes(episodeList) { rootElem.textContent = ""; const counter = document.createElement("p"); - counter.textContent = `Got ${episodeList.length} episode(s)`; + counter.textContent = + `Displaying ${episodeList.length} / ${allEpisodes.length} episodes`; rootElem.appendChild(counter); episodeList.forEach((episode) => { @@ -37,7 +40,45 @@ function makePageForEpisodes(episodeList) { }); } +function setupSearch() { + const rootElem = document.getElementById("root"); + + const searchContainer = document.createElement("div"); + + const label = document.createElement("label"); + label.setAttribute("for", "search-input"); + label.textContent = "Search episodes: "; + + const searchInput = document.createElement("input"); + searchInput.id = "search-input"; + searchInput.type = "search"; + searchInput.placeholder = "Search by name or summary"; + + searchContainer.appendChild(label); + searchContainer.appendChild(searchInput); + + rootElem.parentNode.insertBefore(searchContainer, rootElem); + + searchInput.addEventListener("input", function () { + const searchTerm = searchInput.value.toLowerCase().trim(); + + const filteredEpisodes = allEpisodes.filter((episode) => { + const episodeName = episode.name.toLowerCase(); + const episodeSummary = (episode.summary || "").toLowerCase(); + + return ( + episodeName.includes(searchTerm) || + episodeSummary.includes(searchTerm) + ); + }); + + makePageForEpisodes(filteredEpisodes); + }); +} + window.onload = function () { - const allEpisodes = getAllEpisodes(); + allEpisodes = getAllEpisodes(); + + setupSearch(); makePageForEpisodes(allEpisodes); }; \ No newline at end of file From e8501ad23ee27c5afab6bed32fc09aff6af74405 Mon Sep 17 00:00:00 2001 From: shafiekwalker7861 Date: Mon, 17 Aug 2026 08:38:39 +0200 Subject: [PATCH 04/11] Add episode selector --- script.js | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/script.js b/script.js index db73398..53139bf 100644 --- a/script.js +++ b/script.js @@ -1,12 +1,18 @@ let allEpisodes = []; -function createEpisodeCard(episode) { +function getEpisodeCode(episode) { const seasonNumber = String(episode.season).padStart(2, "0"); const episodeNumber = String(episode.number).padStart(2, "0"); - const episodeCode = `S${seasonNumber}E${episodeNumber}`; + + return `S${seasonNumber}E${episodeNumber}`; +} + +function createEpisodeCard(episode) { + const episodeCode = getEpisodeCode(episode); const card = document.createElement("div"); card.className = "episode-card"; + card.id = `episode-${episode.id}`; const heading = document.createElement("h2"); heading.textContent = `${episodeCode} - ${episode.name}`; @@ -32,6 +38,7 @@ function makePageForEpisodes(episodeList) { const counter = document.createElement("p"); counter.textContent = `Displaying ${episodeList.length} / ${allEpisodes.length} episodes`; + rootElem.appendChild(counter); episodeList.forEach((episode) => { @@ -76,9 +83,62 @@ function setupSearch() { }); } +function setupEpisodeSelector() { + const rootElem = document.getElementById("root"); + + const selectorContainer = document.createElement("div"); + + const label = document.createElement("label"); + label.setAttribute("for", "episode-selector"); + label.textContent = "Select episode: "; + + const episodeSelector = document.createElement("select"); + episodeSelector.id = "episode-selector"; + + const defaultOption = document.createElement("option"); + defaultOption.value = ""; + defaultOption.textContent = "Choose an episode"; + episodeSelector.appendChild(defaultOption); + + allEpisodes.forEach((episode) => { + const option = document.createElement("option"); + + option.value = episode.id; + option.textContent = `${getEpisodeCode(episode)} - ${episode.name}`; + + episodeSelector.appendChild(option); + }); + + selectorContainer.appendChild(label); + selectorContainer.appendChild(episodeSelector); + + rootElem.parentNode.insertBefore(selectorContainer, rootElem); + + episodeSelector.addEventListener("change", function () { + if (episodeSelector.value === "") { + return; + } + + const searchInput = document.getElementById("search-input"); + searchInput.value = ""; + + makePageForEpisodes(allEpisodes); + + const selectedEpisode = document.getElementById( + `episode-${episodeSelector.value}` + ); + + selectedEpisode.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + }); +} + window.onload = function () { allEpisodes = getAllEpisodes(); setupSearch(); + setupEpisodeSelector(); makePageForEpisodes(allEpisodes); }; \ No newline at end of file From d8232ea2de6248f2868289a72ad5f2606e195e6e Mon Sep 17 00:00:00 2001 From: shafiekwalker7861 Date: Mon, 17 Aug 2026 19:57:03 +0200 Subject: [PATCH 05/11] Load episodes from TVMaze API --- episodes.js | 1855 --------------------------------------------------- index.html | 3 +- script.js | 33 +- 3 files changed, 28 insertions(+), 1863 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", - }, - }, - }, - ]; -} diff --git a/index.html b/index.html index 27ba131..13c884b 100644 --- a/index.html +++ b/index.html @@ -12,8 +12,7 @@
- - + diff --git a/script.js b/script.js index 53139bf..2a1e273 100644 --- a/script.js +++ b/script.js @@ -135,10 +135,31 @@ function setupEpisodeSelector() { }); } -window.onload = function () { - allEpisodes = getAllEpisodes(); +async function setup() { + const rootElem = document.getElementById("root"); + + rootElem.textContent = "Loading episodes..."; + + try { + const response = await fetch( + "https://api.tvmaze.com/shows/82/episodes" + ); + + if (!response.ok) { + throw new Error("Could not load episode data"); + } + + allEpisodes = await response.json(); + + rootElem.textContent = ""; + + setupSearch(); + setupEpisodeSelector(); + makePageForEpisodes(allEpisodes); + } catch (error) { + rootElem.textContent = + "Sorry, we could not load the episodes. Please try again later."; + } +} - setupSearch(); - setupEpisodeSelector(); - makePageForEpisodes(allEpisodes); -}; \ No newline at end of file +window.onload = setup; \ No newline at end of file From b80a01253282ed4d340c0c1ee58cb26f01d84b6a Mon Sep 17 00:00:00 2001 From: Enice-Codes Date: Wed, 19 Aug 2026 12:35:03 +0200 Subject: [PATCH 06/11] followed requirements for level 500 --- index.html | 26 +++---- script.js | 201 +++++++++++++++++++---------------------------------- 2 files changed, 81 insertions(+), 146 deletions(-) diff --git a/index.html b/index.html index 13c884b..c9bacca 100644 --- a/index.html +++ b/index.html @@ -1,20 +1,12 @@ - - - - TV Show Project | Enice Mutanda(Enice-Codes) - - - +
+ +

+
+
- -
-
- - - - - - - + diff --git a/script.js b/script.js index 2a1e273..45f542d 100644 --- a/script.js +++ b/script.js @@ -1,165 +1,108 @@ -let allEpisodes = []; - -function getEpisodeCode(episode) { - const seasonNumber = String(episode.season).padStart(2, "0"); - const episodeNumber = String(episode.number).padStart(2, "0"); - - return `S${seasonNumber}E${episodeNumber}`; -} - -function createEpisodeCard(episode) { - const episodeCode = getEpisodeCode(episode); +const TVMAZE_SHOWS_URL = "https://api.tvmaze.com/shows"; +let allShows = []; +function createShowCard(show) { const card = document.createElement("div"); - card.className = "episode-card"; - card.id = `episode-${episode.id}`; - + card.className = "show-card"; const heading = document.createElement("h2"); - heading.textContent = `${episodeCode} - ${episode.name}`; + heading.textContent = show.name; + heading.style.cursor = "pointer"; + heading.addEventListener("click", () => showEpisodesView(show)); card.appendChild(heading); - const image = document.createElement("img"); - image.src = episode.image.medium; - image.alt = `Image for ${episode.name}`; + image.src = show.image ? show.image.medium : ""; + image.alt = `Image for ${show.name}`; card.appendChild(image); - const summary = document.createElement("div"); - summary.innerHTML = episode.summary; + summary.innerHTML = show.summary || ""; card.appendChild(summary); - + const genres = document.createElement("p"); + genres.textContent = `Genres: ${show.genres.join(", ")}`; + card.appendChild(genres); + const status = document.createElement("p"); + status.textContent = `Status: ${show.status}`; + card.appendChild(status); + const rating = document.createElement("p"); + rating.textContent = `Rating: ${show.rating.average ?? "N/A"}`; + card.appendChild(rating); + const runtime = document.createElement("p"); + runtime.textContent = `Runtime: ${show.runtime ?? "N/A"} min`; + card.appendChild(runtime); return card; } -function makePageForEpisodes(episodeList) { - const rootElem = document.getElementById("root"); - - rootElem.textContent = ""; - - const counter = document.createElement("p"); - counter.textContent = - `Displaying ${episodeList.length} / ${allEpisodes.length} episodes`; +function makePageForShows(showList) { + const showsListElem = document.getElementById("shows-list"); + showsListElem.textContent = ""; - rootElem.appendChild(counter); + const count = document.getElementById("show-search-count"); + count.textContent = `Displaying ${showList.length} / ${allShows.length} shows`; - episodeList.forEach((episode) => { - const card = createEpisodeCard(episode); - rootElem.appendChild(card); + showList.forEach((show) => { + showsListElem.appendChild(createShowCard(show)); }); } -function setupSearch() { - const rootElem = document.getElementById("root"); - - const searchContainer = document.createElement("div"); - - const label = document.createElement("label"); - label.setAttribute("for", "search-input"); - label.textContent = "Search episodes: "; - - const searchInput = document.createElement("input"); - searchInput.id = "search-input"; - searchInput.type = "search"; - searchInput.placeholder = "Search by name or summary"; - - searchContainer.appendChild(label); - searchContainer.appendChild(searchInput); - - rootElem.parentNode.insertBefore(searchContainer, rootElem); - - searchInput.addEventListener("input", function () { - const searchTerm = searchInput.value.toLowerCase().trim(); - - const filteredEpisodes = allEpisodes.filter((episode) => { - const episodeName = episode.name.toLowerCase(); - const episodeSummary = (episode.summary || "").toLowerCase(); - - return ( - episodeName.includes(searchTerm) || - episodeSummary.includes(searchTerm) - ); - }); - - makePageForEpisodes(filteredEpisodes); +function filterShows(searchTerm) { + const term = searchTerm.toLowerCase().trim(); + return allShows.filter((show) => { + const name = show.name.toLowerCase(); + const genres = show.genres.join(" ").toLowerCase(); + const summary = (show.summary || "").toLowerCase(); + return name.includes(term) || genres.includes(term) || summary.includes(term); }); } -function setupEpisodeSelector() { - const rootElem = document.getElementById("root"); - - const selectorContainer = document.createElement("div"); - - const label = document.createElement("label"); - label.setAttribute("for", "episode-selector"); - label.textContent = "Select episode: "; - - const episodeSelector = document.createElement("select"); - episodeSelector.id = "episode-selector"; - - const defaultOption = document.createElement("option"); - defaultOption.value = ""; - defaultOption.textContent = "Choose an episode"; - episodeSelector.appendChild(defaultOption); - - allEpisodes.forEach((episode) => { - const option = document.createElement("option"); - - option.value = episode.id; - option.textContent = `${getEpisodeCode(episode)} - ${episode.name}`; - - episodeSelector.appendChild(option); +function setupShowSearch() { + const input = document.getElementById("show-search-input"); + input.addEventListener("input", () => { + makePageForShows(filterShows(input.value)); }); +} - selectorContainer.appendChild(label); - selectorContainer.appendChild(episodeSelector); +const episodeCache = {}; // showId -> episodes array - rootElem.parentNode.insertBefore(selectorContainer, rootElem); +async function showEpisodesView(show) { + document.getElementById("shows-view").style.display = "none"; + document.getElementById("episodes-view").style.display = "block"; - episodeSelector.addEventListener("change", function () { - if (episodeSelector.value === "") { - return; - } + const oldSearch = document.getElementById("search-input"); + if (oldSearch) oldSearch.closest("div").remove(); - const searchInput = document.getElementById("search-input"); - searchInput.value = ""; + const oldSelector = document.getElementById("episode-selector"); + if (oldSelector) oldSelector.closest("div").remove(); - makePageForEpisodes(allEpisodes); + if (!episodeCache[show.id]) { + const episodesUrl = `https://api.tvmaze.com/shows/${show.id}/episodes`; + episodeCache[show.id] = await fetch(episodesUrl).then((r) => r.json()); + } - const selectedEpisode = document.getElementById( - `episode-${episodeSelector.value}` - ); + allEpisodes = episodeCache[show.id]; + makePageForEpisodes(allEpisodes); + setupSearch(); + setupEpisodeSelector(); +} - selectedEpisode.scrollIntoView({ - behavior: "smooth", - block: "start", - }); - }); +function showShowsView() { + document.getElementById("episodes-view").style.display = "none"; + document.getElementById("shows-view").style.display = "block"; } async function setup() { - const rootElem = document.getElementById("root"); - - rootElem.textContent = "Loading episodes..."; - + const showsListElem = document.getElementById("shows-list"); + showsListElem.textContent = "Loading shows..."; try { - const response = await fetch( - "https://api.tvmaze.com/shows/82/episodes" - ); - - if (!response.ok) { - throw new Error("Could not load episode data"); - } - - allEpisodes = await response.json(); - - rootElem.textContent = ""; - - setupSearch(); - setupEpisodeSelector(); - makePageForEpisodes(allEpisodes); + allShows = await fetch(TVMAZE_SHOWS_URL).then((r) => r.json()); + allShows.sort((a, b) => a.name.localeCompare(b.name)); + makePageForShows(allShows); + setupShowSearch(); } catch (error) { - rootElem.textContent = - "Sorry, we could not load the episodes. Please try again later."; + showsListElem.textContent = "Sorry, we could not load the shows. Please try again later."; } + document.getElementById("back-to-shows").addEventListener("click", (e) => { + e.preventDefault(); + showShowsView(); + }); } window.onload = setup; \ No newline at end of file From 4272d3c08dd0f2a936c1ecbe0d211f84dc1504d9 Mon Sep 17 00:00:00 2001 From: Enice-Codes Date: Wed, 19 Aug 2026 13:45:34 +0200 Subject: [PATCH 07/11] modified files --- index.html | 29 +++++++++----- script.js | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++- style.css | 99 +++++++++++++++++++++++++++++++++++++--------- 3 files changed, 212 insertions(+), 29 deletions(-) diff --git a/index.html b/index.html index c9bacca..b05db43 100644 --- a/index.html +++ b/index.html @@ -1,12 +1,23 @@ -
- -

-
-
+ + + TV Show Project + + + +
+ +

+
+
- + + + + + \ No newline at end of file diff --git a/script.js b/script.js index 45f542d..2e44c93 100644 --- a/script.js +++ b/script.js @@ -1,33 +1,42 @@ const TVMAZE_SHOWS_URL = "https://api.tvmaze.com/shows"; let allShows = []; +let allEpisodes = []; function createShowCard(show) { const card = document.createElement("div"); card.className = "show-card"; + const heading = document.createElement("h2"); heading.textContent = show.name; heading.style.cursor = "pointer"; heading.addEventListener("click", () => showEpisodesView(show)); card.appendChild(heading); + const image = document.createElement("img"); image.src = show.image ? show.image.medium : ""; image.alt = `Image for ${show.name}`; card.appendChild(image); + const summary = document.createElement("div"); summary.innerHTML = show.summary || ""; card.appendChild(summary); + const genres = document.createElement("p"); genres.textContent = `Genres: ${show.genres.join(", ")}`; card.appendChild(genres); + const status = document.createElement("p"); status.textContent = `Status: ${show.status}`; card.appendChild(status); + const rating = document.createElement("p"); - rating.textContent = `Rating: ${show.rating.average ?? "N/A"}`; + rating.textContent = `Rating: ${show.rating?.average ?? "N/A"}`; card.appendChild(rating); + const runtime = document.createElement("p"); runtime.textContent = `Runtime: ${show.runtime ?? "N/A"} min`; card.appendChild(runtime); + return card; } @@ -62,6 +71,108 @@ function setupShowSearch() { const episodeCache = {}; // showId -> episodes array +function getEpisodeCode(episode) { + const season = String(episode.season).padStart(2, "0"); + const number = String(episode.number).padStart(2, "0"); + return `S${season}E${number}`; +} + +function createEpisodeCard(episode) { + const card = document.createElement("div"); + card.className = "episode-card"; + + const heading = document.createElement("h3"); + heading.textContent = `${episode.name} - ${getEpisodeCode(episode)}`; + card.appendChild(heading); + + const image = document.createElement("img"); + image.src = episode.image ? episode.image.medium : ""; + image.alt = `Image for ${episode.name}`; + card.appendChild(image); + + const summary = document.createElement("div"); + summary.innerHTML = episode.summary || ""; + card.appendChild(summary); + + return card; +} + +function makePageForEpisodes(episodeList) { + const episodesListElem = document.getElementById("episodes-list"); + episodesListElem.textContent = ""; + + const count = document.getElementById("episode-search-count"); + if (count) { + count.textContent = `Displaying ${episodeList.length} / ${allEpisodes.length} episodes`; + } + + episodeList.forEach((episode) => { + episodesListElem.appendChild(createEpisodeCard(episode)); + }); +} + +function filterEpisodes(searchTerm) { + const term = searchTerm.toLowerCase().trim(); + return allEpisodes.filter((episode) => { + const name = episode.name.toLowerCase(); + const summary = (episode.summary || "").toLowerCase(); + return name.includes(term) || summary.includes(term); + }); +} + +function setupSearch() { + const wrapper = document.createElement("div"); + + const input = document.createElement("input"); + input.type = "search"; + input.id = "search-input"; + input.placeholder = "Search episodes by name or summary"; + wrapper.appendChild(input); + + document.getElementById("episodes-view").insertBefore( + wrapper, + document.getElementById("episode-search-count") + ); + + input.addEventListener("input", () => { + makePageForEpisodes(filterEpisodes(input.value)); + }); +} + +function setupEpisodeSelector() { + const wrapper = document.createElement("div"); + + const select = document.createElement("select"); + select.id = "episode-selector"; + + const allOption = document.createElement("option"); + allOption.value = ""; + allOption.textContent = "All episodes"; + select.appendChild(allOption); + + allEpisodes.forEach((episode) => { + const option = document.createElement("option"); + option.value = episode.id; + option.textContent = `${getEpisodeCode(episode)} - ${episode.name}`; + select.appendChild(option); + }); + + wrapper.appendChild(select); + document.getElementById("episodes-view").insertBefore( + wrapper, + document.getElementById("episode-search-count") + ); + + select.addEventListener("change", () => { + if (!select.value) { + makePageForEpisodes(allEpisodes); + } else { + const chosen = allEpisodes.filter((ep) => String(ep.id) === select.value); + makePageForEpisodes(chosen); + } + }); +} + async function showEpisodesView(show) { document.getElementById("shows-view").style.display = "none"; document.getElementById("episodes-view").style.display = "block"; diff --git a/style.css b/style.css index de44e6e..61f2a13 100644 --- a/style.css +++ b/style.css @@ -1,34 +1,95 @@ -#root { - color: rgb(39, 38, 38); - display: flex; - flex-wrap: wrap; - gap: 20px; +body { + font-family: Arial, sans-serif; + margin: 0; padding: 20px; - justify-content: center; + background: #f7f7f7; + color: #222; +} + +#show-search-input, +#search-input { + width: 100%; + max-width: 400px; + padding: 8px; + margin-bottom: 10px; + font-size: 1rem; +} + +#episode-selector { + padding: 8px; + margin-bottom: 10px; + font-size: 1rem; +} + +#show-search-count, +#episode-search-count { + font-weight: bold; + margin-bottom: 15px; +} + +#shows-list, +#episodes-list { + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 16px; +} + +/* Fall back to fewer columns on narrower screens so cards don't get too squished */ +@media (max-width: 1400px) { + #shows-list, + #episodes-list { + grid-template-columns: repeat(4, 1fr); + } +} + +@media (max-width: 900px) { + #shows-list, + #episodes-list { + grid-template-columns: repeat(2, 1fr); + } } +@media (max-width: 500px) { + #shows-list, + #episodes-list { + grid-template-columns: 1fr; + } +} +.show-card, .episode-card { -border: 1px solid whitesmoke; + background: #fff; + border: 1px solid #ddd; border-radius: 8px; padding: 15px; - width: 300px; - -} - -.episode-card h2 { - text-align: center; - font-size: 16px; - margin-top: 0; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; } +.show-card img, .episode-card img { width: 100%; + height: auto; border-radius: 4px; - display: block; + margin: 8px 0; +} + +.show-card h2, +.episode-card h3 { + margin: 0; + font-size: 1.1rem; +} + +.show-card p, +.episode-card div { + font-size: 0.9rem; + color: #444; } -.episode-card p { - font-size: 14px; - color: #333; +#back-to-shows { + display: inline-block; + margin-bottom: 15px; + text-decoration: none; + color: #0066cc; } \ No newline at end of file From b514ca25dbe94cc2b996bfe9bb228ea9872810de Mon Sep 17 00:00:00 2001 From: Enice-Codes Date: Wed, 19 Aug 2026 21:40:13 +0200 Subject: [PATCH 08/11] modified these files --- index.html | 17 ++++++++++- script.js | 83 +++++++++++++++++++++++++++++------------------------- style.css | 23 +++++++++++++++ 3 files changed, 83 insertions(+), 40 deletions(-) diff --git a/index.html b/index.html index b05db43..b0490df 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ TV Show Project - +
@@ -14,6 +14,21 @@ diff --git a/script.js b/script.js index 2e44c93..4f0db44 100644 --- a/script.js +++ b/script.js @@ -1,6 +1,7 @@ const TVMAZE_SHOWS_URL = "https://api.tvmaze.com/shows"; let allShows = []; let allEpisodes = []; +let currentShow = null; function createShowCard(show) { const card = document.createElement("div"); @@ -82,7 +83,7 @@ function createEpisodeCard(episode) { card.className = "episode-card"; const heading = document.createElement("h3"); - heading.textContent = `${episode.name} - ${getEpisodeCode(episode)}`; + heading.textContent = `${getEpisodeCode(episode)} - ${episode.name}`; card.appendChild(heading); const image = document.createElement("img"); @@ -102,9 +103,7 @@ function makePageForEpisodes(episodeList) { episodesListElem.textContent = ""; const count = document.getElementById("episode-search-count"); - if (count) { - count.textContent = `Displaying ${episodeList.length} / ${allEpisodes.length} episodes`; - } + count.textContent = `Displaying ${episodeList.length} / ${allEpisodes.length} episodes`; episodeList.forEach((episode) => { episodesListElem.appendChild(createEpisodeCard(episode)); @@ -121,33 +120,23 @@ function filterEpisodes(searchTerm) { } function setupSearch() { - const wrapper = document.createElement("div"); - - const input = document.createElement("input"); - input.type = "search"; - input.id = "search-input"; - input.placeholder = "Search episodes by name or summary"; - wrapper.appendChild(input); - - document.getElementById("episodes-view").insertBefore( - wrapper, - document.getElementById("episode-search-count") - ); - - input.addEventListener("input", () => { - makePageForEpisodes(filterEpisodes(input.value)); + const input = document.getElementById("search-input"); + input.value = ""; + // Clone+replace to strip any old listener before adding a fresh one + const freshInput = input.cloneNode(true); + input.replaceWith(freshInput); + freshInput.addEventListener("input", () => { + makePageForEpisodes(filterEpisodes(freshInput.value)); }); } function setupEpisodeSelector() { - const wrapper = document.createElement("div"); - - const select = document.createElement("select"); - select.id = "episode-selector"; + const select = document.getElementById("episode-selector"); + select.textContent = ""; const allOption = document.createElement("option"); allOption.value = ""; - allOption.textContent = "All episodes"; + allOption.textContent = "Choose an episode"; select.appendChild(allOption); allEpisodes.forEach((episode) => { @@ -157,32 +146,47 @@ function setupEpisodeSelector() { select.appendChild(option); }); - wrapper.appendChild(select); - document.getElementById("episodes-view").insertBefore( - wrapper, - document.getElementById("episode-search-count") - ); - - select.addEventListener("change", () => { - if (!select.value) { + const freshSelect = select.cloneNode(true); + select.replaceWith(freshSelect); + freshSelect.addEventListener("change", () => { + if (!freshSelect.value) { makePageForEpisodes(allEpisodes); } else { - const chosen = allEpisodes.filter((ep) => String(ep.id) === select.value); + const chosen = allEpisodes.filter((ep) => String(ep.id) === freshSelect.value); makePageForEpisodes(chosen); } }); } +function setupShowSelector() { + const select = document.getElementById("show-selector"); + + if (select.dataset.populated !== "true") { + allShows.forEach((show) => { + const option = document.createElement("option"); + option.value = show.id; + option.textContent = show.name; + select.appendChild(option); + }); + select.dataset.populated = "true"; + + select.addEventListener("change", () => { + const chosen = allShows.find((s) => String(s.id) === select.value); + if (chosen) showEpisodesView(chosen); + }); + } + + if (currentShow) { + select.value = currentShow.id; + } +} + async function showEpisodesView(show) { + currentShow = show; + document.getElementById("shows-view").style.display = "none"; document.getElementById("episodes-view").style.display = "block"; - const oldSearch = document.getElementById("search-input"); - if (oldSearch) oldSearch.closest("div").remove(); - - const oldSelector = document.getElementById("episode-selector"); - if (oldSelector) oldSelector.closest("div").remove(); - if (!episodeCache[show.id]) { const episodesUrl = `https://api.tvmaze.com/shows/${show.id}/episodes`; episodeCache[show.id] = await fetch(episodesUrl).then((r) => r.json()); @@ -190,6 +194,7 @@ async function showEpisodesView(show) { allEpisodes = episodeCache[show.id]; makePageForEpisodes(allEpisodes); + setupShowSelector(); setupSearch(); setupEpisodeSelector(); } diff --git a/style.css b/style.css index 61f2a13..ed9cc96 100644 --- a/style.css +++ b/style.css @@ -92,4 +92,27 @@ body { margin-bottom: 15px; text-decoration: none; color: #0066cc; +} + +#episodes-controls { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 20px; + margin-bottom: 10px; +} + +#episodes-controls label { + display: flex; + align-items: center; + gap: 6px; + font-weight: bold; + font-size: 0.9rem; +} + +#episodes-controls select, +#episodes-controls input { + padding: 6px; + font-size: 0.9rem; + font-weight: normal; } \ No newline at end of file From 6d630143d92cc844b5e6119be583523d4fdd5d81 Mon Sep 17 00:00:00 2001 From: Enice-Codes Date: Wed, 19 Aug 2026 22:12:42 +0200 Subject: [PATCH 09/11] modified --- index.html | 51 +++----- script.js | 369 +++++++++++++++++++++++++++++++++++++---------------- style.css | 47 ++----- 3 files changed, 287 insertions(+), 180 deletions(-) diff --git a/index.html b/index.html index b0490df..c71b418 100644 --- a/index.html +++ b/index.html @@ -1,38 +1,25 @@ - - - TV Show Project - - - -
- -

-
-
+ + + + TV Show Project | Enice Mutanda(Enice-Codes) + + + - + - + \ No newline at end of file diff --git a/script.js b/script.js index afd57b3..880997a 100644 --- a/script.js +++ b/script.js @@ -1,6 +1,5 @@ -let allShows = []; let allEpisodes = []; -let currentShow = null; +let allShows = []; // Stores API results so the same URL is never fetched twice const apiCache = new Map(); @@ -11,7 +10,6 @@ function fetchJsonOnce(url) { if (!response.ok) { throw new Error(`Could not load data from ${url}`); } - return response.json(); }); @@ -21,101 +19,11 @@ function fetchJsonOnce(url) { return apiCache.get(url); } -/* ---------- Shows listing ---------- */ - -function createShowCard(show) { - const card = document.createElement("div"); - card.className = "show-card"; - card.id = `show-${show.id}`; - - const heading = document.createElement("h2"); - heading.textContent = show.name; - heading.style.cursor = "pointer"; - heading.addEventListener("click", () => showEpisodesView(show)); - - const image = document.createElement("img"); - - if (show.image) { - image.src = show.image.medium; - } - - image.alt = `Image for ${show.name}`; - - const summary = document.createElement("div"); - summary.innerHTML = show.summary || "No summary available."; - - const genres = document.createElement("p"); - genres.textContent = `Genres: ${show.genres.join(", ")}`; - - const status = document.createElement("p"); - status.textContent = `Status: ${show.status}`; - - const rating = document.createElement("p"); - rating.textContent = `Rating: ${show.rating?.average ?? "N/A"}`; - - const runtime = document.createElement("p"); - runtime.textContent = `Runtime: ${show.runtime ?? "N/A"} min`; - - card.appendChild(heading); - - if (show.image) { - card.appendChild(image); - } - - card.appendChild(summary); - card.appendChild(genres); - card.appendChild(status); - card.appendChild(rating); - card.appendChild(runtime); - - return card; -} - -function makePageForShows(showList) { - const showsRoot = document.getElementById("shows-root"); - - showsRoot.textContent = ""; - - const counter = document.createElement("p"); - counter.textContent = - `Displaying ${showList.length} / ${allShows.length} shows`; - - showsRoot.appendChild(counter); - - showList.forEach((show) => { - const card = createShowCard(show); - showsRoot.appendChild(card); - }); -} - -function setupShowSearch() { - const showSearchInput = document.getElementById("show-search-input"); - - showSearchInput.addEventListener("input", function () { - const searchTerm = showSearchInput.value.toLowerCase().trim(); - - const filteredShows = allShows.filter((show) => { - const showName = show.name.toLowerCase(); - const showGenres = show.genres.join(" ").toLowerCase(); - const showSummary = (show.summary || "").toLowerCase(); - - return ( - showName.includes(searchTerm) || - showGenres.includes(searchTerm) || - showSummary.includes(searchTerm) - ); - }); - - makePageForShows(filteredShows); - }); -} - -/* ---------- Episodes listing ---------- */ +// ---------- Episode helpers ---------- function getEpisodeCode(episode) { const seasonNumber = String(episode.season).padStart(2, "0"); const episodeNumber = String(episode.number).padStart(2, "0"); - return `S${seasonNumber}E${episodeNumber}`; } @@ -128,64 +36,36 @@ function createEpisodeCard(episode) { const heading = document.createElement("h2"); heading.textContent = `${episodeCode} - ${episode.name}`; - - const image = document.createElement("img"); + card.appendChild(heading); if (episode.image) { + const image = document.createElement("img"); image.src = episode.image.medium; + image.alt = `Image for ${episode.name}`; + card.appendChild(image); } - image.alt = `Image for ${episode.name}`; - const summary = document.createElement("div"); summary.innerHTML = episode.summary || "No summary available."; - - card.appendChild(heading); - - if (episode.image) { - card.appendChild(image); - } - card.appendChild(summary); return card; } function makePageForEpisodes(episodeList) { - const episodesRoot = document.getElementById("episodes-root"); - - episodesRoot.textContent = ""; + const rootElem = document.getElementById("root"); + rootElem.textContent = ""; const counter = document.createElement("p"); - counter.textContent = - `Displaying ${episodeList.length} / ${allEpisodes.length} episodes`; + counter.textContent = `Displaying ${episodeList.length} / ${allEpisodes.length} episodes`; + rootElem.appendChild(counter); - episodesRoot.appendChild(counter); + const attribution = document.createElement("p"); + attribution.innerHTML = `Data originally from TVMaze.com`; + rootElem.appendChild(attribution); episodeList.forEach((episode) => { - const card = createEpisodeCard(episode); - episodesRoot.appendChild(card); - }); -} - -function updateEpisodeSelector() { - const episodeSelector = document.getElementById("episode-selector"); - - episodeSelector.textContent = ""; - - const defaultOption = document.createElement("option"); - defaultOption.value = ""; - defaultOption.textContent = "Choose an episode"; - - episodeSelector.appendChild(defaultOption); - - allEpisodes.forEach((episode) => { - const option = document.createElement("option"); - - option.value = episode.id; - option.textContent = `${getEpisodeCode(episode)} - ${episode.name}`; - - episodeSelector.appendChild(option); + rootElem.appendChild(createEpisodeCard(episode)); }); } @@ -198,11 +78,7 @@ function setupSearch() { const filteredEpisodes = allEpisodes.filter((episode) => { const episodeName = episode.name.toLowerCase(); const episodeSummary = (episode.summary || "").toLowerCase(); - - return ( - episodeName.includes(searchTerm) || - episodeSummary.includes(searchTerm) - ); + return episodeName.includes(searchTerm) || episodeSummary.includes(searchTerm); }); makePageForEpisodes(filteredEpisodes); @@ -212,84 +88,36 @@ function setupSearch() { function setupEpisodeSelector() { const episodeSelector = document.getElementById("episode-selector"); + allEpisodes.forEach((episode) => { + const option = document.createElement("option"); + option.value = episode.id; + option.textContent = `${getEpisodeCode(episode)} - ${episode.name}`; + episodeSelector.appendChild(option); + }); + episodeSelector.addEventListener("change", function () { - if (episodeSelector.value === "") { - return; - } + if (episodeSelector.value === "") return; const searchInput = document.getElementById("search-input"); - searchInput.value = ""; makePageForEpisodes(allEpisodes); - const selectedEpisode = document.getElementById( - `episode-${episodeSelector.value}` - ); - + const selectedEpisode = document.getElementById(`episode-${episodeSelector.value}`); if (selectedEpisode) { - selectedEpisode.scrollIntoView({ - behavior: "smooth", - block: "start", - }); + selectedEpisode.scrollIntoView({ behavior: "smooth", block: "start" }); } }); } -async function loadEpisodesForShow(showId) { - const episodesRoot = document.getElementById("episodes-root"); - const searchInput = document.getElementById("search-input"); - - episodesRoot.textContent = "Loading episodes..."; - searchInput.value = ""; - - try { - const episodeUrl = - `https://api.tvmaze.com/shows/${showId}/episodes`; - - allEpisodes = await fetchJsonOnce(episodeUrl); - - updateEpisodeSelector(); - makePageForEpisodes(allEpisodes); - } catch (error) { - episodesRoot.textContent = - "Sorry, we could not load the episodes. Please try again later."; - } -} - -/* ---------- View switching ---------- */ - -function showEpisodesView(show) { - currentShow = show; - - document.getElementById("shows-view").style.display = "none"; - document.getElementById("episodes-view").style.display = "block"; - - loadEpisodesForShow(show.id); -} - -function showShowsView() { - document.getElementById("episodes-view").style.display = "none"; - document.getElementById("shows-view").style.display = "block"; -} - function createEpisodeControls() { - const episodesRoot = document.getElementById("episodes-root"); - + const rootElem = document.getElementById("root"); const controls = document.createElement("div"); - - const backLink = document.createElement("a"); - backLink.href = "#"; - backLink.id = "back-to-shows"; - backLink.textContent = "← Back to shows"; - backLink.addEventListener("click", function (e) { - e.preventDefault(); - showShowsView(); - }); + controls.id = "episode-controls"; const searchLabel = document.createElement("label"); searchLabel.setAttribute("for", "search-input"); - searchLabel.textContent = " Search episodes: "; + searchLabel.textContent = "Search episodes: "; const searchInput = document.createElement("input"); searchInput.id = "search-input"; @@ -298,7 +126,7 @@ function createEpisodeControls() { const episodeLabel = document.createElement("label"); episodeLabel.setAttribute("for", "episode-selector"); - episodeLabel.textContent = " Select episode: "; + episodeLabel.textContent = "Select episode: "; const episodeSelector = document.createElement("select"); episodeSelector.id = "episode-selector"; @@ -306,64 +134,137 @@ function createEpisodeControls() { const episodeDefault = document.createElement("option"); episodeDefault.value = ""; episodeDefault.textContent = "Choose an episode"; - episodeSelector.appendChild(episodeDefault); - controls.appendChild(backLink); - controls.appendChild(searchLabel); - controls.appendChild(searchInput); - controls.appendChild(episodeLabel); - controls.appendChild(episodeSelector); + controls.append(searchLabel, searchInput, episodeLabel, episodeSelector); + rootElem.parentNode.insertBefore(controls, rootElem); +} + +// ---------- Shows listing ---------- + +function createShowCard(show) { + const card = document.createElement("div"); + card.className = "show-card"; + + const heading = document.createElement("h2"); + heading.textContent = show.name; + heading.style.cursor = "pointer"; + heading.addEventListener("click", () => showEpisodesView(show)); + card.appendChild(heading); + + if (show.image) { + const image = document.createElement("img"); + image.src = show.image.medium; + image.alt = `Image for ${show.name}`; + card.appendChild(image); + } + + const summary = document.createElement("div"); + summary.innerHTML = show.summary || "No summary available."; + card.appendChild(summary); + + const genres = document.createElement("p"); + genres.textContent = `Genres: ${show.genres.join(", ")}`; + card.appendChild(genres); - episodesRoot.parentNode.insertBefore(controls, episodesRoot); + const status = document.createElement("p"); + status.textContent = `Status: ${show.status}`; + card.appendChild(status); + + const rating = document.createElement("p"); + rating.textContent = `Rating: ${show.rating.average ?? "N/A"}`; + card.appendChild(rating); + + const runtime = document.createElement("p"); + runtime.textContent = `Runtime: ${show.runtime ?? "N/A"} min`; + card.appendChild(runtime); + + return card; } -function createShowControls() { - const showsRoot = document.getElementById("shows-root"); +function makePageForShows(showList) { + const showsListElem = document.getElementById("shows-list"); + showsListElem.textContent = ""; - const controls = document.createElement("div"); + const count = document.getElementById("show-search-count"); + count.textContent = `Displaying ${showList.length} / ${allShows.length} shows`; - const searchLabel = document.createElement("label"); - searchLabel.setAttribute("for", "show-search-input"); - searchLabel.textContent = "Search shows: "; + showList.forEach((show) => { + showsListElem.appendChild(createShowCard(show)); + }); +} - const searchInput = document.createElement("input"); - searchInput.id = "show-search-input"; - searchInput.type = "search"; - searchInput.placeholder = "Search by name, genre, or summary"; +function filterShows(searchTerm) { + const term = searchTerm.toLowerCase().trim(); - controls.appendChild(searchLabel); - controls.appendChild(searchInput); + return allShows.filter((show) => { + const name = show.name.toLowerCase(); + const genres = show.genres.join(" ").toLowerCase(); + const summary = (show.summary || "").toLowerCase(); + return name.includes(term) || genres.includes(term) || summary.includes(term); + }); +} - showsRoot.parentNode.insertBefore(controls, showsRoot); +function setupShowSearch() { + const input = document.getElementById("show-search-input"); + input.addEventListener("input", () => { + makePageForShows(filterShows(input.value)); + }); } -/* ---------- Setup ---------- */ +// ---------- View switching ---------- -async function setup() { - createShowControls(); - createEpisodeControls(); +async function showEpisodesView(show) { + document.getElementById("shows-view").style.display = "none"; + document.getElementById("episodes-view").style.display = "block"; - const showsRoot = document.getElementById("shows-root"); - showsRoot.textContent = "Loading TV shows..."; + const rootElem = document.getElementById("root"); + const oldControls = document.getElementById("episode-controls"); + if (oldControls) oldControls.remove(); + + rootElem.textContent = "Loading episodes..."; try { - allShows = await fetchJsonOnce("https://api.tvmaze.com/shows"); + const episodeUrl = `https://api.tvmaze.com/shows/${show.id}/episodes`; + allEpisodes = await fetchJsonOnce(episodeUrl); + + rootElem.textContent = ""; + createEpisodeControls(); + setupSearch(); + setupEpisodeSelector(); + makePageForEpisodes(allEpisodes); + } catch (error) { + rootElem.textContent = "Sorry, we could not load the episodes. Please try again later."; + } +} + +function showShowsView() { + document.getElementById("episodes-view").style.display = "none"; + document.getElementById("shows-view").style.display = "block"; +} - allShows.sort((showA, showB) => - showA.name.localeCompare(showB.name, undefined, { - sensitivity: "base", - }) +// ---------- Setup ---------- + +async function setup() { + const showsListElem = document.getElementById("shows-list"); + showsListElem.textContent = "Loading shows..."; + + try { + allShows = await fetchJsonOnce("https://api.tvmaze.com/shows"); + allShows.sort((a, b) => + a.name.localeCompare(b.name, undefined, { sensitivity: "base" }) ); makePageForShows(allShows); setupShowSearch(); - setupSearch(); - setupEpisodeSelector(); } catch (error) { - showsRoot.textContent = - "Sorry, we could not load the TV shows. Please try again later."; + showsListElem.textContent = "Sorry, we could not load the shows. Please try again later."; } + + document.getElementById("back-to-shows").addEventListener("click", (e) => { + e.preventDefault(); + showShowsView(); + }); } window.onload = setup; \ No newline at end of file From d40f0762b9874a15c3a73820786817dff4a4b468 Mon Sep 17 00:00:00 2001 From: Enice-Codes Date: Fri, 21 Aug 2026 14:33:28 +0200 Subject: [PATCH 11/11] modified files --- index.html | 37 +++-- script.js | 441 +++++++++++++++++++++++++++++++++-------------------- style.css | 223 ++++++++++++++++++++------- 3 files changed, 468 insertions(+), 233 deletions(-) diff --git a/index.html b/index.html index 86a37a2..ed6a91e 100644 --- a/index.html +++ b/index.html @@ -1,26 +1,37 @@ + TV Show Project | Enice Mutanda(Enice-Codes) + -
- -

-
-
- - - - + + +
+ +

+ +
+ +
+

+ TV show and episode data provided by + TVMaze.com +

+
+ - \ No newline at end of file + diff --git a/script.js b/script.js index 880997a..c4f9b3b 100644 --- a/script.js +++ b/script.js @@ -1,7 +1,6 @@ -let allEpisodes = []; let allShows = []; +let allEpisodes = []; -// Stores API results so the same URL is never fetched twice const apiCache = new Map(); function fetchJsonOnce(url) { @@ -10,6 +9,7 @@ function fetchJsonOnce(url) { if (!response.ok) { throw new Error(`Could not load data from ${url}`); } + return response.json(); }); @@ -19,252 +19,367 @@ function fetchJsonOnce(url) { return apiCache.get(url); } -// ---------- Episode helpers ---------- +function removeHtml(htmlText) { + const temporaryElement = document.createElement("div"); + temporaryElement.innerHTML = htmlText || ""; -function getEpisodeCode(episode) { - const seasonNumber = String(episode.season).padStart(2, "0"); - const episodeNumber = String(episode.number).padStart(2, "0"); - return `S${seasonNumber}E${episodeNumber}`; + return temporaryElement.textContent || ""; } -function createEpisodeCard(episode) { - const episodeCode = getEpisodeCode(episode); - - const card = document.createElement("div"); - card.className = "episode-card"; - card.id = `episode-${episode.id}`; - - const heading = document.createElement("h2"); - heading.textContent = `${episodeCode} - ${episode.name}`; - card.appendChild(heading); +function createShowCard(show) { + const card = document.createElement("article"); + card.className = "show-card"; - if (episode.image) { + if (show.image) { const image = document.createElement("img"); - image.src = episode.image.medium; - image.alt = `Image for ${episode.name}`; + image.src = show.image.medium; + image.alt = `Poster for ${show.name}`; card.appendChild(image); } - const summary = document.createElement("div"); - summary.innerHTML = episode.summary || "No summary available."; - card.appendChild(summary); + const titleButton = document.createElement("button"); + titleButton.className = "show-title"; + titleButton.textContent = show.name; + + titleButton.addEventListener("click", function () { + showEpisodes(show); + }); + + const details = document.createElement("div"); + details.className = "show-details"; + + const genres = document.createElement("p"); + genres.innerHTML = + `Genres: ${(show.genres || []).join(", ") || "Not available"}`; + + const status = document.createElement("p"); + status.innerHTML = + `Status: ${show.status || "Not available"}`; + + const rating = document.createElement("p"); + rating.innerHTML = + `Rating: ${show.rating?.average ?? "Not rated"}`; + + const runtime = document.createElement("p"); + runtime.innerHTML = + `Runtime: ${ + show.runtime ? `${show.runtime} minutes` : "Not available" + }`; + + const summary = document.createElement("p"); + summary.className = "show-summary"; + summary.textContent = + removeHtml(show.summary) || "No summary available."; + + details.appendChild(titleButton); + details.appendChild(genres); + details.appendChild(status); + details.appendChild(rating); + details.appendChild(runtime); + details.appendChild(summary); + + card.appendChild(details); return card; } -function makePageForEpisodes(episodeList) { +function displayShows(showList) { const rootElem = document.getElementById("root"); - rootElem.textContent = ""; + const statusText = document.getElementById("status-text"); - const counter = document.createElement("p"); - counter.textContent = `Displaying ${episodeList.length} / ${allEpisodes.length} episodes`; - rootElem.appendChild(counter); + rootElem.className = "shows-grid"; + rootElem.textContent = ""; - const attribution = document.createElement("p"); - attribution.innerHTML = `Data originally from TVMaze.com`; - rootElem.appendChild(attribution); + statusText.textContent = + `Displaying ${showList.length} / ${allShows.length} shows`; - episodeList.forEach((episode) => { - rootElem.appendChild(createEpisodeCard(episode)); + showList.forEach((show) => { + rootElem.appendChild(createShowCard(show)); }); } -function setupSearch() { - const searchInput = document.getElementById("search-input"); +function showShowsPage() { + const backButton = document.getElementById("back-to-shows"); + const controls = document.getElementById("controls"); - searchInput.addEventListener("input", function () { - const searchTerm = searchInput.value.toLowerCase().trim(); + backButton.hidden = true; - const filteredEpisodes = allEpisodes.filter((episode) => { - const episodeName = episode.name.toLowerCase(); - const episodeSummary = (episode.summary || "").toLowerCase(); - return episodeName.includes(searchTerm) || episodeSummary.includes(searchTerm); - }); + controls.innerHTML = ` + - makePageForEpisodes(filteredEpisodes); - }); -} + -function setupEpisodeSelector() { - const episodeSelector = document.getElementById("episode-selector"); + - allEpisodes.forEach((episode) => { + + `; + + const showSelector = document.getElementById("show-selector"); + + allShows.forEach((show) => { const option = document.createElement("option"); - option.value = episode.id; - option.textContent = `${getEpisodeCode(episode)} - ${episode.name}`; - episodeSelector.appendChild(option); - }); - episodeSelector.addEventListener("change", function () { - if (episodeSelector.value === "") return; + option.value = show.id; + option.textContent = show.name; - const searchInput = document.getElementById("search-input"); - searchInput.value = ""; + showSelector.appendChild(option); + }); - makePageForEpisodes(allEpisodes); + showSelector.addEventListener("change", function () { + if (showSelector.value === "") { + return; + } - const selectedEpisode = document.getElementById(`episode-${episodeSelector.value}`); - if (selectedEpisode) { - selectedEpisode.scrollIntoView({ behavior: "smooth", block: "start" }); + const selectedShow = allShows.find( + (show) => show.id === Number(showSelector.value) + ); + + if (selectedShow) { + showEpisodes(selectedShow); } }); -} -function createEpisodeControls() { - const rootElem = document.getElementById("root"); - const controls = document.createElement("div"); - controls.id = "episode-controls"; + displayShows(allShows); + + const showSearch = document.getElementById("show-search"); - const searchLabel = document.createElement("label"); - searchLabel.setAttribute("for", "search-input"); - searchLabel.textContent = "Search episodes: "; + showSearch.addEventListener("input", function () { + const searchTerm = showSearch.value.toLowerCase().trim(); - const searchInput = document.createElement("input"); - searchInput.id = "search-input"; - searchInput.type = "search"; - searchInput.placeholder = "Search by name or summary"; + const filteredShows = allShows.filter((show) => { + const showName = show.name.toLowerCase(); - const episodeLabel = document.createElement("label"); - episodeLabel.setAttribute("for", "episode-selector"); - episodeLabel.textContent = "Select episode: "; + const showGenres = (show.genres || []) + .join(" ") + .toLowerCase(); - const episodeSelector = document.createElement("select"); - episodeSelector.id = "episode-selector"; + const showSummary = removeHtml(show.summary) + .toLowerCase(); - const episodeDefault = document.createElement("option"); - episodeDefault.value = ""; - episodeDefault.textContent = "Choose an episode"; - episodeSelector.appendChild(episodeDefault); + return ( + showName.includes(searchTerm) || + showGenres.includes(searchTerm) || + showSummary.includes(searchTerm) + ); + }); - controls.append(searchLabel, searchInput, episodeLabel, episodeSelector); - rootElem.parentNode.insertBefore(controls, rootElem); + displayShows(filteredShows); + }); } -// ---------- Shows listing ---------- +function getEpisodeCode(episode) { + const seasonNumber = String(episode.season).padStart(2, "0"); + const episodeNumber = String(episode.number).padStart(2, "0"); -function createShowCard(show) { - const card = document.createElement("div"); - card.className = "show-card"; + return `S${seasonNumber}E${episodeNumber}`; +} + +function createEpisodeCard(episode) { + const card = document.createElement("article"); + card.className = "episode-card"; + card.id = `episode-${episode.id}`; const heading = document.createElement("h2"); - heading.textContent = show.name; - heading.style.cursor = "pointer"; - heading.addEventListener("click", () => showEpisodesView(show)); + heading.textContent = + `${getEpisodeCode(episode)} - ${episode.name}`; + card.appendChild(heading); - if (show.image) { + if (episode.image) { const image = document.createElement("img"); - image.src = show.image.medium; - image.alt = `Image for ${show.name}`; + image.src = episode.image.medium; + image.alt = `Image for ${episode.name}`; + card.appendChild(image); } - const summary = document.createElement("div"); - summary.innerHTML = show.summary || "No summary available."; + const summary = document.createElement("p"); + summary.textContent = + removeHtml(episode.summary) || "No summary available."; + card.appendChild(summary); - const genres = document.createElement("p"); - genres.textContent = `Genres: ${show.genres.join(", ")}`; - card.appendChild(genres); + return card; +} - const status = document.createElement("p"); - status.textContent = `Status: ${show.status}`; - card.appendChild(status); +function displayEpisodes(episodeList) { + const rootElem = document.getElementById("root"); + const statusText = document.getElementById("status-text"); - const rating = document.createElement("p"); - rating.textContent = `Rating: ${show.rating.average ?? "N/A"}`; - card.appendChild(rating); + rootElem.className = "episodes-grid"; + rootElem.textContent = ""; - const runtime = document.createElement("p"); - runtime.textContent = `Runtime: ${show.runtime ?? "N/A"} min`; - card.appendChild(runtime); + statusText.textContent = + `Displaying ${episodeList.length} / ${allEpisodes.length} episodes`; - return card; + episodeList.forEach((episode) => { + rootElem.appendChild(createEpisodeCard(episode)); + }); } -function makePageForShows(showList) { - const showsListElem = document.getElementById("shows-list"); - showsListElem.textContent = ""; +function updateEpisodeSelector() { + const selector = document.getElementById("episode-selector"); - const count = document.getElementById("show-search-count"); - count.textContent = `Displaying ${showList.length} / ${allShows.length} shows`; + selector.innerHTML = ""; - showList.forEach((show) => { - showsListElem.appendChild(createShowCard(show)); + const defaultOption = document.createElement("option"); + defaultOption.value = ""; + defaultOption.textContent = "Choose an episode"; + + selector.appendChild(defaultOption); + + allEpisodes.forEach((episode) => { + const option = document.createElement("option"); + + option.value = episode.id; + option.textContent = + `${getEpisodeCode(episode)} - ${episode.name}`; + + selector.appendChild(option); }); } -function filterShows(searchTerm) { - const term = searchTerm.toLowerCase().trim(); +function setupEpisodeControls() { + const searchInput = + document.getElementById("episode-search"); + + const episodeSelector = + document.getElementById("episode-selector"); + + searchInput.addEventListener("input", function () { + const searchTerm = + searchInput.value.toLowerCase().trim(); + + const filteredEpisodes = allEpisodes.filter((episode) => { + const episodeName = + episode.name.toLowerCase(); + + const episodeSummary = + removeHtml(episode.summary).toLowerCase(); - return allShows.filter((show) => { - const name = show.name.toLowerCase(); - const genres = show.genres.join(" ").toLowerCase(); - const summary = (show.summary || "").toLowerCase(); - return name.includes(term) || genres.includes(term) || summary.includes(term); + return ( + episodeName.includes(searchTerm) || + episodeSummary.includes(searchTerm) + ); + }); + + displayEpisodes(filteredEpisodes); }); -} -function setupShowSearch() { - const input = document.getElementById("show-search-input"); - input.addEventListener("input", () => { - makePageForShows(filterShows(input.value)); + episodeSelector.addEventListener("change", function () { + if (episodeSelector.value === "") { + return; + } + + searchInput.value = ""; + + displayEpisodes(allEpisodes); + + const selectedEpisode = document.getElementById( + `episode-${episodeSelector.value}` + ); + + if (selectedEpisode) { + selectedEpisode.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + } }); } -// ---------- View switching ---------- +async function showEpisodes(show) { + const controls = document.getElementById("controls"); + const rootElem = document.getElementById("root"); + const statusText = document.getElementById("status-text"); + const backButton = document.getElementById("back-to-shows"); + + backButton.hidden = false; -async function showEpisodesView(show) { - document.getElementById("shows-view").style.display = "none"; - document.getElementById("episodes-view").style.display = "block"; + controls.innerHTML = ` +
+

${show.name} Episodes

- const rootElem = document.getElementById("root"); - const oldControls = document.getElementById("episode-controls"); - if (oldControls) oldControls.remove(); + - rootElem.textContent = "Loading episodes..."; + + + + + +
+ `; + + rootElem.textContent = ""; + statusText.textContent = "Loading episodes..."; try { - const episodeUrl = `https://api.tvmaze.com/shows/${show.id}/episodes`; - allEpisodes = await fetchJsonOnce(episodeUrl); - - rootElem.textContent = ""; - createEpisodeControls(); - setupSearch(); - setupEpisodeSelector(); - makePageForEpisodes(allEpisodes); + const url = + `https://api.tvmaze.com/shows/${show.id}/episodes`; + + allEpisodes = await fetchJsonOnce(url); + + updateEpisodeSelector(); + setupEpisodeControls(); + displayEpisodes(allEpisodes); } catch (error) { - rootElem.textContent = "Sorry, we could not load the episodes. Please try again later."; + statusText.textContent = + "Unable to load episodes."; + + rootElem.textContent = + "Sorry, we could not load the episodes."; } } -function showShowsView() { - document.getElementById("episodes-view").style.display = "none"; - document.getElementById("shows-view").style.display = "block"; -} +async function setup() { + const rootElem = document.getElementById("root"); + const statusText = document.getElementById("status-text"); + const backButton = document.getElementById("back-to-shows"); -// ---------- Setup ---------- + statusText.textContent = "Loading TV shows..."; + rootElem.textContent = "Please wait..."; -async function setup() { - const showsListElem = document.getElementById("shows-list"); - showsListElem.textContent = "Loading shows..."; + backButton.addEventListener("click", function () { + showShowsPage(); + }); try { - allShows = await fetchJsonOnce("https://api.tvmaze.com/shows"); - allShows.sort((a, b) => - a.name.localeCompare(b.name, undefined, { sensitivity: "base" }) + allShows = await fetchJsonOnce( + "https://api.tvmaze.com/shows" + ); + + allShows.sort((showA, showB) => + showA.name.localeCompare(showB.name, undefined, { + sensitivity: "base", + }) ); - makePageForShows(allShows); - setupShowSearch(); + showShowsPage(); } catch (error) { - showsListElem.textContent = "Sorry, we could not load the shows. Please try again later."; - } + statusText.textContent = + "Unable to load TV shows."; - document.getElementById("back-to-shows").addEventListener("click", (e) => { - e.preventDefault(); - showShowsView(); - }); + rootElem.textContent = + "Sorry, we could not load the TV shows."; + } } window.onload = setup; \ No newline at end of file diff --git a/style.css b/style.css index 76f1f9b..4023d2c 100644 --- a/style.css +++ b/style.css @@ -1,93 +1,202 @@ +* { + box-sizing: border-box; +} + body { - font-family: Arial, sans-serif; margin: 0; - padding: 20px; - background: #f7f7f7; + font-family: Arial, sans-serif; + background: #f4f4f4; color: #222; } -#show-search-input, -#search-input { - width: 100%; - max-width: 400px; - padding: 8px; - margin-bottom: 10px; - font-size: 1rem; +.site-header { + background: #1f2937; + color: white; + padding: 24px; + display: flex; + justify-content: space-between; + align-items: center; } -#episode-selector { - padding: 8px; - margin-bottom: 10px; - font-size: 1rem; +.site-header h1 { + margin: 0; + font-size: 30px; } -#show-search-count { +.back-button { + border: none; + background: white; + color: #1f2937; + padding: 10px 16px; + border-radius: 6px; + cursor: pointer; font-weight: bold; - margin-bottom: 15px; } -#shows-list, -#root { - display: grid; - grid-template-columns: repeat(6, 1fr); - gap: 16px; +.back-button:hover { + opacity: 0.9; } -/* Fewer columns on narrower screens */ -@media (max-width: 1400px) { - #shows-list, - #root { - grid-template-columns: repeat(4, 1fr); - } +.controls { + max-width: 1200px; + margin: 25px auto 10px; + padding: 0 20px; + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; } -@media (max-width: 900px) { - #shows-list, - #root { - grid-template-columns: repeat(2, 1fr); - } +.controls label { + font-weight: bold; } -@media (max-width: 500px) { - #shows-list, - #root { - grid-template-columns: 1fr; - } +.controls input, +.controls select { + padding: 10px; + border: 1px solid #ccc; + border-radius: 6px; + font-size: 15px; +} + +#show-search { + width: 320px; + max-width: 100%; +} + +.status-text { + max-width: 1200px; + margin: 15px auto; + padding: 0 20px; + font-weight: bold; +} + +.shows-grid, +.episodes-grid { + max-width: 1200px; + margin: 0 auto; + padding: 20px; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; } .show-card, .episode-card { - background: #fff; - border: 1px solid #ddd; - border-radius: 8px; - padding: 12px; + background: white; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12); +} + +.show-card { display: flex; flex-direction: column; } -.show-card img, -.episode-card img { +.show-card > img { width: 100%; - height: 300px; + height: 360px; object-fit: cover; - border-radius: 4px; } -.show-card h2, -.episode-card h2 { +.show-details { + padding: 18px; +} + +.show-title { + width: 100%; + border: none; + background: none; + padding: 0; + margin-bottom: 14px; + text-align: left; + font-size: 22px; + font-weight: bold; + cursor: pointer; + color: #1f2937; +} + +.show-title:hover { + text-decoration: underline; +} + +.show-details p { + line-height: 1.5; margin: 8px 0; - font-size: 1.1rem; } -.show-card p, -.episode-card div { - font-size: 0.9rem; - color: #444; +.show-summary { + margin-top: 16px; +} + +.episode-controls { + width: 100%; +} + +.episode-controls h2 { + width: 100%; + margin-top: 0; +} + +.episode-card { + padding: 16px; +} + +.episode-card h2 { + font-size: 18px; + text-align: center; + margin-top: 0; +} + +.episode-card img { + width: 100%; + display: block; + border-radius: 8px; +} + +.episode-card p { + line-height: 1.5; +} + +footer { + max-width: 1200px; + margin: 30px auto; + padding: 20px; + text-align: center; +} + +footer a { + color: inherit; } -#back-to-shows { - display: inline-block; - margin-bottom: 15px; - text-decoration: none; - color: #0066cc; +@media (max-width: 900px) { + .shows-grid, + .episodes-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 600px) { + .site-header { + align-items: flex-start; + gap: 15px; + flex-direction: column; + } + + .shows-grid, + .episodes-grid { + grid-template-columns: 1fr; + } + + .controls { + align-items: stretch; + flex-direction: column; + } + + .controls input, + .controls select, + #show-search { + width: 100%; + } } \ No newline at end of file