From 84c0c597866be2b6dfea613fdfe28d6f4cec4039 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Thu, 6 Feb 2025 14:19:26 +0000 Subject: [PATCH 001/108] Initial attempt for banner in plugin --- src/.gitignore | 9 + .../Controllers/MediaBarController.cs | 32 + .../Inject/home-html.chunk.js | 1 + .../Inject/index.html | 4 + .../Inject/media-bar.js | 55 ++ .../Inject/slideshowpure.css | 685 ++++++++++++++++++ .../Inject/slideshowpure.js | 426 +++++++++++ .../Jellyfin.Plugin.MediaBar.csproj | 26 + .../MediaBarPlugin.cs | 56 ++ 9 files changed, 1294 insertions(+) create mode 100644 src/.gitignore create mode 100644 src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs create mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/home-html.chunk.js create mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/index.html create mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js create mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css create mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js create mode 100644 src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj create mode 100644 src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..b3e292d --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,9 @@ +bin/ +obj/ +.vs/ +.idea/ +artifacts +Release +*.zip +node_modules +dist/ \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs new file mode 100644 index 0000000..9193c35 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Plugin.MediaBar.Controllers; + +[Route("[controller]")] +public class MediaBarController : ControllerBase +{ + [HttpGet("{file}")] + public ActionResult GetFile([FromRoute] string file) + { + Stream fileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Jellyfin.Plugin.MediaBar.Inject." + file); + string fileContents = new StreamReader(fileStream).ReadToEnd(); + + string contentType = "text/plain"; + + if (Path.GetExtension(file) == ".js") + { + contentType = "text/javascript"; + } + else if (Path.GetExtension(file) == ".css") + { + contentType = "text/css"; + } + else if (Path.GetExtension(file) == ".html") + { + contentType = "text/html"; + } + + return Content(fileContents, contentType); + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/home-html.chunk.js b/src/Jellyfin.Plugin.MediaBar/Inject/home-html.chunk.js new file mode 100644 index 0000000..522fa7f --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Inject/home-html.chunk.js @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/index.html b/src/Jellyfin.Plugin.MediaBar/Inject/index.html new file mode 100644 index 0000000..41c07a8 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Inject/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js new file mode 100644 index 0000000..70724e6 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js @@ -0,0 +1,55 @@ + +function saveCredentialsToSessionStorage(credentials) { + try { + sessionStorage.setItem( + "json-credentials", + JSON.stringify(credentials) + ); + console.log("Credentials saved to sessionStorage."); + } catch (error) { + console.error("Error saving credentials:", error); + } +} +function saveApiKey(apiKey) { + try { + sessionStorage.setItem("api-key", apiKey); + console.log("API key saved to sessionStorage."); + } catch (error) { + console.error("Error saving API key:", error); + } +} +(function () { + var originalConsoleLog = console.log; + console.log = function (message) { + originalConsoleLog.apply(console, arguments); + if ( + typeof message === "string" && + message.startsWith("Stored JSON credentials:") + ) { + try { + var jsonString = message.substring( + "Stored JSON credentials: ".length + ); + var credentials = JSON.parse(jsonString); + saveCredentialsToSessionStorage(credentials); + } catch (error) { + console.error("Error parsing credentials:", error); + } + } + if ( + typeof message === "string" && + message.startsWith("opening web socket with url:") + ) { + try { + var url = message.split("url:")[1].trim(); + var urlParams = new URL(url).searchParams; + var apiKey = urlParams.get("api_key"); + if (apiKey) { + saveApiKey(apiKey); + } + } catch (error) { + console.error("Error extracting API key:", error); + } + } + }; +})(); \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css new file mode 100644 index 0000000..2270e6b --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css @@ -0,0 +1,685 @@ +@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap"); + +@media only screen and (min-width: 767px) { + div[data-role="page"].page { + overflow-y: scroll; /* Keeps vertical scrolling active */ + scrollbar-width: none; /* For Firefox */ + -ms-overflow-style: none; /* For Internet Explorer & Edge */ + } + + div[data-role="page"].page::-webkit-scrollbar { + display: none; /* Hides scrollbar in Webkit browsers */ + } + + .backdrop, + .slide { + position: absolute; + } + + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + + .plot, + body { + overflow: hidden; + } + + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: white; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } + + .arrow::before { + content: ""; /* Creates an empty element */ + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; /* Size of the circle */ + height: 40px; /* Size of the circle */ + background-color: rgba(12, 110, 162, 0.7); /* Translucent background */ + border-radius: 50%; /* Makes it a circle */ + z-index: -1; /* Places the circle behind the arrow */ + } + + .left-arrow { + left: 10px; /* Position to the left */ + } + + .right-arrow { + right: 10px; /* Position to the right */ + } + + .dots-container { + position: absolute; + bottom: 5%; + left: 0; + right: 0; + z-index: 2; + display: flex; + justify-content: center; /* Centers the dots horizontally */ + align-items: center; + width: 100vw; /* Full screen width */ + height: 22px; + background-color: #ffffff00; + gap: 2vw; /* Evenly space the rectangles with a gap of 2vw */ + } + + .dot { + width: 15vw; /* Each rectangle takes 18% of the viewport width */ + height: 4px; /* Thin rectangle height */ + background-color: #cecece; + border-radius: 2px; /* Slightly rounded corners */ + cursor: pointer; + transition: background-color 0.3s; + } + + .dot.active { + background-color: rgba(12, 110, 162, 0.7) !important; + } + + body { + margin: 0; + padding: 0; + } + + .fade-in { + opacity: 1 !important; + } + + .fade-out { + opacity: 0 !important; + } + + #slides-container { + position: relative; + width: 100vw; + height: 750px; + overflow: hidden; + margin: 0 auto; + border-radius: 7px; + top: -5em; + } + + .slide { + transition: opacity 0.5s ease-in-out; /* Smooth fade transition */ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + + .slide.active { + opacity: 1; + display: block; + } + + .slide:focus { + outline: #fff solid 2px; + } + + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } + + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); /* Adjust the alpha for dimness */ + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } + + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + + .logo { + position: relative; + max-height: 100%; + max-width: 100%; + width: auto; + z-index: 3; + top: 5em; + left: 8em; + } + + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: transparent; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 50%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + + .plot { + display: -webkit-box; + line-clamp: 4; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + left: 7.9em; + position: absolute; + top: 190px; + } + + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, rgba(0, 0, 0, 0) 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 8%, + rgba(0, 0, 0, 0.5) 10%, + rgba(0, 0, 0, 1) 12% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 8%, + rgba(0, 0, 0, 0.5) 10%, + rgba(0, 0, 0, 1) 12% + ); + } + .genre { + position: absolute; + top: calc(50% + 75px); + left: 8.4em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + top: 1.5em; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .play-button { + position: absolute; + display: flex; + align-items: center; /* Center vertically */ + justify-content: center; /* Keep content aligned horizontally */ + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; /* Round the edges */ + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + top: calc(50% + 190px); + left: 6.8em; + } + + .play-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; /* Adjust the size of the play icon */ + width: 30px; /* Ensure the icon area is circular */ + height: 30px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); /* Same as the button's background */ + border: 3px solid rgba(12, 110, 162, 1); + border-radius: 50%; /* Make the icon a perfect circle */ + } + + .play-text { + font-family: "Titillium Web", sans-serif; + margin-left: 10px; /* Space between icon and text */ + font-size: 14px; + color: #fff; + } +} + +@media only screen and (max-width: 767px) { + .backdrop, + .slide { + position: absolute; + } + + .arrow { + position: absolute; + /*top: 50%; + transform: translateY(-50%);*/ + bottom: 25px; + font-size: 24px; + cursor: pointer; + color: rgba(255, 255, 255, 0.6); + z-index: 4; + -webkit-tap-highlight-color: transparent; /* Disable blue tap highlight */ + } + + .left-arrow { + left: 10px; /* Position to the left */ + } + + .right-arrow { + right: 10px; /* Position to the right */ + } + + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + + .plot, + body { + overflow: hidden; + } + + body { + margin: 0; + padding: 0; + } + + .fade-in { + opacity: 1 !important; + } + + .fade-out { + opacity: 0 !important; + } + + .dots-container { + position: absolute; + bottom: 11%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff00; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } + + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + + .dot.active { + width: 32px; + height: 12px; + background-color: rgba(12, 110, 162, 0.7); + border-radius: 10px; /* Rounded rectangle shape */ + } + + #slides-container { + position: relative; + width: 92vw; + height: 650px; + overflow: hidden; + margin: 0 auto; + border-radius: 10px; + } + + .slide { + transition: opacity 0.5s ease-in-out; /* Smooth fade transition */ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + + .slide.active { + opacity: 1; + display: block; + } + + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + bottom: 28%; + left: 50%; + transform: translateX(-50%); + max-height: 60%; + max-width: 80%; + width: auto; + z-index: 3; + } + + .backdrop { + position: absolute; + top: 45px; + left: 0; + width: 100%; + height: calc(100% - 50px); + object-fit: cover; + object-position: center 20%; + z-index: 1; + border-radius: 13px; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } + + .gradient-overlay { + position: absolute; + top: 45px; + left: 0; + width: 100%; + height: calc(100% - 50px); + background: rgba(0, 0, 0, 0.55); /* Semi-transparent overlay color */ + z-index: 2; + border-radius: 13px; + pointer-events: none; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } + + .genre { + position: absolute; + bottom: 16%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Titillium Web", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 20%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } + + .play-button { + position: absolute; + display: flex; + align-items: center; /* Center vertically */ + justify-content: flex-start; /* Keep content aligned horizontally */ + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; /* Round the edges */ + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + bottom: 20px; + /*right: 0%;*/ + left: 50%; + transform: translateX(-50%) scale(1.2); + outline: none; /* Remove the default blue focus outline */ + -webkit-tap-highlight-color: transparent; /* Disable blue tap highlight */ + } + + .play-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; /* Adjust the size of the play icon */ + width: 20px; /* Ensure the icon area is circular */ + height: 20px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); /* Same as the button's background */ + border: 1.5px solid rgba(12, 110, 162, 1); + border-radius: 50%; /* Make the icon a perfect circle */ + outline: none; /* Remove the default blue focus outline */ + -webkit-tap-highlight-color: transparent; /* Disable blue tap highlight */ + } + + .play-text { + display: block; + font-family: "Titillium Web", sans-serif; + margin-left: 5px; /* Space between icon and text */ + font-size: 14px; + color: #fff; + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js new file mode 100644 index 0000000..33853c3 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -0,0 +1,426 @@ +const slidesInit = () => { + const shuffleInterval = 8000; + let isTransitioning = false; + const listFileName = `${window.location.origin}/web/avatars/list.txt`; + const jsonCredentials = sessionStorage.getItem("json-credentials"); + const apiKey = sessionStorage.getItem("api-key"); + let userId = null; + let token = null; + const deviceId = localStorage.getItem("_deviceId2"); + console.log(deviceId); + if (jsonCredentials) { + const credentials = JSON.parse(jsonCredentials); + userId = credentials.Servers[0].UserId; + token = credentials.Servers[0].AccessToken; + } + const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); + const truncateText = (element, maxLength) => { + let text = element.innerText; + if (text.length > maxLength) + element.innerText = text.substring(0, maxLength) + "..."; + }; + const createSlideElement = (item, title) => { + const itemId = item.Id; + const plot = item.Overview || "No overview available"; + const rating = item.CommunityRating; + const tomato = item.CriticRating; + const runtime = item.RunTimeTicks; + const genre = item.Genres; + const youtube = item.RemoteTrailers; + const age = item.OfficialRating; + const date = item.PremiereDate; + const season = item.ChildCount; + function createSeparator() { + const separatorHtml = + ''; + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = separatorHtml; + return tempDiv.firstChild; + } + const slide = document.createElement("a"); + slide.className = "slide"; + slide.target = "_top"; + slide.rel = "noreferrer"; + slide.tabIndex = 0; + slide.style.display = "none"; + const backdrop = document.createElement("img"); + backdrop.className = "backdrop"; + backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; + backdrop.alt = "Backdrop"; + const backdropOverlay = document.createElement("div"); + backdropOverlay.className = "backdrop-overlay"; + const backdropContainer = document.createElement("div"); + backdropContainer.className = "backdrop-container"; + backdropContainer.appendChild(backdrop); + backdropContainer.appendChild(backdropOverlay); + const logo = document.createElement("img"); + logo.className = "logo"; + logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; + logo.alt = "Logo"; + const logoContainer = document.createElement("div"); + logoContainer.className = "logo-container"; + logoContainer.appendChild(logo); + const featuredContent = document.createElement("div"); + featuredContent.className = "featured-content"; + featuredContent.textContent = title; + const plotElement = document.createElement("div"); + plotElement.className = "plot"; + plotElement.textContent = plot; + truncateText(plotElement, 360); + const plotContainer = document.createElement("div"); + plotContainer.className = "plot-container"; + plotContainer.appendChild(plotElement); + const gradientOverlay = document.createElement("div"); + gradientOverlay.className = "gradient-overlay"; + const runTimeElement = document.createElement("div"); + runTimeElement.className = "runTime"; + if (season === undefined) { + const milliseconds = runtime / 10000; + const currentTime = new Date(); + const endTime = new Date(currentTime.getTime() + milliseconds); + const options = { hour: "2-digit", minute: "2-digit", hour12: !0 }; + const formattedEndTime = endTime.toLocaleTimeString([], options); + runTimeElement.textContent = `Ends at ${formattedEndTime}`; + } else { + runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; + } + const ratingTest = document.createElement("div"); + ratingTest.className = "rating-value"; + const imdbLogo = document.createElement("img"); + imdbLogo.className = "imdb-logo"; + imdbLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg"; + imdbLogo.alt = "IMDb Logo"; + imdbLogo.style.width = "30px"; + imdbLogo.style.height = "30px"; + ratingTest.appendChild(imdbLogo); + if (typeof rating === "number") { + const formattedRating = rating.toFixed(1); + ratingTest.innerHTML += `${formattedRating} ⭐`; + } else { + console.error("Rating is undefined or not a number:", rating); + ratingTest.innerHTML += `N/A ⭐`; + } + ratingTest.appendChild(createSeparator()); + const tomatoRatingDiv = document.createElement("div"); + tomatoRatingDiv.className = "tomato-rating"; + const criticRating = item.CriticRating; + const tomatoLogo = document.createElement("img"); + tomatoLogo.className = "tomato-logo"; + const criticLogo = document.createElement("img"); + criticLogo.className = "critic-logo"; + tomatoLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; + let valueElement; + if (typeof criticRating === "number") { + valueElement = document.createTextNode(`${criticRating}% `); + } else { + valueElement = document.createTextNode(`N/A `); + } + if (criticRating === undefined || criticRating <= 59) { + criticLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; + criticLogo.alt = "Rotten Tomato"; + } else { + criticLogo.src = "https://i.imgur.com/iMfwDk7.png"; + criticLogo.alt = "Fresh Tomato"; + } + tomatoLogo.style.width = "15px"; + tomatoLogo.style.height = "17px"; + criticLogo.style.width = "15px"; + criticLogo.style.height = "15px"; + tomatoRatingDiv.appendChild(tomatoLogo); + tomatoRatingDiv.appendChild(valueElement); + tomatoRatingDiv.appendChild(criticLogo); + tomatoRatingDiv.appendChild(createSeparator()); + const ageRatingDiv = document.createElement("div"); + ageRatingDiv.className = "age-rating"; + if (item.OfficialRating) { + ageRatingDiv.innerHTML = `${item.OfficialRating}`; + } else { + ageRatingDiv.innerHTML = `N/A`; + } + const calender = "📅"; + const premiereDate = document.createElement("div"); + premiereDate.className = "date"; + const year = date ? new Date(date).getFullYear() : "N/A"; + premiereDate.textContent = isNaN(year) ? "N/A" : year; + ratingTest.appendChild(tomatoRatingDiv); + ratingTest.appendChild(document.createTextNode(calender)); + ratingTest.appendChild(premiereDate); + ratingTest.appendChild(createSeparator()); + ratingTest.appendChild(ageRatingDiv); + ratingTest.appendChild(createSeparator()); + ratingTest.appendChild(runTimeElement); + const genresArray = item.Genres; + function parseGenres(genresArray) { + if (genresArray && genresArray.length > 0) { + return genresArray.slice(0, 3).join(" 🔹 "); + } else { + return "No Genre Available"; + } + } + const genreElement = document.createElement("div"); + genreElement.className = "genre"; + genreElement.innerHTML = parseGenres(genresArray); + const infoContainer = document.createElement("div"); + infoContainer.className = "info-container"; + infoContainer.appendChild(ratingTest); + const playButton = document.createElement("button"); + playButton.className = "play-button"; + playButton.innerHTML = ` + + Details + `; + playButton.onclick = () => { + window.top.location.href = `/#!/details?id=${itemId}`; + //window.location.href = `/Videos/${itemId}/stream.mp4?Static=true&mediaSourceId=${itemId}&deviceId=${deviceId}&api_key=${apiKey}`; + //window.top.location.href = streamUrl; + }; + slide.append( + logoContainer, + backdropContainer, + gradientOverlay, + featuredContent, + plotContainer, + infoContainer, + genreElement, + playButton + ); + return slide; + }; + const createSlideForItem = async (item, title) => { + const container = document.getElementById("slides-container"); + const itemId = item.Id; + const backdropUrl = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; + const logoUrl = `${window.location.origin}/Items/${itemId}/Images/Logo`; + const [backdropExists, logoExists] = await Promise.all([ + fetch(backdropUrl, { method: "HEAD" }).then((res) => res.ok), + fetch(logoUrl, { method: "HEAD" }).then((res) => res.ok), + ]); + if (backdropExists && logoExists) { + const slideElement = createSlideElement(item, title); + container.appendChild(slideElement); + console.log(`Added slide for item ${itemId}`); + if (container.children.length === 1) { + showSlide(0); + } + } else { + console.warn(`Skipping item ${itemId}: Missing backdrop or logo.`); + } + }; + const fetchItemDetails = async (itemId) => { + const response = await fetch( + `${window.location.origin}/Users/${userId}/Items/${itemId}`, + { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + } + ); + const item = await response.json(); + console.log("Item Title:", item.Name); + return item; + }; + const fetchItemIdsFromList = async () => { + try { + const response = await fetch(listFileName); + if (!response.ok) { + throw new Error("Failed to fetch list.txt"); + } + const text = await response.text(); + return text + .split("\n") + .map((id) => id.trim()) + .filter((id) => id); + } catch (error) { + console.error("Error fetching list.txt:", error); + return []; + } + }; + const fetchItemsFromServer = async () => { + try { + const response = await fetch( + `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, + { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + } + ); + const data = await response.json(); + const items = data.Items; + const movies = items.filter((item) => item.Type === "Movie"); + const tvShows = items.filter((item) => item.Type === "Series"); + const shuffledMovies = shuffleArray(movies); + const shuffledTvShows = shuffleArray(tvShows); + const selectedMovies = shuffledMovies.slice(0, 15); + const selectedTvShows = shuffledTvShows.slice(0, 15); + const allItems = []; + const maxLength = Math.max(selectedMovies.length, selectedTvShows.length); + for (let i = 0; i < maxLength; i++) { + if (i < selectedMovies.length) allItems.push(selectedMovies[i]); + if (i < selectedTvShows.length) allItems.push(selectedTvShows[i]); + } + return allItems; + } catch (error) { + console.error("Error fetching items:", error); + return []; + } + }; + const createSlidesForItems = async (items) => { + await Promise.all( + items.map((item) => + createSlideForItem(item, item.Type === "Movie" ? "Movie" : "TV Show") + ) + ); + }; + const createPaginationDots = () => { + const dotsContainer = document.createElement("div"); + dotsContainer.className = "dots-container"; + for (let i = 0; i < 5; i++) { + const dot = document.createElement("span"); + dot.className = "dot"; + dotsContainer.appendChild(dot); + } + const container = document.getElementById("slides-container"); + container.appendChild(dotsContainer); + }; + const showSlide = (index) => { + const slides = document.querySelectorAll(".slide"); + const dots = document.querySelectorAll(".dot"); + slides.forEach((slide, i) => { + if (i === index) { + slide.style.display = "block"; + slide.offsetHeight; + slide.style.opacity = "1"; + slide.classList.add("active"); + } else { + slide.style.opacity = "0"; + slide.classList.remove("active"); + setTimeout(() => { + slide.style.display = "none"; + }, 500); + } + }); + dots.forEach((dot, i) => { + dot.classList.toggle("active", i === index % 5); + }); + }; + const initializeSlideshow = () => { + const slides = document.querySelectorAll(".slide"); + createPaginationDots(); + const container = document.getElementById("slides-container"); + let currentSlideIndex = 0; + let focusedSlide = null; + let containerFocused = !1; + const updateCurrentSlide = (index) => { + if (isTransitioning) return; // Skip if already transitioning + isTransitioning = true; // Set transitioning to true at the start + currentSlideIndex = (index + slides.length) % slides.length; + showSlide(currentSlideIndex); + // Re-enable arrows after a short delay to prevent rapid clicking + setTimeout(() => { + isTransitioning = false; // Reset after transition completes + }, 500); // Adjust the delay as needed (500ms is a reasonable starting point) + }; + const openActiveSlide = () => { + if (focusedSlide) { + window.location.href = focusedSlide.href; + } + }; + if (slides.length > 0) { + showSlide(currentSlideIndex); + container.style.display = "block"; + setTimeout(() => { + setInterval(() => { + updateCurrentSlide(currentSlideIndex + 1); + }, shuffleInterval); + }, 8000); + } + slides.forEach((slide) => { + slide.addEventListener( + "focus", + () => { + focusedSlide = slide; + container.classList.remove("disable-interaction"); + }, + !0 + ); + slide.addEventListener( + "blur", + () => { + if (focusedSlide === slide) { + focusedSlide = null; + } + }, + !0 + ); + }); + document.addEventListener("keydown", (event) => { + if (containerFocused) { + switch (event.keyCode) { + case 37: + updateCurrentSlide(currentSlideIndex - 1); + break; + case 39: + updateCurrentSlide(currentSlideIndex + 1); + break; + case 13: + case 32: + openActiveSlide(); + break; + } + } + }); + + const leftArrow = document.createElement("div"); + const rightArrow = document.createElement("div"); + leftArrow.classList.add("arrow", "left-arrow"); + rightArrow.classList.add("arrow", "right-arrow"); + leftArrow.innerHTML = ''; + rightArrow.innerHTML = ''; + container.appendChild(leftArrow); + container.appendChild(rightArrow); + leftArrow.addEventListener("click", () => + updateCurrentSlide(currentSlideIndex - 1) + ); + rightArrow.addEventListener("click", () => + updateCurrentSlide(currentSlideIndex + 1) + ); + container.addEventListener("focus", () => { + containerFocused = !0; + }); + container.addEventListener("blur", () => { + containerFocused = !1; + }); + }; + const initializeSlides = async () => { + const itemIds = await fetchItemIdsFromList(); + let items; + if (itemIds.length > 0) { + const itemPromises = itemIds.map((id) => fetchItemDetails(id)); + items = await Promise.all(itemPromises); + } else { + const allItems = await fetchItemsFromServer(); + const itemPromises = allItems.map((item) => fetchItemDetails(item.Id)); + items = await Promise.all(itemPromises); + } + items = shuffleArray(items); + await createSlidesForItems(items); + initializeSlideshow(); + }; + initializeSlides(); +}; +document.addEventListener("DOMContentLoaded", () => { + document.body.addEventListener("click", (event) => { + const homeButton = event.target.matches( + ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" + ); + if (homeButton) { + event.preventDefault(); + window.location.href = "/web/index.html#/home.html"; + setTimeout(handleHomeNavigation, 300); // Delay for UI update + } + }); +}); \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj new file mode 100644 index 0000000..f133d34 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + diff --git a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs new file mode 100644 index 0000000..5536311 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs @@ -0,0 +1,56 @@ +using System.Reflection; +using System.Text.RegularExpressions; +using Jellyfin.Plugin.FileTransformation.Controller; +using Jellyfin.Plugin.FileTransformation.Infrastructure; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.DependencyInjection; + +namespace Jellyfin.Plugin.MediaBar; + +public class MediaBarPlugin : BasePlugin +{ + public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448"); + + public override string Name => "Media Bar"; + + public MediaBarPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IServiceProvider serviceProvider) : base(applicationPaths, xmlSerializer) + { + IWebFileTransformationWriteService webFileTransformationWriteService = serviceProvider.GetRequiredService(); + + webFileTransformationWriteService.AddTransformation("index.html", IndexHtml_Transformation); + webFileTransformationWriteService.AddTransformation("home-html\\.[a-zA-z0-9]+\\.chunk\\.js", HomeHtmlChunk_Transformation); + } + + private void IndexHtml_Transformation(string path, Stream contents) + { + Stream? stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{GetType().Namespace}.Inject.index.html"); + using TextReader reader = new StreamReader(stream); + + using var textReader = new StreamReader(contents, null, true, -1, true); + var text = textReader.ReadToEnd(); + + var regex = Regex.Replace(text, "()", $"{reader.ReadToEnd()}$1"); + contents.Seek(0, SeekOrigin.Begin); + + using var textWriter = new StreamWriter(contents, null, -1, true); + textWriter.Write(regex); + } + + private void HomeHtmlChunk_Transformation(string path, Stream contents) + { + Stream? stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{GetType().Namespace}.Inject.home-html.chunk.js"); + using TextReader reader = new StreamReader(stream); + + using var textReader = new StreamReader(contents, null, true, -1, true); + var text = textReader.ReadToEnd(); + + var regex = Regex.Replace(text, "(id=\"homeTab\" data-index=\"0\">)", $"$1{reader.ReadToEnd()}"); + contents.Seek(0, SeekOrigin.Begin); + + using var textWriter = new StreamWriter(contents, null, -1, true); + textWriter.Write(regex); + } +} \ No newline at end of file From 8909326b548a6b125a43ba1b0fd78e5c79329a42 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Fri, 7 Feb 2025 13:23:10 +0000 Subject: [PATCH 002/108] Integrated latest changed from fork into installable plugin --- .../Inject/media-bar.js | 3 +- .../Inject/slideshowpure.css | 189 +++++++++++++----- .../Inject/slideshowpure.js | 68 +++++-- 3 files changed, 194 insertions(+), 66 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js index 70724e6..ec47dd9 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js @@ -1,5 +1,4 @@ - -function saveCredentialsToSessionStorage(credentials) { +function saveCredentialsToSessionStorage(credentials) { try { sessionStorage.setItem( "json-credentials", diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css index 2270e6b..e00974a 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css @@ -2,13 +2,13 @@ @media only screen and (min-width: 767px) { div[data-role="page"].page { - overflow-y: scroll; /* Keeps vertical scrolling active */ - scrollbar-width: none; /* For Firefox */ - -ms-overflow-style: none; /* For Internet Explorer & Edge */ + overflow-y: scroll; + scrollbar-width: none; + -ms-overflow-style: none; } div[data-role="page"].page::-webkit-scrollbar { - display: none; /* Hides scrollbar in Webkit browsers */ + display: none; } .backdrop, @@ -41,24 +41,24 @@ } .arrow::before { - content: ""; /* Creates an empty element */ + content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); - width: 40px; /* Size of the circle */ - height: 40px; /* Size of the circle */ - background-color: rgba(12, 110, 162, 0.7); /* Translucent background */ - border-radius: 50%; /* Makes it a circle */ - z-index: -1; /* Places the circle behind the arrow */ + width: 40px; + height: 40px; + background-color: rgba(12, 110, 162, 0.7); + border-radius: 50%; + z-index: -1; } .left-arrow { - left: 10px; /* Position to the left */ + left: 10px; } .right-arrow { - right: 10px; /* Position to the right */ + right: 10px; } .dots-container { @@ -68,19 +68,19 @@ right: 0; z-index: 2; display: flex; - justify-content: center; /* Centers the dots horizontally */ + justify-content: center; align-items: center; - width: 100vw; /* Full screen width */ + width: 100vw; height: 22px; background-color: #ffffff00; - gap: 2vw; /* Evenly space the rectangles with a gap of 2vw */ + gap: 2vw; } .dot { - width: 15vw; /* Each rectangle takes 18% of the viewport width */ - height: 4px; /* Thin rectangle height */ + width: 15vw; + height: 4px; background-color: #cecece; - border-radius: 2px; /* Slightly rounded corners */ + border-radius: 2px; cursor: pointer; transition: background-color 0.3s; } @@ -113,7 +113,7 @@ } .slide { - transition: opacity 0.5s ease-in-out; /* Smooth fade transition */ + transition: opacity 0.5s ease-in-out; position: absolute; top: 0; left: 0; @@ -167,7 +167,7 @@ left: 0; width: 100%; height: 100%; - background-color: rgba(0, 0, 0, 0.6); /* Adjust the alpha for dimness */ + background-color: rgba(0, 0, 0, 0.6); border-radius: 5px; z-index: 2; mask-image: linear-gradient( @@ -197,7 +197,9 @@ position: relative; max-height: 100%; max-width: 100%; + height: auto; width: auto; + object-fit: contain; z-index: 3; top: 5em; left: 8em; @@ -343,13 +345,13 @@ .play-button { position: absolute; display: flex; - align-items: center; /* Center vertically */ - justify-content: center; /* Keep content aligned horizontally */ + align-items: center; + justify-content: center; padding: 10px 20px; color: #fff; background-color: rgba(255, 255, 255, 0); border: none; - border-radius: 50px; /* Round the edges */ + border-radius: 50px; cursor: pointer; font-size: 14px; font-weight: 300; @@ -363,27 +365,70 @@ display: flex; align-items: center; justify-content: center; - font-size: 20px; /* Adjust the size of the play icon */ - width: 30px; /* Ensure the icon area is circular */ + font-size: 20px; + width: 30px; height: 30px; background-color: rgba( 12, 110, 162, 0.7 - ); /* Same as the button's background */ + ); border: 3px solid rgba(12, 110, 162, 1); - border-radius: 50%; /* Make the icon a perfect circle */ + border-radius: 50%; } .play-text { font-family: "Titillium Web", sans-serif; - margin-left: 10px; /* Space between icon and text */ + margin-left: 10px; + font-size: 14px; + color: #fff; + } + .detail-button { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + top: calc(50% + 190px); + left: 15.8em; + } + + .detail-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 30px; + height: 30px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); + border: 3px solid rgba(12, 110, 162, 1); + border-radius: 50%; + } + + .detail-text { + font-family: "Titillium Web", sans-serif; + margin-left: 10px; font-size: 14px; color: #fff; } } + @media only screen and (max-width: 767px) { .backdrop, .slide { @@ -392,22 +437,20 @@ .arrow { position: absolute; - /*top: 50%; - transform: translateY(-50%);*/ bottom: 25px; font-size: 24px; cursor: pointer; color: rgba(255, 255, 255, 0.6); z-index: 4; - -webkit-tap-highlight-color: transparent; /* Disable blue tap highlight */ + -webkit-tap-highlight-color: transparent; } .left-arrow { - left: 10px; /* Position to the left */ + left: 10px; } .right-arrow { - right: 10px; /* Position to the right */ + right: 10px; } .featured-content, @@ -465,7 +508,7 @@ width: 32px; height: 12px; background-color: rgba(12, 110, 162, 0.7); - border-radius: 10px; /* Rounded rectangle shape */ + border-radius: 10px; } #slides-container { @@ -478,7 +521,7 @@ } .slide { - transition: opacity 0.5s ease-in-out; /* Smooth fade transition */ + transition: opacity 0.5s ease-in-out; position: absolute; top: 0; left: 0; @@ -536,7 +579,7 @@ left: 0; width: 100%; height: calc(100% - 50px); - background: rgba(0, 0, 0, 0.55); /* Semi-transparent overlay color */ + background: rgba(0, 0, 0, 0.55); z-index: 2; border-radius: 13px; pointer-events: none; @@ -636,50 +679,96 @@ .play-button { position: absolute; display: flex; - align-items: center; /* Center vertically */ - justify-content: flex-start; /* Keep content aligned horizontally */ + align-items: center; + justify-content: flex-start; padding: 10px 20px; color: #fff; background-color: rgba(255, 255, 255, 0); border: none; - border-radius: 50px; /* Round the edges */ + border-radius: 50px; cursor: pointer; font-size: 14px; font-weight: 300; transition: background-color 0.3s; z-index: 4; bottom: 20px; - /*right: 0%;*/ left: 50%; - transform: translateX(-50%) scale(1.2); - outline: none; /* Remove the default blue focus outline */ - -webkit-tap-highlight-color: transparent; /* Disable blue tap highlight */ + transform: translateX(-110%) scale(1.2); + outline: none; + -webkit-tap-highlight-color: transparent; } .play-icon { display: flex; align-items: center; justify-content: center; - font-size: 20px; /* Adjust the size of the play icon */ - width: 20px; /* Ensure the icon area is circular */ + font-size: 20px; + width: 20px; height: 20px; background-color: rgba( 12, 110, 162, 0.7 - ); /* Same as the button's background */ + ); border: 1.5px solid rgba(12, 110, 162, 1); - border-radius: 50%; /* Make the icon a perfect circle */ - outline: none; /* Remove the default blue focus outline */ - -webkit-tap-highlight-color: transparent; /* Disable blue tap highlight */ + border-radius: 50%; + outline: none; + -webkit-tap-highlight-color: transparent; } .play-text { display: block; font-family: "Titillium Web", sans-serif; - margin-left: 5px; /* Space between icon and text */ + margin-left: 5px; font-size: 14px; color: #fff; } + .detail-button { + position: absolute; + display: flex; + align-items: center; + justify-content: flex-start; + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + bottom: 20px; + left: 50%; + transform: translateX(10%) scale(1.2); + outline: none; + -webkit-tap-highlight-color: transparent; + } + + .detail-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 20px; + height: 20px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); + border: 1.5px solid rgba(12, 110, 162, 1); + border-radius: 50%; + outline: none; + -webkit-tap-highlight-color: transparent; + } + + .detail-text { + display: block; + font-family: "Titillium Web", sans-serif; + margin-left: 5px; + color: #fff; + } } \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js index 33853c3..95609be 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -3,7 +3,6 @@ let isTransitioning = false; const listFileName = `${window.location.origin}/web/avatars/list.txt`; const jsonCredentials = sessionStorage.getItem("json-credentials"); - const apiKey = sessionStorage.getItem("api-key"); let userId = null; let token = null; const deviceId = localStorage.getItem("_deviceId2"); @@ -169,13 +168,55 @@ const playButton = document.createElement("button"); playButton.className = "play-button"; playButton.innerHTML = ` - - Details + + Play Now `; - playButton.onclick = () => { + playButton.onclick = async () => { + if (!window.ApiClient) { + console.error("Jellyfin API client is not available."); + return; + } + const apiClient = window.ApiClient; + const userId = apiClient.getCurrentUserId(); + if (!userId) { + console.error("User not found."); + return; + } + try { + const sessions = await apiClient.getJSON( + apiClient.serverAddress() + "/Sessions" + ); + if (!sessions.length) { + console.error("No active Jellyfin sessions found."); + return; + } + const sessionId = sessions[0].Id; + const playbackUrl = `${apiClient.serverAddress()}/Sessions/${sessionId}/Playing?StartPositionTicks=0&PlayCommand=PlayNow&StartIndex=0&ItemIds=${itemId}&api_key=${token}`; + const response = await fetch(playbackUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDevice", DeviceId="YourDeviceId", Version="YourVersion", Token="${token}"`, + }, + }); + if (response.status === 204) { + console.log("Playback started successfully."); + } else { + const errorData = await response.json(); + console.error("Failed to start playback:", errorData); + } + } catch (error) { + console.error("Error fetching sessions:", error); + } + }; + const detailButton = document.createElement("button"); + detailButton.className = "detail-button"; + detailButton.innerHTML = ` + + More Details + `; + detailButton.onclick = () => { window.top.location.href = `/#!/details?id=${itemId}`; - //window.location.href = `/Videos/${itemId}/stream.mp4?Static=true&mediaSourceId=${itemId}&deviceId=${deviceId}&api_key=${apiKey}`; - //window.top.location.href = streamUrl; }; slide.append( logoContainer, @@ -185,7 +226,8 @@ plotContainer, infoContainer, genreElement, - playButton + playButton, + detailButton ); return slide; }; @@ -315,14 +357,13 @@ let focusedSlide = null; let containerFocused = !1; const updateCurrentSlide = (index) => { - if (isTransitioning) return; // Skip if already transitioning - isTransitioning = true; // Set transitioning to true at the start + if (isTransitioning) return; + isTransitioning = true; currentSlideIndex = (index + slides.length) % slides.length; showSlide(currentSlideIndex); - // Re-enable arrows after a short delay to prevent rapid clicking setTimeout(() => { - isTransitioning = false; // Reset after transition completes - }, 500); // Adjust the delay as needed (500ms is a reasonable starting point) + isTransitioning = false; + }, 500); }; const openActiveSlide = () => { if (focusedSlide) { @@ -373,7 +414,6 @@ } } }); - const leftArrow = document.createElement("div"); const rightArrow = document.createElement("div"); leftArrow.classList.add("arrow", "left-arrow"); @@ -420,7 +460,7 @@ document.addEventListener("DOMContentLoaded", () => { if (homeButton) { event.preventDefault(); window.location.href = "/web/index.html#/home.html"; - setTimeout(handleHomeNavigation, 300); // Delay for UI update + setTimeout(handleHomeNavigation, 300); } }); }); \ No newline at end of file From c65281f5674cc4902df92aa5498c800eb8a13cd6 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Fri, 7 Feb 2025 18:45:51 +0000 Subject: [PATCH 003/108] Added assembly info for media bar plugin --- src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs diff --git a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..38c759a --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using System.Reflection; +[assembly: AssemblyCompany("IAmParadox27")] +[assembly: AssemblyProduct("Jellyfin.Plugins.MediaBar")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyTitle("Jellyfin.Plugins.MediaBar")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file From 46d9d5623fdbe51c800f923a348e987b2bf2accf Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Fri, 7 Feb 2025 19:00:53 +0000 Subject: [PATCH 004/108] Added flags to csproj --- src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index f133d34..896f431 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -4,6 +4,8 @@ net8.0 enable enable + false + 1.0.0 From 067acaffc9c8519297169a0bea9be50fe32e8dda Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Fri, 7 Feb 2025 19:07:20 +0000 Subject: [PATCH 005/108] Forgot to update media bar to use 1.2.2 for filetransformation --- src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 896f431..22b4945 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -9,7 +9,7 @@ - + From c6c62cd0d1ff798d5adc57edf9366217eed7dabe Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Fri, 7 Feb 2025 19:34:50 +0000 Subject: [PATCH 006/108] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index e6c2f4f..8915480 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # Jellyfin-Media-Bar - Now with Play Now Function +1. Add `https://www.iamparadox.dev/jellyfin/plugins/manifest.json` to your repositories +2. Install "File Transformation" 1.2.2 +3. Install "Media Bar" 1.0.1 +4. Restart Jellyfin +
+ Original README.md from MakD ## TODO - Design changes upcoming next release Thanks to the Man, the Legend [BobHasNoSoul](https://github.com/BobHasNoSoul) for his work on the [jellyfinfeatured](https://github.com/BobHasNoSoul/jellyfin-featured) and [SethBacon](https://forum.jellyfin.org/u-sethbacon) and [TedHinklater](https://github.com/tedhinklater) for their take on the [Jellyfin-Featured-Content-Bar](https://github.com/tedhinklater/Jellyfin-Featured-Content-Bar). @@ -137,3 +143,4 @@ ItemID4 ItemID5 ``` The next time it loads, it will display these items. +
From de1cfcaefc44f173929b87213be119287bc2365f Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Fri, 7 Feb 2025 19:35:51 +0000 Subject: [PATCH 007/108] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8915480..9899adf 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ # Jellyfin-Media-Bar - Now with Play Now Function +This plugin uses JF Referenceable + 1. Add `https://www.iamparadox.dev/jellyfin/plugins/manifest.json` to your repositories 2. Install "File Transformation" 1.2.2 3. Install "Media Bar" 1.0.1 4. Restart Jellyfin +
Original README.md from MakD + ## TODO - Design changes upcoming next release Thanks to the Man, the Legend [BobHasNoSoul](https://github.com/BobHasNoSoul) for his work on the [jellyfinfeatured](https://github.com/BobHasNoSoul/jellyfin-featured) and [SethBacon](https://forum.jellyfin.org/u-sethbacon) and [TedHinklater](https://github.com/tedhinklater) for their take on the [Jellyfin-Featured-Content-Bar](https://github.com/tedhinklater/Jellyfin-Featured-Content-Bar). From 817a9e1eafef9f47ea05474367dcbd88b3fe979a Mon Sep 17 00:00:00 2001 From: Jam <1347620+JamsRepos@users.noreply.github.com> Date: Fri, 7 Feb 2025 21:31:43 +0000 Subject: [PATCH 008/108] Added Plugin Logo --- src/logo.png | Bin 0 -> 32205 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/logo.png diff --git a/src/logo.png b/src/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d1421be90eca1e1a19875cb08667902c6877594a GIT binary patch literal 32205 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 K73@8B=|2Eu`yycg literal 0 HcmV?d00001 From 782661a3b13488998a044fc279771e96f3777f42 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sun, 9 Feb 2025 17:47:47 +0000 Subject: [PATCH 009/108] Changed media bar to use the HTTP Request approach for file transformation. Much more stable --- .../Controllers/MediaBarController.cs | 69 +++++++++++----- .../Jellyfin.Plugin.MediaBar.csproj | 12 ++- .../MediaBarPlugin.cs | 56 ++----------- .../Model/PatchRequestPayload.cs | 10 +++ .../Services/StartupService.cs | 82 +++++++++++++++++++ 5 files changed, 158 insertions(+), 71 deletions(-) create mode 100644 src/Jellyfin.Plugin.MediaBar/Model/PatchRequestPayload.cs create mode 100644 src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs diff --git a/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs index 9193c35..38db0f9 100644 --- a/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs +++ b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs @@ -1,32 +1,57 @@ using System.Reflection; +using System.Text.RegularExpressions; +using Jellyfin.Plugin.MediaBar.Model; using Microsoft.AspNetCore.Mvc; -namespace Jellyfin.Plugin.MediaBar.Controllers; - -[Route("[controller]")] -public class MediaBarController : ControllerBase +namespace Jellyfin.Plugin.MediaBar.Controllers { - [HttpGet("{file}")] - public ActionResult GetFile([FromRoute] string file) + [Route("[controller]")] + public class MediaBarController : ControllerBase { - Stream fileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Jellyfin.Plugin.MediaBar.Inject." + file); - string fileContents = new StreamReader(fileStream).ReadToEnd(); + [HttpGet("{file}")] + public ActionResult GetFile([FromRoute] string file) + { + Stream fileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Jellyfin.Plugin.MediaBar.Inject." + file)!; + string fileContents = new StreamReader(fileStream).ReadToEnd(); - string contentType = "text/plain"; + string contentType = "text/plain"; - if (Path.GetExtension(file) == ".js") - { - contentType = "text/javascript"; - } - else if (Path.GetExtension(file) == ".css") - { - contentType = "text/css"; - } - else if (Path.GetExtension(file) == ".html") - { - contentType = "text/html"; - } + if (Path.GetExtension(file) == ".js") + { + contentType = "text/javascript"; + } + else if (Path.GetExtension(file) == ".css") + { + contentType = "text/css"; + } + else if (Path.GetExtension(file) == ".html") + { + contentType = "text/html"; + } - return Content(fileContents, contentType); + return Content(fileContents, contentType); + } + + [HttpPost("Patch/IndexHtml")] + public ActionResult PatchIndexHtml([FromBody] PatchRequestPayload payload) + { + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(MediaBarPlugin).Namespace}.Inject.index.html")!; + using TextReader reader = new StreamReader(stream); + + string regex = Regex.Replace(payload.Contents!, "()", $"{reader.ReadToEnd()}$1"); + + return Content(regex, "text/html"); + } + + [HttpPost("Patch/HomeHtmlChunk")] + public ActionResult PatchHomeHtmlChunk([FromBody] PatchRequestPayload payload) + { + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(MediaBarPlugin).Namespace}.Inject.home-html.chunk.js")!; + using TextReader reader = new StreamReader(stream); + + string regex = Regex.Replace(payload.Contents!, "(id=\"homeTab\" data-index=\"0\">)", $"$1{reader.ReadToEnd()}"); + + return Content(regex, "text/html"); + } } } \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 22b4945..3d31bab 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -5,11 +5,16 @@ enable enable false - 1.0.0 + 2.0.0 - + + + + + + @@ -25,4 +30,7 @@ + + + diff --git a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs index 5536311..172ac70 100644 --- a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs +++ b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs @@ -1,56 +1,18 @@ -using System.Reflection; -using System.Text.RegularExpressions; -using Jellyfin.Plugin.FileTransformation.Controller; -using Jellyfin.Plugin.FileTransformation.Infrastructure; -using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.DependencyInjection; -namespace Jellyfin.Plugin.MediaBar; - -public class MediaBarPlugin : BasePlugin +namespace Jellyfin.Plugin.MediaBar { - public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448"); + public class MediaBarPlugin : BasePlugin + { + public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448"); - public override string Name => "Media Bar"; + public override string Name => "Media Bar"; - public MediaBarPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IServiceProvider serviceProvider) : base(applicationPaths, xmlSerializer) - { - IWebFileTransformationWriteService webFileTransformationWriteService = serviceProvider.GetRequiredService(); - - webFileTransformationWriteService.AddTransformation("index.html", IndexHtml_Transformation); - webFileTransformationWriteService.AddTransformation("home-html\\.[a-zA-z0-9]+\\.chunk\\.js", HomeHtmlChunk_Transformation); - } - - private void IndexHtml_Transformation(string path, Stream contents) - { - Stream? stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{GetType().Namespace}.Inject.index.html"); - using TextReader reader = new StreamReader(stream); - - using var textReader = new StreamReader(contents, null, true, -1, true); - var text = textReader.ReadToEnd(); - - var regex = Regex.Replace(text, "()", $"{reader.ReadToEnd()}$1"); - contents.Seek(0, SeekOrigin.Begin); - - using var textWriter = new StreamWriter(contents, null, -1, true); - textWriter.Write(regex); - } - - private void HomeHtmlChunk_Transformation(string path, Stream contents) - { - Stream? stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{GetType().Namespace}.Inject.home-html.chunk.js"); - using TextReader reader = new StreamReader(stream); - - using var textReader = new StreamReader(contents, null, true, -1, true); - var text = textReader.ReadToEnd(); - - var regex = Regex.Replace(text, "(id=\"homeTab\" data-index=\"0\">)", $"$1{reader.ReadToEnd()}"); - contents.Seek(0, SeekOrigin.Begin); - - using var textWriter = new StreamWriter(contents, null, -1, true); - textWriter.Write(regex); + public MediaBarPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IServiceProvider serviceProvider) : base(applicationPaths, xmlSerializer) + { + } } } \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Model/PatchRequestPayload.cs b/src/Jellyfin.Plugin.MediaBar/Model/PatchRequestPayload.cs new file mode 100644 index 0000000..a43eac2 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Model/PatchRequestPayload.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Jellyfin.Plugin.MediaBar.Model +{ + public class PatchRequestPayload + { + [JsonPropertyName("contents")] + public string? Contents { get; set; } + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs new file mode 100644 index 0000000..4f45027 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs @@ -0,0 +1,82 @@ +using System.Net.Http.Headers; +using System.Reflection; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Jellyfin.Plugin.MediaBar.Services +{ + public class StartupService : IScheduledTask + { + public string Name => "MediaBar Startup"; + + public string Key => "Jellyfin.Plugin.MediaBar.Startup"; + + public string Description => "Startup Service for MediaBar"; + + public string Category => "Startup Services"; + + private readonly IServerApplicationHost m_serverApplicationHost; + private readonly ILogger m_logger; + + public StartupService(IServerApplicationHost serverApplicationHost, ILogger logger) + { + m_serverApplicationHost = serverApplicationHost; + m_logger = logger; + } + + public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) + { + List payloads = new List(); + + { + JObject payload = new JObject(); + payload.Add("id", "0dfac9d7-d898-4944-900b-1c1837707279"); + payload.Add("fileNamePattern", "index.html"); + payload.Add("tranformationEndpoint", "/MediaBar/Patch/IndexHtml"); + + payloads.Add(payload); + } + { + JObject payload = new JObject(); + payload.Add("id", "e6d32b76-d54b-4946-b73e-c5c9c50575c9"); + payload.Add("fileNamePattern", "home-html\\.[a-zA-z0-9]+\\.chunk\\.js"); + payload.Add("tranformationEndpoint", "/MediaBar/Patch/HomeHtmlChunk"); + + payloads.Add(payload); + } + + string? publishedServerUrl = m_serverApplicationHost.GetType() + .GetProperty("PublishedServerUrl", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(m_serverApplicationHost) as string; + + foreach (JObject payload in payloads) + { + HttpClient client = new HttpClient(); + client.BaseAddress = new Uri(publishedServerUrl ?? $"http://localhost:{m_serverApplicationHost.HttpPort}"); + + try + { + await client.PostAsync("/FileTransformation/RegisterTransformation", + new StringContent(payload.ToString(Formatting.None), + MediaTypeHeaderValue.Parse("application/json"))); + } + catch (Exception ex) + { + m_logger.LogError(ex, $"Caught exception when attempting to register file transformation. Ensure you have `File Transformation` plugin installed on your server."); + return; + } + } + } + + public IEnumerable GetDefaultTriggers() + { + yield return new TaskTriggerInfo() + { + Type = TaskTriggerInfo.TriggerStartup + }; + } + } +} \ No newline at end of file From 64c7c36f73f0d91444067b69a3d3938446a45261 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sun, 9 Feb 2025 18:23:41 +0000 Subject: [PATCH 010/108] Forgot to update Version/AssemblyVersion values --- src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs index 38c759a..7109049 100644 --- a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs +++ b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs @@ -3,5 +3,4 @@ [assembly: AssemblyProduct("Jellyfin.Plugins.MediaBar")] [assembly: AssemblyDescription("")] [assembly: AssemblyTitle("Jellyfin.Plugins.MediaBar")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file +[assembly: AssemblyVersion("2.0.0.0")] \ No newline at end of file From bb0ba526c9ca872ccb11463220977dd28002d127 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sun, 9 Feb 2025 18:37:55 +0000 Subject: [PATCH 011/108] Update README.md --- README.md | 6 ++---- .../Jellyfin.Plugin.MediaBar.csproj | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9899adf..f26a04c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ # Jellyfin-Media-Bar - Now with Play Now Function -This plugin uses JF Referenceable - 1. Add `https://www.iamparadox.dev/jellyfin/plugins/manifest.json` to your repositories -2. Install "File Transformation" 1.2.2 -3. Install "Media Bar" 1.0.1 +2. Install "File Transformation" 2.0.0 +3. Install "Media Bar" 2.0.0 4. Restart Jellyfin
diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 3d31bab..8068433 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -30,6 +30,10 @@ + + + + From 31ccec3ab3c482bae6257fe2e933da59e5195bb4 Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:39:41 +0000 Subject: [PATCH 012/108] Update slideshowpure.js --- .../Inject/slideshowpure.js | 941 +++++++++--------- 1 file changed, 481 insertions(+), 460 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js index 95609be..548c8de 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -1,466 +1,487 @@ -const slidesInit = () => { - const shuffleInterval = 8000; - let isTransitioning = false; - const listFileName = `${window.location.origin}/web/avatars/list.txt`; - const jsonCredentials = sessionStorage.getItem("json-credentials"); - let userId = null; - let token = null; - const deviceId = localStorage.getItem("_deviceId2"); - console.log(deviceId); - if (jsonCredentials) { - const credentials = JSON.parse(jsonCredentials); - userId = credentials.Servers[0].UserId; - token = credentials.Servers[0].AccessToken; +const slidesInit = () => { + const shuffleInterval = 8000; + let isTransitioning = false; + const listFileName = `${window.location.origin}/web/avatars/list.txt`; + const jsonCredentials = sessionStorage.getItem("json-credentials"); + let userId = null; + let token = null; + //const deviceId = localStorage.getItem("_deviceId2"); + //console.log(deviceId); + if (jsonCredentials) { + const credentials = JSON.parse(jsonCredentials); + userId = credentials.Servers[0].UserId; + token = credentials.Servers[0].AccessToken; + } + const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); + const truncateText = (element, maxLength) => { + let text = element.innerText; + if (text.length > maxLength) + element.innerText = text.substring(0, maxLength) + "..."; + }; + const createSlideElement = (item, title) => { + const itemId = item.Id; + const plot = item.Overview || "No overview available"; + const rating = item.CommunityRating; + const tomato = item.CriticRating; + const runtime = item.RunTimeTicks; + const genre = item.Genres; + const youtube = item.RemoteTrailers; + const age = item.OfficialRating; + const date = item.PremiereDate; + const season = item.ChildCount; + function createSeparator() { + const separatorHtml = + ''; + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = separatorHtml; + return tempDiv.firstChild; } - const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); - const truncateText = (element, maxLength) => { - let text = element.innerText; - if (text.length > maxLength) - element.innerText = text.substring(0, maxLength) + "..."; + const slide = document.createElement("a"); + slide.className = "slide"; + slide.target = "_top"; + slide.rel = "noreferrer"; + slide.tabIndex = 0; + slide.style.display = "none"; + const backdrop = document.createElement("img"); + backdrop.className = "backdrop"; + backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; + backdrop.alt = "Backdrop"; + const backdropOverlay = document.createElement("div"); + backdropOverlay.className = "backdrop-overlay"; + const backdropContainer = document.createElement("div"); + backdropContainer.className = "backdrop-container"; + backdropContainer.appendChild(backdrop); + backdropContainer.appendChild(backdropOverlay); + const logo = document.createElement("img"); + logo.className = "logo"; + logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; + logo.alt = "Logo"; + const logoContainer = document.createElement("div"); + logoContainer.className = "logo-container"; + logoContainer.appendChild(logo); + const featuredContent = document.createElement("div"); + featuredContent.className = "featured-content"; + featuredContent.textContent = title; + const plotElement = document.createElement("div"); + plotElement.className = "plot"; + plotElement.textContent = plot; + truncateText(plotElement, 360); + const plotContainer = document.createElement("div"); + plotContainer.className = "plot-container"; + plotContainer.appendChild(plotElement); + const gradientOverlay = document.createElement("div"); + gradientOverlay.className = "gradient-overlay"; + const runTimeElement = document.createElement("div"); + runTimeElement.className = "runTime"; + if (season === undefined) { + const milliseconds = runtime / 10000; + const currentTime = new Date(); + const endTime = new Date(currentTime.getTime() + milliseconds); + const options = { hour: "2-digit", minute: "2-digit", hour12: !0 }; + const formattedEndTime = endTime.toLocaleTimeString([], options); + runTimeElement.textContent = `Ends at ${formattedEndTime}`; + } else { + runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; + } + const ratingTest = document.createElement("div"); + ratingTest.className = "rating-value"; + const imdbLogo = document.createElement("img"); + imdbLogo.className = "imdb-logo"; + imdbLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg"; + imdbLogo.alt = "IMDb Logo"; + imdbLogo.style.width = "30px"; + imdbLogo.style.height = "30px"; + ratingTest.appendChild(imdbLogo); + if (typeof rating === "number") { + const formattedRating = rating.toFixed(1); + ratingTest.innerHTML += `${formattedRating} ⭐`; + } else { + console.error("Rating is undefined or not a number:", rating); + ratingTest.innerHTML += `N/A ⭐`; + } + ratingTest.appendChild(createSeparator()); + const tomatoRatingDiv = document.createElement("div"); + tomatoRatingDiv.className = "tomato-rating"; + const criticRating = item.CriticRating; + const tomatoLogo = document.createElement("img"); + tomatoLogo.className = "tomato-logo"; + const criticLogo = document.createElement("img"); + criticLogo.className = "critic-logo"; + tomatoLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; + let valueElement; + if (typeof criticRating === "number") { + valueElement = document.createTextNode(`${criticRating}% `); + } else { + valueElement = document.createTextNode(`N/A `); + } + if (criticRating === undefined || criticRating <= 59) { + criticLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; + criticLogo.alt = "Rotten Tomato"; + } else { + criticLogo.src = "https://i.imgur.com/iMfwDk7.png"; + criticLogo.alt = "Fresh Tomato"; + } + tomatoLogo.style.width = "15px"; + tomatoLogo.style.height = "17px"; + criticLogo.style.width = "15px"; + criticLogo.style.height = "15px"; + tomatoRatingDiv.appendChild(tomatoLogo); + tomatoRatingDiv.appendChild(valueElement); + tomatoRatingDiv.appendChild(criticLogo); + tomatoRatingDiv.appendChild(createSeparator()); + const ageRatingDiv = document.createElement("div"); + ageRatingDiv.className = "age-rating"; + if (item.OfficialRating) { + ageRatingDiv.innerHTML = `${item.OfficialRating}`; + } else { + ageRatingDiv.innerHTML = `N/A`; + } + const calender = "📅"; + const premiereDate = document.createElement("div"); + premiereDate.className = "date"; + const year = date ? new Date(date).getFullYear() : "N/A"; + premiereDate.textContent = isNaN(year) ? "N/A" : year; + ratingTest.appendChild(tomatoRatingDiv); + ratingTest.appendChild(document.createTextNode(calender)); + ratingTest.appendChild(premiereDate); + ratingTest.appendChild(createSeparator()); + ratingTest.appendChild(ageRatingDiv); + ratingTest.appendChild(createSeparator()); + ratingTest.appendChild(runTimeElement); + const genresArray = item.Genres; + function parseGenres(genresArray) { + if (genresArray && genresArray.length > 0) { + return genresArray.slice(0, 3).join(" 🔹 "); + } else { + return "No Genre Available"; + } + } + const genreElement = document.createElement("div"); + genreElement.className = "genre"; + genreElement.innerHTML = parseGenres(genresArray); + const infoContainer = document.createElement("div"); + infoContainer.className = "info-container"; + infoContainer.appendChild(ratingTest); + // Create a container for the buttons +const buttonContainer = document.createElement("div"); +buttonContainer.className = "button-container"; // Add a class for styling + +// Create the Play button +const playButton = document.createElement("button"); +playButton.className = "play-button"; +playButton.innerHTML = ` + + Play Now +`; +playButton.onclick = async () => { + if (!window.ApiClient) { + console.error("Jellyfin API client is not available."); + return; + } + const apiClient = window.ApiClient; + const userId = apiClient.getCurrentUserId(); + if (!userId) { + console.error("User not found."); + return; + } + try { + const sessions = await apiClient.getJSON(apiClient.serverAddress() + "/Sessions"); + if (!sessions.length) { + console.error("No active Jellyfin sessions found."); + return; + } + + // Find the session matching the current user + const userSession = sessions.find(session => session.UserId === userId); + if (!userSession) { + console.error("No active session found for the current user."); + return; + } + + const sessionId = userSession.Id; + const playbackUrl = `${apiClient.serverAddress()}/Sessions/${sessionId}/Playing?StartPositionTicks=0&PlayCommand=PlayNow&StartIndex=0&ItemIds=${itemId}&api_key=${token}`; + + const response = await fetch(playbackUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `DeviceId="unique-device-id", Token="${token}"`, + }, + }); + + if (response.status === 204) { + console.log("Playback started successfully."); + } else { + const errorData = await response.json(); + console.error("Failed to start playback:", errorData); + } + } catch (error) { + console.error("Error fetching sessions:", error); + } +}; + +// Create the Details button +const detailButton = document.createElement("button"); +detailButton.className = "detail-button"; +detailButton.innerHTML = ` + + More Info +`; +detailButton.onclick = () => { + window.top.location.href = `/#!/details?id=${itemId}`; +}; + +// Append buttons to the button container +buttonContainer.appendChild(playButton); +buttonContainer.appendChild(detailButton); + +// Append all elements to the slide +slide.append( + logoContainer, + backdropContainer, + gradientOverlay, + featuredContent, + plotContainer, + infoContainer, + genreElement, + buttonContainer // Add button container instead of individual buttons +); + +return slide; + }; + const createSlideForItem = async (item, title) => { + const container = document.getElementById("slides-container"); + const itemId = item.Id; + const backdropUrl = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; + const logoUrl = `${window.location.origin}/Items/${itemId}/Images/Logo`; + const [backdropExists, logoExists] = await Promise.all([ + fetch(backdropUrl, { method: "HEAD" }).then((res) => res.ok), + fetch(logoUrl, { method: "HEAD" }).then((res) => res.ok), + ]); + if (backdropExists && logoExists) { + const slideElement = createSlideElement(item, title); + container.appendChild(slideElement); + console.log(`Added slide for item ${itemId}`); + if (container.children.length === 1) { + showSlide(0); + } + } else { + console.warn(`Skipping item ${itemId}: Missing backdrop or logo.`); + } + }; + const fetchItemDetails = async (itemId) => { + const response = await fetch( + `${window.location.origin}/Users/${userId}/Items/${itemId}`, + { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + } + ); + const item = await response.json(); + console.log("Item Title:", item.Name); + return item; + }; + const fetchItemIdsFromList = async () => { + try { + const response = await fetch(listFileName); + if (!response.ok) { + throw new Error("Failed to fetch list.txt"); + } + const text = await response.text(); + return text + .split("\n") + .map((id) => id.trim()) + .filter((id) => id); + } catch (error) { + console.error("Error fetching list.txt:", error); + return []; + } + }; + const fetchItemsFromServer = async () => { + try { + const response = await fetch( + `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, + { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + } + ); + const data = await response.json(); + const items = data.Items; + const movies = items.filter((item) => item.Type === "Movie"); + const tvShows = items.filter((item) => item.Type === "Series"); + const shuffledMovies = shuffleArray(movies); + const shuffledTvShows = shuffleArray(tvShows); + const selectedMovies = shuffledMovies.slice(0, 15); + const selectedTvShows = shuffledTvShows.slice(0, 15); + const allItems = []; + const maxLength = Math.max(selectedMovies.length, selectedTvShows.length); + for (let i = 0; i < maxLength; i++) { + if (i < selectedMovies.length) allItems.push(selectedMovies[i]); + if (i < selectedTvShows.length) allItems.push(selectedTvShows[i]); + } + return allItems; + } catch (error) { + console.error("Error fetching items:", error); + return []; + } + }; + const createSlidesForItems = async (items) => { + await Promise.all( + items.map((item) => + createSlideForItem(item, item.Type === "Movie" ? "Movie" : "TV Show") + ) + ); + }; + const createPaginationDots = () => { + const dotsContainer = document.createElement("div"); + dotsContainer.className = "dots-container"; + for (let i = 0; i < 5; i++) { + const dot = document.createElement("span"); + dot.className = "dot"; + dotsContainer.appendChild(dot); + } + const container = document.getElementById("slides-container"); + container.appendChild(dotsContainer); + }; + const showSlide = (index) => { + const slides = document.querySelectorAll(".slide"); + const dots = document.querySelectorAll(".dot"); + slides.forEach((slide, i) => { + if (i === index) { + slide.style.display = "block"; + slide.offsetHeight; + slide.style.opacity = "1"; + slide.classList.add("active"); + } else { + slide.style.opacity = "0"; + slide.classList.remove("active"); + setTimeout(() => { + slide.style.display = "none"; + }, 500); + } + }); + dots.forEach((dot, i) => { + dot.classList.toggle("active", i === index % 5); + }); + }; + const initializeSlideshow = () => { + const slides = document.querySelectorAll(".slide"); + createPaginationDots(); + const container = document.getElementById("slides-container"); + let currentSlideIndex = 0; + let focusedSlide = null; + let containerFocused = !1; + const updateCurrentSlide = (index) => { + if (isTransitioning) return; + isTransitioning = true; + currentSlideIndex = (index + slides.length) % slides.length; + showSlide(currentSlideIndex); + setTimeout(() => { + isTransitioning = false; + }, 500); }; - const createSlideElement = (item, title) => { - const itemId = item.Id; - const plot = item.Overview || "No overview available"; - const rating = item.CommunityRating; - const tomato = item.CriticRating; - const runtime = item.RunTimeTicks; - const genre = item.Genres; - const youtube = item.RemoteTrailers; - const age = item.OfficialRating; - const date = item.PremiereDate; - const season = item.ChildCount; - function createSeparator() { - const separatorHtml = - ''; - const tempDiv = document.createElement("div"); - tempDiv.innerHTML = separatorHtml; - return tempDiv.firstChild; - } - const slide = document.createElement("a"); - slide.className = "slide"; - slide.target = "_top"; - slide.rel = "noreferrer"; - slide.tabIndex = 0; - slide.style.display = "none"; - const backdrop = document.createElement("img"); - backdrop.className = "backdrop"; - backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; - backdrop.alt = "Backdrop"; - const backdropOverlay = document.createElement("div"); - backdropOverlay.className = "backdrop-overlay"; - const backdropContainer = document.createElement("div"); - backdropContainer.className = "backdrop-container"; - backdropContainer.appendChild(backdrop); - backdropContainer.appendChild(backdropOverlay); - const logo = document.createElement("img"); - logo.className = "logo"; - logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; - logo.alt = "Logo"; - const logoContainer = document.createElement("div"); - logoContainer.className = "logo-container"; - logoContainer.appendChild(logo); - const featuredContent = document.createElement("div"); - featuredContent.className = "featured-content"; - featuredContent.textContent = title; - const plotElement = document.createElement("div"); - plotElement.className = "plot"; - plotElement.textContent = plot; - truncateText(plotElement, 360); - const plotContainer = document.createElement("div"); - plotContainer.className = "plot-container"; - plotContainer.appendChild(plotElement); - const gradientOverlay = document.createElement("div"); - gradientOverlay.className = "gradient-overlay"; - const runTimeElement = document.createElement("div"); - runTimeElement.className = "runTime"; - if (season === undefined) { - const milliseconds = runtime / 10000; - const currentTime = new Date(); - const endTime = new Date(currentTime.getTime() + milliseconds); - const options = { hour: "2-digit", minute: "2-digit", hour12: !0 }; - const formattedEndTime = endTime.toLocaleTimeString([], options); - runTimeElement.textContent = `Ends at ${formattedEndTime}`; - } else { - runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; - } - const ratingTest = document.createElement("div"); - ratingTest.className = "rating-value"; - const imdbLogo = document.createElement("img"); - imdbLogo.className = "imdb-logo"; - imdbLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg"; - imdbLogo.alt = "IMDb Logo"; - imdbLogo.style.width = "30px"; - imdbLogo.style.height = "30px"; - ratingTest.appendChild(imdbLogo); - if (typeof rating === "number") { - const formattedRating = rating.toFixed(1); - ratingTest.innerHTML += `${formattedRating} ⭐`; - } else { - console.error("Rating is undefined or not a number:", rating); - ratingTest.innerHTML += `N/A ⭐`; - } - ratingTest.appendChild(createSeparator()); - const tomatoRatingDiv = document.createElement("div"); - tomatoRatingDiv.className = "tomato-rating"; - const criticRating = item.CriticRating; - const tomatoLogo = document.createElement("img"); - tomatoLogo.className = "tomato-logo"; - const criticLogo = document.createElement("img"); - criticLogo.className = "critic-logo"; - tomatoLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; - let valueElement; - if (typeof criticRating === "number") { - valueElement = document.createTextNode(`${criticRating}% `); - } else { - valueElement = document.createTextNode(`N/A `); - } - if (criticRating === undefined || criticRating <= 59) { - criticLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; - criticLogo.alt = "Rotten Tomato"; - } else { - criticLogo.src = "https://i.imgur.com/iMfwDk7.png"; - criticLogo.alt = "Fresh Tomato"; - } - tomatoLogo.style.width = "15px"; - tomatoLogo.style.height = "17px"; - criticLogo.style.width = "15px"; - criticLogo.style.height = "15px"; - tomatoRatingDiv.appendChild(tomatoLogo); - tomatoRatingDiv.appendChild(valueElement); - tomatoRatingDiv.appendChild(criticLogo); - tomatoRatingDiv.appendChild(createSeparator()); - const ageRatingDiv = document.createElement("div"); - ageRatingDiv.className = "age-rating"; - if (item.OfficialRating) { - ageRatingDiv.innerHTML = `${item.OfficialRating}`; - } else { - ageRatingDiv.innerHTML = `N/A`; - } - const calender = "📅"; - const premiereDate = document.createElement("div"); - premiereDate.className = "date"; - const year = date ? new Date(date).getFullYear() : "N/A"; - premiereDate.textContent = isNaN(year) ? "N/A" : year; - ratingTest.appendChild(tomatoRatingDiv); - ratingTest.appendChild(document.createTextNode(calender)); - ratingTest.appendChild(premiereDate); - ratingTest.appendChild(createSeparator()); - ratingTest.appendChild(ageRatingDiv); - ratingTest.appendChild(createSeparator()); - ratingTest.appendChild(runTimeElement); - const genresArray = item.Genres; - function parseGenres(genresArray) { - if (genresArray && genresArray.length > 0) { - return genresArray.slice(0, 3).join(" 🔹 "); - } else { - return "No Genre Available"; - } - } - const genreElement = document.createElement("div"); - genreElement.className = "genre"; - genreElement.innerHTML = parseGenres(genresArray); - const infoContainer = document.createElement("div"); - infoContainer.className = "info-container"; - infoContainer.appendChild(ratingTest); - const playButton = document.createElement("button"); - playButton.className = "play-button"; - playButton.innerHTML = ` - - Play Now - `; - playButton.onclick = async () => { - if (!window.ApiClient) { - console.error("Jellyfin API client is not available."); - return; - } - const apiClient = window.ApiClient; - const userId = apiClient.getCurrentUserId(); - if (!userId) { - console.error("User not found."); - return; - } - try { - const sessions = await apiClient.getJSON( - apiClient.serverAddress() + "/Sessions" - ); - if (!sessions.length) { - console.error("No active Jellyfin sessions found."); - return; - } - const sessionId = sessions[0].Id; - const playbackUrl = `${apiClient.serverAddress()}/Sessions/${sessionId}/Playing?StartPositionTicks=0&PlayCommand=PlayNow&StartIndex=0&ItemIds=${itemId}&api_key=${token}`; - const response = await fetch(playbackUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDevice", DeviceId="YourDeviceId", Version="YourVersion", Token="${token}"`, - }, - }); - if (response.status === 204) { - console.log("Playback started successfully."); - } else { - const errorData = await response.json(); - console.error("Failed to start playback:", errorData); - } - } catch (error) { - console.error("Error fetching sessions:", error); - } - }; - const detailButton = document.createElement("button"); - detailButton.className = "detail-button"; - detailButton.innerHTML = ` - - More Details - `; - detailButton.onclick = () => { - window.top.location.href = `/#!/details?id=${itemId}`; - }; - slide.append( - logoContainer, - backdropContainer, - gradientOverlay, - featuredContent, - plotContainer, - infoContainer, - genreElement, - playButton, - detailButton - ); - return slide; + const openActiveSlide = () => { + if (focusedSlide) { + window.location.href = focusedSlide.href; + } }; - const createSlideForItem = async (item, title) => { - const container = document.getElementById("slides-container"); - const itemId = item.Id; - const backdropUrl = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; - const logoUrl = `${window.location.origin}/Items/${itemId}/Images/Logo`; - const [backdropExists, logoExists] = await Promise.all([ - fetch(backdropUrl, { method: "HEAD" }).then((res) => res.ok), - fetch(logoUrl, { method: "HEAD" }).then((res) => res.ok), - ]); - if (backdropExists && logoExists) { - const slideElement = createSlideElement(item, title); - container.appendChild(slideElement); - console.log(`Added slide for item ${itemId}`); - if (container.children.length === 1) { - showSlide(0); - } - } else { - console.warn(`Skipping item ${itemId}: Missing backdrop or logo.`); + if (slides.length > 0) { + showSlide(currentSlideIndex); + container.style.display = "block"; + setTimeout(() => { + setInterval(() => { + updateCurrentSlide(currentSlideIndex + 1); + }, shuffleInterval); + }, 8000); + } + slides.forEach((slide) => { + slide.addEventListener( + "focus", + () => { + focusedSlide = slide; + container.classList.remove("disable-interaction"); + }, + !0 + ); + slide.addEventListener( + "blur", + () => { + if (focusedSlide === slide) { + focusedSlide = null; + } + }, + !0 + ); + }); + document.addEventListener("keydown", (event) => { + if (containerFocused) { + switch (event.keyCode) { + case 37: + updateCurrentSlide(currentSlideIndex - 1); + break; + case 39: + updateCurrentSlide(currentSlideIndex + 1); + break; + case 13: + case 32: + openActiveSlide(); + break; } - }; - const fetchItemDetails = async (itemId) => { - const response = await fetch( - `${window.location.origin}/Users/${userId}/Items/${itemId}`, - { - headers: { - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, - }, - } - ); - const item = await response.json(); - console.log("Item Title:", item.Name); - return item; - }; - const fetchItemIdsFromList = async () => { - try { - const response = await fetch(listFileName); - if (!response.ok) { - throw new Error("Failed to fetch list.txt"); - } - const text = await response.text(); - return text - .split("\n") - .map((id) => id.trim()) - .filter((id) => id); - } catch (error) { - console.error("Error fetching list.txt:", error); - return []; - } - }; - const fetchItemsFromServer = async () => { - try { - const response = await fetch( - `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, - { - headers: { - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, - }, - } - ); - const data = await response.json(); - const items = data.Items; - const movies = items.filter((item) => item.Type === "Movie"); - const tvShows = items.filter((item) => item.Type === "Series"); - const shuffledMovies = shuffleArray(movies); - const shuffledTvShows = shuffleArray(tvShows); - const selectedMovies = shuffledMovies.slice(0, 15); - const selectedTvShows = shuffledTvShows.slice(0, 15); - const allItems = []; - const maxLength = Math.max(selectedMovies.length, selectedTvShows.length); - for (let i = 0; i < maxLength; i++) { - if (i < selectedMovies.length) allItems.push(selectedMovies[i]); - if (i < selectedTvShows.length) allItems.push(selectedTvShows[i]); - } - return allItems; - } catch (error) { - console.error("Error fetching items:", error); - return []; - } - }; - const createSlidesForItems = async (items) => { - await Promise.all( - items.map((item) => - createSlideForItem(item, item.Type === "Movie" ? "Movie" : "TV Show") - ) - ); - }; - const createPaginationDots = () => { - const dotsContainer = document.createElement("div"); - dotsContainer.className = "dots-container"; - for (let i = 0; i < 5; i++) { - const dot = document.createElement("span"); - dot.className = "dot"; - dotsContainer.appendChild(dot); - } - const container = document.getElementById("slides-container"); - container.appendChild(dotsContainer); - }; - const showSlide = (index) => { - const slides = document.querySelectorAll(".slide"); - const dots = document.querySelectorAll(".dot"); - slides.forEach((slide, i) => { - if (i === index) { - slide.style.display = "block"; - slide.offsetHeight; - slide.style.opacity = "1"; - slide.classList.add("active"); - } else { - slide.style.opacity = "0"; - slide.classList.remove("active"); - setTimeout(() => { - slide.style.display = "none"; - }, 500); - } - }); - dots.forEach((dot, i) => { - dot.classList.toggle("active", i === index % 5); - }); - }; - const initializeSlideshow = () => { - const slides = document.querySelectorAll(".slide"); - createPaginationDots(); - const container = document.getElementById("slides-container"); - let currentSlideIndex = 0; - let focusedSlide = null; - let containerFocused = !1; - const updateCurrentSlide = (index) => { - if (isTransitioning) return; - isTransitioning = true; - currentSlideIndex = (index + slides.length) % slides.length; - showSlide(currentSlideIndex); - setTimeout(() => { - isTransitioning = false; - }, 500); - }; - const openActiveSlide = () => { - if (focusedSlide) { - window.location.href = focusedSlide.href; - } - }; - if (slides.length > 0) { - showSlide(currentSlideIndex); - container.style.display = "block"; - setTimeout(() => { - setInterval(() => { - updateCurrentSlide(currentSlideIndex + 1); - }, shuffleInterval); - }, 8000); - } - slides.forEach((slide) => { - slide.addEventListener( - "focus", - () => { - focusedSlide = slide; - container.classList.remove("disable-interaction"); - }, - !0 - ); - slide.addEventListener( - "blur", - () => { - if (focusedSlide === slide) { - focusedSlide = null; - } - }, - !0 - ); - }); - document.addEventListener("keydown", (event) => { - if (containerFocused) { - switch (event.keyCode) { - case 37: - updateCurrentSlide(currentSlideIndex - 1); - break; - case 39: - updateCurrentSlide(currentSlideIndex + 1); - break; - case 13: - case 32: - openActiveSlide(); - break; - } - } - }); - const leftArrow = document.createElement("div"); - const rightArrow = document.createElement("div"); - leftArrow.classList.add("arrow", "left-arrow"); - rightArrow.classList.add("arrow", "right-arrow"); - leftArrow.innerHTML = ''; - rightArrow.innerHTML = ''; - container.appendChild(leftArrow); - container.appendChild(rightArrow); - leftArrow.addEventListener("click", () => - updateCurrentSlide(currentSlideIndex - 1) - ); - rightArrow.addEventListener("click", () => - updateCurrentSlide(currentSlideIndex + 1) - ); - container.addEventListener("focus", () => { - containerFocused = !0; - }); - container.addEventListener("blur", () => { - containerFocused = !1; - }); - }; - const initializeSlides = async () => { - const itemIds = await fetchItemIdsFromList(); - let items; - if (itemIds.length > 0) { - const itemPromises = itemIds.map((id) => fetchItemDetails(id)); - items = await Promise.all(itemPromises); - } else { - const allItems = await fetchItemsFromServer(); - const itemPromises = allItems.map((item) => fetchItemDetails(item.Id)); - items = await Promise.all(itemPromises); - } - items = shuffleArray(items); - await createSlidesForItems(items); - initializeSlideshow(); - }; - initializeSlides(); + } + }); + const leftArrow = document.createElement("div"); + const rightArrow = document.createElement("div"); + leftArrow.classList.add("arrow", "left-arrow"); + rightArrow.classList.add("arrow", "right-arrow"); + leftArrow.innerHTML = ''; + rightArrow.innerHTML = ''; + container.appendChild(leftArrow); + container.appendChild(rightArrow); + leftArrow.addEventListener("click", () => + updateCurrentSlide(currentSlideIndex - 1) + ); + rightArrow.addEventListener("click", () => + updateCurrentSlide(currentSlideIndex + 1) + ); + container.addEventListener("focus", () => { + containerFocused = !0; + }); + container.addEventListener("blur", () => { + containerFocused = !1; + }); + }; + const initializeSlides = async () => { + const itemIds = await fetchItemIdsFromList(); + let items; + if (itemIds.length > 0) { + const itemPromises = itemIds.map((id) => fetchItemDetails(id)); + items = await Promise.all(itemPromises); + } else { + const allItems = await fetchItemsFromServer(); + const itemPromises = allItems.map((item) => fetchItemDetails(item.Id)); + items = await Promise.all(itemPromises); + } + items = shuffleArray(items); + await createSlidesForItems(items); + initializeSlideshow(); + }; + initializeSlides(); }; document.addEventListener("DOMContentLoaded", () => { - document.body.addEventListener("click", (event) => { - const homeButton = event.target.matches( - ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" - ); - if (homeButton) { - event.preventDefault(); - window.location.href = "/web/index.html#/home.html"; - setTimeout(handleHomeNavigation, 300); - } - }); -}); \ No newline at end of file + document.body.addEventListener("click", (event) => { + const homeButton = event.target.matches( + ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" + ); + if (homeButton) { + event.preventDefault(); + window.location.href = "/web/index.html#/home.html"; + setTimeout(handleHomeNavigation, 300); + } + }); +}); From 95f921add538077cd7f20afa8d93660f26dac698 Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:39:59 +0000 Subject: [PATCH 013/108] Update slideshowpure.css --- .../Inject/slideshowpure.css | 1428 ++++++++--------- 1 file changed, 714 insertions(+), 714 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css index e00974a..4afc598 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css @@ -1,774 +1,774 @@ -@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap"); @media only screen and (min-width: 767px) { - div[data-role="page"].page { - overflow-y: scroll; - scrollbar-width: none; - -ms-overflow-style: none; - } + div[data-role="page"].page { + overflow-y: scroll; + scrollbar-width: none; + -ms-overflow-style: none; + } - div[data-role="page"].page::-webkit-scrollbar { - display: none; - } + div[data-role="page"].page::-webkit-scrollbar { + display: none; + } - .backdrop, - .slide { - position: absolute; - } + .backdrop, + .slide { + position: absolute; + } - .featured-content, - .plot-container { - font-family: "Titillium Web", sans-serif; - left: 0; - } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } - .plot, - body { - overflow: hidden; - } + .plot, + body { + overflow: hidden; + } - .arrow { - position: absolute; - top: 50%; - transform: translateY(-50%); - font-size: 24px; - cursor: pointer; - color: white; - z-index: 3; - display: flex; - justify-content: center; - align-items: center; - } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: white; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } - .arrow::before { - content: ""; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 40px; - height: 40px; - background-color: rgba(12, 110, 162, 0.7); - border-radius: 50%; - z-index: -1; - } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgba(12, 110, 162, 0.7); + border-radius: 50%; + z-index: -1; + } - .left-arrow { - left: 10px; - } + .left-arrow { + left: 10px; + } - .right-arrow { - right: 10px; - } + .right-arrow { + right: 10px; + } - .dots-container { - position: absolute; - bottom: 5%; - left: 0; - right: 0; - z-index: 2; - display: flex; - justify-content: center; - align-items: center; - width: 100vw; - height: 22px; - background-color: #ffffff00; - gap: 2vw; - } + .dots-container { + position: absolute; + bottom: 5%; + left: 0; + right: 0; + z-index: 2; + display: flex; + justify-content: center; + align-items: center; + width: 100vw; + height: 22px; + background-color: #ffffff00; + gap: 2vw; + } - .dot { - width: 15vw; - height: 4px; - background-color: #cecece; - border-radius: 2px; - cursor: pointer; - transition: background-color 0.3s; - } + .dot { + width: 15vw; + height: 4px; + background-color: #cecece; + border-radius: 2px; + cursor: pointer; + transition: background-color 0.3s; + } - .dot.active { - background-color: rgba(12, 110, 162, 0.7) !important; - } + .dot.active { + background-color: rgba(12, 110, 162, 0.7) !important; + } - body { - margin: 0; - padding: 0; - } + body { + margin: 0; + padding: 0; + } - .fade-in { - opacity: 1 !important; - } + .fade-in { + opacity: 1 !important; + } - .fade-out { - opacity: 0 !important; - } + .fade-out { + opacity: 0 !important; + } - #slides-container { - position: relative; - width: 100vw; - height: 750px; - overflow: hidden; - margin: 0 auto; - border-radius: 7px; - top: -5em; - } + #slides-container { + position: relative; + width: 100vw; + height: 750px; + overflow: hidden; + margin: 0 auto; + border-radius: 7px; + top: -5em; + } - .slide { - transition: opacity 0.5s ease-in-out; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; - } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } - .slide.active { - opacity: 1; - display: block; - } + .slide.active { + opacity: 1; + display: block; + } - .slide:focus { - outline: #fff solid 2px; - } + .slide:focus { + outline: #fff solid 2px; + } - .backdrop-container { - position: relative; - top: -40%; - right: -35%; - width: 70%; - height: 100%; - } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } - .backdrop { - right: 0; - width: 100%; - height: 100%; - object-fit: cover; - object-position: center 20%; - border-radius: 5px; - z-index: 1; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .backdrop-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.6); - border-radius: 5px; - z-index: 2; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .logo-container { - width: 45%; - height: 40%; - position: relative; - display: flex; - justify-content: left; - align-items: left; - } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } - .logo { - position: relative; - max-height: 100%; - max-width: 100%; - height: auto; - width: auto; - object-fit: contain; - z-index: 3; - top: 5em; - left: 8em; - } + .logo { + position: relative; + max-height: 100%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 3; + top: 5em; + left: 8em; + } - .featured-content { - position: absolute; - top: 0; - width: 100%; - height: 50px; - background-color: transparent; - color: #d3d3d3; - font-size: 22px; - display: none; - align-items: center; - justify-content: flex-start; - z-index: 2; - } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: transparent; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } - .plot-container { - position: absolute; - bottom: 0; - color: #fff; - width: 50%; - height: 60%; - font-size: 15px; - padding: 10px 0 25px 15px; - border-radius: 5px; - z-index: 3; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - text-align: justify; - } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 50%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } - .plot { - display: -webkit-box; - line-clamp: 4; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - left: 7.9em; - position: absolute; - top: 190px; - } + .plot { + display: -webkit-box; + line-clamp: 4; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + left: 7.9em; + position: absolute; + top: 190px; + } - .gradient-overlay { - position: absolute; - top: 0; - left: 0; - width: 70%; - height: 100%; - background: linear-gradient(to right, #000 49%, rgba(0, 0, 0, 0) 70%); - z-index: 2; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0.5) 10%, - rgba(0, 0, 0, 1) 12% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0.5) 10%, - rgba(0, 0, 0, 1) 12% - ); - } - .genre { - position: absolute; - top: calc(50% + 75px); - left: 8.4em; - text-align: center; - color: #fff; - font-family: "Titillium Web", sans-serif; - font-size: 14px; - z-index: 4; - } - .info-container { - position: absolute; - top: calc(50% + 0px); - display: flex; - align-items: center; - justify-content: center; - left: 2em !important; - color: #fff; - z-index: 4; - } - .rating-value { - font-family: "Titillium Web", sans-serif; - display: flex; - align-items: center; - margin: 0 6em; - z-index: 5; - position: relative; - top: 1.5em; - } - .runTime { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin: 0 8px; - margin-right: -3px; - } - .rating-value img { - margin-right: 6px; - } - .tomato-rating { - display: flex; - align-items: center; - margin-left: 0; - } - .tomato-rating img { - margin-left: 5px; - } - .age-rating { - display: flex; - align-items: center; - margin-left: 5px; - margin-right: 5px; - border-radius: 5px; - background: rgb(255 255 255 / 0.8); - color: #000; - border: none; - font-weight: 600; - white-space: nowrap; - padding: 0 0.6em; - } - .date { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin-right: 5px; - margin-left: 2px; - } - .separator-icon { - margin-left: 5px; - margin-right: 5px; - font-size: 10px; - color: aquamarine; - } - .play-button { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - top: calc(50% + 190px); - left: 6.8em; - } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, rgba(0, 0, 0, 0) 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 8%, + rgba(0, 0, 0, 0.5) 10%, + rgba(0, 0, 0, 1) 12% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 8%, + rgba(0, 0, 0, 0.5) 10%, + rgba(0, 0, 0, 1) 12% + ); + } + .genre { + position: absolute; + top: calc(50% + 75px); + left: 8.4em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + top: 1.5em; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .play-button { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + top: calc(50% + 190px); + left: 6.8em; + } - .play-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 30px; - height: 30px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 3px solid rgba(12, 110, 162, 1); - border-radius: 50%; - } + .play-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 30px; + height: 30px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); + border: 3px solid rgba(12, 110, 162, 1); + border-radius: 50%; + } - .play-text { - font-family: "Titillium Web", sans-serif; - margin-left: 10px; - font-size: 14px; - color: #fff; - } - .detail-button { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - top: calc(50% + 190px); - left: 15.8em; - } + .play-text { + font-family: "Titillium Web", sans-serif; + margin-left: 10px; + font-size: 14px; + color: #fff; + } + .detail-button { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + top: calc(50% + 190px); + left: 15.8em; + } - .detail-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 30px; - height: 30px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 3px solid rgba(12, 110, 162, 1); - border-radius: 50%; - } + .detail-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 30px; + height: 30px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); + border: 3px solid rgba(12, 110, 162, 1); + border-radius: 50%; + } - .detail-text { - font-family: "Titillium Web", sans-serif; - margin-left: 10px; - font-size: 14px; - color: #fff; - } + .detail-text { + font-family: "Titillium Web", sans-serif; + margin-left: 10px; + font-size: 14px; + color: #fff; + } } @media only screen and (max-width: 767px) { - .backdrop, - .slide { - position: absolute; - } + .backdrop, + .slide { + position: absolute; + } - .arrow { - position: absolute; - bottom: 25px; - font-size: 24px; - cursor: pointer; - color: rgba(255, 255, 255, 0.6); - z-index: 4; - -webkit-tap-highlight-color: transparent; - } + .arrow { + position: absolute; + bottom: 25px; + font-size: 24px; + cursor: pointer; + color: rgba(255, 255, 255, 0.6); + z-index: 4; + -webkit-tap-highlight-color: transparent; + } - .left-arrow { - left: 10px; - } + .left-arrow { + left: 10px; + } - .right-arrow { - right: 10px; - } + .right-arrow { + right: 10px; + } - .featured-content, - .plot-container { - font-family: "Titillium Web", sans-serif; - left: 0; - } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } - .plot, - body { - overflow: hidden; - } + .plot, + body { + overflow: hidden; + } - body { - margin: 0; - padding: 0; - } + body { + margin: 0; + padding: 0; + } - .fade-in { - opacity: 1 !important; - } + .fade-in { + opacity: 1 !important; + } - .fade-out { - opacity: 0 !important; - } + .fade-out { + opacity: 0 !important; + } - .dots-container { - position: absolute; - bottom: 11%; - left: 50%; - transform: translateX(-50%) scale(0.6); - z-index: 2; - display: flex; - gap: 5px; - background-color: #ffffff00; - border-radius: 50px; - justify-content: center; - align-items: center; - width: 140px; - height: 22px; - transition: opacity 0.5s ease-in-out; - } + .dots-container { + position: absolute; + bottom: 11%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff00; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } - .dot { - display: inline-block; - width: 9px; - height: 9px; - margin: 0 5px; - background-color: #cecece; - border-radius: 50%; - transition: background-color 0.3s; - } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } - .dot.active { - width: 32px; - height: 12px; - background-color: rgba(12, 110, 162, 0.7); - border-radius: 10px; - } + .dot.active { + width: 32px; + height: 12px; + background-color: rgba(12, 110, 162, 0.7); + border-radius: 10px; + } - #slides-container { - position: relative; - width: 92vw; - height: 650px; - overflow: hidden; - margin: 0 auto; - border-radius: 10px; - } + #slides-container { + position: relative; + width: 92vw; + height: 650px; + overflow: hidden; + margin: 0 auto; + border-radius: 10px; + } - .slide { - transition: opacity 0.5s ease-in-out; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; - } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } - .slide.active { - opacity: 1; - display: block; - } + .slide.active { + opacity: 1; + display: block; + } - .slide:focus { - outline: #fff solid 2px; - } - .logo { - position: absolute; - bottom: 28%; - left: 50%; - transform: translateX(-50%); - max-height: 60%; - max-width: 80%; - width: auto; - z-index: 3; - } + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + bottom: 28%; + left: 50%; + transform: translateX(-50%); + max-height: 60%; + max-width: 80%; + width: auto; + z-index: 3; + } - .backdrop { - position: absolute; - top: 45px; - left: 0; - width: 100%; - height: calc(100% - 50px); - object-fit: cover; - object-position: center 20%; - z-index: 1; - border-radius: 13px; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .backdrop { + position: absolute; + top: 45px; + left: 0; + width: 100%; + height: calc(100% - 50px); + object-fit: cover; + object-position: center 20%; + z-index: 1; + border-radius: 13px; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .gradient-overlay { - position: absolute; - top: 45px; - left: 0; - width: 100%; - height: calc(100% - 50px); - background: rgba(0, 0, 0, 0.55); - z-index: 2; - border-radius: 13px; - pointer-events: none; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .gradient-overlay { + position: absolute; + top: 45px; + left: 0; + width: 100%; + height: calc(100% - 50px); + background: rgba(0, 0, 0, 0.55); + z-index: 2; + border-radius: 13px; + pointer-events: none; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .genre { - position: absolute; - bottom: 16%; - left: 50%; - transform: translateX(-50%); - text-align: center; - color: #fff; - width: 90%; - max-width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-family: "Titillium Web", sans-serif; - font-size: 1.1em; - font-weight: 500; - z-index: 4; - box-sizing: border-box; - } - .info-container { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - bottom: 20%; - left: 50%; - transform: translateX(-50%); - font-size: 1.1em; - color: #fff; - z-index: 5; - width: 100%; - } - .rating-value { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - } - .date { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin-left: 2px; - } - .tomato-rating { - display: flex; - align-items: center; - } - .tomato-logo { - margin-right: 3px; - } - .critic-logo { - margin-left: 3px; - } - .imdb-logo { - margin-right: 3px; - } - .age-rating { - display: flex; - align-items: center; - border-radius: 5px; - background: rgb(255 255 255 / 0.8); - color: #000; - border: none; - font-weight: 600; - white-space: nowrap; - padding: 0 0.4em; - } - .separator-icon { - margin-left: 5px; - margin-right: 5px; - font-size: 1px; - color: aquamarine; - } - .featured-content, - .plot { - display: none; - } + .genre { + position: absolute; + bottom: 16%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Titillium Web", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 20%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } - .play-button { - position: absolute; - display: flex; - align-items: center; - justify-content: flex-start; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - bottom: 20px; - left: 50%; - transform: translateX(-110%) scale(1.2); - outline: none; - -webkit-tap-highlight-color: transparent; - } + .play-button { + position: absolute; + display: flex; + align-items: center; + justify-content: flex-start; + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + bottom: 20px; + left: 50%; + transform: translateX(-110%) scale(1.2); + outline: none; + -webkit-tap-highlight-color: transparent; + } - .play-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 20px; - height: 20px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 1.5px solid rgba(12, 110, 162, 1); - border-radius: 50%; - outline: none; - -webkit-tap-highlight-color: transparent; - } + .play-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 20px; + height: 20px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); + border: 1.5px solid rgba(12, 110, 162, 1); + border-radius: 50%; + outline: none; + -webkit-tap-highlight-color: transparent; + } - .play-text { - display: block; - font-family: "Titillium Web", sans-serif; - margin-left: 5px; - font-size: 14px; - color: #fff; - } - .detail-button { - position: absolute; - display: flex; - align-items: center; - justify-content: flex-start; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - bottom: 20px; - left: 50%; - transform: translateX(10%) scale(1.2); - outline: none; - -webkit-tap-highlight-color: transparent; - } + .play-text { + display: block; + font-family: "Titillium Web", sans-serif; + margin-left: 5px; + font-size: 14px; + color: #fff; + } + .detail-button { + position: absolute; + display: flex; + align-items: center; + justify-content: flex-start; + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + bottom: 20px; + left: 50%; + transform: translateX(10%) scale(1.2); + outline: none; + -webkit-tap-highlight-color: transparent; + } - .detail-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 20px; - height: 20px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 1.5px solid rgba(12, 110, 162, 1); - border-radius: 50%; - outline: none; - -webkit-tap-highlight-color: transparent; - } + .detail-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 20px; + height: 20px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); + border: 1.5px solid rgba(12, 110, 162, 1); + border-radius: 50%; + outline: none; + -webkit-tap-highlight-color: transparent; + } - .detail-text { - display: block; - font-family: "Titillium Web", sans-serif; - margin-left: 5px; - color: #fff; - } -} \ No newline at end of file + .detail-text { + display: block; + font-family: "Titillium Web", sans-serif; + margin-left: 5px; + color: #fff; + } +} From 4518d30f08d36229256907a5a0208c164fb26261 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Tue, 11 Feb 2025 20:45:33 +0000 Subject: [PATCH 014/108] Updated to include fix for playback and updated styling. --- .../Controllers/MediaBarController.cs | 11 + .../Inject/slideshowpure.css | 1343 ++++++++--------- .../Inject/slideshowpure.js | 936 ++++++------ .../Services/StartupService.cs | 8 + 4 files changed, 1114 insertions(+), 1184 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs index 38db0f9..e12eac0 100644 --- a/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs +++ b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs @@ -53,5 +53,16 @@ namespace Jellyfin.Plugin.MediaBar.Controllers return Content(regex, "text/html"); } + + [HttpPost("Patch/MainJellyfinBundle")] + public ActionResult PatchMainBundle([FromBody] PatchRequestPayload payload) + { + string replacementText = + "window.PlaybackManager=this.playbackManager;console.log(\"PlaybackManager is now globally available:\",window.PlaybackManager);"; + + string regex = Regex.Replace(payload.Contents!, @"(this\.playbackManager=e,)", $"$1{replacementText}"); + + return Content(regex, "text/html"); + } } } \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css index 4afc598..e3b74dd 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css @@ -1,774 +1,687 @@ -@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap"); @media only screen and (min-width: 767px) { - div[data-role="page"].page { - overflow-y: scroll; - scrollbar-width: none; - -ms-overflow-style: none; - } + div[data-role="page"].page { + overflow-y: scroll; + scrollbar-width: none; + -ms-overflow-style: none; + } - div[data-role="page"].page::-webkit-scrollbar { - display: none; - } + div[data-role="page"].page::-webkit-scrollbar { + display: none; + } - .backdrop, - .slide { - position: absolute; - } + .backdrop, + .slide { + position: absolute; + } - .featured-content, - .plot-container { - font-family: "Titillium Web", sans-serif; - left: 0; - } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } - .plot, - body { - overflow: hidden; - } + .plot, + body { + overflow: hidden; + } - .arrow { - position: absolute; - top: 50%; - transform: translateY(-50%); - font-size: 24px; - cursor: pointer; - color: white; - z-index: 3; - display: flex; - justify-content: center; - align-items: center; - } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: white; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } - .arrow::before { - content: ""; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 40px; - height: 40px; - background-color: rgba(12, 110, 162, 0.7); - border-radius: 50%; - z-index: -1; - } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgba(12, 110, 162, 0.7); + border-radius: 50%; + z-index: -1; + } - .left-arrow { - left: 10px; - } + .left-arrow { + left: 20px; + } - .right-arrow { - right: 10px; - } + .right-arrow { + right: 20px; + } - .dots-container { - position: absolute; - bottom: 5%; - left: 0; - right: 0; - z-index: 2; - display: flex; - justify-content: center; - align-items: center; - width: 100vw; - height: 22px; - background-color: #ffffff00; - gap: 2vw; - } + .dots-container { + position: absolute; + bottom: 5%; + left: 0; + right: 0; + z-index: 2; + display: flex; + justify-content: center; + align-items: center; + width: 100vw; + height: 22px; + background-color: #ffffff00; + gap: 2vw; + } - .dot { - width: 15vw; - height: 4px; - background-color: #cecece; - border-radius: 2px; - cursor: pointer; - transition: background-color 0.3s; - } + .dot { + width: 15vw; + height: 4px; + background-color: #cecece; + border-radius: 2px; + cursor: pointer; + transition: background-color 0.3s; + } - .dot.active { - background-color: rgba(12, 110, 162, 0.7) !important; - } + .dot.active { + background-color: rgba(12, 110, 162, 0.7) !important; + } - body { - margin: 0; - padding: 0; - } + body { + margin: 0; + padding: 0; + } - .fade-in { - opacity: 1 !important; - } + .fade-in { + opacity: 1 !important; + } - .fade-out { - opacity: 0 !important; - } + .fade-out { + opacity: 0 !important; + } - #slides-container { - position: relative; - width: 100vw; - height: 750px; - overflow: hidden; - margin: 0 auto; - border-radius: 7px; - top: -5em; - } + #slides-container { + position: relative; + width: 92vw; + height: 650px; + overflow: hidden; + margin: 0 auto; + border-radius: 15px; + } - .slide { - transition: opacity 0.5s ease-in-out; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; - } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } - .slide.active { - opacity: 1; - display: block; - } + .slide.active { + opacity: 1; + display: block; + } - .slide:focus { - outline: #fff solid 2px; - } + .slide:focus { + outline: #fff solid 2px; + } - .backdrop-container { - position: relative; - top: -40%; - right: -35%; - width: 70%; - height: 100%; - } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } - .backdrop { - right: 0; - width: 100%; - height: 100%; - object-fit: cover; - object-position: center 20%; - border-radius: 5px; - z-index: 1; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .backdrop-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.6); - border-radius: 5px; - z-index: 2; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .logo-container { - width: 45%; - height: 40%; - position: relative; - display: flex; - justify-content: left; - align-items: left; - } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } - .logo { - position: relative; - max-height: 100%; - max-width: 100%; - height: auto; - width: auto; - object-fit: contain; - z-index: 3; - top: 5em; - left: 8em; - } + .logo { + position: relative; + max-height: 100%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 3; + top: 5em; + left: 8em; + } - .featured-content { - position: absolute; - top: 0; - width: 100%; - height: 50px; - background-color: transparent; - color: #d3d3d3; - font-size: 22px; - display: none; - align-items: center; - justify-content: flex-start; - z-index: 2; - } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: transparent; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } - .plot-container { - position: absolute; - bottom: 0; - color: #fff; - width: 50%; - height: 60%; - font-size: 15px; - padding: 10px 0 25px 15px; - border-radius: 5px; - z-index: 3; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - text-align: justify; - } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } - .plot { - display: -webkit-box; - line-clamp: 4; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - left: 7.9em; - position: absolute; - top: 190px; - } + .plot { + display: -webkit-box; + line-clamp: 4; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + left: 7.9em; + position: absolute; + top: 190px; + } - .gradient-overlay { - position: absolute; - top: 0; - left: 0; - width: 70%; - height: 100%; - background: linear-gradient(to right, #000 49%, rgba(0, 0, 0, 0) 70%); - z-index: 2; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0.5) 10%, - rgba(0, 0, 0, 1) 12% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0.5) 10%, - rgba(0, 0, 0, 1) 12% - ); - } - .genre { - position: absolute; - top: calc(50% + 75px); - left: 8.4em; - text-align: center; - color: #fff; - font-family: "Titillium Web", sans-serif; - font-size: 14px; - z-index: 4; - } - .info-container { - position: absolute; - top: calc(50% + 0px); - display: flex; - align-items: center; - justify-content: center; - left: 2em !important; - color: #fff; - z-index: 4; - } - .rating-value { - font-family: "Titillium Web", sans-serif; - display: flex; - align-items: center; - margin: 0 6em; - z-index: 5; - position: relative; - top: 1.5em; - } - .runTime { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin: 0 8px; - margin-right: -3px; - } - .rating-value img { - margin-right: 6px; - } - .tomato-rating { - display: flex; - align-items: center; - margin-left: 0; - } - .tomato-rating img { - margin-left: 5px; - } - .age-rating { - display: flex; - align-items: center; - margin-left: 5px; - margin-right: 5px; - border-radius: 5px; - background: rgb(255 255 255 / 0.8); - color: #000; - border: none; - font-weight: 600; - white-space: nowrap; - padding: 0 0.6em; - } - .date { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin-right: 5px; - margin-left: 2px; - } - .separator-icon { - margin-left: 5px; - margin-right: 5px; - font-size: 10px; - color: aquamarine; - } - .play-button { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - top: calc(50% + 190px); - left: 6.8em; - } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, rgba(0, 0, 0, 0) 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 8%, + rgba(0, 0, 0, 0.5) 10%, + rgba(0, 0, 0, 1) 12% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 8%, + rgba(0, 0, 0, 0.5) 10%, + rgba(0, 0, 0, 1) 12% + ); + } + .genre { + position: absolute; + top: calc(50% + 75px); + left: 8.4em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + top: 1.5em; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 190px); /* Same as the previous button positions */ + left: 7.5em; + display: flex; + gap: 2em; /* Space between buttons */ + z-index: 5; /* Ensures it's above other elements */ + } + .play-button, + .detail-button { + display: inline-flex; /* Ensures icon and text are on the same line */ + align-items: center; + justify-content: center; + padding: 8px 16px; /* Slightly reduced padding for thinner buttons */ + border: solid 2px; + border-radius: 25px; /* Sleeker look */ + font-size: 14px; + white-space: nowrap; /* Prevents text wrapping */ + cursor: pointer; + transition: all 0.3s ease; + font-weight: bold; + gap: 6px; /* Adds space between icon and text */ + -webkit-tap-highlight-color: transparent; + } - .play-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 30px; - height: 30px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 3px solid rgba(12, 110, 162, 1); - border-radius: 50%; - } + .play-button { + background-color: #ffffff; + color: #000; + } - .play-text { - font-family: "Titillium Web", sans-serif; - margin-left: 10px; - font-size: 14px; - color: #fff; - } - .detail-button { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - top: calc(50% + 190px); - left: 15.8em; - } + .detail-button { + background-color: rgba(255, 255, 255, 0); + color: #fff; + } - .detail-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 30px; - height: 30px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 3px solid rgba(12, 110, 162, 1); - border-radius: 50%; - } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } - .detail-text { - font-family: "Titillium Web", sans-serif; - margin-left: 10px; - font-size: 14px; - color: #fff; - } + .play-icon, + .detail-icon { + font-size: 18px; /* Ensures icons are aligned with text */ + display: flex; + align-items: center; + } } - @media only screen and (max-width: 767px) { - .backdrop, - .slide { - position: absolute; - } + .backdrop, + .slide { + position: absolute; + } - .arrow { - position: absolute; - bottom: 25px; - font-size: 24px; - cursor: pointer; - color: rgba(255, 255, 255, 0.6); - z-index: 4; - -webkit-tap-highlight-color: transparent; - } + .arrow { + position: absolute; + bottom: 25px; + font-size: 24px; + cursor: pointer; + color: rgba(255, 255, 255, 0.6); + z-index: 4; + -webkit-tap-highlight-color: transparent; + } - .left-arrow { - left: 10px; - } + .left-arrow { + left: 10px; + } - .right-arrow { - right: 10px; - } + .right-arrow { + right: 10px; + } - .featured-content, - .plot-container { - font-family: "Titillium Web", sans-serif; - left: 0; - } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } - .plot, - body { - overflow: hidden; - } + .plot, + body { + overflow: hidden; + } - body { - margin: 0; - padding: 0; - } + body { + margin: 0; + padding: 0; + } - .fade-in { - opacity: 1 !important; - } + .fade-in { + opacity: 1 !important; + } - .fade-out { - opacity: 0 !important; - } + .fade-out { + opacity: 0 !important; + } - .dots-container { - position: absolute; - bottom: 11%; - left: 50%; - transform: translateX(-50%) scale(0.6); - z-index: 2; - display: flex; - gap: 5px; - background-color: #ffffff00; - border-radius: 50px; - justify-content: center; - align-items: center; - width: 140px; - height: 22px; - transition: opacity 0.5s ease-in-out; - } + .dots-container { + position: absolute; + bottom: 11%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff00; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } - .dot { - display: inline-block; - width: 9px; - height: 9px; - margin: 0 5px; - background-color: #cecece; - border-radius: 50%; - transition: background-color 0.3s; - } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } - .dot.active { - width: 32px; - height: 12px; - background-color: rgba(12, 110, 162, 0.7); - border-radius: 10px; - } + .dot.active { + width: 32px; + height: 12px; + background-color: rgba(12, 110, 162, 0.7); + border-radius: 10px; + } - #slides-container { - position: relative; - width: 92vw; - height: 650px; - overflow: hidden; - margin: 0 auto; - border-radius: 10px; - } + #slides-container { + position: relative; + width: 92vw; + height: 650px; + overflow: hidden; + margin: 0 auto; + border-radius: 10px; + } - .slide { - transition: opacity 0.5s ease-in-out; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; - } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } - .slide.active { - opacity: 1; - display: block; - } + .slide.active { + opacity: 1; + display: block; + } - .slide:focus { - outline: #fff solid 2px; - } - .logo { - position: absolute; - bottom: 28%; - left: 50%; - transform: translateX(-50%); - max-height: 60%; - max-width: 80%; - width: auto; - z-index: 3; - } + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + bottom: 28%; + left: 50%; + transform: translateX(-50%); + max-height: 60%; + max-width: 80%; + width: auto; + z-index: 3; + } - .backdrop { - position: absolute; - top: 45px; - left: 0; - width: 100%; - height: calc(100% - 50px); - object-fit: cover; - object-position: center 20%; - z-index: 1; - border-radius: 13px; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .backdrop { + position: absolute; + top: 45px; + left: 0; + width: 100%; + height: calc(100% - 50px); + object-fit: cover; + object-position: center 20%; + z-index: 1; + border-radius: 13px; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .gradient-overlay { - position: absolute; - top: 45px; - left: 0; - width: 100%; - height: calc(100% - 50px); - background: rgba(0, 0, 0, 0.55); - z-index: 2; - border-radius: 13px; - pointer-events: none; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .gradient-overlay { + position: absolute; + top: 45px; + left: 0; + width: 100%; + height: calc(100% - 50px); + background: rgba(0, 0, 0, 0.55); + z-index: 2; + border-radius: 13px; + pointer-events: none; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .genre { - position: absolute; - bottom: 16%; - left: 50%; - transform: translateX(-50%); - text-align: center; - color: #fff; - width: 90%; - max-width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-family: "Titillium Web", sans-serif; - font-size: 1.1em; - font-weight: 500; - z-index: 4; - box-sizing: border-box; - } - .info-container { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - bottom: 20%; - left: 50%; - transform: translateX(-50%); - font-size: 1.1em; - color: #fff; - z-index: 5; - width: 100%; - } - .rating-value { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - } - .date { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin-left: 2px; - } - .tomato-rating { - display: flex; - align-items: center; - } - .tomato-logo { - margin-right: 3px; - } - .critic-logo { - margin-left: 3px; - } - .imdb-logo { - margin-right: 3px; - } - .age-rating { - display: flex; - align-items: center; - border-radius: 5px; - background: rgb(255 255 255 / 0.8); - color: #000; - border: none; - font-weight: 600; - white-space: nowrap; - padding: 0 0.4em; - } - .separator-icon { - margin-left: 5px; - margin-right: 5px; - font-size: 1px; - color: aquamarine; - } - .featured-content, - .plot { - display: none; - } + .genre { + position: absolute; + bottom: 16%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Titillium Web", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 20%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } - .play-button { - position: absolute; - display: flex; - align-items: center; - justify-content: flex-start; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - bottom: 20px; - left: 50%; - transform: translateX(-110%) scale(1.2); - outline: none; - -webkit-tap-highlight-color: transparent; - } + .button-container { + position: absolute; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 15px; /* Adjust spacing between buttons */ + z-index: 4; + } - .play-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 20px; - height: 20px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 1.5px solid rgba(12, 110, 162, 1); - border-radius: 50%; - outline: none; - -webkit-tap-highlight-color: transparent; - } + .play-button, + .detail-button { + display: inline-flex; /* Ensures icon and text are on the same line */ + align-items: center; + justify-content: center; + padding: 8px 16px; /* Slightly reduced padding for thinner buttons */ + border: none; + border-radius: 20px; /* Sleeker look */ + font-size: 14px; + white-space: nowrap; /* Prevents text wrapping */ + cursor: pointer; + transition: all 0.3s ease; + font-weight: bold; + gap: 6px; /* Adds space between icon and text */ + -webkit-tap-highlight-color: transparent; + } - .play-text { - display: block; - font-family: "Titillium Web", sans-serif; - margin-left: 5px; - font-size: 14px; - color: #fff; - } - .detail-button { - position: absolute; - display: flex; - align-items: center; - justify-content: flex-start; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - bottom: 20px; - left: 50%; - transform: translateX(10%) scale(1.2); - outline: none; - -webkit-tap-highlight-color: transparent; - } + .play-button { + background-color: #ffffff; + color: #000; + } - .detail-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 20px; - height: 20px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 1.5px solid rgba(12, 110, 162, 1); - border-radius: 50%; - outline: none; - -webkit-tap-highlight-color: transparent; - } + .detail-button { + background-color: rgba(255, 255, 255, 0.2); + color: #fff; + } - .detail-text { - display: block; - font-family: "Titillium Web", sans-serif; - margin-left: 5px; - color: #fff; - } -} + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + + .play-icon, + .detail-icon { + font-size: 18px; /* Ensures icons are aligned with text */ + display: flex; + align-items: center; + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js index 548c8de..27dc4e0 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -1,487 +1,485 @@ -const slidesInit = () => { - const shuffleInterval = 8000; - let isTransitioning = false; - const listFileName = `${window.location.origin}/web/avatars/list.txt`; - const jsonCredentials = sessionStorage.getItem("json-credentials"); - let userId = null; - let token = null; - //const deviceId = localStorage.getItem("_deviceId2"); - //console.log(deviceId); - if (jsonCredentials) { - const credentials = JSON.parse(jsonCredentials); - userId = credentials.Servers[0].UserId; - token = credentials.Servers[0].AccessToken; - } - const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); - const truncateText = (element, maxLength) => { - let text = element.innerText; - if (text.length > maxLength) - element.innerText = text.substring(0, maxLength) + "..."; - }; - const createSlideElement = (item, title) => { - const itemId = item.Id; - const plot = item.Overview || "No overview available"; - const rating = item.CommunityRating; - const tomato = item.CriticRating; - const runtime = item.RunTimeTicks; - const genre = item.Genres; - const youtube = item.RemoteTrailers; - const age = item.OfficialRating; - const date = item.PremiereDate; - const season = item.ChildCount; - function createSeparator() { - const separatorHtml = - ''; - const tempDiv = document.createElement("div"); - tempDiv.innerHTML = separatorHtml; - return tempDiv.firstChild; +const slidesInit = () => { + const shuffleInterval = 8000; + let isTransitioning = false; + const listFileName = `${window.location.origin}/web/avatars/list.txt`; + const jsonCredentials = sessionStorage.getItem("json-credentials"); + let userId = null; + let token = null; + //const deviceId = localStorage.getItem("_deviceId2"); + //console.log(deviceId); + if (jsonCredentials) { + const credentials = JSON.parse(jsonCredentials); + userId = credentials.Servers[0].UserId; + token = credentials.Servers[0].AccessToken; } - const slide = document.createElement("a"); - slide.className = "slide"; - slide.target = "_top"; - slide.rel = "noreferrer"; - slide.tabIndex = 0; - slide.style.display = "none"; - const backdrop = document.createElement("img"); - backdrop.className = "backdrop"; - backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; - backdrop.alt = "Backdrop"; - const backdropOverlay = document.createElement("div"); - backdropOverlay.className = "backdrop-overlay"; - const backdropContainer = document.createElement("div"); - backdropContainer.className = "backdrop-container"; - backdropContainer.appendChild(backdrop); - backdropContainer.appendChild(backdropOverlay); - const logo = document.createElement("img"); - logo.className = "logo"; - logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; - logo.alt = "Logo"; - const logoContainer = document.createElement("div"); - logoContainer.className = "logo-container"; - logoContainer.appendChild(logo); - const featuredContent = document.createElement("div"); - featuredContent.className = "featured-content"; - featuredContent.textContent = title; - const plotElement = document.createElement("div"); - plotElement.className = "plot"; - plotElement.textContent = plot; - truncateText(plotElement, 360); - const plotContainer = document.createElement("div"); - plotContainer.className = "plot-container"; - plotContainer.appendChild(plotElement); - const gradientOverlay = document.createElement("div"); - gradientOverlay.className = "gradient-overlay"; - const runTimeElement = document.createElement("div"); - runTimeElement.className = "runTime"; - if (season === undefined) { - const milliseconds = runtime / 10000; - const currentTime = new Date(); - const endTime = new Date(currentTime.getTime() + milliseconds); - const options = { hour: "2-digit", minute: "2-digit", hour12: !0 }; - const formattedEndTime = endTime.toLocaleTimeString([], options); - runTimeElement.textContent = `Ends at ${formattedEndTime}`; - } else { - runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; - } - const ratingTest = document.createElement("div"); - ratingTest.className = "rating-value"; - const imdbLogo = document.createElement("img"); - imdbLogo.className = "imdb-logo"; - imdbLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg"; - imdbLogo.alt = "IMDb Logo"; - imdbLogo.style.width = "30px"; - imdbLogo.style.height = "30px"; - ratingTest.appendChild(imdbLogo); - if (typeof rating === "number") { - const formattedRating = rating.toFixed(1); - ratingTest.innerHTML += `${formattedRating} ⭐`; - } else { - console.error("Rating is undefined or not a number:", rating); - ratingTest.innerHTML += `N/A ⭐`; - } - ratingTest.appendChild(createSeparator()); - const tomatoRatingDiv = document.createElement("div"); - tomatoRatingDiv.className = "tomato-rating"; - const criticRating = item.CriticRating; - const tomatoLogo = document.createElement("img"); - tomatoLogo.className = "tomato-logo"; - const criticLogo = document.createElement("img"); - criticLogo.className = "critic-logo"; - tomatoLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; - let valueElement; - if (typeof criticRating === "number") { - valueElement = document.createTextNode(`${criticRating}% `); - } else { - valueElement = document.createTextNode(`N/A `); - } - if (criticRating === undefined || criticRating <= 59) { - criticLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; - criticLogo.alt = "Rotten Tomato"; - } else { - criticLogo.src = "https://i.imgur.com/iMfwDk7.png"; - criticLogo.alt = "Fresh Tomato"; - } - tomatoLogo.style.width = "15px"; - tomatoLogo.style.height = "17px"; - criticLogo.style.width = "15px"; - criticLogo.style.height = "15px"; - tomatoRatingDiv.appendChild(tomatoLogo); - tomatoRatingDiv.appendChild(valueElement); - tomatoRatingDiv.appendChild(criticLogo); - tomatoRatingDiv.appendChild(createSeparator()); - const ageRatingDiv = document.createElement("div"); - ageRatingDiv.className = "age-rating"; - if (item.OfficialRating) { - ageRatingDiv.innerHTML = `${item.OfficialRating}`; - } else { - ageRatingDiv.innerHTML = `N/A`; - } - const calender = "📅"; - const premiereDate = document.createElement("div"); - premiereDate.className = "date"; - const year = date ? new Date(date).getFullYear() : "N/A"; - premiereDate.textContent = isNaN(year) ? "N/A" : year; - ratingTest.appendChild(tomatoRatingDiv); - ratingTest.appendChild(document.createTextNode(calender)); - ratingTest.appendChild(premiereDate); - ratingTest.appendChild(createSeparator()); - ratingTest.appendChild(ageRatingDiv); - ratingTest.appendChild(createSeparator()); - ratingTest.appendChild(runTimeElement); - const genresArray = item.Genres; - function parseGenres(genresArray) { - if (genresArray && genresArray.length > 0) { - return genresArray.slice(0, 3).join(" 🔹 "); - } else { - return "No Genre Available"; - } - } - const genreElement = document.createElement("div"); - genreElement.className = "genre"; - genreElement.innerHTML = parseGenres(genresArray); - const infoContainer = document.createElement("div"); - infoContainer.className = "info-container"; - infoContainer.appendChild(ratingTest); - // Create a container for the buttons -const buttonContainer = document.createElement("div"); -buttonContainer.className = "button-container"; // Add a class for styling + const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); + const truncateText = (element, maxLength) => { + let text = element.innerText; + if (text.length > maxLength) + element.innerText = text.substring(0, maxLength) + "..."; + }; + const createSlideElement = (item, title) => { + const itemId = item.Id; + const plot = item.Overview || "No overview available"; + const rating = item.CommunityRating; + const tomato = item.CriticRating; + const runtime = item.RunTimeTicks; + const genre = item.Genres; + const youtube = item.RemoteTrailers; + const age = item.OfficialRating; + const date = item.PremiereDate; + const season = item.ChildCount; + function createSeparator() { + const separatorHtml = + ''; + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = separatorHtml; + return tempDiv.firstChild; + } + const slide = document.createElement("a"); + slide.className = "slide"; + slide.target = "_top"; + slide.rel = "noreferrer"; + slide.tabIndex = 0; + slide.style.display = "none"; + const backdrop = document.createElement("img"); + backdrop.className = "backdrop"; + backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; + backdrop.alt = "Backdrop"; + const backdropOverlay = document.createElement("div"); + backdropOverlay.className = "backdrop-overlay"; + const backdropContainer = document.createElement("div"); + backdropContainer.className = "backdrop-container"; + backdropContainer.appendChild(backdrop); + backdropContainer.appendChild(backdropOverlay); + const logo = document.createElement("img"); + logo.className = "logo"; + logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; + logo.alt = "Logo"; + const logoContainer = document.createElement("div"); + logoContainer.className = "logo-container"; + logoContainer.appendChild(logo); + const featuredContent = document.createElement("div"); + featuredContent.className = "featured-content"; + featuredContent.textContent = title; + const plotElement = document.createElement("div"); + plotElement.className = "plot"; + plotElement.textContent = plot; + truncateText(plotElement, 360); + const plotContainer = document.createElement("div"); + plotContainer.className = "plot-container"; + plotContainer.appendChild(plotElement); + const gradientOverlay = document.createElement("div"); + gradientOverlay.className = "gradient-overlay"; + const runTimeElement = document.createElement("div"); + runTimeElement.className = "runTime"; + if (season === undefined) { + const milliseconds = runtime / 10000; + const currentTime = new Date(); + const endTime = new Date(currentTime.getTime() + milliseconds); + const options = { hour: "2-digit", minute: "2-digit", hour12: !0 }; + const formattedEndTime = endTime.toLocaleTimeString([], options); + runTimeElement.textContent = `Ends at ${formattedEndTime}`; + } else { + runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; + } + const ratingTest = document.createElement("div"); + ratingTest.className = "rating-value"; + const imdbLogo = document.createElement("img"); + imdbLogo.className = "imdb-logo"; + imdbLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg"; + imdbLogo.alt = "IMDb Logo"; + imdbLogo.style.width = "30px"; + imdbLogo.style.height = "30px"; + ratingTest.appendChild(imdbLogo); + if (typeof rating === "number") { + const formattedRating = rating.toFixed(1); + ratingTest.innerHTML += `${formattedRating} ⭐`; + } else { + console.error("Rating is undefined or not a number:", rating); + ratingTest.innerHTML += `N/A ⭐`; + } + ratingTest.appendChild(createSeparator()); + const tomatoRatingDiv = document.createElement("div"); + tomatoRatingDiv.className = "tomato-rating"; + const criticRating = item.CriticRating; + const tomatoLogo = document.createElement("img"); + tomatoLogo.className = "tomato-logo"; + const criticLogo = document.createElement("img"); + criticLogo.className = "critic-logo"; + tomatoLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; + let valueElement; + if (typeof criticRating === "number") { + valueElement = document.createTextNode(`${criticRating}% `); + } else { + valueElement = document.createTextNode(`N/A `); + } + if (criticRating === undefined || criticRating <= 59) { + criticLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; + criticLogo.alt = "Rotten Tomato"; + } else { + criticLogo.src = "https://i.imgur.com/iMfwDk7.png"; + criticLogo.alt = "Fresh Tomato"; + } + tomatoLogo.style.width = "15px"; + tomatoLogo.style.height = "17px"; + criticLogo.style.width = "15px"; + criticLogo.style.height = "15px"; + tomatoRatingDiv.appendChild(tomatoLogo); + tomatoRatingDiv.appendChild(valueElement); + tomatoRatingDiv.appendChild(criticLogo); + tomatoRatingDiv.appendChild(createSeparator()); + const ageRatingDiv = document.createElement("div"); + ageRatingDiv.className = "age-rating"; + if (item.OfficialRating) { + ageRatingDiv.innerHTML = `${item.OfficialRating}`; + } else { + ageRatingDiv.innerHTML = `N/A`; + } + const calender = "📅"; + const premiereDate = document.createElement("div"); + premiereDate.className = "date"; + const year = date ? new Date(date).getFullYear() : "N/A"; + premiereDate.textContent = isNaN(year) ? "N/A" : year; + ratingTest.appendChild(tomatoRatingDiv); + ratingTest.appendChild(document.createTextNode(calender)); + ratingTest.appendChild(premiereDate); + ratingTest.appendChild(createSeparator()); + ratingTest.appendChild(ageRatingDiv); + ratingTest.appendChild(createSeparator()); + ratingTest.appendChild(runTimeElement); + const genresArray = item.Genres; + function parseGenres(genresArray) { + if (genresArray && genresArray.length > 0) { + return genresArray.slice(0, 3).join(" 🔹 "); + } else { + return "No Genre Available"; + } + } + const genreElement = document.createElement("div"); + genreElement.className = "genre"; + genreElement.innerHTML = parseGenres(genresArray); + const infoContainer = document.createElement("div"); + infoContainer.className = "info-container"; + infoContainer.appendChild(ratingTest); + // Create a container for the buttons + const buttonContainer = document.createElement("div"); + buttonContainer.className = "button-container"; // Add a class for styling -// Create the Play button -const playButton = document.createElement("button"); -playButton.className = "play-button"; -playButton.innerHTML = ` + // Create the Play button + const playButton = document.createElement("button"); + playButton.className = "play-button"; + playButton.innerHTML = ` - Play Now + Play `; -playButton.onclick = async () => { - if (!window.ApiClient) { - console.error("Jellyfin API client is not available."); - return; - } - const apiClient = window.ApiClient; - const userId = apiClient.getCurrentUserId(); - if (!userId) { - console.error("User not found."); - return; - } - try { - const sessions = await apiClient.getJSON(apiClient.serverAddress() + "/Sessions"); - if (!sessions.length) { - console.error("No active Jellyfin sessions found."); - return; - } + playButton.onclick = async () => { + if (!window.PlaybackManager) { + console.error("PlaybackManager is not available."); + return; + } - // Find the session matching the current user - const userSession = sessions.find(session => session.UserId === userId); - if (!userSession) { - console.error("No active session found for the current user."); - return; - } + if (!window.ApiClient) { + console.error("Jellyfin API client is not available."); + return; + } - const sessionId = userSession.Id; - const playbackUrl = `${apiClient.serverAddress()}/Sessions/${sessionId}/Playing?StartPositionTicks=0&PlayCommand=PlayNow&StartIndex=0&ItemIds=${itemId}&api_key=${token}`; + const apiClient = window.ApiClient; + const userId = apiClient.getCurrentUserId(); - const response = await fetch(playbackUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `DeviceId="unique-device-id", Token="${token}"`, - }, - }); + if (!userId) { + console.error("User not found."); + return; + } - if (response.status === 204) { - console.log("Playback started successfully."); - } else { - const errorData = await response.json(); - console.error("Failed to start playback:", errorData); - } - } catch (error) { - console.error("Error fetching sessions:", error); - } -}; + try { + // Fetch the media item details + const item = await apiClient.getItem(userId, itemId); + if (!item) { + console.error("Media item not found."); + return; + } -// Create the Details button -const detailButton = document.createElement("button"); -detailButton.className = "detail-button"; -detailButton.innerHTML = ` + // Initiate playback using PlaybackManager + window.PlaybackManager.play({ + items: [item], + startPositionTicks: 0, // Start from beginning + isMuted: false, + isPaused: false, + }) + .then(() => { + console.log("Playback started successfully."); + }) + .catch((error) => { + console.error("Failed to start playback:", error); + }); + } catch (error) { + console.error("Error starting playback:", error); + } + }; + + // Create the Details button + const detailButton = document.createElement("button"); + detailButton.className = "detail-button"; + detailButton.innerHTML = ` - More Info + Info `; -detailButton.onclick = () => { - window.top.location.href = `/#!/details?id=${itemId}`; -}; + detailButton.onclick = () => { + window.top.location.href = `/#!/details?id=${itemId}`; + }; -// Append buttons to the button container -buttonContainer.appendChild(playButton); -buttonContainer.appendChild(detailButton); + // Append buttons to the button container + buttonContainer.appendChild(playButton); + buttonContainer.appendChild(detailButton); -// Append all elements to the slide -slide.append( - logoContainer, - backdropContainer, - gradientOverlay, - featuredContent, - plotContainer, - infoContainer, - genreElement, - buttonContainer // Add button container instead of individual buttons -); + // Append all elements to the slide + slide.append( + logoContainer, + backdropContainer, + gradientOverlay, + featuredContent, + plotContainer, + infoContainer, + genreElement, + buttonContainer + ); -return slide; - }; - const createSlideForItem = async (item, title) => { - const container = document.getElementById("slides-container"); - const itemId = item.Id; - const backdropUrl = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; - const logoUrl = `${window.location.origin}/Items/${itemId}/Images/Logo`; - const [backdropExists, logoExists] = await Promise.all([ - fetch(backdropUrl, { method: "HEAD" }).then((res) => res.ok), - fetch(logoUrl, { method: "HEAD" }).then((res) => res.ok), - ]); - if (backdropExists && logoExists) { - const slideElement = createSlideElement(item, title); - container.appendChild(slideElement); - console.log(`Added slide for item ${itemId}`); - if (container.children.length === 1) { - showSlide(0); - } - } else { - console.warn(`Skipping item ${itemId}: Missing backdrop or logo.`); - } - }; - const fetchItemDetails = async (itemId) => { - const response = await fetch( - `${window.location.origin}/Users/${userId}/Items/${itemId}`, - { - headers: { - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, - }, - } - ); - const item = await response.json(); - console.log("Item Title:", item.Name); - return item; - }; - const fetchItemIdsFromList = async () => { - try { - const response = await fetch(listFileName); - if (!response.ok) { - throw new Error("Failed to fetch list.txt"); - } - const text = await response.text(); - return text - .split("\n") - .map((id) => id.trim()) - .filter((id) => id); - } catch (error) { - console.error("Error fetching list.txt:", error); - return []; - } - }; - const fetchItemsFromServer = async () => { - try { - const response = await fetch( - `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, - { - headers: { - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, - }, - } - ); - const data = await response.json(); - const items = data.Items; - const movies = items.filter((item) => item.Type === "Movie"); - const tvShows = items.filter((item) => item.Type === "Series"); - const shuffledMovies = shuffleArray(movies); - const shuffledTvShows = shuffleArray(tvShows); - const selectedMovies = shuffledMovies.slice(0, 15); - const selectedTvShows = shuffledTvShows.slice(0, 15); - const allItems = []; - const maxLength = Math.max(selectedMovies.length, selectedTvShows.length); - for (let i = 0; i < maxLength; i++) { - if (i < selectedMovies.length) allItems.push(selectedMovies[i]); - if (i < selectedTvShows.length) allItems.push(selectedTvShows[i]); - } - return allItems; - } catch (error) { - console.error("Error fetching items:", error); - return []; - } - }; - const createSlidesForItems = async (items) => { - await Promise.all( - items.map((item) => - createSlideForItem(item, item.Type === "Movie" ? "Movie" : "TV Show") - ) - ); - }; - const createPaginationDots = () => { - const dotsContainer = document.createElement("div"); - dotsContainer.className = "dots-container"; - for (let i = 0; i < 5; i++) { - const dot = document.createElement("span"); - dot.className = "dot"; - dotsContainer.appendChild(dot); - } - const container = document.getElementById("slides-container"); - container.appendChild(dotsContainer); - }; - const showSlide = (index) => { - const slides = document.querySelectorAll(".slide"); - const dots = document.querySelectorAll(".dot"); - slides.forEach((slide, i) => { - if (i === index) { - slide.style.display = "block"; - slide.offsetHeight; - slide.style.opacity = "1"; - slide.classList.add("active"); - } else { - slide.style.opacity = "0"; - slide.classList.remove("active"); - setTimeout(() => { - slide.style.display = "none"; - }, 500); - } - }); - dots.forEach((dot, i) => { - dot.classList.toggle("active", i === index % 5); - }); - }; - const initializeSlideshow = () => { - const slides = document.querySelectorAll(".slide"); - createPaginationDots(); - const container = document.getElementById("slides-container"); - let currentSlideIndex = 0; - let focusedSlide = null; - let containerFocused = !1; - const updateCurrentSlide = (index) => { - if (isTransitioning) return; - isTransitioning = true; - currentSlideIndex = (index + slides.length) % slides.length; - showSlide(currentSlideIndex); - setTimeout(() => { - isTransitioning = false; - }, 500); + return slide; }; - const openActiveSlide = () => { - if (focusedSlide) { - window.location.href = focusedSlide.href; - } - }; - if (slides.length > 0) { - showSlide(currentSlideIndex); - container.style.display = "block"; - setTimeout(() => { - setInterval(() => { - updateCurrentSlide(currentSlideIndex + 1); - }, shuffleInterval); - }, 8000); - } - slides.forEach((slide) => { - slide.addEventListener( - "focus", - () => { - focusedSlide = slide; - container.classList.remove("disable-interaction"); - }, - !0 - ); - slide.addEventListener( - "blur", - () => { - if (focusedSlide === slide) { - focusedSlide = null; - } - }, - !0 - ); - }); - document.addEventListener("keydown", (event) => { - if (containerFocused) { - switch (event.keyCode) { - case 37: - updateCurrentSlide(currentSlideIndex - 1); - break; - case 39: - updateCurrentSlide(currentSlideIndex + 1); - break; - case 13: - case 32: - openActiveSlide(); - break; + const createSlideForItem = async (item, title) => { + const container = document.getElementById("slides-container"); + const itemId = item.Id; + const backdropUrl = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; + const logoUrl = `${window.location.origin}/Items/${itemId}/Images/Logo`; + const [backdropExists, logoExists] = await Promise.all([ + fetch(backdropUrl, { method: "HEAD" }).then((res) => res.ok), + fetch(logoUrl, { method: "HEAD" }).then((res) => res.ok), + ]); + if (backdropExists && logoExists) { + const slideElement = createSlideElement(item, title); + container.appendChild(slideElement); + console.log(`Added slide for item ${itemId}`); + if (container.children.length === 1) { + showSlide(0); + } + } else { + console.warn(`Skipping item ${itemId}: Missing backdrop or logo.`); } - } - }); - const leftArrow = document.createElement("div"); - const rightArrow = document.createElement("div"); - leftArrow.classList.add("arrow", "left-arrow"); - rightArrow.classList.add("arrow", "right-arrow"); - leftArrow.innerHTML = ''; - rightArrow.innerHTML = ''; - container.appendChild(leftArrow); - container.appendChild(rightArrow); - leftArrow.addEventListener("click", () => - updateCurrentSlide(currentSlideIndex - 1) - ); - rightArrow.addEventListener("click", () => - updateCurrentSlide(currentSlideIndex + 1) - ); - container.addEventListener("focus", () => { - containerFocused = !0; - }); - container.addEventListener("blur", () => { - containerFocused = !1; - }); - }; - const initializeSlides = async () => { - const itemIds = await fetchItemIdsFromList(); - let items; - if (itemIds.length > 0) { - const itemPromises = itemIds.map((id) => fetchItemDetails(id)); - items = await Promise.all(itemPromises); - } else { - const allItems = await fetchItemsFromServer(); - const itemPromises = allItems.map((item) => fetchItemDetails(item.Id)); - items = await Promise.all(itemPromises); - } - items = shuffleArray(items); - await createSlidesForItems(items); - initializeSlideshow(); - }; - initializeSlides(); + }; + const fetchItemDetails = async (itemId) => { + const response = await fetch( + `${window.location.origin}/Users/${userId}/Items/${itemId}`, + { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + } + ); + const item = await response.json(); + console.log("Item Title:", item.Name); + return item; + }; + const fetchItemIdsFromList = async () => { + try { + const response = await fetch(listFileName); + if (!response.ok) { + throw new Error("Failed to fetch list.txt"); + } + const text = await response.text(); + return text + .split("\n") + .map((id) => id.trim()) + .filter((id) => id); + } catch (error) { + console.error("Error fetching list.txt:", error); + return []; + } + }; + const fetchItemsFromServer = async () => { + try { + const response = await fetch( + `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, + { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + } + ); + const data = await response.json(); + const items = data.Items; + const movies = items.filter((item) => item.Type === "Movie"); + const tvShows = items.filter((item) => item.Type === "Series"); + const shuffledMovies = shuffleArray(movies); + const shuffledTvShows = shuffleArray(tvShows); + const selectedMovies = shuffledMovies.slice(0, 15); + const selectedTvShows = shuffledTvShows.slice(0, 15); + const allItems = []; + const maxLength = Math.max(selectedMovies.length, selectedTvShows.length); + for (let i = 0; i < maxLength; i++) { + if (i < selectedMovies.length) allItems.push(selectedMovies[i]); + if (i < selectedTvShows.length) allItems.push(selectedTvShows[i]); + } + return allItems; + } catch (error) { + console.error("Error fetching items:", error); + return []; + } + }; + const createSlidesForItems = async (items) => { + await Promise.all( + items.map((item) => + createSlideForItem(item, item.Type === "Movie" ? "Movie" : "TV Show") + ) + ); + }; + const createPaginationDots = () => { + const dotsContainer = document.createElement("div"); + dotsContainer.className = "dots-container"; + for (let i = 0; i < 5; i++) { + const dot = document.createElement("span"); + dot.className = "dot"; + dotsContainer.appendChild(dot); + } + const container = document.getElementById("slides-container"); + container.appendChild(dotsContainer); + }; + const showSlide = (index) => { + const slides = document.querySelectorAll(".slide"); + const dots = document.querySelectorAll(".dot"); + slides.forEach((slide, i) => { + if (i === index) { + slide.style.display = "block"; + slide.offsetHeight; + slide.style.opacity = "1"; + slide.classList.add("active"); + } else { + slide.style.opacity = "0"; + slide.classList.remove("active"); + setTimeout(() => { + slide.style.display = "none"; + }, 500); + } + }); + dots.forEach((dot, i) => { + dot.classList.toggle("active", i === index % 5); + }); + }; + const initializeSlideshow = () => { + const slides = document.querySelectorAll(".slide"); + createPaginationDots(); + const container = document.getElementById("slides-container"); + let currentSlideIndex = 0; + let focusedSlide = null; + let containerFocused = !1; + const updateCurrentSlide = (index) => { + if (isTransitioning) return; + isTransitioning = true; + currentSlideIndex = (index + slides.length) % slides.length; + showSlide(currentSlideIndex); + setTimeout(() => { + isTransitioning = false; + }, 500); + }; + const openActiveSlide = () => { + if (focusedSlide) { + window.location.href = focusedSlide.href; + } + }; + if (slides.length > 0) { + showSlide(currentSlideIndex); + container.style.display = "block"; + setTimeout(() => { + setInterval(() => { + updateCurrentSlide(currentSlideIndex + 1); + }, shuffleInterval); + }, 8000); + } + slides.forEach((slide) => { + slide.addEventListener( + "focus", + () => { + focusedSlide = slide; + container.classList.remove("disable-interaction"); + }, + !0 + ); + slide.addEventListener( + "blur", + () => { + if (focusedSlide === slide) { + focusedSlide = null; + } + }, + !0 + ); + }); + document.addEventListener("keydown", (event) => { + if (containerFocused) { + switch (event.keyCode) { + case 37: + updateCurrentSlide(currentSlideIndex - 1); + break; + case 39: + updateCurrentSlide(currentSlideIndex + 1); + break; + case 13: + case 32: + openActiveSlide(); + break; + } + } + }); + const leftArrow = document.createElement("div"); + const rightArrow = document.createElement("div"); + leftArrow.classList.add("arrow", "left-arrow"); + rightArrow.classList.add("arrow", "right-arrow"); + leftArrow.innerHTML = ''; + rightArrow.innerHTML = ''; + container.appendChild(leftArrow); + container.appendChild(rightArrow); + leftArrow.addEventListener("click", () => + updateCurrentSlide(currentSlideIndex - 1) + ); + rightArrow.addEventListener("click", () => + updateCurrentSlide(currentSlideIndex + 1) + ); + container.addEventListener("focus", () => { + containerFocused = !0; + }); + container.addEventListener("blur", () => { + containerFocused = !1; + }); + }; + const initializeSlides = async () => { + const itemIds = await fetchItemIdsFromList(); + let items; + if (itemIds.length > 0) { + const itemPromises = itemIds.map((id) => fetchItemDetails(id)); + items = await Promise.all(itemPromises); + } else { + const allItems = await fetchItemsFromServer(); + const itemPromises = allItems.map((item) => fetchItemDetails(item.Id)); + items = await Promise.all(itemPromises); + } + items = shuffleArray(items); + await createSlidesForItems(items); + initializeSlideshow(); + }; + initializeSlides(); }; document.addEventListener("DOMContentLoaded", () => { - document.body.addEventListener("click", (event) => { - const homeButton = event.target.matches( - ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" - ); - if (homeButton) { - event.preventDefault(); - window.location.href = "/web/index.html#/home.html"; - setTimeout(handleHomeNavigation, 300); - } - }); -}); + document.body.addEventListener("click", (event) => { + const homeButton = event.target.matches( + ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" + ); + if (homeButton) { + event.preventDefault(); + window.location.href = "/web/index.html#/home.html"; + setTimeout(handleHomeNavigation, 300); + } + }); +}); \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs index 4f45027..ced7f3e 100644 --- a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs +++ b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs @@ -48,6 +48,14 @@ namespace Jellyfin.Plugin.MediaBar.Services payloads.Add(payload); } + { + JObject payload = new JObject(); + payload.Add("id", "3d171ef1-a198-48ac-9a60-f6aa98e5fd6d"); + payload.Add("fileNamePattern", "main.jellyfin.bundle.js"); + payload.Add("tranformationEndpoint", "/MediaBar/Patch/MainJellyfinBundle"); + + payloads.Add(payload); + } string? publishedServerUrl = m_serverApplicationHost.GetType() .GetProperty("PublishedServerUrl", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(m_serverApplicationHost) as string; From 6ef98f77a0498f81961380cd7806e803557266a6 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 12 Feb 2025 13:42:26 +0000 Subject: [PATCH 015/108] Updated with additional diagnostic logging and corrected a typo. --- .../Services/StartupService.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs index ced7f3e..4b2ffe8 100644 --- a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs +++ b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs @@ -30,13 +30,15 @@ namespace Jellyfin.Plugin.MediaBar.Services public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { + m_logger.LogInformation($"MediaBar Startup. Registering file transformations."); + List payloads = new List(); { JObject payload = new JObject(); payload.Add("id", "0dfac9d7-d898-4944-900b-1c1837707279"); payload.Add("fileNamePattern", "index.html"); - payload.Add("tranformationEndpoint", "/MediaBar/Patch/IndexHtml"); + payload.Add("transformationEndpoint", "/MediaBar/Patch/IndexHtml"); payloads.Add(payload); } @@ -44,7 +46,7 @@ namespace Jellyfin.Plugin.MediaBar.Services JObject payload = new JObject(); payload.Add("id", "e6d32b76-d54b-4946-b73e-c5c9c50575c9"); payload.Add("fileNamePattern", "home-html\\.[a-zA-z0-9]+\\.chunk\\.js"); - payload.Add("tranformationEndpoint", "/MediaBar/Patch/HomeHtmlChunk"); + payload.Add("transformationEndpoint", "/MediaBar/Patch/HomeHtmlChunk"); payloads.Add(payload); } @@ -52,21 +54,26 @@ namespace Jellyfin.Plugin.MediaBar.Services JObject payload = new JObject(); payload.Add("id", "3d171ef1-a198-48ac-9a60-f6aa98e5fd6d"); payload.Add("fileNamePattern", "main.jellyfin.bundle.js"); - payload.Add("tranformationEndpoint", "/MediaBar/Patch/MainJellyfinBundle"); + payload.Add("transformationEndpoint", "/MediaBar/Patch/MainJellyfinBundle"); payloads.Add(payload); } string? publishedServerUrl = m_serverApplicationHost.GetType() .GetProperty("PublishedServerUrl", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(m_serverApplicationHost) as string; + m_logger.LogInformation($"Retrieved value for published server URL: {publishedServerUrl}"); + HttpClient client = new HttpClient(); + client.BaseAddress = new Uri(publishedServerUrl ?? $"http://localhost:{m_serverApplicationHost.HttpPort}"); + + m_logger.LogInformation($"Setting base address to: {client.BaseAddress}."); + m_logger.LogInformation($"Retrieving media bar payloads."); foreach (JObject payload in payloads) { - HttpClient client = new HttpClient(); - client.BaseAddress = new Uri(publishedServerUrl ?? $"http://localhost:{m_serverApplicationHost.HttpPort}"); - try { + m_logger.LogInformation($"Registering transformation '{payload.Value("id")}' with endpoint '{payload.Value("transformationEndpoint")}'"); + await client.PostAsync("/FileTransformation/RegisterTransformation", new StringContent(payload.ToString(Formatting.None), MediaTypeHeaderValue.Parse("application/json"))); From 420313704b8b3c2692b89ada45284c70c8f651ad Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 12 Feb 2025 23:25:58 +0000 Subject: [PATCH 016/108] Added 5 second delay to the startup script for all plugins to ensure that file transformation would be ready to receive requests when it starts. --- src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs index 4b2ffe8..8b65539 100644 --- a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs +++ b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs @@ -1,4 +1,5 @@ -using System.Net.Http.Headers; +using System.Net; +using System.Net.Http.Headers; using System.Reflection; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; @@ -31,6 +32,8 @@ namespace Jellyfin.Plugin.MediaBar.Services public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { m_logger.LogInformation($"MediaBar Startup. Registering file transformations."); + m_logger.LogInformation("Delaying 5 seconds to ensure file transformation is ready to receive requests."); + await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); List payloads = new List(); From 23725344778def61b0e045689c47e78994dcca62 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 12 Feb 2025 23:41:14 +0000 Subject: [PATCH 017/108] Merged MakD:main into plugin version. --- .../Inject/media-bar.js | 81 +++++------ .../Inject/slideshowpure.js | 130 ++++++++++++------ 2 files changed, 122 insertions(+), 89 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js index ec47dd9..713367a 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js @@ -1,54 +1,35 @@ -function saveCredentialsToSessionStorage(credentials) { - try { - sessionStorage.setItem( - "json-credentials", - JSON.stringify(credentials) - ); - console.log("Credentials saved to sessionStorage."); - } catch (error) { - console.error("Error saving credentials:", error); - } -} -function saveApiKey(apiKey) { - try { - sessionStorage.setItem("api-key", apiKey); - console.log("API key saved to sessionStorage."); - } catch (error) { - console.error("Error saving API key:", error); - } -} -(function () { - var originalConsoleLog = console.log; - console.log = function (message) { - originalConsoleLog.apply(console, arguments); - if ( - typeof message === "string" && - message.startsWith("Stored JSON credentials:") - ) { - try { - var jsonString = message.substring( - "Stored JSON credentials: ".length - ); - var credentials = JSON.parse(jsonString); - saveCredentialsToSessionStorage(credentials); - } catch (error) { - console.error("Error parsing credentials:", error); - } - } - if ( - typeof message === "string" && - message.startsWith("opening web socket with url:") - ) { - try { - var url = message.split("url:")[1].trim(); - var urlParams = new URL(url).searchParams; - var apiKey = urlParams.get("api_key"); - if (apiKey) { - saveApiKey(apiKey); +(function () { + // List of CSS selectors for Home buttons + const buttonSelectors = [ + ".headerHomeButton.barsMenuButton", + ".css-17c09up", + ".mainDrawer-scrollContainer > a:nth-child(2)" + ]; + + // Polling interval to check for buttons + const intervalId = setInterval(function () { + buttonSelectors.forEach(selector => { + // Try to find the button + const homeButton = document.querySelector(selector); + + // If the button is found + if (homeButton) { + // Attach the click event listener if not already added + if (!homeButton.hasAttribute("data-home-handler")) { + homeButton.addEventListener("click", function (event) { + event.preventDefault(); // Prevent default behavior if necessary + window.location.href = "/web/index.html#/home.html"; + }); + + // Mark the button as handled + homeButton.setAttribute("data-home-handler", "true"); } - } catch (error) { - console.error("Error extracting API key:", error); } + }); + + // Stop polling if all buttons are found + if (buttonSelectors.every(selector => document.querySelector(selector))) { + clearInterval(intervalId); } - }; + }, 100); // Check every 100ms })(); \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js index 27dc4e0..39d5be6 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -1,34 +1,73 @@ -const slidesInit = () => { +/** + * Credentials for a given server + * @typedef {Object} ServerCredentials + * @property {string} Id + * @property {string} AccessToken + */ + +/** + * Credentials + * @typedef {Object} Credentials + * @property {ServerCredentials[]} Servers + */ + +/** + * Fetches the credentials inside of localstorage + * @returns {{token: string, userId: string}} Credential resource + */ +const getJellyfinCredentials = () => { + const jellyfinCreds = localStorage.getItem("jellyfin_credentials"); + + try { + /** + * @type {Credentials} + */ + const serverCredentials = JSON.parse(jellyfinCreds); + + const firstServer = serverCredentials.Servers[0]; + + if (!firstServer) { + console.error("Could not find credentials for the client"); + return; + } + + return { token: firstServer.AccessToken, userId: firstServer.UserId }; + } catch (e) { + console.error("Could not parse jellyfin credentials", e); + } +}; + +const slidesInit = () => { + if (window.hasInitializedSlideshow) { + console.log("Slideshow already initialized. Skipping re-init."); + return; + } + window.hasInitializedSlideshow = true; const shuffleInterval = 8000; let isTransitioning = false; const listFileName = `${window.location.origin}/web/avatars/list.txt`; - const jsonCredentials = sessionStorage.getItem("json-credentials"); - let userId = null; - let token = null; - //const deviceId = localStorage.getItem("_deviceId2"); - //console.log(deviceId); - if (jsonCredentials) { - const credentials = JSON.parse(jsonCredentials); - userId = credentials.Servers[0].UserId; - token = credentials.Servers[0].AccessToken; - } + + const { token, userId } = getJellyfinCredentials(); + const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); const truncateText = (element, maxLength) => { let text = element.innerText; if (text.length > maxLength) element.innerText = text.substring(0, maxLength) + "..."; }; + const createSlideElement = (item, title) => { const itemId = item.Id; const plot = item.Overview || "No overview available"; const rating = item.CommunityRating; - const tomato = item.CriticRating; + const criticRating = item.CriticRating; const runtime = item.RunTimeTicks; - const genre = item.Genres; + const genresArray = item.Genres; const youtube = item.RemoteTrailers; const age = item.OfficialRating; const date = item.PremiereDate; const season = item.ChildCount; + function createSeparator() { const separatorHtml = ''; @@ -46,33 +85,43 @@ backdrop.className = "backdrop"; backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; backdrop.alt = "Backdrop"; + const backdropOverlay = document.createElement("div"); backdropOverlay.className = "backdrop-overlay"; + const backdropContainer = document.createElement("div"); backdropContainer.className = "backdrop-container"; backdropContainer.appendChild(backdrop); backdropContainer.appendChild(backdropOverlay); + const logo = document.createElement("img"); logo.className = "logo"; logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; logo.alt = "Logo"; + const logoContainer = document.createElement("div"); logoContainer.className = "logo-container"; logoContainer.appendChild(logo); + const featuredContent = document.createElement("div"); featuredContent.className = "featured-content"; featuredContent.textContent = title; + const plotElement = document.createElement("div"); plotElement.className = "plot"; plotElement.textContent = plot; truncateText(plotElement, 360); + const plotContainer = document.createElement("div"); plotContainer.className = "plot-container"; plotContainer.appendChild(plotElement); + const gradientOverlay = document.createElement("div"); gradientOverlay.className = "gradient-overlay"; + const runTimeElement = document.createElement("div"); runTimeElement.className = "runTime"; + if (season === undefined) { const milliseconds = runtime / 10000; const currentTime = new Date(); @@ -83,8 +132,10 @@ } else { runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; } + const ratingTest = document.createElement("div"); ratingTest.className = "rating-value"; + const imdbLogo = document.createElement("img"); imdbLogo.className = "imdb-logo"; imdbLogo.src = @@ -93,6 +144,7 @@ imdbLogo.style.width = "30px"; imdbLogo.style.height = "30px"; ratingTest.appendChild(imdbLogo); + if (typeof rating === "number") { const formattedRating = rating.toFixed(1); ratingTest.innerHTML += `${formattedRating} ⭐`; @@ -101,21 +153,25 @@ ratingTest.innerHTML += `N/A ⭐`; } ratingTest.appendChild(createSeparator()); + const tomatoRatingDiv = document.createElement("div"); tomatoRatingDiv.className = "tomato-rating"; - const criticRating = item.CriticRating; + const tomatoLogo = document.createElement("img"); tomatoLogo.className = "tomato-logo"; - const criticLogo = document.createElement("img"); - criticLogo.className = "critic-logo"; tomatoLogo.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; + + const criticLogo = document.createElement("img"); + criticLogo.className = "critic-logo"; + let valueElement; if (typeof criticRating === "number") { valueElement = document.createTextNode(`${criticRating}% `); } else { valueElement = document.createTextNode(`N/A `); } + if (criticRating === undefined || criticRating <= 59) { criticLogo.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; @@ -128,22 +184,28 @@ tomatoLogo.style.height = "17px"; criticLogo.style.width = "15px"; criticLogo.style.height = "15px"; + tomatoRatingDiv.appendChild(tomatoLogo); tomatoRatingDiv.appendChild(valueElement); tomatoRatingDiv.appendChild(criticLogo); tomatoRatingDiv.appendChild(createSeparator()); + const ageRatingDiv = document.createElement("div"); ageRatingDiv.className = "age-rating"; + if (item.OfficialRating) { ageRatingDiv.innerHTML = `${item.OfficialRating}`; } else { ageRatingDiv.innerHTML = `N/A`; } + const calender = "📅"; const premiereDate = document.createElement("div"); premiereDate.className = "date"; + const year = date ? new Date(date).getFullYear() : "N/A"; premiereDate.textContent = isNaN(year) ? "N/A" : year; + ratingTest.appendChild(tomatoRatingDiv); ratingTest.appendChild(document.createTextNode(calender)); ratingTest.appendChild(premiereDate); @@ -151,7 +213,7 @@ ratingTest.appendChild(ageRatingDiv); ratingTest.appendChild(createSeparator()); ratingTest.appendChild(runTimeElement); - const genresArray = item.Genres; + function parseGenres(genresArray) { if (genresArray && genresArray.length > 0) { return genresArray.slice(0, 3).join(" 🔹 "); @@ -159,9 +221,11 @@ return "No Genre Available"; } } + const genreElement = document.createElement("div"); genreElement.className = "genre"; genreElement.innerHTML = parseGenres(genresArray); + const infoContainer = document.createElement("div"); infoContainer.className = "info-container"; infoContainer.appendChild(ratingTest); @@ -271,14 +335,11 @@ } }; const fetchItemDetails = async (itemId) => { - const response = await fetch( - `${window.location.origin}/Users/${userId}/Items/${itemId}`, - { - headers: { - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, - }, - } - ); + const response = await fetch(`${window.location.origin}/Items/${itemId}`, { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + }); const item = await response.json(); console.log("Item Title:", item.Name); return item; @@ -302,7 +363,7 @@ const fetchItemsFromServer = async () => { try { const response = await fetch( - `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, + `${window.location.origin}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&SortBy=random&isPlayed=False&Limit=500`, { headers: { Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, @@ -352,6 +413,8 @@ const dots = document.querySelectorAll(".dot"); slides.forEach((slide, i) => { if (i === index) { + console.log(`Showing slide ${index}`); // Debugging + slide.style.removeProperty("display"); slide.style.display = "block"; slide.offsetHeight; slide.style.opacity = "1"; @@ -363,6 +426,7 @@ slide.style.display = "none"; }, 500); } + document.getElementById("slides-container").offsetHeight; }); dots.forEach((dot, i) => { dot.classList.toggle("active", i === index % 5); @@ -470,16 +534,4 @@ initializeSlideshow(); }; initializeSlides(); -}; -document.addEventListener("DOMContentLoaded", () => { - document.body.addEventListener("click", (event) => { - const homeButton = event.target.matches( - ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" - ); - if (homeButton) { - event.preventDefault(); - window.location.href = "/web/index.html#/home.html"; - setTimeout(handleHomeNavigation, 300); - } - }); -}); \ No newline at end of file +}; \ No newline at end of file From 5de188bb44144418cc88f1a19dfdc0e6370f623f Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 12 Feb 2025 23:50:37 +0000 Subject: [PATCH 018/108] Add logo to install package directory. --- src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 8068433..29684b2 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -32,6 +32,7 @@ + From 725b5ae5e3ac6cba3a1df126c26c51c5dba17089 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 17 Feb 2025 21:46:44 +0000 Subject: [PATCH 019/108] Updated to support Jellyfin Version 10.10.6.0 --- .../Jellyfin.Plugin.MediaBar.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 29684b2..ba43814 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -5,14 +5,14 @@ enable enable false - 2.0.0 + 2.1.0.0 - - - - + + + + From 9fa28de6d276577b7816914ef722016a1991cee9 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 24 Feb 2025 22:09:51 +0000 Subject: [PATCH 020/108] Updated js/css from upstream --- .../Inject/slideshowpure.css | 2705 ++++++++++++++--- .../Inject/slideshowpure.js | 182 +- 2 files changed, 2335 insertions(+), 552 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css index e3b74dd..b44b624 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css @@ -1,451 +1,105 @@ -@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap"); - -@media only screen and (min-width: 767px) { - div[data-role="page"].page { - overflow-y: scroll; - scrollbar-width: none; - -ms-overflow-style: none; +@import url(https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap); +.bar-loading { + z-index: 99999999; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0); + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} +.bar-loading h1 img { + opacity: 1; + display: block; + z-index: 999999999 !important; + visibility: visible; +} +.docspinner-loader { + width: 10px; + height: 10px; + margin: 50px auto 0; + position: relative; + border-radius: 50%; + border: 4px solid #fff0; + border-top-color: #fff; + animation: spin 1s linear infinite; + z-index: 999999999 !important; +} +@keyframes spin { + 0% { + transform: rotate(0deg); } - - div[data-role="page"].page::-webkit-scrollbar { - display: none; + 100% { + transform: rotate(360deg); } - +} +/* Mobile (Portrait) */ +@media only screen and (max-width: 480px) and (orientation: portrait) { .backdrop, .slide { position: absolute; } - - .featured-content, - .plot-container { - font-family: "Titillium Web", sans-serif; - left: 0; - } - - .plot, - body { - overflow: hidden; - } - .arrow { position: absolute; top: 50%; transform: translateY(-50%); font-size: 24px; cursor: pointer; - color: white; - z-index: 3; - display: flex; - justify-content: center; - align-items: center; - } - - .arrow::before { - content: ""; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 40px; - height: 40px; - background-color: rgba(12, 110, 162, 0.7); - border-radius: 50%; - z-index: -1; - } - - .left-arrow { - left: 20px; - } - - .right-arrow { - right: 20px; - } - - .dots-container { - position: absolute; - bottom: 5%; - left: 0; - right: 0; - z-index: 2; - display: flex; - justify-content: center; - align-items: center; - width: 100vw; - height: 22px; - background-color: #ffffff00; - gap: 2vw; - } - - .dot { - width: 15vw; - height: 4px; - background-color: #cecece; - border-radius: 2px; - cursor: pointer; - transition: background-color 0.3s; - } - - .dot.active { - background-color: rgba(12, 110, 162, 0.7) !important; - } - - body { - margin: 0; - padding: 0; - } - - .fade-in { - opacity: 1 !important; - } - - .fade-out { - opacity: 0 !important; - } - - #slides-container { - position: relative; - width: 92vw; - height: 650px; - overflow: hidden; - margin: 0 auto; - border-radius: 15px; - } - - .slide { - transition: opacity 0.5s ease-in-out; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; - } - - .slide.active { - opacity: 1; - display: block; - } - - .slide:focus { - outline: #fff solid 2px; - } - - .backdrop-container { - position: relative; - top: -40%; - right: -35%; - width: 70%; - height: 100%; - } - - .backdrop { - right: 0; - width: 100%; - height: 100%; - object-fit: cover; - object-position: center 20%; - border-radius: 5px; - z-index: 1; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } - - .backdrop-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.6); - border-radius: 5px; - z-index: 2; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } - - .logo-container { - width: 45%; - height: 40%; - position: relative; - display: flex; - justify-content: left; - align-items: left; - } - - .logo { - position: relative; - max-height: 100%; - max-width: 100%; - height: auto; - width: auto; - object-fit: contain; - z-index: 3; - top: 5em; - left: 8em; - } - - .featured-content { - position: absolute; - top: 0; - width: 100%; - height: 50px; - background-color: transparent; - color: #d3d3d3; - font-size: 22px; + color: rgb(255 255 255 / 0.6); + z-index: 4; + -webkit-tap-highlight-color: #fff0; display: none; - align-items: center; - justify-content: flex-start; - z-index: 2; } - - .plot-container { - position: absolute; - bottom: 0; - color: #fff; - width: 90%; - height: 60%; - font-size: 15px; - padding: 10px 0 25px 15px; - border-radius: 5px; - z-index: 3; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - text-align: justify; - } - - .plot { - display: -webkit-box; - line-clamp: 4; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - left: 7.9em; - position: absolute; - top: 190px; - } - - .gradient-overlay { - position: absolute; - top: 0; - left: 0; - width: 70%; - height: 100%; - background: linear-gradient(to right, #000 49%, rgba(0, 0, 0, 0) 70%); - z-index: 2; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0.5) 10%, - rgba(0, 0, 0, 1) 12% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0.5) 10%, - rgba(0, 0, 0, 1) 12% - ); - } - .genre { - position: absolute; - top: calc(50% + 75px); - left: 8.4em; - text-align: center; - color: #fff; - font-family: "Titillium Web", sans-serif; - font-size: 14px; - z-index: 4; - } - .info-container { - position: absolute; - top: calc(50% + 0px); - display: flex; - align-items: center; - justify-content: center; - left: 2em !important; - color: #fff; - z-index: 4; - } - .rating-value { - font-family: "Titillium Web", sans-serif; - display: flex; - align-items: center; - margin: 0 6em; - z-index: 5; - position: relative; - top: 1.5em; - } - .runTime { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin: 0 8px; - margin-right: -3px; - } - .rating-value img { - margin-right: 6px; - } - .tomato-rating { - display: flex; - align-items: center; - margin-left: 0; - } - .tomato-rating img { - margin-left: 5px; - } - .age-rating { - display: flex; - align-items: center; - margin-left: 5px; - margin-right: 5px; - border-radius: 5px; - background: rgb(255 255 255 / 0.8); - color: #000; - border: none; - font-weight: 600; - white-space: nowrap; - padding: 0 0.6em; - } - .date { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin-right: 5px; - margin-left: 2px; - } - .separator-icon { - margin-left: 5px; - margin-right: 5px; - font-size: 10px; - color: aquamarine; - } - .button-container { - position: absolute; - top: calc(50% + 190px); /* Same as the previous button positions */ - left: 7.5em; - display: flex; - gap: 2em; /* Space between buttons */ - z-index: 5; /* Ensures it's above other elements */ - } - .play-button, - .detail-button { - display: inline-flex; /* Ensures icon and text are on the same line */ - align-items: center; - justify-content: center; - padding: 8px 16px; /* Slightly reduced padding for thinner buttons */ - border: solid 2px; - border-radius: 25px; /* Sleeker look */ - font-size: 14px; - white-space: nowrap; /* Prevents text wrapping */ - cursor: pointer; - transition: all 0.3s ease; - font-weight: bold; - gap: 6px; /* Adds space between icon and text */ - -webkit-tap-highlight-color: transparent; - } - - .play-button { - background-color: #ffffff; - color: #000; - } - - .detail-button { - background-color: rgba(255, 255, 255, 0); - color: #fff; - } - - .play-button:hover, - .detail-button:hover { - opacity: 0.8; - } - - .play-icon, - .detail-icon { - font-size: 18px; /* Ensures icons are aligned with text */ - display: flex; - align-items: center; - } -} - -@media only screen and (max-width: 767px) { - .backdrop, - .slide { - position: absolute; - } - - .arrow { - position: absolute; - bottom: 25px; - font-size: 24px; - cursor: pointer; - color: rgba(255, 255, 255, 0.6); - z-index: 4; - -webkit-tap-highlight-color: transparent; - } - .left-arrow { left: 10px; } - .right-arrow { right: 10px; } - .featured-content, .plot-container { font-family: "Titillium Web", sans-serif; left: 0; } - .plot, body { overflow: hidden; } - body { margin: 0; padding: 0; + height: 100%; } - .fade-in { opacity: 1 !important; } - .fade-out { opacity: 0 !important; } - + #homeTab { + top: -5em; + padding-bottom: 5em !important; + position: relative; + padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -11em; + } .dots-container { position: absolute; - bottom: 11%; + bottom: 20%; left: 50%; transform: translateX(-50%) scale(0.6); z-index: 2; display: flex; gap: 5px; - background-color: #ffffff00; + background-color: #ffffff13; border-radius: 50px; justify-content: center; align-items: center; @@ -453,7 +107,6 @@ height: 22px; transition: opacity 0.5s ease-in-out; } - .dot { display: inline-block; width: 9px; @@ -463,23 +116,21 @@ border-radius: 50%; transition: background-color 0.3s; } - .dot.active { - width: 32px; + width: 27px; height: 12px; - background-color: rgba(12, 110, 162, 0.7); + background-color: #fff; border-radius: 10px; } - #slides-container { position: relative; - width: 92vw; - height: 650px; + width: 100vw; + height: 800px; overflow: hidden; - margin: 0 auto; - border-radius: 10px; + margin: 0; + padding: 0%; + top: -3.5em; } - .slide { transition: opacity 0.5s ease-in-out; position: absolute; @@ -489,77 +140,82 @@ height: 100%; display: block; } - .slide.active { opacity: 1; display: block; } - .slide:focus { outline: #fff solid 2px; } .logo { position: absolute; - bottom: 28%; + top: 50%; left: 50%; - transform: translateX(-50%); - max-height: 60%; - max-width: 80%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; width: auto; + z-index: 4; + filter: brightness(1.5); + } + .featured-logo-blur { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + scale: 1.01; + filter: blur(1.1rem); z-index: 3; } - .backdrop { position: absolute; - top: 45px; + top: 0; left: 0; width: 100%; - height: calc(100% - 50px); + height: 100%; object-fit: cover; - object-position: center 20%; z-index: 1; - border-radius: 13px; mask-image: linear-gradient( to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% ); -webkit-mask-image: linear-gradient( to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% ); } - .gradient-overlay { position: absolute; - top: 45px; + top: 0; left: 0; width: 100%; - height: calc(100% - 50px); - background: rgba(0, 0, 0, 0.55); + height: 100%; + background: rgb(0 0 0 / 0.65); z-index: 2; - border-radius: 13px; pointer-events: none; mask-image: linear-gradient( to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% ); -webkit-mask-image: linear-gradient( to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% ); } - .genre { position: absolute; - bottom: 16%; + bottom: 30%; left: 50%; transform: translateX(-50%); text-align: center; @@ -580,7 +236,7 @@ display: flex; align-items: center; justify-content: center; - bottom: 20%; + bottom: 25%; left: 50%; transform: translateX(-50%); font-size: 1.1em; @@ -635,52 +291,2177 @@ .plot { display: none; } - .button-container { position: absolute; - bottom: 20px; + bottom: 13%; left: 50%; transform: translateX(-50%); display: flex; - gap: 15px; /* Adjust spacing between buttons */ + gap: 15px; z-index: 4; } - .play-button, .detail-button { - display: inline-flex; /* Ensures icon and text are on the same line */ + display: inline-flex; align-items: center; justify-content: center; - padding: 8px 16px; /* Slightly reduced padding for thinner buttons */ + padding: 8px 16px; border: none; - border-radius: 20px; /* Sleeker look */ + border-radius: 20px; font-size: 14px; - white-space: nowrap; /* Prevents text wrapping */ + white-space: nowrap; cursor: pointer; transition: all 0.3s ease; - font-weight: bold; - gap: 6px; /* Adds space between icon and text */ - -webkit-tap-highlight-color: transparent; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; } - .play-button { - background-color: #ffffff; - color: #000; + background-color: #0c6da2; + color: #fff; } - .detail-button { - background-color: rgba(255, 255, 255, 0.2); + background-color: rgb(12 109 162 / 0.5); + border: solid 1.5px rgb(12 109 162 / 0.8); color: #fff; } - .play-button:hover, .detail-button:hover { opacity: 0.8; } - .play-icon, .detail-icon { - font-size: 18px; /* Ensures icons are aligned with text */ + font-size: 18px; + display: flex; + align-items: center; + } +} + +/* Mobile (Landscape) */ +@media only screen and (max-width: 767px) and (orientation: landscape) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 11em); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.8; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 155vh; + overflow: hidden; + margin: 0 auto; + /*border-radius: 15px;*/ + top: -7.5em; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -15em; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 8em; + left: 5em; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 4.5em; + position: absolute; + top: 7.5em; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 35px); + left: 4.8em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: -1em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 10em); + left: 4.5em; + display: flex; + z-index: 5; + justify-content: space-between; + width: 16em; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/* Tablet (Portrait) */ +@media only screen and (min-width: 481px) and (max-width: 1024px) and (orientation: portrait) { + .backdrop, + .slide { + position: absolute; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: rgb(255 255 255 / 0.6); + z-index: 4; + -webkit-tap-highlight-color: #fff0; + display: none; + } + .left-arrow { + left: 10px; + } + .right-arrow { + right: 10px; + } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + body { + margin: 0; + padding: 0; + height: 100%; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #homeTab { + top: -5em; + padding-bottom: 5em !important; + position: relative; + padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -150px; + } + .dots-container { + position: absolute; + bottom: 20%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff13; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 27px; + height: 12px; + background-color: #fff; + border-radius: 10px; + } + #slides-container { + position: relative; + width: 100vw; + height: 800px; + overflow: hidden; + margin: 0; + padding: 0%; + top: -3.5em; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + z-index: 4; + filter: brightness(1.5); + } + .featured-logo-blur { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + scale: 1.01; + filter: blur(1.1rem); + z-index: 3; + } + .backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(0 0 0 / 0.65); + z-index: 2; + pointer-events: none; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .genre { + position: absolute; + bottom: 30%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Titillium Web", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 25%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } + .button-container { + position: absolute; + bottom: 13%; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 15px; + z-index: 4; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: none; + border-radius: 20px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + border: solid 1.5px rgb(12 109 162 / 0.8); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/* Tablet (Landscape) */ +@media only screen and (min-width: 768px) and (max-width: 1024px) and (orientation: landscape) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 9em); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.8; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 135vh; + overflow: hidden; + margin: 0 auto; + /*border-radius: 15px;*/ + top: -7.5em; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -15em; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 5em; + left: 5em; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 4.5em; + position: absolute; + top: 6.5em; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 1.5em); + left: 5em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + -1em); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 3em; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 7.5em); + left: 4em; + display: flex; + z-index: 5; + justify-content: space-between; + width: 15em; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/* Laptop (Small to Medium Screens) */ +@media only screen and (min-width: 1025px) and (max-width: 1440px) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 13em); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.8; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 95vh; + overflow: hidden; + margin: 0 auto; + /*border-radius: 15px;*/ + top: -7.8em; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -17em; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 10em; + left: 8em; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 7.9em; + position: absolute; + top: 190px; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 50px); + left: 8.4em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 180px); + left: 7.5em; + display: flex; + z-index: 5; + justify-content: space-between; + width: 16em; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/* Large Screens (2K) */ +@media only screen and (min-width: 1441px) and (max-width: 2560px) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 13em); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.8; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 95vh; + overflow: hidden; + margin: 0 auto; + /*border-radius: 15px;*/ + top: -6.8em; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -20em; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 10em; + left: 8em; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 7.9em; + position: absolute; + top: 190px; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 50px); + left: 8.4em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 180px); + left: 7.5em; + display: flex; + z-index: 5; + justify-content: space-between; + width: 14em; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/* Extra Large Screens (4K) */ +@media only screen and (min-width: 2561px) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 13em); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.8; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 95vh; + overflow: hidden; + margin: 0 auto; + /*border-radius: 15px;*/ + top: -6.8em; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -20em; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 10em; + left: 8em; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 7.9em; + position: absolute; + top: 190px; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 50px); + left: 8.4em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 180px); + left: 7.5em; + display: flex; + z-index: 5; + justify-content: space-between; + width: 16em; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; display: flex; align-items: center; } diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js index 39d5be6..eae1aef 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -1,61 +1,63 @@ -/** - * Credentials for a given server - * @typedef {Object} ServerCredentials - * @property {string} Id - * @property {string} AccessToken - */ - -/** - * Credentials - * @typedef {Object} Credentials - * @property {ServerCredentials[]} Servers - */ - -/** - * Fetches the credentials inside of localstorage - * @returns {{token: string, userId: string}} Credential resource - */ -const getJellyfinCredentials = () => { +const getJellyfinCredentials = () => { const jellyfinCreds = localStorage.getItem("jellyfin_credentials"); - try { - /** - * @type {Credentials} - */ const serverCredentials = JSON.parse(jellyfinCreds); - const firstServer = serverCredentials.Servers[0]; - if (!firstServer) { console.error("Could not find credentials for the client"); return; } - return { token: firstServer.AccessToken, userId: firstServer.UserId }; } catch (e) { console.error("Could not parse jellyfin credentials", e); } }; -const slidesInit = () => { +const initLoadingScreen = () => { + const currentPath = window.location.href.toLowerCase(); + if ( + currentPath.includes("/web/#/home.html") || + currentPath.includes("/web/index.html#/home.html") || + currentPath.endsWith("/web/") + ) { + const loadingHTML = ` +
+

+ Server Logo +

+
+
+
+
`; + document.body.insertAdjacentHTML("beforeend", loadingHTML); + const interval = setInterval(() => { + if (document.querySelector(".manualLoginForm")) { + $(".bar-loading").fadeOut(700, () => $(".bar-loading").remove()); + clearInterval(interval); + } + }, 100); + } +}; +initLoadingScreen(); +const slidesInit = async () => { if (window.hasInitializedSlideshow) { console.log("Slideshow already initialized. Skipping re-init."); return; } - window.hasInitializedSlideshow = true; + + window.hasInitializedSlideshow = !0; const shuffleInterval = 8000; - let isTransitioning = false; + let isTransitioning = !1; const listFileName = `${window.location.origin}/web/avatars/list.txt`; - const { token, userId } = getJellyfinCredentials(); - const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); const truncateText = (element, maxLength) => { let text = element.innerText; if (text.length > maxLength) element.innerText = text.substring(0, maxLength) + "..."; }; - const createSlideElement = (item, title) => { const itemId = item.Id; const plot = item.Overview || "No overview available"; @@ -63,11 +65,9 @@ const slidesInit = () => { const criticRating = item.CriticRating; const runtime = item.RunTimeTicks; const genresArray = item.Genres; - const youtube = item.RemoteTrailers; const age = item.OfficialRating; const date = item.PremiereDate; const season = item.ChildCount; - function createSeparator() { const separatorHtml = ''; @@ -85,43 +85,41 @@ const slidesInit = () => { backdrop.className = "backdrop"; backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; backdrop.alt = "Backdrop"; - + backdrop.loading = "eager"; const backdropOverlay = document.createElement("div"); backdropOverlay.className = "backdrop-overlay"; - const backdropContainer = document.createElement("div"); backdropContainer.className = "backdrop-container"; backdropContainer.appendChild(backdrop); backdropContainer.appendChild(backdropOverlay); - const logo = document.createElement("img"); logo.className = "logo"; logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; logo.alt = "Logo"; - + logo.loading = "eager"; + const logoImgBlur = document.createElement("img"); + logoImgBlur.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; + logoImgBlur.alt = item.Name || "Title"; + logoImgBlur.loading = "eager"; + logoImgBlur.className = "featured-logo-blur"; const logoContainer = document.createElement("div"); logoContainer.className = "logo-container"; logoContainer.appendChild(logo); - + logoContainer.appendChild(logoImgBlur); const featuredContent = document.createElement("div"); featuredContent.className = "featured-content"; featuredContent.textContent = title; - const plotElement = document.createElement("div"); plotElement.className = "plot"; plotElement.textContent = plot; truncateText(plotElement, 360); - const plotContainer = document.createElement("div"); plotContainer.className = "plot-container"; plotContainer.appendChild(plotElement); - const gradientOverlay = document.createElement("div"); gradientOverlay.className = "gradient-overlay"; - const runTimeElement = document.createElement("div"); runTimeElement.className = "runTime"; - if (season === undefined) { const milliseconds = runtime / 10000; const currentTime = new Date(); @@ -132,10 +130,8 @@ const slidesInit = () => { } else { runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; } - const ratingTest = document.createElement("div"); ratingTest.className = "rating-value"; - const imdbLogo = document.createElement("img"); imdbLogo.className = "imdb-logo"; imdbLogo.src = @@ -144,34 +140,27 @@ const slidesInit = () => { imdbLogo.style.width = "30px"; imdbLogo.style.height = "30px"; ratingTest.appendChild(imdbLogo); - if (typeof rating === "number") { const formattedRating = rating.toFixed(1); ratingTest.innerHTML += `${formattedRating} ⭐`; } else { - console.error("Rating is undefined or not a number:", rating); ratingTest.innerHTML += `N/A ⭐`; } ratingTest.appendChild(createSeparator()); - const tomatoRatingDiv = document.createElement("div"); tomatoRatingDiv.className = "tomato-rating"; - const tomatoLogo = document.createElement("img"); tomatoLogo.className = "tomato-logo"; tomatoLogo.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; - const criticLogo = document.createElement("img"); criticLogo.className = "critic-logo"; - let valueElement; if (typeof criticRating === "number") { valueElement = document.createTextNode(`${criticRating}% `); } else { valueElement = document.createTextNode(`N/A `); } - if (criticRating === undefined || criticRating <= 59) { criticLogo.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; @@ -184,28 +173,22 @@ const slidesInit = () => { tomatoLogo.style.height = "17px"; criticLogo.style.width = "15px"; criticLogo.style.height = "15px"; - tomatoRatingDiv.appendChild(tomatoLogo); tomatoRatingDiv.appendChild(valueElement); tomatoRatingDiv.appendChild(criticLogo); tomatoRatingDiv.appendChild(createSeparator()); - const ageRatingDiv = document.createElement("div"); ageRatingDiv.className = "age-rating"; - if (item.OfficialRating) { ageRatingDiv.innerHTML = `${item.OfficialRating}`; } else { ageRatingDiv.innerHTML = `N/A`; } - const calender = "📅"; const premiereDate = document.createElement("div"); premiereDate.className = "date"; - const year = date ? new Date(date).getFullYear() : "N/A"; premiereDate.textContent = isNaN(year) ? "N/A" : year; - ratingTest.appendChild(tomatoRatingDiv); ratingTest.appendChild(document.createTextNode(calender)); ratingTest.appendChild(premiereDate); @@ -213,7 +196,6 @@ const slidesInit = () => { ratingTest.appendChild(ageRatingDiv); ratingTest.appendChild(createSeparator()); ratingTest.appendChild(runTimeElement); - function parseGenres(genresArray) { if (genresArray && genresArray.length > 0) { return genresArray.slice(0, 3).join(" 🔹 "); @@ -221,23 +203,18 @@ const slidesInit = () => { return "No Genre Available"; } } - const genreElement = document.createElement("div"); genreElement.className = "genre"; genreElement.innerHTML = parseGenres(genresArray); - const infoContainer = document.createElement("div"); infoContainer.className = "info-container"; infoContainer.appendChild(ratingTest); - // Create a container for the buttons const buttonContainer = document.createElement("div"); - buttonContainer.className = "button-container"; // Add a class for styling - - // Create the Play button + buttonContainer.className = "button-container"; const playButton = document.createElement("button"); playButton.className = "play-button"; playButton.innerHTML = ` - + play_circle Play `; playButton.onclick = async () => { @@ -245,34 +222,27 @@ const slidesInit = () => { console.error("PlaybackManager is not available."); return; } - if (!window.ApiClient) { console.error("Jellyfin API client is not available."); return; } - const apiClient = window.ApiClient; const userId = apiClient.getCurrentUserId(); - if (!userId) { console.error("User not found."); return; } - try { - // Fetch the media item details const item = await apiClient.getItem(userId, itemId); if (!item) { console.error("Media item not found."); return; } - - // Initiate playback using PlaybackManager window.PlaybackManager.play({ items: [item], - startPositionTicks: 0, // Start from beginning - isMuted: false, - isPaused: false, + startPositionTicks: 0, + isMuted: !1, + isPaused: !1, }) .then(() => { console.log("Playback started successfully."); @@ -284,8 +254,6 @@ const slidesInit = () => { console.error("Error starting playback:", error); } }; - - // Create the Details button const detailButton = document.createElement("button"); detailButton.className = "detail-button"; detailButton.innerHTML = ` @@ -295,12 +263,8 @@ const slidesInit = () => { detailButton.onclick = () => { window.top.location.href = `/#!/details?id=${itemId}`; }; - - // Append buttons to the button container - buttonContainer.appendChild(playButton); buttonContainer.appendChild(detailButton); - - // Append all elements to the slide + buttonContainer.appendChild(playButton); slide.append( logoContainer, backdropContainer, @@ -311,7 +275,6 @@ const slidesInit = () => { genreElement, buttonContainer ); - return slide; }; const createSlideForItem = async (item, title) => { @@ -326,7 +289,6 @@ const slidesInit = () => { if (backdropExists && logoExists) { const slideElement = createSlideElement(item, title); container.appendChild(slideElement); - console.log(`Added slide for item ${itemId}`); if (container.children.length === 1) { showSlide(0); } @@ -341,7 +303,6 @@ const slidesInit = () => { }, }); const item = await response.json(); - console.log("Item Title:", item.Name); return item; }; const fetchItemIdsFromList = async () => { @@ -354,7 +315,8 @@ const slidesInit = () => { return text .split("\n") .map((id) => id.trim()) - .filter((id) => id); + .filter((id) => id) + .slice(1); } catch (error) { console.error("Error fetching list.txt:", error); return []; @@ -413,7 +375,6 @@ const slidesInit = () => { const dots = document.querySelectorAll(".dot"); slides.forEach((slide, i) => { if (i === index) { - console.log(`Showing slide ${index}`); // Debugging slide.style.removeProperty("display"); slide.style.display = "block"; slide.offsetHeight; @@ -441,11 +402,11 @@ const slidesInit = () => { let containerFocused = !1; const updateCurrentSlide = (index) => { if (isTransitioning) return; - isTransitioning = true; + isTransitioning = !0; currentSlideIndex = (index + slides.length) % slides.length; showSlide(currentSlideIndex); setTimeout(() => { - isTransitioning = false; + isTransitioning = !1; }, 500); }; const openActiveSlide = () => { @@ -505,6 +466,46 @@ const slidesInit = () => { rightArrow.innerHTML = ''; container.appendChild(leftArrow); container.appendChild(rightArrow); + let touchStartX = 0; + let touchEndX = 0; + const minSwipeDistance = 50; + let isTouchOnButton = false; + + container.addEventListener( + "touchstart", + (event) => { + touchStartX = event.touches[0].clientX; + isTouchOnButton = + event.target.closest(".play-button, .detail-button") !== null; + }, + { passive: false } + ); + container.addEventListener( + "touchmove", + (event) => { + touchEndX = event.touches[0].clientX; + }, + { passive: false } + ); + + container.addEventListener( + "touchend", + (event) => { + if (isTouchOnButton) return; + + const swipeDistance = touchEndX - touchStartX; + if (Math.abs(swipeDistance) > minSwipeDistance) { + event.preventDefault(); + event.stopPropagation(); + if (swipeDistance < 0) { + updateCurrentSlide(currentSlideIndex + 1); + } else { + updateCurrentSlide(currentSlideIndex - 1); + } + } + }, + { passive: false } + ); leftArrow.addEventListener("click", () => updateCurrentSlide(currentSlideIndex - 1) ); @@ -532,6 +533,7 @@ const slidesInit = () => { items = shuffleArray(items); await createSlidesForItems(items); initializeSlideshow(); + $(".bar-loading").fadeOut(700, () => $(".bar-loading").remove()); }; initializeSlides(); }; \ No newline at end of file From 44c4c7e3a6c1b6e108941ecaba0207638b7f35cb Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 24 Feb 2025 22:10:51 +0000 Subject: [PATCH 021/108] Added configuration page to enable and disable the plugin. Allows for future proofing in case there are optional configurations (like the normal mode before support for it was dropped) --- .../Configuration/PluginConfiguration.cs | 15 ++++ .../Configuration/config.html | 84 +++++++++++++++++++ .../Jellyfin.Plugin.MediaBar.csproj | 1 + .../MediaBarPlugin.cs | 5 +- 4 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs create mode 100644 src/Jellyfin.Plugin.MediaBar/Configuration/config.html diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs b/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs new file mode 100644 index 0000000..28117ae --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs @@ -0,0 +1,15 @@ +using MediaBrowser.Model.Plugins; + +namespace Jellyfin.Plugin.MediaBar.Configuration +{ + public enum MediaBarState + { + Disabled, + Enabled, + } + + public class PluginConfiguration : BasePluginConfiguration + { + public MediaBarState Enabled { get; set; } = MediaBarState.Enabled; + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html new file mode 100644 index 0000000..c3dfc64 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html @@ -0,0 +1,84 @@ + + + + Media Bar + + +
+
+
+
+

Media Bar

+ + + Help + +
+
+
+ +
+
+ Enabled: + + Whether the media bar should appear or not. +
+
+
+ +
+ + \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index ba43814..81c5ef6 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs index 172ac70..7ed4188 100644 --- a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs +++ b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs @@ -1,11 +1,12 @@ -using MediaBrowser.Common.Configuration; +using Jellyfin.Plugin.MediaBar.Configuration; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; namespace Jellyfin.Plugin.MediaBar { - public class MediaBarPlugin : BasePlugin + public class MediaBarPlugin : BasePlugin { public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448"); From f48af0791b9f7a6dba79e719f9798b17e0558e7e Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 24 Feb 2025 22:14:29 +0000 Subject: [PATCH 022/108] Forgot to add IHasPluginConfiguration --- src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs index 7ed4188..478a36f 100644 --- a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs +++ b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Serialization; namespace Jellyfin.Plugin.MediaBar { - public class MediaBarPlugin : BasePlugin + public class MediaBarPlugin : BasePlugin, IHasPluginConfiguration { public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448"); From 919f0f3c55ab7e3b383f5b51861aae790eaff1a5 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 24 Feb 2025 22:18:25 +0000 Subject: [PATCH 023/108] And forgot the IHasWebPages --- src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs index 478a36f..c1d97fa 100644 --- a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs +++ b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Serialization; namespace Jellyfin.Plugin.MediaBar { - public class MediaBarPlugin : BasePlugin, IHasPluginConfiguration + public class MediaBarPlugin : BasePlugin, IHasPluginConfiguration, IHasWebPages { public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448"); @@ -15,5 +15,16 @@ namespace Jellyfin.Plugin.MediaBar public MediaBarPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IServiceProvider serviceProvider) : base(applicationPaths, xmlSerializer) { } + + public IEnumerable GetPages() + { + string? prefix = GetType().Namespace; + + yield return new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = $"{prefix}.Configuration.config.html" + }; + } } } \ No newline at end of file From c4a6eba1ea6ddabab3385c5bfca0c8fa3a8c04f8 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 24 Feb 2025 22:38:50 +0000 Subject: [PATCH 024/108] Forgot to add the save button to the settings lol --- .../Configuration/config.html | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html index c3dfc64..ed45d74 100644 --- a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html @@ -18,16 +18,22 @@
-
-
- Enabled: - - Whether the media bar should appear or not. +
+
+
+ Enabled: + + Whether the media bar should appear or not. +
-
+
+ +
\ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/index.html b/src/Jellyfin.Plugin.MediaBar/Inject/index.html index 41c07a8..b1c02f4 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/index.html +++ b/src/Jellyfin.Plugin.MediaBar/Inject/index.html @@ -1,4 +1,3 @@ - - + - + \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js deleted file mode 100644 index 713367a..0000000 --- a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js +++ /dev/null @@ -1,35 +0,0 @@ -(function () { - // List of CSS selectors for Home buttons - const buttonSelectors = [ - ".headerHomeButton.barsMenuButton", - ".css-17c09up", - ".mainDrawer-scrollContainer > a:nth-child(2)" - ]; - - // Polling interval to check for buttons - const intervalId = setInterval(function () { - buttonSelectors.forEach(selector => { - // Try to find the button - const homeButton = document.querySelector(selector); - - // If the button is found - if (homeButton) { - // Attach the click event listener if not already added - if (!homeButton.hasAttribute("data-home-handler")) { - homeButton.addEventListener("click", function (event) { - event.preventDefault(); // Prevent default behavior if necessary - window.location.href = "/web/index.html#/home.html"; - }); - - // Mark the button as handled - homeButton.setAttribute("data-home-handler", "true"); - } - } - }); - - // Stop polling if all buttons are found - if (buttonSelectors.every(selector => document.querySelector(selector))) { - clearInterval(intervalId); - } - }, 100); // Check every 100ms -})(); \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css index b44b624..f877169 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css @@ -1,4 +1,4 @@ -@import url(https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap); +@import url(https://fonts.googleapis.com/css2?family=Archivo+Narrow:ital,wght@0,400..700;1,400..700&display=swap); .bar-loading { z-index: 99999999; position: fixed; @@ -6,7 +6,7 @@ left: 0; width: 100%; height: 100%; - background-color: rgb(0 0 0); + background-color: #000; display: flex; align-items: center; justify-content: center; @@ -37,8 +37,9 @@ transform: rotate(360deg); } } -/* Mobile (Portrait) */ -@media only screen and (max-width: 480px) and (orientation: portrait) { + +/*Mobiles S*/ +@media only screen and (max-width: 320px) and (orientation: portrait) { .backdrop, .slide { position: absolute; @@ -62,7 +63,7 @@ } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -80,16 +81,16 @@ .fade-out { opacity: 0 !important; } - #homeTab { - top: -5em; - padding-bottom: 5em !important; - position: relative; - padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; - } + /*#homeTab { + top: -5em; + padding-bottom: 5em !important; + position: relative; + padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; + }*/ .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -11em; + top: 71vh; } .dots-container { position: absolute; @@ -129,7 +130,7 @@ overflow: hidden; margin: 0; padding: 0%; - top: -3.5em; + /*top: -3.5em;*/ } .slide { transition: opacity 0.5s ease-in-out; @@ -182,13 +183,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .gradient-overlay { @@ -204,13 +205,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { @@ -225,7 +226,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-size: 1.1em; font-weight: 500; z-index: 4; @@ -245,13 +246,13 @@ width: 100%; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -337,15 +338,14 @@ } } -/* Mobile (Landscape) */ -@media only screen and (max-width: 767px) and (orientation: landscape) { +@media only screen and (max-width: 320px) and (orientation: landscape) { .backdrop, .slide { position: absolute; } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -360,7 +360,7 @@ cursor: pointer; color: #fff; z-index: 3; - display: flex; + display: none; justify-content: center; align-items: center; } @@ -428,16 +428,14 @@ #slides-container { position: relative; width: 100vw; - height: 155vh; + height: 190vh; overflow: hidden; margin: 0 auto; - /*border-radius: 15px;*/ - top: -7.5em; } .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -15em; + top: 125vh; } .slide { transition: opacity 0.5s ease-in-out; @@ -474,13 +472,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .backdrop-overlay { @@ -496,13 +494,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .logo-container { @@ -521,8 +519,8 @@ width: auto; object-fit: contain; z-index: 4; - top: 8em; - left: 5em; + top: 8vh; + left: 5vw; filter: brightness(1.5); } .featured-content { @@ -559,9 +557,9 @@ line-clamp: 2; -webkit-line-clamp: 2; -webkit-box-orient: vertical; - left: 4.5em; + left: 5vw; position: absolute; - top: 7.5em; + top: 8.5em; } .gradient-overlay { position: absolute; @@ -575,22 +573,22 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { position: absolute; top: calc(50% + 35px); - left: 4.8em; + left: 5vw; text-align: center; color: #fff; - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-size: 14px; z-index: 4; } @@ -605,15 +603,14 @@ z-index: 4; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; display: flex; align-items: center; - margin: 0 6em; z-index: 5; position: relative; } .runTime { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -645,7 +642,7 @@ padding: 0 0.6em; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -703,8 +700,8 @@ } } -/* Tablet (Portrait) */ -@media only screen and (min-width: 481px) and (max-width: 1024px) and (orientation: portrait) { +/*Mobiles M*/ +@media only screen and (min-width: 321px) and (max-width: 375px) and (orientation: portrait) { .backdrop, .slide { position: absolute; @@ -728,7 +725,7 @@ } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -746,16 +743,16 @@ .fade-out { opacity: 0 !important; } - #homeTab { - top: -5em; - padding-bottom: 5em !important; - position: relative; - padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; - } + /*#homeTab { + top: -5em; + padding-bottom: 5em !important; + position: relative; + padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; + }*/ .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -150px; + top: 91vh; } .dots-container { position: absolute; @@ -791,11 +788,11 @@ #slides-container { position: relative; width: 100vw; - height: 800px; + height: 120vh; overflow: hidden; margin: 0; padding: 0%; - top: -3.5em; + /*top: -3.5em;*/ } .slide { transition: opacity 0.5s ease-in-out; @@ -848,13 +845,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .gradient-overlay { @@ -870,13 +867,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { @@ -891,7 +888,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-size: 1.1em; font-weight: 500; z-index: 4; @@ -911,13 +908,13 @@ width: 100%; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -1003,15 +1000,14 @@ } } -/* Tablet (Landscape) */ -@media only screen and (min-width: 768px) and (max-width: 1024px) and (orientation: landscape) { +@media only screen and (min-width: 321px) and (max-width: 375px) and (orientation: landscape) { .backdrop, .slide { position: absolute; } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -1026,7 +1022,7 @@ cursor: pointer; color: #fff; z-index: 3; - display: flex; + display: none; justify-content: center; align-items: center; } @@ -1052,7 +1048,670 @@ } .dots-container { position: absolute; - top: calc(50% + 9em); + top: calc(50% + 22vh); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.6; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 135vh; + overflow: hidden; + margin: 0 auto; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: 75vh; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 50%; + max-width: 70%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 25vh; + left: 5vw; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 5vw; + position: absolute; + top: calc(50% + -20vh); + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 0vh); + left: 5vw; + text-align: center; + color: #fff; + font-family: "Archivo Narrow", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + -10vh); + display: flex; + align-items: center; + justify-content: center; + left: 5vw; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Archivo Narrow", sans-serif; + display: flex; + align-items: center; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 20vh); + left: 2vw; + display: flex; + z-index: 5; + justify-content: space-between; + width: 16em; + scale: 0.8; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/*Mobiles L*/ +@media only screen and (min-width: 376px) and (max-width: 425px) and (orientation: portrait) { + .backdrop, + .slide { + position: absolute; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: rgb(255 255 255 / 0.6); + z-index: 4; + -webkit-tap-highlight-color: #fff0; + display: none; + } + .left-arrow { + left: 10px; + } + .right-arrow { + right: 10px; + } + .featured-content, + .plot-container { + font-family: "Archivo Narrow", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + body { + margin: 0; + padding: 0; + height: 100%; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + /*#homeTab { + top: -5em; + padding-bottom: 5em !important; + position: relative; + padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; + }*/ + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: 73vh; + } + .dots-container { + position: absolute; + bottom: 20%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff13; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 27px; + height: 12px; + background-color: #fff; + border-radius: 10px; + } + #slides-container { + position: relative; + width: 100vw; + height: 95vh; + overflow: hidden; + margin: 0; + padding: 0%; + /*top: -3.5em;*/ + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + z-index: 4; + filter: brightness(1.5); + } + .featured-logo-blur { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + scale: 1.01; + filter: blur(1.1rem); + z-index: 3; + } + .backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(0 0 0 / 0.65); + z-index: 2; + pointer-events: none; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .genre { + position: absolute; + bottom: 30%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Archivo Narrow", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 25%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } + .button-container { + position: absolute; + bottom: 13%; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 15px; + z-index: 4; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: none; + border-radius: 20px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + border: solid 1.5px rgb(12 109 162 / 0.8); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +@media only screen and (min-width: 376px) and (max-width: 425px) and (orientation: landscape) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Archivo Narrow", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: none; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 11em); right: 3%; z-index: 2; display: flex; @@ -1094,16 +1753,14 @@ #slides-container { position: relative; width: 100vw; - height: 135vh; + height: 190vh; overflow: hidden; margin: 0 auto; - /*border-radius: 15px;*/ - top: -7.5em; } .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -15em; + top: 125vh; } .slide { transition: opacity 0.5s ease-in-out; @@ -1140,13 +1797,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .backdrop-overlay { @@ -1162,13 +1819,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .logo-container { @@ -1187,7 +1844,7 @@ width: auto; object-fit: contain; z-index: 4; - top: 5em; + top: 8em; left: 5em; filter: brightness(1.5); } @@ -1227,7 +1884,7 @@ -webkit-box-orient: vertical; left: 4.5em; position: absolute; - top: 6.5em; + top: 35vh; } .gradient-overlay { position: absolute; @@ -1241,45 +1898,44 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { position: absolute; - top: calc(50% + 1.5em); - left: 5em; + top: calc(50% + 35px); + left: 4.8em; text-align: center; color: #fff; - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-size: 14px; z-index: 4; } .info-container { position: absolute; - top: calc(50% + -1em); + top: calc(50% + 0px); display: flex; align-items: center; justify-content: center; - left: 2em !important; + left: -1em !important; color: #fff; z-index: 4; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; display: flex; align-items: center; - margin: 0 3em; z-index: 5; position: relative; } .runTime { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -1311,7 +1967,1324 @@ padding: 0 0.6em; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 10em); + left: 4.5em; + display: flex; + z-index: 5; + justify-content: space-between; + width: 16em; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/*Mobiles UL*/ +@media only screen and (min-width: 426px) and (max-width: 768px) and (orientation: portrait) { + .backdrop, + .slide { + position: absolute; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: rgb(255 255 255 / 0.6); + z-index: 4; + -webkit-tap-highlight-color: #fff0; + display: none; + } + .left-arrow { + left: 10px; + } + .right-arrow { + right: 10px; + } + .featured-content, + .plot-container { + font-family: "Archivo Narrow", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + body { + margin: 0; + padding: 0; + height: 100%; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: 73vh; + } + .dots-container { + position: absolute; + bottom: 24%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff13; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 27px; + height: 12px; + background-color: #fff; + border-radius: 10px; + } + #slides-container { + position: relative; + width: 100vw; + height: 100vh; + overflow: hidden; + margin: 0; + padding: 0%; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + z-index: 4; + filter: brightness(1.5); + } + .featured-logo-blur { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + scale: 1.01; + filter: blur(1.1rem); + z-index: 3; + } + .backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(0 0 0 / 0.65); + z-index: 2; + pointer-events: none; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .genre { + position: absolute; + bottom: 33%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Archivo Narrow", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 28%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } + .button-container { + position: absolute; + bottom: 18vh; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 15px; + z-index: 4; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: none; + border-radius: 20px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + border: solid 1.5px rgb(12 109 162 / 0.8); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +@media only screen and (min-width: 426px) and (max-width: 768px) and (orientation: landscape) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Archivo Narrow", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: none; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 34vh); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.6; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 145vh; + overflow: hidden; + margin: 0 auto; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: 90vh; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 8em; + left: 4vw; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 4vw; + position: absolute; + top: 33vh; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 10vh); + left: 4vw; + text-align: center; + color: #fff; + font-family: "Archivo Narrow", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 4vw; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Archivo Narrow", sans-serif; + display: flex; + align-items: center; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 32vh); + left: 0.5vw; + display: flex; + z-index: 5; + justify-content: space-between; + width: 16em; + scale: 0.8; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} +/*upwards changed lanscape width*/ + +@media only screen and (min-width: 769px) and (max-width: 1024px) and (orientation: portrait) { + .backdrop, + .slide { + position: absolute; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: rgb(255 255 255 / 0.6); + z-index: 4; + -webkit-tap-highlight-color: #fff0; + display: none; + } + .left-arrow { + left: 10px; + } + .right-arrow { + right: 10px; + } + .featured-content, + .plot-container { + font-family: "Archivo Narrow", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + body { + margin: 0; + padding: 0; + height: 100%; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + /*#homeTab { + top: -5em; + padding-bottom: 5em !important; + position: relative; + padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; + }*/ + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: 73vh; + } + .dots-container { + position: absolute; + bottom: 20%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff13; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 27px; + height: 12px; + background-color: #fff; + border-radius: 10px; + } + #slides-container { + position: relative; + width: 100vw; + height: 95vh; + overflow: hidden; + margin: 0; + padding: 0%; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + z-index: 4; + filter: brightness(1.5); + } + .featured-logo-blur { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + scale: 1.01; + filter: blur(1.1rem); + z-index: 3; + } + .backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(0 0 0 / 0.65); + z-index: 2; + pointer-events: none; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .genre { + position: absolute; + bottom: 30%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Archivo Narrow", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 25%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } + .button-container { + position: absolute; + bottom: 13%; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 15px; + z-index: 4; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: none; + border-radius: 20px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + border: solid 1.5px rgb(12 109 162 / 0.8); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +@media only screen and (min-width: 769px) and (max-width: 1024px) and (orientation: landscape) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Archivo Narrow", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: none; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 9em); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.6; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 135vh; + overflow: hidden; + margin: 0 auto; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: 83vh; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 20vh; + left: 5vw; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 5vw; + position: absolute; + top: 25vh; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 3.5vh); + left: 5vw; + text-align: center; + color: #fff; + font-family: "Archivo Narrow", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + -6vh); + display: flex; + align-items: center; + justify-content: center; + left: 5vw !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Archivo Narrow", sans-serif; + display: flex; + align-items: center; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -1327,7 +3300,7 @@ .button-container { position: absolute; top: calc(50% + 7.5em); - left: 4em; + left: 5vw; display: flex; z-index: 5; justify-content: space-between; @@ -1369,7 +3342,6 @@ } } -/* Laptop (Small to Medium Screens) */ @media only screen and (min-width: 1025px) and (max-width: 1440px) { .backdrop, .slide { @@ -1377,7 +3349,7 @@ } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -1418,7 +3390,7 @@ } .dots-container { position: absolute; - top: calc(50% + 13em); + top: calc(50% + 21vh); right: 3%; z-index: 2; display: flex; @@ -1463,13 +3435,11 @@ height: 95vh; overflow: hidden; margin: 0 auto; - /*border-radius: 15px;*/ - top: -7.8em; } .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -17em; + top: 65vh; } .slide { transition: opacity 0.5s ease-in-out; @@ -1506,13 +3476,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .backdrop-overlay { @@ -1528,13 +3498,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .logo-container { @@ -1554,7 +3524,7 @@ object-fit: contain; z-index: 4; top: 10em; - left: 8em; + left: 6vw; filter: brightness(1.5); } .featured-content { @@ -1591,9 +3561,9 @@ line-clamp: 2; -webkit-line-clamp: 2; -webkit-box-orient: vertical; - left: 7.9em; + left: 6vw; position: absolute; - top: 190px; + top: 20vh; } .gradient-overlay { position: absolute; @@ -1607,22 +3577,22 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { position: absolute; top: calc(50% + 50px); - left: 8.4em; + left: 6vw; text-align: center; color: #fff; - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-size: 14px; z-index: 4; } @@ -1632,20 +3602,19 @@ display: flex; align-items: center; justify-content: center; - left: 2em !important; + left: 6vw !important; color: #fff; z-index: 4; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; display: flex; align-items: center; - margin: 0 6em; z-index: 5; position: relative; } .runTime { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -1677,7 +3646,7 @@ padding: 0 0.6em; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -1692,12 +3661,12 @@ } .button-container { position: absolute; - top: calc(50% + 180px); - left: 7.5em; + top: calc(50% + 20vh); + left: 5vw; display: flex; z-index: 5; justify-content: space-between; - width: 16em; + width: 15em; } .play-button, .detail-button { @@ -1734,8 +3703,6 @@ align-items: center; } } - -/* Large Screens (2K) */ @media only screen and (min-width: 1441px) and (max-width: 2560px) { .backdrop, .slide { @@ -1743,7 +3710,7 @@ } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -1784,7 +3751,7 @@ } .dots-container { position: absolute; - top: calc(50% + 13em); + top: calc(50% + 20vh); right: 3%; z-index: 2; display: flex; @@ -1826,16 +3793,14 @@ #slides-container { position: relative; width: 100vw; - height: 95vh; + height: 98vh; overflow: hidden; margin: 0 auto; - /*border-radius: 15px;*/ - top: -6.8em; } .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -20em; + top: 65vh; } .slide { transition: opacity 0.5s ease-in-out; @@ -1872,13 +3837,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .backdrop-overlay { @@ -1894,13 +3859,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .logo-container { @@ -1919,8 +3884,8 @@ width: auto; object-fit: contain; z-index: 4; - top: 10em; - left: 8em; + top: 15vh; + left: 5vw; filter: brightness(1.5); } .featured-content { @@ -1957,9 +3922,9 @@ line-clamp: 2; -webkit-line-clamp: 2; -webkit-box-orient: vertical; - left: 7.9em; + left: 5vw; position: absolute; - top: 190px; + top: 20vh; } .gradient-overlay { position: absolute; @@ -1973,45 +3938,44 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { position: absolute; - top: calc(50% + 50px); - left: 8.4em; + top: calc(50% + 4.5vh); + left: 5vw; text-align: center; color: #fff; - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-size: 14px; z-index: 4; } .info-container { position: absolute; - top: calc(50% + 0px); + top: calc(50% + -2vh); display: flex; align-items: center; justify-content: center; - left: 2em !important; + left: 5vw !important; color: #fff; z-index: 4; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; display: flex; align-items: center; - margin: 0 6em; z-index: 5; position: relative; } .runTime { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -2043,7 +4007,7 @@ padding: 0 0.6em; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -2058,8 +4022,8 @@ } .button-container { position: absolute; - top: calc(50% + 180px); - left: 7.5em; + top: calc(50% + 19vh); + left: 5vw; display: flex; z-index: 5; justify-content: space-between; @@ -2100,8 +4064,6 @@ align-items: center; } } - -/* Extra Large Screens (4K) */ @media only screen and (min-width: 2561px) { .backdrop, .slide { @@ -2109,7 +4071,7 @@ } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -2150,7 +4112,7 @@ } .dots-container { position: absolute; - top: calc(50% + 13em); + top: calc(50% + 21vh); right: 3%; z-index: 2; display: flex; @@ -2195,13 +4157,11 @@ height: 95vh; overflow: hidden; margin: 0 auto; - /*border-radius: 15px;*/ - top: -6.8em; } .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -20em; + top: 65vh; } .slide { transition: opacity 0.5s ease-in-out; @@ -2238,13 +4198,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .backdrop-overlay { @@ -2260,13 +4220,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .logo-container { @@ -2286,7 +4246,7 @@ object-fit: contain; z-index: 4; top: 10em; - left: 8em; + left: 5vw; filter: brightness(1.5); } .featured-content { @@ -2322,10 +4282,11 @@ display: -webkit-box; line-clamp: 2; -webkit-line-clamp: 2; + font-size: x-large; -webkit-box-orient: vertical; - left: 7.9em; + left: 5vw; position: absolute; - top: 190px; + top: 20vh; } .gradient-overlay { position: absolute; @@ -2339,45 +4300,45 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { position: absolute; - top: calc(50% + 50px); - left: 8.4em; + top: calc(50% + 5vh); + left: 5vw; text-align: center; color: #fff; - font-family: "Titillium Web", sans-serif; - font-size: 14px; + font-family: "Archivo Narrow", sans-serif; + font-size: x-large; z-index: 4; } .info-container { position: absolute; - top: calc(50% + 0px); + top: calc(50% + 0vh); + font-size: x-large; display: flex; align-items: center; justify-content: center; - left: 2em !important; + left: 5vw !important; color: #fff; z-index: 4; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; display: flex; align-items: center; - margin: 0 6em; z-index: 5; position: relative; } .runTime { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -2409,7 +4370,7 @@ padding: 0 0.6em; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -2424,12 +4385,13 @@ } .button-container { position: absolute; - top: calc(50% + 180px); - left: 7.5em; + top: calc(50% + 20vh); + left: 12vh; display: flex; z-index: 5; justify-content: space-between; - width: 16em; + width: 15em; + scale: 1.3; } .play-button, .detail-button { diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js index eae1aef..a2f9a73 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -1,17 +1,30 @@ const getJellyfinCredentials = () => { const jellyfinCreds = localStorage.getItem("jellyfin_credentials"); + const deviceID = localStorage.getItem("_deviceId2"); + if (!jellyfinCreds) { + console.error("No Jellyfin credentials found in localStorage"); + return; + } try { const serverCredentials = JSON.parse(jellyfinCreds); - const firstServer = serverCredentials.Servers[0]; - if (!firstServer) { - console.error("Could not find credentials for the client"); + const firstServer = serverCredentials?.Servers?.[0]; + if (!firstServer || !firstServer.AccessToken || !firstServer.UserId) { + console.error("Invalid Jellyfin credentials structure"); return; } - return { token: firstServer.AccessToken, userId: firstServer.UserId }; + return { + token: firstServer.AccessToken, + userId: firstServer.UserId, + deviceID + }; } catch (e) { - console.error("Could not parse jellyfin credentials", e); + console.error("Could not parse Jellyfin credentials", e); } }; +const credentials = getJellyfinCredentials(); +if (credentials) { + console.log("Token:", credentials.token, "UserId:", credentials.userId, "DeviceID:", credentials.deviceID); +} const initLoadingScreen = () => { const currentPath = window.location.href.toLowerCase(); @@ -32,8 +45,15 @@ const initLoadingScreen = () => { `; document.body.insertAdjacentHTML("beforeend", loadingHTML); + const interval = setInterval(() => { - if (document.querySelector(".manualLoginForm")) { + if ( + document.querySelector(".manualLoginForm") || + ( + document.querySelector(".homeSectionsContainer") && + document.querySelector(".slide.active") + ) + ) { $(".bar-loading").fadeOut(700, () => $(".bar-loading").remove()); clearInterval(interval); } @@ -41,12 +61,33 @@ const initLoadingScreen = () => { } }; initLoadingScreen(); -const slidesInit = async () => { + +const observer = new MutationObserver((mutations) => { + for (let mutation of mutations) { + if (window.location.hash.includes("#/home.html")) { + let container = document.getElementById("slides-container"); + + if (!container) { + container = document.createElement("div"); + container.id = "slides-container"; + document.body.appendChild(container); + slidesInit(); + } + container.style.display = "block"; + } else { + const container = document.getElementById("slides-container"); + if (container) container.style.display = "none"; + } + } +}); +observer.observe(document.body, { childList: true, subtree: true }); + + +const slidesInit = () => { if (window.hasInitializedSlideshow) { console.log("Slideshow already initialized. Skipping re-init."); return; } - window.hasInitializedSlideshow = !0; const shuffleInterval = 8000; let isTransitioning = !1; @@ -97,15 +138,9 @@ const slidesInit = async () => { logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; logo.alt = "Logo"; logo.loading = "eager"; - const logoImgBlur = document.createElement("img"); - logoImgBlur.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; - logoImgBlur.alt = item.Name || "Title"; - logoImgBlur.loading = "eager"; - logoImgBlur.className = "featured-logo-blur"; const logoContainer = document.createElement("div"); logoContainer.className = "logo-container"; logoContainer.appendChild(logo); - logoContainer.appendChild(logoImgBlur); const featuredContent = document.createElement("div"); featuredContent.className = "featured-content"; featuredContent.textContent = title; @@ -142,9 +177,9 @@ const slidesInit = async () => { ratingTest.appendChild(imdbLogo); if (typeof rating === "number") { const formattedRating = rating.toFixed(1); - ratingTest.innerHTML += `${formattedRating} ⭐`; + ratingTest.innerHTML += `${formattedRating}`; } else { - ratingTest.innerHTML += `N/A ⭐`; + ratingTest.innerHTML += `N/A`; } ratingTest.appendChild(createSeparator()); const tomatoRatingDiv = document.createElement("div"); @@ -159,7 +194,10 @@ const slidesInit = async () => { if (typeof criticRating === "number") { valueElement = document.createTextNode(`${criticRating}% `); } else { - valueElement = document.createTextNode(`N/A `); + const naSpan = document.createElement("span"); + naSpan.textContent = "N/A "; + naSpan.style.color = "#fff9"; + valueElement = naSpan; } if (criticRating === undefined || criticRating <= 59) { criticLogo.src = @@ -184,13 +222,19 @@ const slidesInit = async () => { } else { ageRatingDiv.innerHTML = `N/A`; } - const calender = "📅"; const premiereDate = document.createElement("div"); premiereDate.className = "date"; - const year = date ? new Date(date).getFullYear() : "N/A"; - premiereDate.textContent = isNaN(year) ? "N/A" : year; + const year = date ? new Date(date).getFullYear() : NaN; + if (isNaN(year)) { + const naSpan = document.createElement("span"); + naSpan.textContent = "N/A"; + naSpan.style.color = "#fff9"; + premiereDate.innerHTML = ""; + premiereDate.appendChild(naSpan); + } else { + premiereDate.textContent = year; + } ratingTest.appendChild(tomatoRatingDiv); - ratingTest.appendChild(document.createTextNode(calender)); ratingTest.appendChild(premiereDate); ratingTest.appendChild(createSeparator()); ratingTest.appendChild(ageRatingDiv); @@ -214,44 +258,62 @@ const slidesInit = async () => { const playButton = document.createElement("button"); playButton.className = "play-button"; playButton.innerHTML = ` - play_circle + play_circle_fill Play `; playButton.onclick = async () => { - if (!window.PlaybackManager) { - console.error("PlaybackManager is not available."); - return; - } - if (!window.ApiClient) { - console.error("Jellyfin API client is not available."); - return; - } const apiClient = window.ApiClient; const userId = apiClient.getCurrentUserId(); - if (!userId) { - console.error("User not found."); + const mediabrowser = apiClient.appName(); + const appVersion = apiClient.appVersion(); + const deviceName = apiClient.deviceName(); + const deviceId = apiClient.deviceId(); + const accessToken = apiClient.accessToken(); + const baseUrl = apiClient.serverAddress(); + if (!userId || !mediabrowser || !deviceName || !deviceId || !appVersion || !accessToken || !baseUrl) { + console.error("Details not found."); return; } + let sessionId = null; try { - const item = await apiClient.getItem(userId, itemId); - if (!item) { - console.error("Media item not found."); + const response = await fetch(`${baseUrl}/Sessions?deviceId=${encodeURIComponent(deviceId)}`, { + headers: { + 'X-Emby-Authorization': `MediaBrowser Client="${mediabrowser}", Device="${deviceName}", DeviceId="${deviceId}", Version="${appVersion}", Token="${accessToken}"` + } + }); + if (!response.ok) { + throw new Error(`Failed to fetch session data: ${response.statusText}`); + } + const sessions = await response.json(); + if (sessions.length === 0) { + console.warn("No sessions found for deviceId:", deviceId); return; } - window.PlaybackManager.play({ - items: [item], - startPositionTicks: 0, - isMuted: !1, - isPaused: !1, - }) - .then(() => { - console.log("Playback started successfully."); - }) - .catch((error) => { - console.error("Failed to start playback:", error); - }); + const session = sessions[0]; + sessionId = session.Id; + console.log("Found sessionId:", sessionId); } catch (error) { - console.error("Error starting playback:", error); + console.error("Error fetching session data:", error); + return; + } + if (!sessionId) { + console.error("Session ID not found."); + return; + } + const playUrl = `${baseUrl}/Sessions/${sessionId}/Playing?playCommand=PlayNow&itemIds=${itemId}`; + try { + const playResponse = await fetch(playUrl, { + method: 'POST', + headers: { + 'X-Emby-Authorization': `MediaBrowser Client="${mediabrowser}", Device="${deviceName}", DeviceId="${deviceId}", Version="${appVersion}", Token="${accessToken}"` + } + }); + if (!playResponse.ok) { + throw new Error(`Failed to send play command: ${playResponse.statusText}`); + } + console.log("Play command sent successfully to session:", sessionId); + } catch (error) { + console.error("Error sending play command:", error); } }; const detailButton = document.createElement("button"); @@ -261,8 +323,8 @@ const slidesInit = async () => { Info `; detailButton.onclick = () => { - window.top.location.href = `/#!/details?id=${itemId}`; - }; + Emby.Page.show(`/details?id=${itemId}`); + } buttonContainer.appendChild(detailButton); buttonContainer.appendChild(playButton); slide.append( @@ -280,20 +342,10 @@ const slidesInit = async () => { const createSlideForItem = async (item, title) => { const container = document.getElementById("slides-container"); const itemId = item.Id; - const backdropUrl = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; - const logoUrl = `${window.location.origin}/Items/${itemId}/Images/Logo`; - const [backdropExists, logoExists] = await Promise.all([ - fetch(backdropUrl, { method: "HEAD" }).then((res) => res.ok), - fetch(logoUrl, { method: "HEAD" }).then((res) => res.ok), - ]); - if (backdropExists && logoExists) { - const slideElement = createSlideElement(item, title); - container.appendChild(slideElement); - if (container.children.length === 1) { - showSlide(0); - } - } else { - console.warn(`Skipping item ${itemId}: Missing backdrop or logo.`); + const slideElement = createSlideElement(item, title); + container.appendChild(slideElement); + if (container.children.length === 1) { + showSlide(0); } }; const fetchItemDetails = async (itemId) => { @@ -325,7 +377,7 @@ const slidesInit = async () => { const fetchItemsFromServer = async () => { try { const response = await fetch( - `${window.location.origin}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&SortBy=random&isPlayed=False&Limit=500`, + `${window.location.origin}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&SortBy=random&isPlayed=False&Limit=100`, { headers: { Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, @@ -394,6 +446,15 @@ const slidesInit = async () => { }); }; const initializeSlideshow = () => { + var slideInterval = null; + class SlideTimer { + constructor(fnctn, time) { + var timerObject = setInterval(fnctn, time); + this.stop = function () { if (timerObject) { clearInterval(timerObject); timerObject = null; } return this; }; + this.start = function () { if (!timerObject) { this.stop(); timerObject = setInterval(fnctn, time); } return this; }; + this.restart = function () { return this.stop().start(); }; + } + } const slides = document.querySelectorAll(".slide"); createPaginationDots(); const container = document.getElementById("slides-container"); @@ -405,6 +466,7 @@ const slidesInit = async () => { isTransitioning = !0; currentSlideIndex = (index + slides.length) % slides.length; showSlide(currentSlideIndex); + slideInterval.restart(); setTimeout(() => { isTransitioning = !1; }, 500); @@ -417,11 +479,10 @@ const slidesInit = async () => { if (slides.length > 0) { showSlide(currentSlideIndex); container.style.display = "block"; - setTimeout(() => { - setInterval(() => { + slideInterval = + new SlideTimer(function () { updateCurrentSlide(currentSlideIndex + 1); }, shuffleInterval); - }, 8000); } slides.forEach((slide) => { slide.addEventListener( @@ -533,7 +594,6 @@ const slidesInit = async () => { items = shuffleArray(items); await createSlidesForItems(items); initializeSlideshow(); - $(".bar-loading").fadeOut(700, () => $(".bar-loading").remove()); }; initializeSlides(); }; \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 81c5ef6..16f6c4c 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -18,13 +18,9 @@
- - - - diff --git a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs index a45eaa6..0552d13 100644 --- a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs +++ b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs @@ -57,26 +57,6 @@ namespace Jellyfin.Plugin.MediaBar.Services payloads.Add(payload); } - { - JObject payload = new JObject(); - payload.Add("id", "e6d32b76-d54b-4946-b73e-c5c9c50575c9"); - payload.Add("fileNamePattern", "home-html\\.[a-zA-z0-9]+\\.chunk\\.js"); - payload.Add("callbackAssembly", GetType().Assembly.FullName); - payload.Add("callbackClass", typeof(TransformationPatches).FullName); - payload.Add("callbackMethod", nameof(TransformationPatches.HomeHtmlChunk)); - - payloads.Add(payload); - } - { - JObject payload = new JObject(); - payload.Add("id", "3d171ef1-a198-48ac-9a60-f6aa98e5fd6d"); - payload.Add("fileNamePattern", "main.jellyfin.bundle.js"); - payload.Add("callbackAssembly", GetType().Assembly.FullName); - payload.Add("callbackClass", typeof(TransformationPatches).FullName); - payload.Add("callbackMethod", nameof(TransformationPatches.MainBundle)); - - payloads.Add(payload); - } { JObject payload = new JObject(); payload.Add("id", "8d374d6b-3c5b-464a-a2a2-96e92fa81345"); From eddc4d60786f60e86de4775ed1ee810052a577d6 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 10 Mar 2025 22:08:42 +0000 Subject: [PATCH 034/108] Updated README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3a62b71..af4b4f8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Jellyfin-Media-Bar - Now with Play Now Function 1. Add `https://www.iamparadox.dev/jellyfin/plugins/manifest.json` to your repositories -2. Install "File Transformation" 2.0.0 -3. Install "Media Bar" 2.0.0 +2. Install "File Transformation" at least 2.2.1.0 +3. Install latest "Media Bar" 4. Restart Jellyfin
From ec3661cbdfdf3cc8202c67b4064103e9cf6bc1e9 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 19 Mar 2025 22:29:55 +0000 Subject: [PATCH 035/108] Changed plugin to use js and css from jsdelivr rather than embedded directly. Added setting for user to specify which release should be used (or "latest", default) --- .../Configuration/PluginConfiguration.cs | 2 + .../Configuration/config.html | 8 + .../Helpers/TransformationPatches.cs | 6 +- .../Inject/index.html | 5 +- .../Inject/slideshowpure.css | 4430 ----------------- .../Inject/slideshowpure.js | 599 --- .../Jellyfin.Plugin.MediaBar.csproj | 4 - 7 files changed, 17 insertions(+), 5037 deletions(-) delete mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css delete mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs b/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs index e9fbbd7..3757b2c 100644 --- a/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs @@ -12,6 +12,8 @@ namespace Jellyfin.Plugin.MediaBar.Configuration { public MediaBarState Enabled { get; set; } = MediaBarState.Enabled; + public string VersionString { get; set; } = "latest"; + public bool UseAvatarsFile { get; set; } = true; public string AvatarsPlaylist { get; set; } = string.Empty; diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html index 16baa58..ce6fefd 100644 --- a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html @@ -28,6 +28,12 @@ Whether the media bar should appear or not. +
+ + + +
Which version of the jsdelivr'd plugin should be used.
+
From 1d5bf8e2b007d6ad2e880690403ad8eefc8966fa Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Sun, 20 Jul 2025 11:18:36 +0100 Subject: [PATCH 041/108] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index af4b4f8..0d222e6 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,11 @@ 3. Install latest "Media Bar" 4. Restart Jellyfin +## Reporting Issues +If you issues relating to the visuals or behaviour of buttons added by the Media Bar please report them on MakD's repo (the one this is forked from). This plugin pulls the content from their repo directly and is only in control of adding it without the need for modifying your JF install files. + +Any issues with plugin's settings (including using a playlist as your avatar's playlist) should be made here. +
Original README.md from MakD From 5edeabc51159bcb241c9bab27ff10f337b7506a5 Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Sun, 20 Jul 2025 11:19:01 +0100 Subject: [PATCH 042/108] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d222e6..47289ae 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ 4. Restart Jellyfin ## Reporting Issues -If you issues relating to the visuals or behaviour of buttons added by the Media Bar please report them on MakD's repo (the one this is forked from). This plugin pulls the content from their repo directly and is only in control of adding it without the need for modifying your JF install files. +If you face issues relating to the visuals or behaviour of buttons added by the Media Bar please report them on MakD's repo (the one this is forked from). This plugin pulls the content from their repo directly and is only in control of adding it without the need for modifying your JF install files. Any issues with plugin's settings (including using a playlist as your avatar's playlist) should be made here. From 29066ce5748e8a71623364e7681ec6a68ee19c7f Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Thu, 6 Feb 2025 14:19:26 +0000 Subject: [PATCH 043/108] Initial attempt for banner in plugin --- src/.gitignore | 9 + .../Controllers/MediaBarController.cs | 32 + .../Inject/home-html.chunk.js | 1 + .../Inject/index.html | 4 + .../Inject/media-bar.js | 55 ++ .../Inject/slideshowpure.css | 685 ++++++++++++++++++ .../Inject/slideshowpure.js | 426 +++++++++++ .../Jellyfin.Plugin.MediaBar.csproj | 26 + .../MediaBarPlugin.cs | 56 ++ 9 files changed, 1294 insertions(+) create mode 100644 src/.gitignore create mode 100644 src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs create mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/home-html.chunk.js create mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/index.html create mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js create mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css create mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js create mode 100644 src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj create mode 100644 src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..b3e292d --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,9 @@ +bin/ +obj/ +.vs/ +.idea/ +artifacts +Release +*.zip +node_modules +dist/ \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs new file mode 100644 index 0000000..9193c35 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Plugin.MediaBar.Controllers; + +[Route("[controller]")] +public class MediaBarController : ControllerBase +{ + [HttpGet("{file}")] + public ActionResult GetFile([FromRoute] string file) + { + Stream fileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Jellyfin.Plugin.MediaBar.Inject." + file); + string fileContents = new StreamReader(fileStream).ReadToEnd(); + + string contentType = "text/plain"; + + if (Path.GetExtension(file) == ".js") + { + contentType = "text/javascript"; + } + else if (Path.GetExtension(file) == ".css") + { + contentType = "text/css"; + } + else if (Path.GetExtension(file) == ".html") + { + contentType = "text/html"; + } + + return Content(fileContents, contentType); + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/home-html.chunk.js b/src/Jellyfin.Plugin.MediaBar/Inject/home-html.chunk.js new file mode 100644 index 0000000..522fa7f --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Inject/home-html.chunk.js @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/index.html b/src/Jellyfin.Plugin.MediaBar/Inject/index.html new file mode 100644 index 0000000..41c07a8 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Inject/index.html @@ -0,0 +1,4 @@ + + + + diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js new file mode 100644 index 0000000..70724e6 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js @@ -0,0 +1,55 @@ + +function saveCredentialsToSessionStorage(credentials) { + try { + sessionStorage.setItem( + "json-credentials", + JSON.stringify(credentials) + ); + console.log("Credentials saved to sessionStorage."); + } catch (error) { + console.error("Error saving credentials:", error); + } +} +function saveApiKey(apiKey) { + try { + sessionStorage.setItem("api-key", apiKey); + console.log("API key saved to sessionStorage."); + } catch (error) { + console.error("Error saving API key:", error); + } +} +(function () { + var originalConsoleLog = console.log; + console.log = function (message) { + originalConsoleLog.apply(console, arguments); + if ( + typeof message === "string" && + message.startsWith("Stored JSON credentials:") + ) { + try { + var jsonString = message.substring( + "Stored JSON credentials: ".length + ); + var credentials = JSON.parse(jsonString); + saveCredentialsToSessionStorage(credentials); + } catch (error) { + console.error("Error parsing credentials:", error); + } + } + if ( + typeof message === "string" && + message.startsWith("opening web socket with url:") + ) { + try { + var url = message.split("url:")[1].trim(); + var urlParams = new URL(url).searchParams; + var apiKey = urlParams.get("api_key"); + if (apiKey) { + saveApiKey(apiKey); + } + } catch (error) { + console.error("Error extracting API key:", error); + } + } + }; +})(); \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css new file mode 100644 index 0000000..2270e6b --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css @@ -0,0 +1,685 @@ +@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap"); + +@media only screen and (min-width: 767px) { + div[data-role="page"].page { + overflow-y: scroll; /* Keeps vertical scrolling active */ + scrollbar-width: none; /* For Firefox */ + -ms-overflow-style: none; /* For Internet Explorer & Edge */ + } + + div[data-role="page"].page::-webkit-scrollbar { + display: none; /* Hides scrollbar in Webkit browsers */ + } + + .backdrop, + .slide { + position: absolute; + } + + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + + .plot, + body { + overflow: hidden; + } + + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: white; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } + + .arrow::before { + content: ""; /* Creates an empty element */ + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; /* Size of the circle */ + height: 40px; /* Size of the circle */ + background-color: rgba(12, 110, 162, 0.7); /* Translucent background */ + border-radius: 50%; /* Makes it a circle */ + z-index: -1; /* Places the circle behind the arrow */ + } + + .left-arrow { + left: 10px; /* Position to the left */ + } + + .right-arrow { + right: 10px; /* Position to the right */ + } + + .dots-container { + position: absolute; + bottom: 5%; + left: 0; + right: 0; + z-index: 2; + display: flex; + justify-content: center; /* Centers the dots horizontally */ + align-items: center; + width: 100vw; /* Full screen width */ + height: 22px; + background-color: #ffffff00; + gap: 2vw; /* Evenly space the rectangles with a gap of 2vw */ + } + + .dot { + width: 15vw; /* Each rectangle takes 18% of the viewport width */ + height: 4px; /* Thin rectangle height */ + background-color: #cecece; + border-radius: 2px; /* Slightly rounded corners */ + cursor: pointer; + transition: background-color 0.3s; + } + + .dot.active { + background-color: rgba(12, 110, 162, 0.7) !important; + } + + body { + margin: 0; + padding: 0; + } + + .fade-in { + opacity: 1 !important; + } + + .fade-out { + opacity: 0 !important; + } + + #slides-container { + position: relative; + width: 100vw; + height: 750px; + overflow: hidden; + margin: 0 auto; + border-radius: 7px; + top: -5em; + } + + .slide { + transition: opacity 0.5s ease-in-out; /* Smooth fade transition */ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + + .slide.active { + opacity: 1; + display: block; + } + + .slide:focus { + outline: #fff solid 2px; + } + + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } + + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); /* Adjust the alpha for dimness */ + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } + + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + + .logo { + position: relative; + max-height: 100%; + max-width: 100%; + width: auto; + z-index: 3; + top: 5em; + left: 8em; + } + + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: transparent; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 50%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + + .plot { + display: -webkit-box; + line-clamp: 4; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + left: 7.9em; + position: absolute; + top: 190px; + } + + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, rgba(0, 0, 0, 0) 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 8%, + rgba(0, 0, 0, 0.5) 10%, + rgba(0, 0, 0, 1) 12% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 8%, + rgba(0, 0, 0, 0.5) 10%, + rgba(0, 0, 0, 1) 12% + ); + } + .genre { + position: absolute; + top: calc(50% + 75px); + left: 8.4em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + top: 1.5em; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .play-button { + position: absolute; + display: flex; + align-items: center; /* Center vertically */ + justify-content: center; /* Keep content aligned horizontally */ + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; /* Round the edges */ + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + top: calc(50% + 190px); + left: 6.8em; + } + + .play-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; /* Adjust the size of the play icon */ + width: 30px; /* Ensure the icon area is circular */ + height: 30px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); /* Same as the button's background */ + border: 3px solid rgba(12, 110, 162, 1); + border-radius: 50%; /* Make the icon a perfect circle */ + } + + .play-text { + font-family: "Titillium Web", sans-serif; + margin-left: 10px; /* Space between icon and text */ + font-size: 14px; + color: #fff; + } +} + +@media only screen and (max-width: 767px) { + .backdrop, + .slide { + position: absolute; + } + + .arrow { + position: absolute; + /*top: 50%; + transform: translateY(-50%);*/ + bottom: 25px; + font-size: 24px; + cursor: pointer; + color: rgba(255, 255, 255, 0.6); + z-index: 4; + -webkit-tap-highlight-color: transparent; /* Disable blue tap highlight */ + } + + .left-arrow { + left: 10px; /* Position to the left */ + } + + .right-arrow { + right: 10px; /* Position to the right */ + } + + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + + .plot, + body { + overflow: hidden; + } + + body { + margin: 0; + padding: 0; + } + + .fade-in { + opacity: 1 !important; + } + + .fade-out { + opacity: 0 !important; + } + + .dots-container { + position: absolute; + bottom: 11%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff00; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } + + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + + .dot.active { + width: 32px; + height: 12px; + background-color: rgba(12, 110, 162, 0.7); + border-radius: 10px; /* Rounded rectangle shape */ + } + + #slides-container { + position: relative; + width: 92vw; + height: 650px; + overflow: hidden; + margin: 0 auto; + border-radius: 10px; + } + + .slide { + transition: opacity 0.5s ease-in-out; /* Smooth fade transition */ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + + .slide.active { + opacity: 1; + display: block; + } + + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + bottom: 28%; + left: 50%; + transform: translateX(-50%); + max-height: 60%; + max-width: 80%; + width: auto; + z-index: 3; + } + + .backdrop { + position: absolute; + top: 45px; + left: 0; + width: 100%; + height: calc(100% - 50px); + object-fit: cover; + object-position: center 20%; + z-index: 1; + border-radius: 13px; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } + + .gradient-overlay { + position: absolute; + top: 45px; + left: 0; + width: 100%; + height: calc(100% - 50px); + background: rgba(0, 0, 0, 0.55); /* Semi-transparent overlay color */ + z-index: 2; + border-radius: 13px; + pointer-events: none; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } + + .genre { + position: absolute; + bottom: 16%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Titillium Web", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 20%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } + + .play-button { + position: absolute; + display: flex; + align-items: center; /* Center vertically */ + justify-content: flex-start; /* Keep content aligned horizontally */ + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; /* Round the edges */ + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + bottom: 20px; + /*right: 0%;*/ + left: 50%; + transform: translateX(-50%) scale(1.2); + outline: none; /* Remove the default blue focus outline */ + -webkit-tap-highlight-color: transparent; /* Disable blue tap highlight */ + } + + .play-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; /* Adjust the size of the play icon */ + width: 20px; /* Ensure the icon area is circular */ + height: 20px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); /* Same as the button's background */ + border: 1.5px solid rgba(12, 110, 162, 1); + border-radius: 50%; /* Make the icon a perfect circle */ + outline: none; /* Remove the default blue focus outline */ + -webkit-tap-highlight-color: transparent; /* Disable blue tap highlight */ + } + + .play-text { + display: block; + font-family: "Titillium Web", sans-serif; + margin-left: 5px; /* Space between icon and text */ + font-size: 14px; + color: #fff; + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js new file mode 100644 index 0000000..33853c3 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -0,0 +1,426 @@ +const slidesInit = () => { + const shuffleInterval = 8000; + let isTransitioning = false; + const listFileName = `${window.location.origin}/web/avatars/list.txt`; + const jsonCredentials = sessionStorage.getItem("json-credentials"); + const apiKey = sessionStorage.getItem("api-key"); + let userId = null; + let token = null; + const deviceId = localStorage.getItem("_deviceId2"); + console.log(deviceId); + if (jsonCredentials) { + const credentials = JSON.parse(jsonCredentials); + userId = credentials.Servers[0].UserId; + token = credentials.Servers[0].AccessToken; + } + const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); + const truncateText = (element, maxLength) => { + let text = element.innerText; + if (text.length > maxLength) + element.innerText = text.substring(0, maxLength) + "..."; + }; + const createSlideElement = (item, title) => { + const itemId = item.Id; + const plot = item.Overview || "No overview available"; + const rating = item.CommunityRating; + const tomato = item.CriticRating; + const runtime = item.RunTimeTicks; + const genre = item.Genres; + const youtube = item.RemoteTrailers; + const age = item.OfficialRating; + const date = item.PremiereDate; + const season = item.ChildCount; + function createSeparator() { + const separatorHtml = + ''; + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = separatorHtml; + return tempDiv.firstChild; + } + const slide = document.createElement("a"); + slide.className = "slide"; + slide.target = "_top"; + slide.rel = "noreferrer"; + slide.tabIndex = 0; + slide.style.display = "none"; + const backdrop = document.createElement("img"); + backdrop.className = "backdrop"; + backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; + backdrop.alt = "Backdrop"; + const backdropOverlay = document.createElement("div"); + backdropOverlay.className = "backdrop-overlay"; + const backdropContainer = document.createElement("div"); + backdropContainer.className = "backdrop-container"; + backdropContainer.appendChild(backdrop); + backdropContainer.appendChild(backdropOverlay); + const logo = document.createElement("img"); + logo.className = "logo"; + logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; + logo.alt = "Logo"; + const logoContainer = document.createElement("div"); + logoContainer.className = "logo-container"; + logoContainer.appendChild(logo); + const featuredContent = document.createElement("div"); + featuredContent.className = "featured-content"; + featuredContent.textContent = title; + const plotElement = document.createElement("div"); + plotElement.className = "plot"; + plotElement.textContent = plot; + truncateText(plotElement, 360); + const plotContainer = document.createElement("div"); + plotContainer.className = "plot-container"; + plotContainer.appendChild(plotElement); + const gradientOverlay = document.createElement("div"); + gradientOverlay.className = "gradient-overlay"; + const runTimeElement = document.createElement("div"); + runTimeElement.className = "runTime"; + if (season === undefined) { + const milliseconds = runtime / 10000; + const currentTime = new Date(); + const endTime = new Date(currentTime.getTime() + milliseconds); + const options = { hour: "2-digit", minute: "2-digit", hour12: !0 }; + const formattedEndTime = endTime.toLocaleTimeString([], options); + runTimeElement.textContent = `Ends at ${formattedEndTime}`; + } else { + runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; + } + const ratingTest = document.createElement("div"); + ratingTest.className = "rating-value"; + const imdbLogo = document.createElement("img"); + imdbLogo.className = "imdb-logo"; + imdbLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg"; + imdbLogo.alt = "IMDb Logo"; + imdbLogo.style.width = "30px"; + imdbLogo.style.height = "30px"; + ratingTest.appendChild(imdbLogo); + if (typeof rating === "number") { + const formattedRating = rating.toFixed(1); + ratingTest.innerHTML += `${formattedRating} ⭐`; + } else { + console.error("Rating is undefined or not a number:", rating); + ratingTest.innerHTML += `N/A ⭐`; + } + ratingTest.appendChild(createSeparator()); + const tomatoRatingDiv = document.createElement("div"); + tomatoRatingDiv.className = "tomato-rating"; + const criticRating = item.CriticRating; + const tomatoLogo = document.createElement("img"); + tomatoLogo.className = "tomato-logo"; + const criticLogo = document.createElement("img"); + criticLogo.className = "critic-logo"; + tomatoLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; + let valueElement; + if (typeof criticRating === "number") { + valueElement = document.createTextNode(`${criticRating}% `); + } else { + valueElement = document.createTextNode(`N/A `); + } + if (criticRating === undefined || criticRating <= 59) { + criticLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; + criticLogo.alt = "Rotten Tomato"; + } else { + criticLogo.src = "https://i.imgur.com/iMfwDk7.png"; + criticLogo.alt = "Fresh Tomato"; + } + tomatoLogo.style.width = "15px"; + tomatoLogo.style.height = "17px"; + criticLogo.style.width = "15px"; + criticLogo.style.height = "15px"; + tomatoRatingDiv.appendChild(tomatoLogo); + tomatoRatingDiv.appendChild(valueElement); + tomatoRatingDiv.appendChild(criticLogo); + tomatoRatingDiv.appendChild(createSeparator()); + const ageRatingDiv = document.createElement("div"); + ageRatingDiv.className = "age-rating"; + if (item.OfficialRating) { + ageRatingDiv.innerHTML = `${item.OfficialRating}`; + } else { + ageRatingDiv.innerHTML = `N/A`; + } + const calender = "📅"; + const premiereDate = document.createElement("div"); + premiereDate.className = "date"; + const year = date ? new Date(date).getFullYear() : "N/A"; + premiereDate.textContent = isNaN(year) ? "N/A" : year; + ratingTest.appendChild(tomatoRatingDiv); + ratingTest.appendChild(document.createTextNode(calender)); + ratingTest.appendChild(premiereDate); + ratingTest.appendChild(createSeparator()); + ratingTest.appendChild(ageRatingDiv); + ratingTest.appendChild(createSeparator()); + ratingTest.appendChild(runTimeElement); + const genresArray = item.Genres; + function parseGenres(genresArray) { + if (genresArray && genresArray.length > 0) { + return genresArray.slice(0, 3).join(" 🔹 "); + } else { + return "No Genre Available"; + } + } + const genreElement = document.createElement("div"); + genreElement.className = "genre"; + genreElement.innerHTML = parseGenres(genresArray); + const infoContainer = document.createElement("div"); + infoContainer.className = "info-container"; + infoContainer.appendChild(ratingTest); + const playButton = document.createElement("button"); + playButton.className = "play-button"; + playButton.innerHTML = ` + + Details + `; + playButton.onclick = () => { + window.top.location.href = `/#!/details?id=${itemId}`; + //window.location.href = `/Videos/${itemId}/stream.mp4?Static=true&mediaSourceId=${itemId}&deviceId=${deviceId}&api_key=${apiKey}`; + //window.top.location.href = streamUrl; + }; + slide.append( + logoContainer, + backdropContainer, + gradientOverlay, + featuredContent, + plotContainer, + infoContainer, + genreElement, + playButton + ); + return slide; + }; + const createSlideForItem = async (item, title) => { + const container = document.getElementById("slides-container"); + const itemId = item.Id; + const backdropUrl = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; + const logoUrl = `${window.location.origin}/Items/${itemId}/Images/Logo`; + const [backdropExists, logoExists] = await Promise.all([ + fetch(backdropUrl, { method: "HEAD" }).then((res) => res.ok), + fetch(logoUrl, { method: "HEAD" }).then((res) => res.ok), + ]); + if (backdropExists && logoExists) { + const slideElement = createSlideElement(item, title); + container.appendChild(slideElement); + console.log(`Added slide for item ${itemId}`); + if (container.children.length === 1) { + showSlide(0); + } + } else { + console.warn(`Skipping item ${itemId}: Missing backdrop or logo.`); + } + }; + const fetchItemDetails = async (itemId) => { + const response = await fetch( + `${window.location.origin}/Users/${userId}/Items/${itemId}`, + { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + } + ); + const item = await response.json(); + console.log("Item Title:", item.Name); + return item; + }; + const fetchItemIdsFromList = async () => { + try { + const response = await fetch(listFileName); + if (!response.ok) { + throw new Error("Failed to fetch list.txt"); + } + const text = await response.text(); + return text + .split("\n") + .map((id) => id.trim()) + .filter((id) => id); + } catch (error) { + console.error("Error fetching list.txt:", error); + return []; + } + }; + const fetchItemsFromServer = async () => { + try { + const response = await fetch( + `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, + { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + } + ); + const data = await response.json(); + const items = data.Items; + const movies = items.filter((item) => item.Type === "Movie"); + const tvShows = items.filter((item) => item.Type === "Series"); + const shuffledMovies = shuffleArray(movies); + const shuffledTvShows = shuffleArray(tvShows); + const selectedMovies = shuffledMovies.slice(0, 15); + const selectedTvShows = shuffledTvShows.slice(0, 15); + const allItems = []; + const maxLength = Math.max(selectedMovies.length, selectedTvShows.length); + for (let i = 0; i < maxLength; i++) { + if (i < selectedMovies.length) allItems.push(selectedMovies[i]); + if (i < selectedTvShows.length) allItems.push(selectedTvShows[i]); + } + return allItems; + } catch (error) { + console.error("Error fetching items:", error); + return []; + } + }; + const createSlidesForItems = async (items) => { + await Promise.all( + items.map((item) => + createSlideForItem(item, item.Type === "Movie" ? "Movie" : "TV Show") + ) + ); + }; + const createPaginationDots = () => { + const dotsContainer = document.createElement("div"); + dotsContainer.className = "dots-container"; + for (let i = 0; i < 5; i++) { + const dot = document.createElement("span"); + dot.className = "dot"; + dotsContainer.appendChild(dot); + } + const container = document.getElementById("slides-container"); + container.appendChild(dotsContainer); + }; + const showSlide = (index) => { + const slides = document.querySelectorAll(".slide"); + const dots = document.querySelectorAll(".dot"); + slides.forEach((slide, i) => { + if (i === index) { + slide.style.display = "block"; + slide.offsetHeight; + slide.style.opacity = "1"; + slide.classList.add("active"); + } else { + slide.style.opacity = "0"; + slide.classList.remove("active"); + setTimeout(() => { + slide.style.display = "none"; + }, 500); + } + }); + dots.forEach((dot, i) => { + dot.classList.toggle("active", i === index % 5); + }); + }; + const initializeSlideshow = () => { + const slides = document.querySelectorAll(".slide"); + createPaginationDots(); + const container = document.getElementById("slides-container"); + let currentSlideIndex = 0; + let focusedSlide = null; + let containerFocused = !1; + const updateCurrentSlide = (index) => { + if (isTransitioning) return; // Skip if already transitioning + isTransitioning = true; // Set transitioning to true at the start + currentSlideIndex = (index + slides.length) % slides.length; + showSlide(currentSlideIndex); + // Re-enable arrows after a short delay to prevent rapid clicking + setTimeout(() => { + isTransitioning = false; // Reset after transition completes + }, 500); // Adjust the delay as needed (500ms is a reasonable starting point) + }; + const openActiveSlide = () => { + if (focusedSlide) { + window.location.href = focusedSlide.href; + } + }; + if (slides.length > 0) { + showSlide(currentSlideIndex); + container.style.display = "block"; + setTimeout(() => { + setInterval(() => { + updateCurrentSlide(currentSlideIndex + 1); + }, shuffleInterval); + }, 8000); + } + slides.forEach((slide) => { + slide.addEventListener( + "focus", + () => { + focusedSlide = slide; + container.classList.remove("disable-interaction"); + }, + !0 + ); + slide.addEventListener( + "blur", + () => { + if (focusedSlide === slide) { + focusedSlide = null; + } + }, + !0 + ); + }); + document.addEventListener("keydown", (event) => { + if (containerFocused) { + switch (event.keyCode) { + case 37: + updateCurrentSlide(currentSlideIndex - 1); + break; + case 39: + updateCurrentSlide(currentSlideIndex + 1); + break; + case 13: + case 32: + openActiveSlide(); + break; + } + } + }); + + const leftArrow = document.createElement("div"); + const rightArrow = document.createElement("div"); + leftArrow.classList.add("arrow", "left-arrow"); + rightArrow.classList.add("arrow", "right-arrow"); + leftArrow.innerHTML = ''; + rightArrow.innerHTML = ''; + container.appendChild(leftArrow); + container.appendChild(rightArrow); + leftArrow.addEventListener("click", () => + updateCurrentSlide(currentSlideIndex - 1) + ); + rightArrow.addEventListener("click", () => + updateCurrentSlide(currentSlideIndex + 1) + ); + container.addEventListener("focus", () => { + containerFocused = !0; + }); + container.addEventListener("blur", () => { + containerFocused = !1; + }); + }; + const initializeSlides = async () => { + const itemIds = await fetchItemIdsFromList(); + let items; + if (itemIds.length > 0) { + const itemPromises = itemIds.map((id) => fetchItemDetails(id)); + items = await Promise.all(itemPromises); + } else { + const allItems = await fetchItemsFromServer(); + const itemPromises = allItems.map((item) => fetchItemDetails(item.Id)); + items = await Promise.all(itemPromises); + } + items = shuffleArray(items); + await createSlidesForItems(items); + initializeSlideshow(); + }; + initializeSlides(); +}; +document.addEventListener("DOMContentLoaded", () => { + document.body.addEventListener("click", (event) => { + const homeButton = event.target.matches( + ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" + ); + if (homeButton) { + event.preventDefault(); + window.location.href = "/web/index.html#/home.html"; + setTimeout(handleHomeNavigation, 300); // Delay for UI update + } + }); +}); \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj new file mode 100644 index 0000000..f133d34 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + diff --git a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs new file mode 100644 index 0000000..5536311 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs @@ -0,0 +1,56 @@ +using System.Reflection; +using System.Text.RegularExpressions; +using Jellyfin.Plugin.FileTransformation.Controller; +using Jellyfin.Plugin.FileTransformation.Infrastructure; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.DependencyInjection; + +namespace Jellyfin.Plugin.MediaBar; + +public class MediaBarPlugin : BasePlugin +{ + public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448"); + + public override string Name => "Media Bar"; + + public MediaBarPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IServiceProvider serviceProvider) : base(applicationPaths, xmlSerializer) + { + IWebFileTransformationWriteService webFileTransformationWriteService = serviceProvider.GetRequiredService(); + + webFileTransformationWriteService.AddTransformation("index.html", IndexHtml_Transformation); + webFileTransformationWriteService.AddTransformation("home-html\\.[a-zA-z0-9]+\\.chunk\\.js", HomeHtmlChunk_Transformation); + } + + private void IndexHtml_Transformation(string path, Stream contents) + { + Stream? stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{GetType().Namespace}.Inject.index.html"); + using TextReader reader = new StreamReader(stream); + + using var textReader = new StreamReader(contents, null, true, -1, true); + var text = textReader.ReadToEnd(); + + var regex = Regex.Replace(text, "()", $"{reader.ReadToEnd()}$1"); + contents.Seek(0, SeekOrigin.Begin); + + using var textWriter = new StreamWriter(contents, null, -1, true); + textWriter.Write(regex); + } + + private void HomeHtmlChunk_Transformation(string path, Stream contents) + { + Stream? stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{GetType().Namespace}.Inject.home-html.chunk.js"); + using TextReader reader = new StreamReader(stream); + + using var textReader = new StreamReader(contents, null, true, -1, true); + var text = textReader.ReadToEnd(); + + var regex = Regex.Replace(text, "(id=\"homeTab\" data-index=\"0\">)", $"$1{reader.ReadToEnd()}"); + contents.Seek(0, SeekOrigin.Begin); + + using var textWriter = new StreamWriter(contents, null, -1, true); + textWriter.Write(regex); + } +} \ No newline at end of file From fba5914aa5850d4bde41e6681c41f2515399de4a Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Fri, 7 Feb 2025 13:23:10 +0000 Subject: [PATCH 044/108] Integrated latest changed from fork into installable plugin --- .../Inject/media-bar.js | 3 +- .../Inject/slideshowpure.css | 189 +++++++++++++----- .../Inject/slideshowpure.js | 68 +++++-- 3 files changed, 194 insertions(+), 66 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js index 70724e6..ec47dd9 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js @@ -1,5 +1,4 @@ - -function saveCredentialsToSessionStorage(credentials) { +function saveCredentialsToSessionStorage(credentials) { try { sessionStorage.setItem( "json-credentials", diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css index 2270e6b..e00974a 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css @@ -2,13 +2,13 @@ @media only screen and (min-width: 767px) { div[data-role="page"].page { - overflow-y: scroll; /* Keeps vertical scrolling active */ - scrollbar-width: none; /* For Firefox */ - -ms-overflow-style: none; /* For Internet Explorer & Edge */ + overflow-y: scroll; + scrollbar-width: none; + -ms-overflow-style: none; } div[data-role="page"].page::-webkit-scrollbar { - display: none; /* Hides scrollbar in Webkit browsers */ + display: none; } .backdrop, @@ -41,24 +41,24 @@ } .arrow::before { - content: ""; /* Creates an empty element */ + content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); - width: 40px; /* Size of the circle */ - height: 40px; /* Size of the circle */ - background-color: rgba(12, 110, 162, 0.7); /* Translucent background */ - border-radius: 50%; /* Makes it a circle */ - z-index: -1; /* Places the circle behind the arrow */ + width: 40px; + height: 40px; + background-color: rgba(12, 110, 162, 0.7); + border-radius: 50%; + z-index: -1; } .left-arrow { - left: 10px; /* Position to the left */ + left: 10px; } .right-arrow { - right: 10px; /* Position to the right */ + right: 10px; } .dots-container { @@ -68,19 +68,19 @@ right: 0; z-index: 2; display: flex; - justify-content: center; /* Centers the dots horizontally */ + justify-content: center; align-items: center; - width: 100vw; /* Full screen width */ + width: 100vw; height: 22px; background-color: #ffffff00; - gap: 2vw; /* Evenly space the rectangles with a gap of 2vw */ + gap: 2vw; } .dot { - width: 15vw; /* Each rectangle takes 18% of the viewport width */ - height: 4px; /* Thin rectangle height */ + width: 15vw; + height: 4px; background-color: #cecece; - border-radius: 2px; /* Slightly rounded corners */ + border-radius: 2px; cursor: pointer; transition: background-color 0.3s; } @@ -113,7 +113,7 @@ } .slide { - transition: opacity 0.5s ease-in-out; /* Smooth fade transition */ + transition: opacity 0.5s ease-in-out; position: absolute; top: 0; left: 0; @@ -167,7 +167,7 @@ left: 0; width: 100%; height: 100%; - background-color: rgba(0, 0, 0, 0.6); /* Adjust the alpha for dimness */ + background-color: rgba(0, 0, 0, 0.6); border-radius: 5px; z-index: 2; mask-image: linear-gradient( @@ -197,7 +197,9 @@ position: relative; max-height: 100%; max-width: 100%; + height: auto; width: auto; + object-fit: contain; z-index: 3; top: 5em; left: 8em; @@ -343,13 +345,13 @@ .play-button { position: absolute; display: flex; - align-items: center; /* Center vertically */ - justify-content: center; /* Keep content aligned horizontally */ + align-items: center; + justify-content: center; padding: 10px 20px; color: #fff; background-color: rgba(255, 255, 255, 0); border: none; - border-radius: 50px; /* Round the edges */ + border-radius: 50px; cursor: pointer; font-size: 14px; font-weight: 300; @@ -363,27 +365,70 @@ display: flex; align-items: center; justify-content: center; - font-size: 20px; /* Adjust the size of the play icon */ - width: 30px; /* Ensure the icon area is circular */ + font-size: 20px; + width: 30px; height: 30px; background-color: rgba( 12, 110, 162, 0.7 - ); /* Same as the button's background */ + ); border: 3px solid rgba(12, 110, 162, 1); - border-radius: 50%; /* Make the icon a perfect circle */ + border-radius: 50%; } .play-text { font-family: "Titillium Web", sans-serif; - margin-left: 10px; /* Space between icon and text */ + margin-left: 10px; + font-size: 14px; + color: #fff; + } + .detail-button { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + top: calc(50% + 190px); + left: 15.8em; + } + + .detail-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 30px; + height: 30px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); + border: 3px solid rgba(12, 110, 162, 1); + border-radius: 50%; + } + + .detail-text { + font-family: "Titillium Web", sans-serif; + margin-left: 10px; font-size: 14px; color: #fff; } } + @media only screen and (max-width: 767px) { .backdrop, .slide { @@ -392,22 +437,20 @@ .arrow { position: absolute; - /*top: 50%; - transform: translateY(-50%);*/ bottom: 25px; font-size: 24px; cursor: pointer; color: rgba(255, 255, 255, 0.6); z-index: 4; - -webkit-tap-highlight-color: transparent; /* Disable blue tap highlight */ + -webkit-tap-highlight-color: transparent; } .left-arrow { - left: 10px; /* Position to the left */ + left: 10px; } .right-arrow { - right: 10px; /* Position to the right */ + right: 10px; } .featured-content, @@ -465,7 +508,7 @@ width: 32px; height: 12px; background-color: rgba(12, 110, 162, 0.7); - border-radius: 10px; /* Rounded rectangle shape */ + border-radius: 10px; } #slides-container { @@ -478,7 +521,7 @@ } .slide { - transition: opacity 0.5s ease-in-out; /* Smooth fade transition */ + transition: opacity 0.5s ease-in-out; position: absolute; top: 0; left: 0; @@ -536,7 +579,7 @@ left: 0; width: 100%; height: calc(100% - 50px); - background: rgba(0, 0, 0, 0.55); /* Semi-transparent overlay color */ + background: rgba(0, 0, 0, 0.55); z-index: 2; border-radius: 13px; pointer-events: none; @@ -636,50 +679,96 @@ .play-button { position: absolute; display: flex; - align-items: center; /* Center vertically */ - justify-content: flex-start; /* Keep content aligned horizontally */ + align-items: center; + justify-content: flex-start; padding: 10px 20px; color: #fff; background-color: rgba(255, 255, 255, 0); border: none; - border-radius: 50px; /* Round the edges */ + border-radius: 50px; cursor: pointer; font-size: 14px; font-weight: 300; transition: background-color 0.3s; z-index: 4; bottom: 20px; - /*right: 0%;*/ left: 50%; - transform: translateX(-50%) scale(1.2); - outline: none; /* Remove the default blue focus outline */ - -webkit-tap-highlight-color: transparent; /* Disable blue tap highlight */ + transform: translateX(-110%) scale(1.2); + outline: none; + -webkit-tap-highlight-color: transparent; } .play-icon { display: flex; align-items: center; justify-content: center; - font-size: 20px; /* Adjust the size of the play icon */ - width: 20px; /* Ensure the icon area is circular */ + font-size: 20px; + width: 20px; height: 20px; background-color: rgba( 12, 110, 162, 0.7 - ); /* Same as the button's background */ + ); border: 1.5px solid rgba(12, 110, 162, 1); - border-radius: 50%; /* Make the icon a perfect circle */ - outline: none; /* Remove the default blue focus outline */ - -webkit-tap-highlight-color: transparent; /* Disable blue tap highlight */ + border-radius: 50%; + outline: none; + -webkit-tap-highlight-color: transparent; } .play-text { display: block; font-family: "Titillium Web", sans-serif; - margin-left: 5px; /* Space between icon and text */ + margin-left: 5px; font-size: 14px; color: #fff; } + .detail-button { + position: absolute; + display: flex; + align-items: center; + justify-content: flex-start; + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + bottom: 20px; + left: 50%; + transform: translateX(10%) scale(1.2); + outline: none; + -webkit-tap-highlight-color: transparent; + } + + .detail-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 20px; + height: 20px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); + border: 1.5px solid rgba(12, 110, 162, 1); + border-radius: 50%; + outline: none; + -webkit-tap-highlight-color: transparent; + } + + .detail-text { + display: block; + font-family: "Titillium Web", sans-serif; + margin-left: 5px; + color: #fff; + } } \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js index 33853c3..95609be 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -3,7 +3,6 @@ let isTransitioning = false; const listFileName = `${window.location.origin}/web/avatars/list.txt`; const jsonCredentials = sessionStorage.getItem("json-credentials"); - const apiKey = sessionStorage.getItem("api-key"); let userId = null; let token = null; const deviceId = localStorage.getItem("_deviceId2"); @@ -169,13 +168,55 @@ const playButton = document.createElement("button"); playButton.className = "play-button"; playButton.innerHTML = ` - - Details + + Play Now `; - playButton.onclick = () => { + playButton.onclick = async () => { + if (!window.ApiClient) { + console.error("Jellyfin API client is not available."); + return; + } + const apiClient = window.ApiClient; + const userId = apiClient.getCurrentUserId(); + if (!userId) { + console.error("User not found."); + return; + } + try { + const sessions = await apiClient.getJSON( + apiClient.serverAddress() + "/Sessions" + ); + if (!sessions.length) { + console.error("No active Jellyfin sessions found."); + return; + } + const sessionId = sessions[0].Id; + const playbackUrl = `${apiClient.serverAddress()}/Sessions/${sessionId}/Playing?StartPositionTicks=0&PlayCommand=PlayNow&StartIndex=0&ItemIds=${itemId}&api_key=${token}`; + const response = await fetch(playbackUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDevice", DeviceId="YourDeviceId", Version="YourVersion", Token="${token}"`, + }, + }); + if (response.status === 204) { + console.log("Playback started successfully."); + } else { + const errorData = await response.json(); + console.error("Failed to start playback:", errorData); + } + } catch (error) { + console.error("Error fetching sessions:", error); + } + }; + const detailButton = document.createElement("button"); + detailButton.className = "detail-button"; + detailButton.innerHTML = ` + + More Details + `; + detailButton.onclick = () => { window.top.location.href = `/#!/details?id=${itemId}`; - //window.location.href = `/Videos/${itemId}/stream.mp4?Static=true&mediaSourceId=${itemId}&deviceId=${deviceId}&api_key=${apiKey}`; - //window.top.location.href = streamUrl; }; slide.append( logoContainer, @@ -185,7 +226,8 @@ plotContainer, infoContainer, genreElement, - playButton + playButton, + detailButton ); return slide; }; @@ -315,14 +357,13 @@ let focusedSlide = null; let containerFocused = !1; const updateCurrentSlide = (index) => { - if (isTransitioning) return; // Skip if already transitioning - isTransitioning = true; // Set transitioning to true at the start + if (isTransitioning) return; + isTransitioning = true; currentSlideIndex = (index + slides.length) % slides.length; showSlide(currentSlideIndex); - // Re-enable arrows after a short delay to prevent rapid clicking setTimeout(() => { - isTransitioning = false; // Reset after transition completes - }, 500); // Adjust the delay as needed (500ms is a reasonable starting point) + isTransitioning = false; + }, 500); }; const openActiveSlide = () => { if (focusedSlide) { @@ -373,7 +414,6 @@ } } }); - const leftArrow = document.createElement("div"); const rightArrow = document.createElement("div"); leftArrow.classList.add("arrow", "left-arrow"); @@ -420,7 +460,7 @@ document.addEventListener("DOMContentLoaded", () => { if (homeButton) { event.preventDefault(); window.location.href = "/web/index.html#/home.html"; - setTimeout(handleHomeNavigation, 300); // Delay for UI update + setTimeout(handleHomeNavigation, 300); } }); }); \ No newline at end of file From 1452aaab7fabfb240eaf0d129eda1324b981ebd9 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Fri, 7 Feb 2025 18:45:51 +0000 Subject: [PATCH 045/108] Added assembly info for media bar plugin --- src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs diff --git a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..38c759a --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using System.Reflection; +[assembly: AssemblyCompany("IAmParadox27")] +[assembly: AssemblyProduct("Jellyfin.Plugins.MediaBar")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyTitle("Jellyfin.Plugins.MediaBar")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file From 314f065e2c2423793a5b69f29b4dfc7ddd382935 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Fri, 7 Feb 2025 19:00:53 +0000 Subject: [PATCH 046/108] Added flags to csproj --- src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index f133d34..896f431 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -4,6 +4,8 @@ net8.0 enable enable + false + 1.0.0 From 269f1117445f83a1328dbe40c9d730c57f94f544 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Fri, 7 Feb 2025 19:07:20 +0000 Subject: [PATCH 047/108] Forgot to update media bar to use 1.2.2 for filetransformation --- src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 896f431..22b4945 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -9,7 +9,7 @@ - + From aa1a737c1881ac7f568a3596b30f4656d3eb7221 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 20 Aug 2025 01:01:07 +0100 Subject: [PATCH 048/108] Remerge README.md over the top --- README.md | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fb90013..c8788a2 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,19 @@ # Jellyfin-Media-Bar - Now with Play Now Function +1. Add `https://www.iamparadox.dev/jellyfin/plugins/manifest.json` to your repositories +2. Install "File Transformation" 1.2.2 +3. Install "Media Bar" 1.0.1 +4. Restart Jellyfin + +
+ Original README.md from MakD + **IMP UPDATE — We have dropped support for the normal CSS version (for now). _(It still works, but there will be no further updates till the fullscreen mode is stabilized)_** The fullscreen version has a new look (in beta), and support for different screen sizes has been added. For any visual goof-ups, please open a bug report, including the device being used and whether it is encountered in portrait or landscape mode. -Thanks to the Man, the Legend [BobHasNoSoul](https://github.com/BobHasNoSoul) for his work on the [jellyfinfeatured](https://github.com/BobHasNoSoul/jellyfin-featured) and [SethBacon](https://forum.jellyfin.org/u-sethbacon) and [TedHinklater](https://github.com/tedhinklater) for their take on the [Jellyfin-Featured-Content-Bar](https://github.com/tedhinklater/Jellyfin-Featured-Content-Bar). +Thanks to the Man, the Legend [BobHasNoSoul](https://github.com/BobHasNoSoul) for his work on the [jellyfinfeatured](https://github.com/BobHasNoSoul/jellyfin-featured) and [SethBacon](https://forum.jellyfin.org/u-sethbacon) and [TedHinklater](https://github.com/tedhinklater) for their take on the [Jellyfin-Featured-Content-Bar](https://github.com/tedhinklater/Jellyfin-Featured-Content-Bar). Here I present my version with some code improvements, loading optimizations, and security enhancements. Works best with the [Zombie theme](https://github.com/MakD/zombie-release) (_Shameless Plug_ `@import url(https://cdn.jsdelivr.net/gh/MakD/zombie-release@latest/zombie_revived.css);`, visit the repo for more color schemes). @@ -14,15 +22,15 @@ Here I present my version with some code improvements, loading optimizations, an
Desktop Layout - + ![Jellyfin Desktop Layout](https://raw.githubusercontent.com/MakD/Jellyfin-Media-Bar/refs/heads/main/img/Jelly-Web%20-%20Fullscreen%20Mode.png) - +
Mobile Layout - + ![Jellyfin Mobile Layout](https://raw.githubusercontent.com/MakD/Jellyfin-Media-Bar/refs/heads/main/img/Jelly-Mobile-Fullscreen.png)
@@ -30,12 +38,12 @@ Here I present my version with some code improvements, loading optimizations, an # Prepping the files
- + index.html - 1. Navigate to your `jellyfin-web` folder and search for the file index.html. (you can use any code editor, just remember to open with administrator privileges. - 2. Search for `` - 3. Just before the ``, plug the below code +1. Navigate to your `jellyfin-web` folder and search for the file index.html. (you can use any code editor, just remember to open with administrator privileges. +2. Search for `` +3. Just before the ``, plug the below code ``` @@ -46,7 +54,7 @@ And that is it. Hard refresh your web page (CTRL+Shift+R) twice, and Profit! # Want a Custom List to be showcased instead of random items?? -No worries this got you covered. +No worries this got you covered. ## Steps @@ -67,7 +75,7 @@ The next time it loads, it will display these items. # Uninstall the Bar
- + Roll Back Restore the `index.html` file / remove the lines added and you are good to go!!! @@ -75,6 +83,8 @@ Restore the `index.html` file / remove the lines added and you are good to go!!!
+
+ ## License [![Custom: DBAD License](https://img.shields.io/badge/License-Don't_Be_A_Dick-red)](LICENSE) From da3f575b5bd0fc5cccd1000a98cf70d65358a0ef Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 20 Aug 2025 01:02:29 +0100 Subject: [PATCH 049/108] Missed part of the README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index c8788a2..62edf0c 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,11 @@ 3. Install "Media Bar" 1.0.1 4. Restart Jellyfin +## Reporting Issues +If you face issues relating to the visuals or behaviour of buttons added by the Media Bar please report them on MakD's repo (the one this is forked from). This plugin pulls the content from their repo directly and is only in control of adding it without the need for modifying your JF install files. + +Any issues with plugin's settings (including using a playlist as your avatar's playlist) should be made here. +
Original README.md from MakD From 5a8709942d7977636dd8fdc67d52f38da28e0556 Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Fri, 7 Feb 2025 19:35:51 +0000 Subject: [PATCH 050/108] Update README.md --- README.md | 150 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 99 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 62edf0c..9899adf 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,136 @@ # Jellyfin-Media-Bar - Now with Play Now Function +This plugin uses JF Referenceable + 1. Add `https://www.iamparadox.dev/jellyfin/plugins/manifest.json` to your repositories 2. Install "File Transformation" 1.2.2 3. Install "Media Bar" 1.0.1 4. Restart Jellyfin -## Reporting Issues -If you face issues relating to the visuals or behaviour of buttons added by the Media Bar please report them on MakD's repo (the one this is forked from). This plugin pulls the content from their repo directly and is only in control of adding it without the need for modifying your JF install files. - -Any issues with plugin's settings (including using a playlist as your avatar's playlist) should be made here. -
Original README.md from MakD + +## TODO - Design changes upcoming next release -**IMP UPDATE — We have dropped support for the normal CSS version (for now). _(It still works, but there will be no further updates till the fullscreen mode is stabilized)_** +Thanks to the Man, the Legend [BobHasNoSoul](https://github.com/BobHasNoSoul) for his work on the [jellyfinfeatured](https://github.com/BobHasNoSoul/jellyfin-featured) and [SethBacon](https://forum.jellyfin.org/u-sethbacon) and [TedHinklater](https://github.com/tedhinklater) for their take on the [Jellyfin-Featured-Content-Bar](https://github.com/tedhinklater/Jellyfin-Featured-Content-Bar). -The fullscreen version has a new look (in beta), and support for different screen sizes has been added. For any visual goof-ups, please open a bug report, including the device being used and whether it is encountered in portrait or landscape mode. - - -Thanks to the Man, the Legend [BobHasNoSoul](https://github.com/BobHasNoSoul) for his work on the [jellyfinfeatured](https://github.com/BobHasNoSoul/jellyfin-featured) and [SethBacon](https://forum.jellyfin.org/u-sethbacon) and [TedHinklater](https://github.com/tedhinklater) for their take on the [Jellyfin-Featured-Content-Bar](https://github.com/tedhinklater/Jellyfin-Featured-Content-Bar). - -Here I present my version with some code improvements, loading optimizations, and security enhancements. Works best with the [Zombie theme](https://github.com/MakD/zombie-release) (_Shameless Plug_ `@import url(https://cdn.jsdelivr.net/gh/MakD/zombie-release@latest/zombie_revived.css);`, visit the repo for more color schemes). - - -> **Before Installing, please take a backup of your index.html file** +Here I present my version of the same with some code improvements, loading optimizations, and Security Enhancements. Works best with the [Zombie theme](https://github.com/MakD/zombie-release) (_Shameless Plug_), but it fits with every other theme the creators have put their hard work in. You might've to edit the color accents in the CSS to match yours.
Desktop Layout - -![Jellyfin Desktop Layout](https://raw.githubusercontent.com/MakD/Jellyfin-Media-Bar/refs/heads/main/img/Jelly-Web%20-%20Fullscreen%20Mode.png) - + +![Jellyfin Desktop Layout](https://raw.githubusercontent.com/MakD/Jellyfin-Media-Bar/refs/heads/main/img/Jelly-Web.png) +
- Mobile Layout - -![Jellyfin Mobile Layout](https://raw.githubusercontent.com/MakD/Jellyfin-Media-Bar/refs/heads/main/img/Jelly-Mobile-Fullscreen.png) + +![Jellyfin Mobile Layout](https://raw.githubusercontent.com/MakD/Jellyfin-Media-Bar/refs/heads/main/img/Jelly-Mobile.png)
+> **Before Installing, please take a backup of your index.html and home-html.xxxxxx.chunk.js files** + +# Prepping the Environment + +
+ + Steps + +1. Create a folder `avatars` in your `jellyfin-web` folder. (Usually in C:\Program Files\Jellyfin\Server) +2. Download the files `slideshowpure.js` and `slideshowpure.css` +3. Paste them inside the avatars folder created, and you are ready to venture down the rabbit hole. + +
# Prepping the files
- + index.html -1. Navigate to your `jellyfin-web` folder and search for the file index.html. (you can use any code editor, just remember to open with administrator privileges. -2. Search for `` -3. Just before the ``, plug the below code + 1. Navigate to your `jellyfin-web` folder and search for the file index.html. (you can use any code editor, just remember to open with administrator privileges. + 2. Search for `` + 3. Just before the ` - + + + + + ```
+
+ +home-html.xxxxxx.chunk.js + +1. Similarly, search for `home-html` in the `jellyfin-web` directory. You should be able to see a file named `home-html.xxxxxx.chunk.js` with random numbers in place of the `xxxx`. Open it with any code editor with administrator privileges. +2. Search for `id="homeTab" data-index="0">` +3. Right after the `>`, paste the code block `
` + +
+ And that is it. Hard refresh your web page (CTRL+Shift+R) twice, and Profit! # Want a Custom List to be showcased instead of random items?? -No worries this got you covered. +No worries this got you covered. ## Steps @@ -76,27 +147,4 @@ ItemID4 ItemID5 ``` The next time it loads, it will display these items. - -# Uninstall the Bar - -
- - Roll Back - -Restore the `index.html` file / remove the lines added and you are good to go!!! -
- - -
- -## License - -[![Custom: DBAD License](https://img.shields.io/badge/License-Don't_Be_A_Dick-red)](LICENSE) - - -This project is licensed under a DBAD license prohibiting any commercial use or redistribution. -All modifications must be contributed back to this repository. -Attribution to the original author (MakD) is required in any use or derivative work. - -Please take a look at the [LICENSE](LICENSE) file for full terms. From bf39865742d9205b209a895cc04062707793ca6c Mon Sep 17 00:00:00 2001 From: Jam <1347620+JamsRepos@users.noreply.github.com> Date: Fri, 7 Feb 2025 21:31:43 +0000 Subject: [PATCH 051/108] Added Plugin Logo --- src/logo.png | Bin 0 -> 32205 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/logo.png diff --git a/src/logo.png b/src/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d1421be90eca1e1a19875cb08667902c6877594a GIT binary patch literal 32205 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 K73@8B=|2Eu`yycg literal 0 HcmV?d00001 From 78c87c70f697424cbcdfd4e71850e352404c7d31 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sun, 9 Feb 2025 17:47:47 +0000 Subject: [PATCH 052/108] Changed media bar to use the HTTP Request approach for file transformation. Much more stable --- .../Controllers/MediaBarController.cs | 69 +++++++++++----- .../Jellyfin.Plugin.MediaBar.csproj | 12 ++- .../MediaBarPlugin.cs | 56 ++----------- .../Model/PatchRequestPayload.cs | 10 +++ .../Services/StartupService.cs | 82 +++++++++++++++++++ 5 files changed, 158 insertions(+), 71 deletions(-) create mode 100644 src/Jellyfin.Plugin.MediaBar/Model/PatchRequestPayload.cs create mode 100644 src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs diff --git a/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs index 9193c35..38db0f9 100644 --- a/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs +++ b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs @@ -1,32 +1,57 @@ using System.Reflection; +using System.Text.RegularExpressions; +using Jellyfin.Plugin.MediaBar.Model; using Microsoft.AspNetCore.Mvc; -namespace Jellyfin.Plugin.MediaBar.Controllers; - -[Route("[controller]")] -public class MediaBarController : ControllerBase +namespace Jellyfin.Plugin.MediaBar.Controllers { - [HttpGet("{file}")] - public ActionResult GetFile([FromRoute] string file) + [Route("[controller]")] + public class MediaBarController : ControllerBase { - Stream fileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Jellyfin.Plugin.MediaBar.Inject." + file); - string fileContents = new StreamReader(fileStream).ReadToEnd(); + [HttpGet("{file}")] + public ActionResult GetFile([FromRoute] string file) + { + Stream fileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Jellyfin.Plugin.MediaBar.Inject." + file)!; + string fileContents = new StreamReader(fileStream).ReadToEnd(); - string contentType = "text/plain"; + string contentType = "text/plain"; - if (Path.GetExtension(file) == ".js") - { - contentType = "text/javascript"; - } - else if (Path.GetExtension(file) == ".css") - { - contentType = "text/css"; - } - else if (Path.GetExtension(file) == ".html") - { - contentType = "text/html"; - } + if (Path.GetExtension(file) == ".js") + { + contentType = "text/javascript"; + } + else if (Path.GetExtension(file) == ".css") + { + contentType = "text/css"; + } + else if (Path.GetExtension(file) == ".html") + { + contentType = "text/html"; + } - return Content(fileContents, contentType); + return Content(fileContents, contentType); + } + + [HttpPost("Patch/IndexHtml")] + public ActionResult PatchIndexHtml([FromBody] PatchRequestPayload payload) + { + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(MediaBarPlugin).Namespace}.Inject.index.html")!; + using TextReader reader = new StreamReader(stream); + + string regex = Regex.Replace(payload.Contents!, "()", $"{reader.ReadToEnd()}$1"); + + return Content(regex, "text/html"); + } + + [HttpPost("Patch/HomeHtmlChunk")] + public ActionResult PatchHomeHtmlChunk([FromBody] PatchRequestPayload payload) + { + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(MediaBarPlugin).Namespace}.Inject.home-html.chunk.js")!; + using TextReader reader = new StreamReader(stream); + + string regex = Regex.Replace(payload.Contents!, "(id=\"homeTab\" data-index=\"0\">)", $"$1{reader.ReadToEnd()}"); + + return Content(regex, "text/html"); + } } } \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 22b4945..3d31bab 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -5,11 +5,16 @@ enable enable false - 1.0.0 + 2.0.0 - + + + + + + @@ -25,4 +30,7 @@ + + + diff --git a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs index 5536311..172ac70 100644 --- a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs +++ b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs @@ -1,56 +1,18 @@ -using System.Reflection; -using System.Text.RegularExpressions; -using Jellyfin.Plugin.FileTransformation.Controller; -using Jellyfin.Plugin.FileTransformation.Infrastructure; -using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.DependencyInjection; -namespace Jellyfin.Plugin.MediaBar; - -public class MediaBarPlugin : BasePlugin +namespace Jellyfin.Plugin.MediaBar { - public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448"); + public class MediaBarPlugin : BasePlugin + { + public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448"); - public override string Name => "Media Bar"; + public override string Name => "Media Bar"; - public MediaBarPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IServiceProvider serviceProvider) : base(applicationPaths, xmlSerializer) - { - IWebFileTransformationWriteService webFileTransformationWriteService = serviceProvider.GetRequiredService(); - - webFileTransformationWriteService.AddTransformation("index.html", IndexHtml_Transformation); - webFileTransformationWriteService.AddTransformation("home-html\\.[a-zA-z0-9]+\\.chunk\\.js", HomeHtmlChunk_Transformation); - } - - private void IndexHtml_Transformation(string path, Stream contents) - { - Stream? stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{GetType().Namespace}.Inject.index.html"); - using TextReader reader = new StreamReader(stream); - - using var textReader = new StreamReader(contents, null, true, -1, true); - var text = textReader.ReadToEnd(); - - var regex = Regex.Replace(text, "()", $"{reader.ReadToEnd()}$1"); - contents.Seek(0, SeekOrigin.Begin); - - using var textWriter = new StreamWriter(contents, null, -1, true); - textWriter.Write(regex); - } - - private void HomeHtmlChunk_Transformation(string path, Stream contents) - { - Stream? stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{GetType().Namespace}.Inject.home-html.chunk.js"); - using TextReader reader = new StreamReader(stream); - - using var textReader = new StreamReader(contents, null, true, -1, true); - var text = textReader.ReadToEnd(); - - var regex = Regex.Replace(text, "(id=\"homeTab\" data-index=\"0\">)", $"$1{reader.ReadToEnd()}"); - contents.Seek(0, SeekOrigin.Begin); - - using var textWriter = new StreamWriter(contents, null, -1, true); - textWriter.Write(regex); + public MediaBarPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IServiceProvider serviceProvider) : base(applicationPaths, xmlSerializer) + { + } } } \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Model/PatchRequestPayload.cs b/src/Jellyfin.Plugin.MediaBar/Model/PatchRequestPayload.cs new file mode 100644 index 0000000..a43eac2 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Model/PatchRequestPayload.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Jellyfin.Plugin.MediaBar.Model +{ + public class PatchRequestPayload + { + [JsonPropertyName("contents")] + public string? Contents { get; set; } + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs new file mode 100644 index 0000000..4f45027 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs @@ -0,0 +1,82 @@ +using System.Net.Http.Headers; +using System.Reflection; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Jellyfin.Plugin.MediaBar.Services +{ + public class StartupService : IScheduledTask + { + public string Name => "MediaBar Startup"; + + public string Key => "Jellyfin.Plugin.MediaBar.Startup"; + + public string Description => "Startup Service for MediaBar"; + + public string Category => "Startup Services"; + + private readonly IServerApplicationHost m_serverApplicationHost; + private readonly ILogger m_logger; + + public StartupService(IServerApplicationHost serverApplicationHost, ILogger logger) + { + m_serverApplicationHost = serverApplicationHost; + m_logger = logger; + } + + public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) + { + List payloads = new List(); + + { + JObject payload = new JObject(); + payload.Add("id", "0dfac9d7-d898-4944-900b-1c1837707279"); + payload.Add("fileNamePattern", "index.html"); + payload.Add("tranformationEndpoint", "/MediaBar/Patch/IndexHtml"); + + payloads.Add(payload); + } + { + JObject payload = new JObject(); + payload.Add("id", "e6d32b76-d54b-4946-b73e-c5c9c50575c9"); + payload.Add("fileNamePattern", "home-html\\.[a-zA-z0-9]+\\.chunk\\.js"); + payload.Add("tranformationEndpoint", "/MediaBar/Patch/HomeHtmlChunk"); + + payloads.Add(payload); + } + + string? publishedServerUrl = m_serverApplicationHost.GetType() + .GetProperty("PublishedServerUrl", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(m_serverApplicationHost) as string; + + foreach (JObject payload in payloads) + { + HttpClient client = new HttpClient(); + client.BaseAddress = new Uri(publishedServerUrl ?? $"http://localhost:{m_serverApplicationHost.HttpPort}"); + + try + { + await client.PostAsync("/FileTransformation/RegisterTransformation", + new StringContent(payload.ToString(Formatting.None), + MediaTypeHeaderValue.Parse("application/json"))); + } + catch (Exception ex) + { + m_logger.LogError(ex, $"Caught exception when attempting to register file transformation. Ensure you have `File Transformation` plugin installed on your server."); + return; + } + } + } + + public IEnumerable GetDefaultTriggers() + { + yield return new TaskTriggerInfo() + { + Type = TaskTriggerInfo.TriggerStartup + }; + } + } +} \ No newline at end of file From 8dbeda369d07009c95c3eecb72c45cc256b7a0ce Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sun, 9 Feb 2025 18:23:41 +0000 Subject: [PATCH 053/108] Forgot to update Version/AssemblyVersion values --- src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs index 38c759a..7109049 100644 --- a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs +++ b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs @@ -3,5 +3,4 @@ [assembly: AssemblyProduct("Jellyfin.Plugins.MediaBar")] [assembly: AssemblyDescription("")] [assembly: AssemblyTitle("Jellyfin.Plugins.MediaBar")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file +[assembly: AssemblyVersion("2.0.0.0")] \ No newline at end of file From 47fbe4a427543ff782b10c792064865291336bee Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sun, 9 Feb 2025 18:37:55 +0000 Subject: [PATCH 054/108] Update README.md --- README.md | 6 ++---- .../Jellyfin.Plugin.MediaBar.csproj | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9899adf..f26a04c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ # Jellyfin-Media-Bar - Now with Play Now Function -This plugin uses JF Referenceable - 1. Add `https://www.iamparadox.dev/jellyfin/plugins/manifest.json` to your repositories -2. Install "File Transformation" 1.2.2 -3. Install "Media Bar" 1.0.1 +2. Install "File Transformation" 2.0.0 +3. Install "Media Bar" 2.0.0 4. Restart Jellyfin
diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 3d31bab..8068433 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -30,6 +30,10 @@ + + + + From 75ab9f7f3206c3476a7c405b6d12b582af84f65e Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:39:41 +0000 Subject: [PATCH 055/108] Update slideshowpure.js --- .../Inject/slideshowpure.js | 941 +++++++++--------- 1 file changed, 481 insertions(+), 460 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js index 95609be..548c8de 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -1,466 +1,487 @@ -const slidesInit = () => { - const shuffleInterval = 8000; - let isTransitioning = false; - const listFileName = `${window.location.origin}/web/avatars/list.txt`; - const jsonCredentials = sessionStorage.getItem("json-credentials"); - let userId = null; - let token = null; - const deviceId = localStorage.getItem("_deviceId2"); - console.log(deviceId); - if (jsonCredentials) { - const credentials = JSON.parse(jsonCredentials); - userId = credentials.Servers[0].UserId; - token = credentials.Servers[0].AccessToken; +const slidesInit = () => { + const shuffleInterval = 8000; + let isTransitioning = false; + const listFileName = `${window.location.origin}/web/avatars/list.txt`; + const jsonCredentials = sessionStorage.getItem("json-credentials"); + let userId = null; + let token = null; + //const deviceId = localStorage.getItem("_deviceId2"); + //console.log(deviceId); + if (jsonCredentials) { + const credentials = JSON.parse(jsonCredentials); + userId = credentials.Servers[0].UserId; + token = credentials.Servers[0].AccessToken; + } + const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); + const truncateText = (element, maxLength) => { + let text = element.innerText; + if (text.length > maxLength) + element.innerText = text.substring(0, maxLength) + "..."; + }; + const createSlideElement = (item, title) => { + const itemId = item.Id; + const plot = item.Overview || "No overview available"; + const rating = item.CommunityRating; + const tomato = item.CriticRating; + const runtime = item.RunTimeTicks; + const genre = item.Genres; + const youtube = item.RemoteTrailers; + const age = item.OfficialRating; + const date = item.PremiereDate; + const season = item.ChildCount; + function createSeparator() { + const separatorHtml = + ''; + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = separatorHtml; + return tempDiv.firstChild; } - const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); - const truncateText = (element, maxLength) => { - let text = element.innerText; - if (text.length > maxLength) - element.innerText = text.substring(0, maxLength) + "..."; + const slide = document.createElement("a"); + slide.className = "slide"; + slide.target = "_top"; + slide.rel = "noreferrer"; + slide.tabIndex = 0; + slide.style.display = "none"; + const backdrop = document.createElement("img"); + backdrop.className = "backdrop"; + backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; + backdrop.alt = "Backdrop"; + const backdropOverlay = document.createElement("div"); + backdropOverlay.className = "backdrop-overlay"; + const backdropContainer = document.createElement("div"); + backdropContainer.className = "backdrop-container"; + backdropContainer.appendChild(backdrop); + backdropContainer.appendChild(backdropOverlay); + const logo = document.createElement("img"); + logo.className = "logo"; + logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; + logo.alt = "Logo"; + const logoContainer = document.createElement("div"); + logoContainer.className = "logo-container"; + logoContainer.appendChild(logo); + const featuredContent = document.createElement("div"); + featuredContent.className = "featured-content"; + featuredContent.textContent = title; + const plotElement = document.createElement("div"); + plotElement.className = "plot"; + plotElement.textContent = plot; + truncateText(plotElement, 360); + const plotContainer = document.createElement("div"); + plotContainer.className = "plot-container"; + plotContainer.appendChild(plotElement); + const gradientOverlay = document.createElement("div"); + gradientOverlay.className = "gradient-overlay"; + const runTimeElement = document.createElement("div"); + runTimeElement.className = "runTime"; + if (season === undefined) { + const milliseconds = runtime / 10000; + const currentTime = new Date(); + const endTime = new Date(currentTime.getTime() + milliseconds); + const options = { hour: "2-digit", minute: "2-digit", hour12: !0 }; + const formattedEndTime = endTime.toLocaleTimeString([], options); + runTimeElement.textContent = `Ends at ${formattedEndTime}`; + } else { + runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; + } + const ratingTest = document.createElement("div"); + ratingTest.className = "rating-value"; + const imdbLogo = document.createElement("img"); + imdbLogo.className = "imdb-logo"; + imdbLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg"; + imdbLogo.alt = "IMDb Logo"; + imdbLogo.style.width = "30px"; + imdbLogo.style.height = "30px"; + ratingTest.appendChild(imdbLogo); + if (typeof rating === "number") { + const formattedRating = rating.toFixed(1); + ratingTest.innerHTML += `${formattedRating} ⭐`; + } else { + console.error("Rating is undefined or not a number:", rating); + ratingTest.innerHTML += `N/A ⭐`; + } + ratingTest.appendChild(createSeparator()); + const tomatoRatingDiv = document.createElement("div"); + tomatoRatingDiv.className = "tomato-rating"; + const criticRating = item.CriticRating; + const tomatoLogo = document.createElement("img"); + tomatoLogo.className = "tomato-logo"; + const criticLogo = document.createElement("img"); + criticLogo.className = "critic-logo"; + tomatoLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; + let valueElement; + if (typeof criticRating === "number") { + valueElement = document.createTextNode(`${criticRating}% `); + } else { + valueElement = document.createTextNode(`N/A `); + } + if (criticRating === undefined || criticRating <= 59) { + criticLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; + criticLogo.alt = "Rotten Tomato"; + } else { + criticLogo.src = "https://i.imgur.com/iMfwDk7.png"; + criticLogo.alt = "Fresh Tomato"; + } + tomatoLogo.style.width = "15px"; + tomatoLogo.style.height = "17px"; + criticLogo.style.width = "15px"; + criticLogo.style.height = "15px"; + tomatoRatingDiv.appendChild(tomatoLogo); + tomatoRatingDiv.appendChild(valueElement); + tomatoRatingDiv.appendChild(criticLogo); + tomatoRatingDiv.appendChild(createSeparator()); + const ageRatingDiv = document.createElement("div"); + ageRatingDiv.className = "age-rating"; + if (item.OfficialRating) { + ageRatingDiv.innerHTML = `${item.OfficialRating}`; + } else { + ageRatingDiv.innerHTML = `N/A`; + } + const calender = "📅"; + const premiereDate = document.createElement("div"); + premiereDate.className = "date"; + const year = date ? new Date(date).getFullYear() : "N/A"; + premiereDate.textContent = isNaN(year) ? "N/A" : year; + ratingTest.appendChild(tomatoRatingDiv); + ratingTest.appendChild(document.createTextNode(calender)); + ratingTest.appendChild(premiereDate); + ratingTest.appendChild(createSeparator()); + ratingTest.appendChild(ageRatingDiv); + ratingTest.appendChild(createSeparator()); + ratingTest.appendChild(runTimeElement); + const genresArray = item.Genres; + function parseGenres(genresArray) { + if (genresArray && genresArray.length > 0) { + return genresArray.slice(0, 3).join(" 🔹 "); + } else { + return "No Genre Available"; + } + } + const genreElement = document.createElement("div"); + genreElement.className = "genre"; + genreElement.innerHTML = parseGenres(genresArray); + const infoContainer = document.createElement("div"); + infoContainer.className = "info-container"; + infoContainer.appendChild(ratingTest); + // Create a container for the buttons +const buttonContainer = document.createElement("div"); +buttonContainer.className = "button-container"; // Add a class for styling + +// Create the Play button +const playButton = document.createElement("button"); +playButton.className = "play-button"; +playButton.innerHTML = ` + + Play Now +`; +playButton.onclick = async () => { + if (!window.ApiClient) { + console.error("Jellyfin API client is not available."); + return; + } + const apiClient = window.ApiClient; + const userId = apiClient.getCurrentUserId(); + if (!userId) { + console.error("User not found."); + return; + } + try { + const sessions = await apiClient.getJSON(apiClient.serverAddress() + "/Sessions"); + if (!sessions.length) { + console.error("No active Jellyfin sessions found."); + return; + } + + // Find the session matching the current user + const userSession = sessions.find(session => session.UserId === userId); + if (!userSession) { + console.error("No active session found for the current user."); + return; + } + + const sessionId = userSession.Id; + const playbackUrl = `${apiClient.serverAddress()}/Sessions/${sessionId}/Playing?StartPositionTicks=0&PlayCommand=PlayNow&StartIndex=0&ItemIds=${itemId}&api_key=${token}`; + + const response = await fetch(playbackUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `DeviceId="unique-device-id", Token="${token}"`, + }, + }); + + if (response.status === 204) { + console.log("Playback started successfully."); + } else { + const errorData = await response.json(); + console.error("Failed to start playback:", errorData); + } + } catch (error) { + console.error("Error fetching sessions:", error); + } +}; + +// Create the Details button +const detailButton = document.createElement("button"); +detailButton.className = "detail-button"; +detailButton.innerHTML = ` + + More Info +`; +detailButton.onclick = () => { + window.top.location.href = `/#!/details?id=${itemId}`; +}; + +// Append buttons to the button container +buttonContainer.appendChild(playButton); +buttonContainer.appendChild(detailButton); + +// Append all elements to the slide +slide.append( + logoContainer, + backdropContainer, + gradientOverlay, + featuredContent, + plotContainer, + infoContainer, + genreElement, + buttonContainer // Add button container instead of individual buttons +); + +return slide; + }; + const createSlideForItem = async (item, title) => { + const container = document.getElementById("slides-container"); + const itemId = item.Id; + const backdropUrl = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; + const logoUrl = `${window.location.origin}/Items/${itemId}/Images/Logo`; + const [backdropExists, logoExists] = await Promise.all([ + fetch(backdropUrl, { method: "HEAD" }).then((res) => res.ok), + fetch(logoUrl, { method: "HEAD" }).then((res) => res.ok), + ]); + if (backdropExists && logoExists) { + const slideElement = createSlideElement(item, title); + container.appendChild(slideElement); + console.log(`Added slide for item ${itemId}`); + if (container.children.length === 1) { + showSlide(0); + } + } else { + console.warn(`Skipping item ${itemId}: Missing backdrop or logo.`); + } + }; + const fetchItemDetails = async (itemId) => { + const response = await fetch( + `${window.location.origin}/Users/${userId}/Items/${itemId}`, + { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + } + ); + const item = await response.json(); + console.log("Item Title:", item.Name); + return item; + }; + const fetchItemIdsFromList = async () => { + try { + const response = await fetch(listFileName); + if (!response.ok) { + throw new Error("Failed to fetch list.txt"); + } + const text = await response.text(); + return text + .split("\n") + .map((id) => id.trim()) + .filter((id) => id); + } catch (error) { + console.error("Error fetching list.txt:", error); + return []; + } + }; + const fetchItemsFromServer = async () => { + try { + const response = await fetch( + `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, + { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + } + ); + const data = await response.json(); + const items = data.Items; + const movies = items.filter((item) => item.Type === "Movie"); + const tvShows = items.filter((item) => item.Type === "Series"); + const shuffledMovies = shuffleArray(movies); + const shuffledTvShows = shuffleArray(tvShows); + const selectedMovies = shuffledMovies.slice(0, 15); + const selectedTvShows = shuffledTvShows.slice(0, 15); + const allItems = []; + const maxLength = Math.max(selectedMovies.length, selectedTvShows.length); + for (let i = 0; i < maxLength; i++) { + if (i < selectedMovies.length) allItems.push(selectedMovies[i]); + if (i < selectedTvShows.length) allItems.push(selectedTvShows[i]); + } + return allItems; + } catch (error) { + console.error("Error fetching items:", error); + return []; + } + }; + const createSlidesForItems = async (items) => { + await Promise.all( + items.map((item) => + createSlideForItem(item, item.Type === "Movie" ? "Movie" : "TV Show") + ) + ); + }; + const createPaginationDots = () => { + const dotsContainer = document.createElement("div"); + dotsContainer.className = "dots-container"; + for (let i = 0; i < 5; i++) { + const dot = document.createElement("span"); + dot.className = "dot"; + dotsContainer.appendChild(dot); + } + const container = document.getElementById("slides-container"); + container.appendChild(dotsContainer); + }; + const showSlide = (index) => { + const slides = document.querySelectorAll(".slide"); + const dots = document.querySelectorAll(".dot"); + slides.forEach((slide, i) => { + if (i === index) { + slide.style.display = "block"; + slide.offsetHeight; + slide.style.opacity = "1"; + slide.classList.add("active"); + } else { + slide.style.opacity = "0"; + slide.classList.remove("active"); + setTimeout(() => { + slide.style.display = "none"; + }, 500); + } + }); + dots.forEach((dot, i) => { + dot.classList.toggle("active", i === index % 5); + }); + }; + const initializeSlideshow = () => { + const slides = document.querySelectorAll(".slide"); + createPaginationDots(); + const container = document.getElementById("slides-container"); + let currentSlideIndex = 0; + let focusedSlide = null; + let containerFocused = !1; + const updateCurrentSlide = (index) => { + if (isTransitioning) return; + isTransitioning = true; + currentSlideIndex = (index + slides.length) % slides.length; + showSlide(currentSlideIndex); + setTimeout(() => { + isTransitioning = false; + }, 500); }; - const createSlideElement = (item, title) => { - const itemId = item.Id; - const plot = item.Overview || "No overview available"; - const rating = item.CommunityRating; - const tomato = item.CriticRating; - const runtime = item.RunTimeTicks; - const genre = item.Genres; - const youtube = item.RemoteTrailers; - const age = item.OfficialRating; - const date = item.PremiereDate; - const season = item.ChildCount; - function createSeparator() { - const separatorHtml = - ''; - const tempDiv = document.createElement("div"); - tempDiv.innerHTML = separatorHtml; - return tempDiv.firstChild; - } - const slide = document.createElement("a"); - slide.className = "slide"; - slide.target = "_top"; - slide.rel = "noreferrer"; - slide.tabIndex = 0; - slide.style.display = "none"; - const backdrop = document.createElement("img"); - backdrop.className = "backdrop"; - backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; - backdrop.alt = "Backdrop"; - const backdropOverlay = document.createElement("div"); - backdropOverlay.className = "backdrop-overlay"; - const backdropContainer = document.createElement("div"); - backdropContainer.className = "backdrop-container"; - backdropContainer.appendChild(backdrop); - backdropContainer.appendChild(backdropOverlay); - const logo = document.createElement("img"); - logo.className = "logo"; - logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; - logo.alt = "Logo"; - const logoContainer = document.createElement("div"); - logoContainer.className = "logo-container"; - logoContainer.appendChild(logo); - const featuredContent = document.createElement("div"); - featuredContent.className = "featured-content"; - featuredContent.textContent = title; - const plotElement = document.createElement("div"); - plotElement.className = "plot"; - plotElement.textContent = plot; - truncateText(plotElement, 360); - const plotContainer = document.createElement("div"); - plotContainer.className = "plot-container"; - plotContainer.appendChild(plotElement); - const gradientOverlay = document.createElement("div"); - gradientOverlay.className = "gradient-overlay"; - const runTimeElement = document.createElement("div"); - runTimeElement.className = "runTime"; - if (season === undefined) { - const milliseconds = runtime / 10000; - const currentTime = new Date(); - const endTime = new Date(currentTime.getTime() + milliseconds); - const options = { hour: "2-digit", minute: "2-digit", hour12: !0 }; - const formattedEndTime = endTime.toLocaleTimeString([], options); - runTimeElement.textContent = `Ends at ${formattedEndTime}`; - } else { - runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; - } - const ratingTest = document.createElement("div"); - ratingTest.className = "rating-value"; - const imdbLogo = document.createElement("img"); - imdbLogo.className = "imdb-logo"; - imdbLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg"; - imdbLogo.alt = "IMDb Logo"; - imdbLogo.style.width = "30px"; - imdbLogo.style.height = "30px"; - ratingTest.appendChild(imdbLogo); - if (typeof rating === "number") { - const formattedRating = rating.toFixed(1); - ratingTest.innerHTML += `${formattedRating} ⭐`; - } else { - console.error("Rating is undefined or not a number:", rating); - ratingTest.innerHTML += `N/A ⭐`; - } - ratingTest.appendChild(createSeparator()); - const tomatoRatingDiv = document.createElement("div"); - tomatoRatingDiv.className = "tomato-rating"; - const criticRating = item.CriticRating; - const tomatoLogo = document.createElement("img"); - tomatoLogo.className = "tomato-logo"; - const criticLogo = document.createElement("img"); - criticLogo.className = "critic-logo"; - tomatoLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; - let valueElement; - if (typeof criticRating === "number") { - valueElement = document.createTextNode(`${criticRating}% `); - } else { - valueElement = document.createTextNode(`N/A `); - } - if (criticRating === undefined || criticRating <= 59) { - criticLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; - criticLogo.alt = "Rotten Tomato"; - } else { - criticLogo.src = "https://i.imgur.com/iMfwDk7.png"; - criticLogo.alt = "Fresh Tomato"; - } - tomatoLogo.style.width = "15px"; - tomatoLogo.style.height = "17px"; - criticLogo.style.width = "15px"; - criticLogo.style.height = "15px"; - tomatoRatingDiv.appendChild(tomatoLogo); - tomatoRatingDiv.appendChild(valueElement); - tomatoRatingDiv.appendChild(criticLogo); - tomatoRatingDiv.appendChild(createSeparator()); - const ageRatingDiv = document.createElement("div"); - ageRatingDiv.className = "age-rating"; - if (item.OfficialRating) { - ageRatingDiv.innerHTML = `${item.OfficialRating}`; - } else { - ageRatingDiv.innerHTML = `N/A`; - } - const calender = "📅"; - const premiereDate = document.createElement("div"); - premiereDate.className = "date"; - const year = date ? new Date(date).getFullYear() : "N/A"; - premiereDate.textContent = isNaN(year) ? "N/A" : year; - ratingTest.appendChild(tomatoRatingDiv); - ratingTest.appendChild(document.createTextNode(calender)); - ratingTest.appendChild(premiereDate); - ratingTest.appendChild(createSeparator()); - ratingTest.appendChild(ageRatingDiv); - ratingTest.appendChild(createSeparator()); - ratingTest.appendChild(runTimeElement); - const genresArray = item.Genres; - function parseGenres(genresArray) { - if (genresArray && genresArray.length > 0) { - return genresArray.slice(0, 3).join(" 🔹 "); - } else { - return "No Genre Available"; - } - } - const genreElement = document.createElement("div"); - genreElement.className = "genre"; - genreElement.innerHTML = parseGenres(genresArray); - const infoContainer = document.createElement("div"); - infoContainer.className = "info-container"; - infoContainer.appendChild(ratingTest); - const playButton = document.createElement("button"); - playButton.className = "play-button"; - playButton.innerHTML = ` - - Play Now - `; - playButton.onclick = async () => { - if (!window.ApiClient) { - console.error("Jellyfin API client is not available."); - return; - } - const apiClient = window.ApiClient; - const userId = apiClient.getCurrentUserId(); - if (!userId) { - console.error("User not found."); - return; - } - try { - const sessions = await apiClient.getJSON( - apiClient.serverAddress() + "/Sessions" - ); - if (!sessions.length) { - console.error("No active Jellyfin sessions found."); - return; - } - const sessionId = sessions[0].Id; - const playbackUrl = `${apiClient.serverAddress()}/Sessions/${sessionId}/Playing?StartPositionTicks=0&PlayCommand=PlayNow&StartIndex=0&ItemIds=${itemId}&api_key=${token}`; - const response = await fetch(playbackUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDevice", DeviceId="YourDeviceId", Version="YourVersion", Token="${token}"`, - }, - }); - if (response.status === 204) { - console.log("Playback started successfully."); - } else { - const errorData = await response.json(); - console.error("Failed to start playback:", errorData); - } - } catch (error) { - console.error("Error fetching sessions:", error); - } - }; - const detailButton = document.createElement("button"); - detailButton.className = "detail-button"; - detailButton.innerHTML = ` - - More Details - `; - detailButton.onclick = () => { - window.top.location.href = `/#!/details?id=${itemId}`; - }; - slide.append( - logoContainer, - backdropContainer, - gradientOverlay, - featuredContent, - plotContainer, - infoContainer, - genreElement, - playButton, - detailButton - ); - return slide; + const openActiveSlide = () => { + if (focusedSlide) { + window.location.href = focusedSlide.href; + } }; - const createSlideForItem = async (item, title) => { - const container = document.getElementById("slides-container"); - const itemId = item.Id; - const backdropUrl = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; - const logoUrl = `${window.location.origin}/Items/${itemId}/Images/Logo`; - const [backdropExists, logoExists] = await Promise.all([ - fetch(backdropUrl, { method: "HEAD" }).then((res) => res.ok), - fetch(logoUrl, { method: "HEAD" }).then((res) => res.ok), - ]); - if (backdropExists && logoExists) { - const slideElement = createSlideElement(item, title); - container.appendChild(slideElement); - console.log(`Added slide for item ${itemId}`); - if (container.children.length === 1) { - showSlide(0); - } - } else { - console.warn(`Skipping item ${itemId}: Missing backdrop or logo.`); + if (slides.length > 0) { + showSlide(currentSlideIndex); + container.style.display = "block"; + setTimeout(() => { + setInterval(() => { + updateCurrentSlide(currentSlideIndex + 1); + }, shuffleInterval); + }, 8000); + } + slides.forEach((slide) => { + slide.addEventListener( + "focus", + () => { + focusedSlide = slide; + container.classList.remove("disable-interaction"); + }, + !0 + ); + slide.addEventListener( + "blur", + () => { + if (focusedSlide === slide) { + focusedSlide = null; + } + }, + !0 + ); + }); + document.addEventListener("keydown", (event) => { + if (containerFocused) { + switch (event.keyCode) { + case 37: + updateCurrentSlide(currentSlideIndex - 1); + break; + case 39: + updateCurrentSlide(currentSlideIndex + 1); + break; + case 13: + case 32: + openActiveSlide(); + break; } - }; - const fetchItemDetails = async (itemId) => { - const response = await fetch( - `${window.location.origin}/Users/${userId}/Items/${itemId}`, - { - headers: { - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, - }, - } - ); - const item = await response.json(); - console.log("Item Title:", item.Name); - return item; - }; - const fetchItemIdsFromList = async () => { - try { - const response = await fetch(listFileName); - if (!response.ok) { - throw new Error("Failed to fetch list.txt"); - } - const text = await response.text(); - return text - .split("\n") - .map((id) => id.trim()) - .filter((id) => id); - } catch (error) { - console.error("Error fetching list.txt:", error); - return []; - } - }; - const fetchItemsFromServer = async () => { - try { - const response = await fetch( - `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, - { - headers: { - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, - }, - } - ); - const data = await response.json(); - const items = data.Items; - const movies = items.filter((item) => item.Type === "Movie"); - const tvShows = items.filter((item) => item.Type === "Series"); - const shuffledMovies = shuffleArray(movies); - const shuffledTvShows = shuffleArray(tvShows); - const selectedMovies = shuffledMovies.slice(0, 15); - const selectedTvShows = shuffledTvShows.slice(0, 15); - const allItems = []; - const maxLength = Math.max(selectedMovies.length, selectedTvShows.length); - for (let i = 0; i < maxLength; i++) { - if (i < selectedMovies.length) allItems.push(selectedMovies[i]); - if (i < selectedTvShows.length) allItems.push(selectedTvShows[i]); - } - return allItems; - } catch (error) { - console.error("Error fetching items:", error); - return []; - } - }; - const createSlidesForItems = async (items) => { - await Promise.all( - items.map((item) => - createSlideForItem(item, item.Type === "Movie" ? "Movie" : "TV Show") - ) - ); - }; - const createPaginationDots = () => { - const dotsContainer = document.createElement("div"); - dotsContainer.className = "dots-container"; - for (let i = 0; i < 5; i++) { - const dot = document.createElement("span"); - dot.className = "dot"; - dotsContainer.appendChild(dot); - } - const container = document.getElementById("slides-container"); - container.appendChild(dotsContainer); - }; - const showSlide = (index) => { - const slides = document.querySelectorAll(".slide"); - const dots = document.querySelectorAll(".dot"); - slides.forEach((slide, i) => { - if (i === index) { - slide.style.display = "block"; - slide.offsetHeight; - slide.style.opacity = "1"; - slide.classList.add("active"); - } else { - slide.style.opacity = "0"; - slide.classList.remove("active"); - setTimeout(() => { - slide.style.display = "none"; - }, 500); - } - }); - dots.forEach((dot, i) => { - dot.classList.toggle("active", i === index % 5); - }); - }; - const initializeSlideshow = () => { - const slides = document.querySelectorAll(".slide"); - createPaginationDots(); - const container = document.getElementById("slides-container"); - let currentSlideIndex = 0; - let focusedSlide = null; - let containerFocused = !1; - const updateCurrentSlide = (index) => { - if (isTransitioning) return; - isTransitioning = true; - currentSlideIndex = (index + slides.length) % slides.length; - showSlide(currentSlideIndex); - setTimeout(() => { - isTransitioning = false; - }, 500); - }; - const openActiveSlide = () => { - if (focusedSlide) { - window.location.href = focusedSlide.href; - } - }; - if (slides.length > 0) { - showSlide(currentSlideIndex); - container.style.display = "block"; - setTimeout(() => { - setInterval(() => { - updateCurrentSlide(currentSlideIndex + 1); - }, shuffleInterval); - }, 8000); - } - slides.forEach((slide) => { - slide.addEventListener( - "focus", - () => { - focusedSlide = slide; - container.classList.remove("disable-interaction"); - }, - !0 - ); - slide.addEventListener( - "blur", - () => { - if (focusedSlide === slide) { - focusedSlide = null; - } - }, - !0 - ); - }); - document.addEventListener("keydown", (event) => { - if (containerFocused) { - switch (event.keyCode) { - case 37: - updateCurrentSlide(currentSlideIndex - 1); - break; - case 39: - updateCurrentSlide(currentSlideIndex + 1); - break; - case 13: - case 32: - openActiveSlide(); - break; - } - } - }); - const leftArrow = document.createElement("div"); - const rightArrow = document.createElement("div"); - leftArrow.classList.add("arrow", "left-arrow"); - rightArrow.classList.add("arrow", "right-arrow"); - leftArrow.innerHTML = ''; - rightArrow.innerHTML = ''; - container.appendChild(leftArrow); - container.appendChild(rightArrow); - leftArrow.addEventListener("click", () => - updateCurrentSlide(currentSlideIndex - 1) - ); - rightArrow.addEventListener("click", () => - updateCurrentSlide(currentSlideIndex + 1) - ); - container.addEventListener("focus", () => { - containerFocused = !0; - }); - container.addEventListener("blur", () => { - containerFocused = !1; - }); - }; - const initializeSlides = async () => { - const itemIds = await fetchItemIdsFromList(); - let items; - if (itemIds.length > 0) { - const itemPromises = itemIds.map((id) => fetchItemDetails(id)); - items = await Promise.all(itemPromises); - } else { - const allItems = await fetchItemsFromServer(); - const itemPromises = allItems.map((item) => fetchItemDetails(item.Id)); - items = await Promise.all(itemPromises); - } - items = shuffleArray(items); - await createSlidesForItems(items); - initializeSlideshow(); - }; - initializeSlides(); + } + }); + const leftArrow = document.createElement("div"); + const rightArrow = document.createElement("div"); + leftArrow.classList.add("arrow", "left-arrow"); + rightArrow.classList.add("arrow", "right-arrow"); + leftArrow.innerHTML = ''; + rightArrow.innerHTML = ''; + container.appendChild(leftArrow); + container.appendChild(rightArrow); + leftArrow.addEventListener("click", () => + updateCurrentSlide(currentSlideIndex - 1) + ); + rightArrow.addEventListener("click", () => + updateCurrentSlide(currentSlideIndex + 1) + ); + container.addEventListener("focus", () => { + containerFocused = !0; + }); + container.addEventListener("blur", () => { + containerFocused = !1; + }); + }; + const initializeSlides = async () => { + const itemIds = await fetchItemIdsFromList(); + let items; + if (itemIds.length > 0) { + const itemPromises = itemIds.map((id) => fetchItemDetails(id)); + items = await Promise.all(itemPromises); + } else { + const allItems = await fetchItemsFromServer(); + const itemPromises = allItems.map((item) => fetchItemDetails(item.Id)); + items = await Promise.all(itemPromises); + } + items = shuffleArray(items); + await createSlidesForItems(items); + initializeSlideshow(); + }; + initializeSlides(); }; document.addEventListener("DOMContentLoaded", () => { - document.body.addEventListener("click", (event) => { - const homeButton = event.target.matches( - ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" - ); - if (homeButton) { - event.preventDefault(); - window.location.href = "/web/index.html#/home.html"; - setTimeout(handleHomeNavigation, 300); - } - }); -}); \ No newline at end of file + document.body.addEventListener("click", (event) => { + const homeButton = event.target.matches( + ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" + ); + if (homeButton) { + event.preventDefault(); + window.location.href = "/web/index.html#/home.html"; + setTimeout(handleHomeNavigation, 300); + } + }); +}); From 6e464180da2b29a7fc047855a0cfaac6de08c2d6 Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:39:59 +0000 Subject: [PATCH 056/108] Update slideshowpure.css --- .../Inject/slideshowpure.css | 1428 ++++++++--------- 1 file changed, 714 insertions(+), 714 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css index e00974a..4afc598 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css @@ -1,774 +1,774 @@ -@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap"); @media only screen and (min-width: 767px) { - div[data-role="page"].page { - overflow-y: scroll; - scrollbar-width: none; - -ms-overflow-style: none; - } + div[data-role="page"].page { + overflow-y: scroll; + scrollbar-width: none; + -ms-overflow-style: none; + } - div[data-role="page"].page::-webkit-scrollbar { - display: none; - } + div[data-role="page"].page::-webkit-scrollbar { + display: none; + } - .backdrop, - .slide { - position: absolute; - } + .backdrop, + .slide { + position: absolute; + } - .featured-content, - .plot-container { - font-family: "Titillium Web", sans-serif; - left: 0; - } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } - .plot, - body { - overflow: hidden; - } + .plot, + body { + overflow: hidden; + } - .arrow { - position: absolute; - top: 50%; - transform: translateY(-50%); - font-size: 24px; - cursor: pointer; - color: white; - z-index: 3; - display: flex; - justify-content: center; - align-items: center; - } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: white; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } - .arrow::before { - content: ""; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 40px; - height: 40px; - background-color: rgba(12, 110, 162, 0.7); - border-radius: 50%; - z-index: -1; - } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgba(12, 110, 162, 0.7); + border-radius: 50%; + z-index: -1; + } - .left-arrow { - left: 10px; - } + .left-arrow { + left: 10px; + } - .right-arrow { - right: 10px; - } + .right-arrow { + right: 10px; + } - .dots-container { - position: absolute; - bottom: 5%; - left: 0; - right: 0; - z-index: 2; - display: flex; - justify-content: center; - align-items: center; - width: 100vw; - height: 22px; - background-color: #ffffff00; - gap: 2vw; - } + .dots-container { + position: absolute; + bottom: 5%; + left: 0; + right: 0; + z-index: 2; + display: flex; + justify-content: center; + align-items: center; + width: 100vw; + height: 22px; + background-color: #ffffff00; + gap: 2vw; + } - .dot { - width: 15vw; - height: 4px; - background-color: #cecece; - border-radius: 2px; - cursor: pointer; - transition: background-color 0.3s; - } + .dot { + width: 15vw; + height: 4px; + background-color: #cecece; + border-radius: 2px; + cursor: pointer; + transition: background-color 0.3s; + } - .dot.active { - background-color: rgba(12, 110, 162, 0.7) !important; - } + .dot.active { + background-color: rgba(12, 110, 162, 0.7) !important; + } - body { - margin: 0; - padding: 0; - } + body { + margin: 0; + padding: 0; + } - .fade-in { - opacity: 1 !important; - } + .fade-in { + opacity: 1 !important; + } - .fade-out { - opacity: 0 !important; - } + .fade-out { + opacity: 0 !important; + } - #slides-container { - position: relative; - width: 100vw; - height: 750px; - overflow: hidden; - margin: 0 auto; - border-radius: 7px; - top: -5em; - } + #slides-container { + position: relative; + width: 100vw; + height: 750px; + overflow: hidden; + margin: 0 auto; + border-radius: 7px; + top: -5em; + } - .slide { - transition: opacity 0.5s ease-in-out; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; - } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } - .slide.active { - opacity: 1; - display: block; - } + .slide.active { + opacity: 1; + display: block; + } - .slide:focus { - outline: #fff solid 2px; - } + .slide:focus { + outline: #fff solid 2px; + } - .backdrop-container { - position: relative; - top: -40%; - right: -35%; - width: 70%; - height: 100%; - } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } - .backdrop { - right: 0; - width: 100%; - height: 100%; - object-fit: cover; - object-position: center 20%; - border-radius: 5px; - z-index: 1; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .backdrop-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.6); - border-radius: 5px; - z-index: 2; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .logo-container { - width: 45%; - height: 40%; - position: relative; - display: flex; - justify-content: left; - align-items: left; - } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } - .logo { - position: relative; - max-height: 100%; - max-width: 100%; - height: auto; - width: auto; - object-fit: contain; - z-index: 3; - top: 5em; - left: 8em; - } + .logo { + position: relative; + max-height: 100%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 3; + top: 5em; + left: 8em; + } - .featured-content { - position: absolute; - top: 0; - width: 100%; - height: 50px; - background-color: transparent; - color: #d3d3d3; - font-size: 22px; - display: none; - align-items: center; - justify-content: flex-start; - z-index: 2; - } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: transparent; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } - .plot-container { - position: absolute; - bottom: 0; - color: #fff; - width: 50%; - height: 60%; - font-size: 15px; - padding: 10px 0 25px 15px; - border-radius: 5px; - z-index: 3; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - text-align: justify; - } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 50%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } - .plot { - display: -webkit-box; - line-clamp: 4; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - left: 7.9em; - position: absolute; - top: 190px; - } + .plot { + display: -webkit-box; + line-clamp: 4; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + left: 7.9em; + position: absolute; + top: 190px; + } - .gradient-overlay { - position: absolute; - top: 0; - left: 0; - width: 70%; - height: 100%; - background: linear-gradient(to right, #000 49%, rgba(0, 0, 0, 0) 70%); - z-index: 2; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0.5) 10%, - rgba(0, 0, 0, 1) 12% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0.5) 10%, - rgba(0, 0, 0, 1) 12% - ); - } - .genre { - position: absolute; - top: calc(50% + 75px); - left: 8.4em; - text-align: center; - color: #fff; - font-family: "Titillium Web", sans-serif; - font-size: 14px; - z-index: 4; - } - .info-container { - position: absolute; - top: calc(50% + 0px); - display: flex; - align-items: center; - justify-content: center; - left: 2em !important; - color: #fff; - z-index: 4; - } - .rating-value { - font-family: "Titillium Web", sans-serif; - display: flex; - align-items: center; - margin: 0 6em; - z-index: 5; - position: relative; - top: 1.5em; - } - .runTime { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin: 0 8px; - margin-right: -3px; - } - .rating-value img { - margin-right: 6px; - } - .tomato-rating { - display: flex; - align-items: center; - margin-left: 0; - } - .tomato-rating img { - margin-left: 5px; - } - .age-rating { - display: flex; - align-items: center; - margin-left: 5px; - margin-right: 5px; - border-radius: 5px; - background: rgb(255 255 255 / 0.8); - color: #000; - border: none; - font-weight: 600; - white-space: nowrap; - padding: 0 0.6em; - } - .date { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin-right: 5px; - margin-left: 2px; - } - .separator-icon { - margin-left: 5px; - margin-right: 5px; - font-size: 10px; - color: aquamarine; - } - .play-button { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - top: calc(50% + 190px); - left: 6.8em; - } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, rgba(0, 0, 0, 0) 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 8%, + rgba(0, 0, 0, 0.5) 10%, + rgba(0, 0, 0, 1) 12% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 8%, + rgba(0, 0, 0, 0.5) 10%, + rgba(0, 0, 0, 1) 12% + ); + } + .genre { + position: absolute; + top: calc(50% + 75px); + left: 8.4em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + top: 1.5em; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .play-button { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + top: calc(50% + 190px); + left: 6.8em; + } - .play-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 30px; - height: 30px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 3px solid rgba(12, 110, 162, 1); - border-radius: 50%; - } + .play-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 30px; + height: 30px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); + border: 3px solid rgba(12, 110, 162, 1); + border-radius: 50%; + } - .play-text { - font-family: "Titillium Web", sans-serif; - margin-left: 10px; - font-size: 14px; - color: #fff; - } - .detail-button { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - top: calc(50% + 190px); - left: 15.8em; - } + .play-text { + font-family: "Titillium Web", sans-serif; + margin-left: 10px; + font-size: 14px; + color: #fff; + } + .detail-button { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + top: calc(50% + 190px); + left: 15.8em; + } - .detail-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 30px; - height: 30px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 3px solid rgba(12, 110, 162, 1); - border-radius: 50%; - } + .detail-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 30px; + height: 30px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); + border: 3px solid rgba(12, 110, 162, 1); + border-radius: 50%; + } - .detail-text { - font-family: "Titillium Web", sans-serif; - margin-left: 10px; - font-size: 14px; - color: #fff; - } + .detail-text { + font-family: "Titillium Web", sans-serif; + margin-left: 10px; + font-size: 14px; + color: #fff; + } } @media only screen and (max-width: 767px) { - .backdrop, - .slide { - position: absolute; - } + .backdrop, + .slide { + position: absolute; + } - .arrow { - position: absolute; - bottom: 25px; - font-size: 24px; - cursor: pointer; - color: rgba(255, 255, 255, 0.6); - z-index: 4; - -webkit-tap-highlight-color: transparent; - } + .arrow { + position: absolute; + bottom: 25px; + font-size: 24px; + cursor: pointer; + color: rgba(255, 255, 255, 0.6); + z-index: 4; + -webkit-tap-highlight-color: transparent; + } - .left-arrow { - left: 10px; - } + .left-arrow { + left: 10px; + } - .right-arrow { - right: 10px; - } + .right-arrow { + right: 10px; + } - .featured-content, - .plot-container { - font-family: "Titillium Web", sans-serif; - left: 0; - } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } - .plot, - body { - overflow: hidden; - } + .plot, + body { + overflow: hidden; + } - body { - margin: 0; - padding: 0; - } + body { + margin: 0; + padding: 0; + } - .fade-in { - opacity: 1 !important; - } + .fade-in { + opacity: 1 !important; + } - .fade-out { - opacity: 0 !important; - } + .fade-out { + opacity: 0 !important; + } - .dots-container { - position: absolute; - bottom: 11%; - left: 50%; - transform: translateX(-50%) scale(0.6); - z-index: 2; - display: flex; - gap: 5px; - background-color: #ffffff00; - border-radius: 50px; - justify-content: center; - align-items: center; - width: 140px; - height: 22px; - transition: opacity 0.5s ease-in-out; - } + .dots-container { + position: absolute; + bottom: 11%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff00; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } - .dot { - display: inline-block; - width: 9px; - height: 9px; - margin: 0 5px; - background-color: #cecece; - border-radius: 50%; - transition: background-color 0.3s; - } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } - .dot.active { - width: 32px; - height: 12px; - background-color: rgba(12, 110, 162, 0.7); - border-radius: 10px; - } + .dot.active { + width: 32px; + height: 12px; + background-color: rgba(12, 110, 162, 0.7); + border-radius: 10px; + } - #slides-container { - position: relative; - width: 92vw; - height: 650px; - overflow: hidden; - margin: 0 auto; - border-radius: 10px; - } + #slides-container { + position: relative; + width: 92vw; + height: 650px; + overflow: hidden; + margin: 0 auto; + border-radius: 10px; + } - .slide { - transition: opacity 0.5s ease-in-out; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; - } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } - .slide.active { - opacity: 1; - display: block; - } + .slide.active { + opacity: 1; + display: block; + } - .slide:focus { - outline: #fff solid 2px; - } - .logo { - position: absolute; - bottom: 28%; - left: 50%; - transform: translateX(-50%); - max-height: 60%; - max-width: 80%; - width: auto; - z-index: 3; - } + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + bottom: 28%; + left: 50%; + transform: translateX(-50%); + max-height: 60%; + max-width: 80%; + width: auto; + z-index: 3; + } - .backdrop { - position: absolute; - top: 45px; - left: 0; - width: 100%; - height: calc(100% - 50px); - object-fit: cover; - object-position: center 20%; - z-index: 1; - border-radius: 13px; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .backdrop { + position: absolute; + top: 45px; + left: 0; + width: 100%; + height: calc(100% - 50px); + object-fit: cover; + object-position: center 20%; + z-index: 1; + border-radius: 13px; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .gradient-overlay { - position: absolute; - top: 45px; - left: 0; - width: 100%; - height: calc(100% - 50px); - background: rgba(0, 0, 0, 0.55); - z-index: 2; - border-radius: 13px; - pointer-events: none; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .gradient-overlay { + position: absolute; + top: 45px; + left: 0; + width: 100%; + height: calc(100% - 50px); + background: rgba(0, 0, 0, 0.55); + z-index: 2; + border-radius: 13px; + pointer-events: none; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .genre { - position: absolute; - bottom: 16%; - left: 50%; - transform: translateX(-50%); - text-align: center; - color: #fff; - width: 90%; - max-width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-family: "Titillium Web", sans-serif; - font-size: 1.1em; - font-weight: 500; - z-index: 4; - box-sizing: border-box; - } - .info-container { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - bottom: 20%; - left: 50%; - transform: translateX(-50%); - font-size: 1.1em; - color: #fff; - z-index: 5; - width: 100%; - } - .rating-value { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - } - .date { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin-left: 2px; - } - .tomato-rating { - display: flex; - align-items: center; - } - .tomato-logo { - margin-right: 3px; - } - .critic-logo { - margin-left: 3px; - } - .imdb-logo { - margin-right: 3px; - } - .age-rating { - display: flex; - align-items: center; - border-radius: 5px; - background: rgb(255 255 255 / 0.8); - color: #000; - border: none; - font-weight: 600; - white-space: nowrap; - padding: 0 0.4em; - } - .separator-icon { - margin-left: 5px; - margin-right: 5px; - font-size: 1px; - color: aquamarine; - } - .featured-content, - .plot { - display: none; - } + .genre { + position: absolute; + bottom: 16%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Titillium Web", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 20%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } - .play-button { - position: absolute; - display: flex; - align-items: center; - justify-content: flex-start; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - bottom: 20px; - left: 50%; - transform: translateX(-110%) scale(1.2); - outline: none; - -webkit-tap-highlight-color: transparent; - } + .play-button { + position: absolute; + display: flex; + align-items: center; + justify-content: flex-start; + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + bottom: 20px; + left: 50%; + transform: translateX(-110%) scale(1.2); + outline: none; + -webkit-tap-highlight-color: transparent; + } - .play-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 20px; - height: 20px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 1.5px solid rgba(12, 110, 162, 1); - border-radius: 50%; - outline: none; - -webkit-tap-highlight-color: transparent; - } + .play-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 20px; + height: 20px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); + border: 1.5px solid rgba(12, 110, 162, 1); + border-radius: 50%; + outline: none; + -webkit-tap-highlight-color: transparent; + } - .play-text { - display: block; - font-family: "Titillium Web", sans-serif; - margin-left: 5px; - font-size: 14px; - color: #fff; - } - .detail-button { - position: absolute; - display: flex; - align-items: center; - justify-content: flex-start; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - bottom: 20px; - left: 50%; - transform: translateX(10%) scale(1.2); - outline: none; - -webkit-tap-highlight-color: transparent; - } + .play-text { + display: block; + font-family: "Titillium Web", sans-serif; + margin-left: 5px; + font-size: 14px; + color: #fff; + } + .detail-button { + position: absolute; + display: flex; + align-items: center; + justify-content: flex-start; + padding: 10px 20px; + color: #fff; + background-color: rgba(255, 255, 255, 0); + border: none; + border-radius: 50px; + cursor: pointer; + font-size: 14px; + font-weight: 300; + transition: background-color 0.3s; + z-index: 4; + bottom: 20px; + left: 50%; + transform: translateX(10%) scale(1.2); + outline: none; + -webkit-tap-highlight-color: transparent; + } - .detail-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 20px; - height: 20px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 1.5px solid rgba(12, 110, 162, 1); - border-radius: 50%; - outline: none; - -webkit-tap-highlight-color: transparent; - } + .detail-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + width: 20px; + height: 20px; + background-color: rgba( + 12, + 110, + 162, + 0.7 + ); + border: 1.5px solid rgba(12, 110, 162, 1); + border-radius: 50%; + outline: none; + -webkit-tap-highlight-color: transparent; + } - .detail-text { - display: block; - font-family: "Titillium Web", sans-serif; - margin-left: 5px; - color: #fff; - } -} \ No newline at end of file + .detail-text { + display: block; + font-family: "Titillium Web", sans-serif; + margin-left: 5px; + color: #fff; + } +} From fbd1016bdbcc440b15b1a4a3e2b2f06ae6cd7e31 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Tue, 11 Feb 2025 20:45:33 +0000 Subject: [PATCH 057/108] Updated to include fix for playback and updated styling. --- .../Controllers/MediaBarController.cs | 11 + .../Inject/slideshowpure.css | 1343 ++++++++--------- .../Inject/slideshowpure.js | 936 ++++++------ .../Services/StartupService.cs | 8 + 4 files changed, 1114 insertions(+), 1184 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs index 38db0f9..e12eac0 100644 --- a/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs +++ b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs @@ -53,5 +53,16 @@ namespace Jellyfin.Plugin.MediaBar.Controllers return Content(regex, "text/html"); } + + [HttpPost("Patch/MainJellyfinBundle")] + public ActionResult PatchMainBundle([FromBody] PatchRequestPayload payload) + { + string replacementText = + "window.PlaybackManager=this.playbackManager;console.log(\"PlaybackManager is now globally available:\",window.PlaybackManager);"; + + string regex = Regex.Replace(payload.Contents!, @"(this\.playbackManager=e,)", $"$1{replacementText}"); + + return Content(regex, "text/html"); + } } } \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css index 4afc598..e3b74dd 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css @@ -1,774 +1,687 @@ -@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap"); @media only screen and (min-width: 767px) { - div[data-role="page"].page { - overflow-y: scroll; - scrollbar-width: none; - -ms-overflow-style: none; - } + div[data-role="page"].page { + overflow-y: scroll; + scrollbar-width: none; + -ms-overflow-style: none; + } - div[data-role="page"].page::-webkit-scrollbar { - display: none; - } + div[data-role="page"].page::-webkit-scrollbar { + display: none; + } - .backdrop, - .slide { - position: absolute; - } + .backdrop, + .slide { + position: absolute; + } - .featured-content, - .plot-container { - font-family: "Titillium Web", sans-serif; - left: 0; - } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } - .plot, - body { - overflow: hidden; - } + .plot, + body { + overflow: hidden; + } - .arrow { - position: absolute; - top: 50%; - transform: translateY(-50%); - font-size: 24px; - cursor: pointer; - color: white; - z-index: 3; - display: flex; - justify-content: center; - align-items: center; - } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: white; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } - .arrow::before { - content: ""; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 40px; - height: 40px; - background-color: rgba(12, 110, 162, 0.7); - border-radius: 50%; - z-index: -1; - } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgba(12, 110, 162, 0.7); + border-radius: 50%; + z-index: -1; + } - .left-arrow { - left: 10px; - } + .left-arrow { + left: 20px; + } - .right-arrow { - right: 10px; - } + .right-arrow { + right: 20px; + } - .dots-container { - position: absolute; - bottom: 5%; - left: 0; - right: 0; - z-index: 2; - display: flex; - justify-content: center; - align-items: center; - width: 100vw; - height: 22px; - background-color: #ffffff00; - gap: 2vw; - } + .dots-container { + position: absolute; + bottom: 5%; + left: 0; + right: 0; + z-index: 2; + display: flex; + justify-content: center; + align-items: center; + width: 100vw; + height: 22px; + background-color: #ffffff00; + gap: 2vw; + } - .dot { - width: 15vw; - height: 4px; - background-color: #cecece; - border-radius: 2px; - cursor: pointer; - transition: background-color 0.3s; - } + .dot { + width: 15vw; + height: 4px; + background-color: #cecece; + border-radius: 2px; + cursor: pointer; + transition: background-color 0.3s; + } - .dot.active { - background-color: rgba(12, 110, 162, 0.7) !important; - } + .dot.active { + background-color: rgba(12, 110, 162, 0.7) !important; + } - body { - margin: 0; - padding: 0; - } + body { + margin: 0; + padding: 0; + } - .fade-in { - opacity: 1 !important; - } + .fade-in { + opacity: 1 !important; + } - .fade-out { - opacity: 0 !important; - } + .fade-out { + opacity: 0 !important; + } - #slides-container { - position: relative; - width: 100vw; - height: 750px; - overflow: hidden; - margin: 0 auto; - border-radius: 7px; - top: -5em; - } + #slides-container { + position: relative; + width: 92vw; + height: 650px; + overflow: hidden; + margin: 0 auto; + border-radius: 15px; + } - .slide { - transition: opacity 0.5s ease-in-out; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; - } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } - .slide.active { - opacity: 1; - display: block; - } + .slide.active { + opacity: 1; + display: block; + } - .slide:focus { - outline: #fff solid 2px; - } + .slide:focus { + outline: #fff solid 2px; + } - .backdrop-container { - position: relative; - top: -40%; - right: -35%; - width: 70%; - height: 100%; - } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } - .backdrop { - right: 0; - width: 100%; - height: 100%; - object-fit: cover; - object-position: center 20%; - border-radius: 5px; - z-index: 1; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .backdrop-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.6); - border-radius: 5px; - z-index: 2; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .logo-container { - width: 45%; - height: 40%; - position: relative; - display: flex; - justify-content: left; - align-items: left; - } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } - .logo { - position: relative; - max-height: 100%; - max-width: 100%; - height: auto; - width: auto; - object-fit: contain; - z-index: 3; - top: 5em; - left: 8em; - } + .logo { + position: relative; + max-height: 100%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 3; + top: 5em; + left: 8em; + } - .featured-content { - position: absolute; - top: 0; - width: 100%; - height: 50px; - background-color: transparent; - color: #d3d3d3; - font-size: 22px; - display: none; - align-items: center; - justify-content: flex-start; - z-index: 2; - } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: transparent; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } - .plot-container { - position: absolute; - bottom: 0; - color: #fff; - width: 50%; - height: 60%; - font-size: 15px; - padding: 10px 0 25px 15px; - border-radius: 5px; - z-index: 3; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - text-align: justify; - } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } - .plot { - display: -webkit-box; - line-clamp: 4; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - left: 7.9em; - position: absolute; - top: 190px; - } + .plot { + display: -webkit-box; + line-clamp: 4; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + left: 7.9em; + position: absolute; + top: 190px; + } - .gradient-overlay { - position: absolute; - top: 0; - left: 0; - width: 70%; - height: 100%; - background: linear-gradient(to right, #000 49%, rgba(0, 0, 0, 0) 70%); - z-index: 2; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0.5) 10%, - rgba(0, 0, 0, 1) 12% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0.5) 10%, - rgba(0, 0, 0, 1) 12% - ); - } - .genre { - position: absolute; - top: calc(50% + 75px); - left: 8.4em; - text-align: center; - color: #fff; - font-family: "Titillium Web", sans-serif; - font-size: 14px; - z-index: 4; - } - .info-container { - position: absolute; - top: calc(50% + 0px); - display: flex; - align-items: center; - justify-content: center; - left: 2em !important; - color: #fff; - z-index: 4; - } - .rating-value { - font-family: "Titillium Web", sans-serif; - display: flex; - align-items: center; - margin: 0 6em; - z-index: 5; - position: relative; - top: 1.5em; - } - .runTime { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin: 0 8px; - margin-right: -3px; - } - .rating-value img { - margin-right: 6px; - } - .tomato-rating { - display: flex; - align-items: center; - margin-left: 0; - } - .tomato-rating img { - margin-left: 5px; - } - .age-rating { - display: flex; - align-items: center; - margin-left: 5px; - margin-right: 5px; - border-radius: 5px; - background: rgb(255 255 255 / 0.8); - color: #000; - border: none; - font-weight: 600; - white-space: nowrap; - padding: 0 0.6em; - } - .date { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin-right: 5px; - margin-left: 2px; - } - .separator-icon { - margin-left: 5px; - margin-right: 5px; - font-size: 10px; - color: aquamarine; - } - .play-button { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - top: calc(50% + 190px); - left: 6.8em; - } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, rgba(0, 0, 0, 0) 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 8%, + rgba(0, 0, 0, 0.5) 10%, + rgba(0, 0, 0, 1) 12% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 8%, + rgba(0, 0, 0, 0.5) 10%, + rgba(0, 0, 0, 1) 12% + ); + } + .genre { + position: absolute; + top: calc(50% + 75px); + left: 8.4em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + top: 1.5em; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 190px); /* Same as the previous button positions */ + left: 7.5em; + display: flex; + gap: 2em; /* Space between buttons */ + z-index: 5; /* Ensures it's above other elements */ + } + .play-button, + .detail-button { + display: inline-flex; /* Ensures icon and text are on the same line */ + align-items: center; + justify-content: center; + padding: 8px 16px; /* Slightly reduced padding for thinner buttons */ + border: solid 2px; + border-radius: 25px; /* Sleeker look */ + font-size: 14px; + white-space: nowrap; /* Prevents text wrapping */ + cursor: pointer; + transition: all 0.3s ease; + font-weight: bold; + gap: 6px; /* Adds space between icon and text */ + -webkit-tap-highlight-color: transparent; + } - .play-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 30px; - height: 30px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 3px solid rgba(12, 110, 162, 1); - border-radius: 50%; - } + .play-button { + background-color: #ffffff; + color: #000; + } - .play-text { - font-family: "Titillium Web", sans-serif; - margin-left: 10px; - font-size: 14px; - color: #fff; - } - .detail-button { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - top: calc(50% + 190px); - left: 15.8em; - } + .detail-button { + background-color: rgba(255, 255, 255, 0); + color: #fff; + } - .detail-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 30px; - height: 30px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 3px solid rgba(12, 110, 162, 1); - border-radius: 50%; - } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } - .detail-text { - font-family: "Titillium Web", sans-serif; - margin-left: 10px; - font-size: 14px; - color: #fff; - } + .play-icon, + .detail-icon { + font-size: 18px; /* Ensures icons are aligned with text */ + display: flex; + align-items: center; + } } - @media only screen and (max-width: 767px) { - .backdrop, - .slide { - position: absolute; - } + .backdrop, + .slide { + position: absolute; + } - .arrow { - position: absolute; - bottom: 25px; - font-size: 24px; - cursor: pointer; - color: rgba(255, 255, 255, 0.6); - z-index: 4; - -webkit-tap-highlight-color: transparent; - } + .arrow { + position: absolute; + bottom: 25px; + font-size: 24px; + cursor: pointer; + color: rgba(255, 255, 255, 0.6); + z-index: 4; + -webkit-tap-highlight-color: transparent; + } - .left-arrow { - left: 10px; - } + .left-arrow { + left: 10px; + } - .right-arrow { - right: 10px; - } + .right-arrow { + right: 10px; + } - .featured-content, - .plot-container { - font-family: "Titillium Web", sans-serif; - left: 0; - } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } - .plot, - body { - overflow: hidden; - } + .plot, + body { + overflow: hidden; + } - body { - margin: 0; - padding: 0; - } + body { + margin: 0; + padding: 0; + } - .fade-in { - opacity: 1 !important; - } + .fade-in { + opacity: 1 !important; + } - .fade-out { - opacity: 0 !important; - } + .fade-out { + opacity: 0 !important; + } - .dots-container { - position: absolute; - bottom: 11%; - left: 50%; - transform: translateX(-50%) scale(0.6); - z-index: 2; - display: flex; - gap: 5px; - background-color: #ffffff00; - border-radius: 50px; - justify-content: center; - align-items: center; - width: 140px; - height: 22px; - transition: opacity 0.5s ease-in-out; - } + .dots-container { + position: absolute; + bottom: 11%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff00; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } - .dot { - display: inline-block; - width: 9px; - height: 9px; - margin: 0 5px; - background-color: #cecece; - border-radius: 50%; - transition: background-color 0.3s; - } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } - .dot.active { - width: 32px; - height: 12px; - background-color: rgba(12, 110, 162, 0.7); - border-radius: 10px; - } + .dot.active { + width: 32px; + height: 12px; + background-color: rgba(12, 110, 162, 0.7); + border-radius: 10px; + } - #slides-container { - position: relative; - width: 92vw; - height: 650px; - overflow: hidden; - margin: 0 auto; - border-radius: 10px; - } + #slides-container { + position: relative; + width: 92vw; + height: 650px; + overflow: hidden; + margin: 0 auto; + border-radius: 10px; + } - .slide { - transition: opacity 0.5s ease-in-out; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; - } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } - .slide.active { - opacity: 1; - display: block; - } + .slide.active { + opacity: 1; + display: block; + } - .slide:focus { - outline: #fff solid 2px; - } - .logo { - position: absolute; - bottom: 28%; - left: 50%; - transform: translateX(-50%); - max-height: 60%; - max-width: 80%; - width: auto; - z-index: 3; - } + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + bottom: 28%; + left: 50%; + transform: translateX(-50%); + max-height: 60%; + max-width: 80%; + width: auto; + z-index: 3; + } - .backdrop { - position: absolute; - top: 45px; - left: 0; - width: 100%; - height: calc(100% - 50px); - object-fit: cover; - object-position: center 20%; - z-index: 1; - border-radius: 13px; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .backdrop { + position: absolute; + top: 45px; + left: 0; + width: 100%; + height: calc(100% - 50px); + object-fit: cover; + object-position: center 20%; + z-index: 1; + border-radius: 13px; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .gradient-overlay { - position: absolute; - top: 45px; - left: 0; - width: 100%; - height: calc(100% - 50px); - background: rgba(0, 0, 0, 0.55); - z-index: 2; - border-radius: 13px; - pointer-events: none; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } + .gradient-overlay { + position: absolute; + top: 45px; + left: 0; + width: 100%; + height: calc(100% - 50px); + background: rgba(0, 0, 0, 0.55); + z-index: 2; + border-radius: 13px; + pointer-events: none; + mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + -webkit-mask-image: linear-gradient( + to top, + rgba(0, 0, 0, 0) 10%, + rgba(0, 0, 0, 0.5) 15%, + rgba(0, 0, 0, 1) 30% + ); + } - .genre { - position: absolute; - bottom: 16%; - left: 50%; - transform: translateX(-50%); - text-align: center; - color: #fff; - width: 90%; - max-width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-family: "Titillium Web", sans-serif; - font-size: 1.1em; - font-weight: 500; - z-index: 4; - box-sizing: border-box; - } - .info-container { - position: absolute; - display: flex; - align-items: center; - justify-content: center; - bottom: 20%; - left: 50%; - transform: translateX(-50%); - font-size: 1.1em; - color: #fff; - z-index: 5; - width: 100%; - } - .rating-value { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - } - .date { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin-left: 2px; - } - .tomato-rating { - display: flex; - align-items: center; - } - .tomato-logo { - margin-right: 3px; - } - .critic-logo { - margin-left: 3px; - } - .imdb-logo { - margin-right: 3px; - } - .age-rating { - display: flex; - align-items: center; - border-radius: 5px; - background: rgb(255 255 255 / 0.8); - color: #000; - border: none; - font-weight: 600; - white-space: nowrap; - padding: 0 0.4em; - } - .separator-icon { - margin-left: 5px; - margin-right: 5px; - font-size: 1px; - color: aquamarine; - } - .featured-content, - .plot { - display: none; - } + .genre { + position: absolute; + bottom: 16%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Titillium Web", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 20%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } - .play-button { - position: absolute; - display: flex; - align-items: center; - justify-content: flex-start; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - bottom: 20px; - left: 50%; - transform: translateX(-110%) scale(1.2); - outline: none; - -webkit-tap-highlight-color: transparent; - } + .button-container { + position: absolute; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 15px; /* Adjust spacing between buttons */ + z-index: 4; + } - .play-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 20px; - height: 20px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 1.5px solid rgba(12, 110, 162, 1); - border-radius: 50%; - outline: none; - -webkit-tap-highlight-color: transparent; - } + .play-button, + .detail-button { + display: inline-flex; /* Ensures icon and text are on the same line */ + align-items: center; + justify-content: center; + padding: 8px 16px; /* Slightly reduced padding for thinner buttons */ + border: none; + border-radius: 20px; /* Sleeker look */ + font-size: 14px; + white-space: nowrap; /* Prevents text wrapping */ + cursor: pointer; + transition: all 0.3s ease; + font-weight: bold; + gap: 6px; /* Adds space between icon and text */ + -webkit-tap-highlight-color: transparent; + } - .play-text { - display: block; - font-family: "Titillium Web", sans-serif; - margin-left: 5px; - font-size: 14px; - color: #fff; - } - .detail-button { - position: absolute; - display: flex; - align-items: center; - justify-content: flex-start; - padding: 10px 20px; - color: #fff; - background-color: rgba(255, 255, 255, 0); - border: none; - border-radius: 50px; - cursor: pointer; - font-size: 14px; - font-weight: 300; - transition: background-color 0.3s; - z-index: 4; - bottom: 20px; - left: 50%; - transform: translateX(10%) scale(1.2); - outline: none; - -webkit-tap-highlight-color: transparent; - } + .play-button { + background-color: #ffffff; + color: #000; + } - .detail-icon { - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - width: 20px; - height: 20px; - background-color: rgba( - 12, - 110, - 162, - 0.7 - ); - border: 1.5px solid rgba(12, 110, 162, 1); - border-radius: 50%; - outline: none; - -webkit-tap-highlight-color: transparent; - } + .detail-button { + background-color: rgba(255, 255, 255, 0.2); + color: #fff; + } - .detail-text { - display: block; - font-family: "Titillium Web", sans-serif; - margin-left: 5px; - color: #fff; - } -} + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + + .play-icon, + .detail-icon { + font-size: 18px; /* Ensures icons are aligned with text */ + display: flex; + align-items: center; + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js index 548c8de..27dc4e0 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -1,487 +1,485 @@ -const slidesInit = () => { - const shuffleInterval = 8000; - let isTransitioning = false; - const listFileName = `${window.location.origin}/web/avatars/list.txt`; - const jsonCredentials = sessionStorage.getItem("json-credentials"); - let userId = null; - let token = null; - //const deviceId = localStorage.getItem("_deviceId2"); - //console.log(deviceId); - if (jsonCredentials) { - const credentials = JSON.parse(jsonCredentials); - userId = credentials.Servers[0].UserId; - token = credentials.Servers[0].AccessToken; - } - const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); - const truncateText = (element, maxLength) => { - let text = element.innerText; - if (text.length > maxLength) - element.innerText = text.substring(0, maxLength) + "..."; - }; - const createSlideElement = (item, title) => { - const itemId = item.Id; - const plot = item.Overview || "No overview available"; - const rating = item.CommunityRating; - const tomato = item.CriticRating; - const runtime = item.RunTimeTicks; - const genre = item.Genres; - const youtube = item.RemoteTrailers; - const age = item.OfficialRating; - const date = item.PremiereDate; - const season = item.ChildCount; - function createSeparator() { - const separatorHtml = - ''; - const tempDiv = document.createElement("div"); - tempDiv.innerHTML = separatorHtml; - return tempDiv.firstChild; +const slidesInit = () => { + const shuffleInterval = 8000; + let isTransitioning = false; + const listFileName = `${window.location.origin}/web/avatars/list.txt`; + const jsonCredentials = sessionStorage.getItem("json-credentials"); + let userId = null; + let token = null; + //const deviceId = localStorage.getItem("_deviceId2"); + //console.log(deviceId); + if (jsonCredentials) { + const credentials = JSON.parse(jsonCredentials); + userId = credentials.Servers[0].UserId; + token = credentials.Servers[0].AccessToken; } - const slide = document.createElement("a"); - slide.className = "slide"; - slide.target = "_top"; - slide.rel = "noreferrer"; - slide.tabIndex = 0; - slide.style.display = "none"; - const backdrop = document.createElement("img"); - backdrop.className = "backdrop"; - backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; - backdrop.alt = "Backdrop"; - const backdropOverlay = document.createElement("div"); - backdropOverlay.className = "backdrop-overlay"; - const backdropContainer = document.createElement("div"); - backdropContainer.className = "backdrop-container"; - backdropContainer.appendChild(backdrop); - backdropContainer.appendChild(backdropOverlay); - const logo = document.createElement("img"); - logo.className = "logo"; - logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; - logo.alt = "Logo"; - const logoContainer = document.createElement("div"); - logoContainer.className = "logo-container"; - logoContainer.appendChild(logo); - const featuredContent = document.createElement("div"); - featuredContent.className = "featured-content"; - featuredContent.textContent = title; - const plotElement = document.createElement("div"); - plotElement.className = "plot"; - plotElement.textContent = plot; - truncateText(plotElement, 360); - const plotContainer = document.createElement("div"); - plotContainer.className = "plot-container"; - plotContainer.appendChild(plotElement); - const gradientOverlay = document.createElement("div"); - gradientOverlay.className = "gradient-overlay"; - const runTimeElement = document.createElement("div"); - runTimeElement.className = "runTime"; - if (season === undefined) { - const milliseconds = runtime / 10000; - const currentTime = new Date(); - const endTime = new Date(currentTime.getTime() + milliseconds); - const options = { hour: "2-digit", minute: "2-digit", hour12: !0 }; - const formattedEndTime = endTime.toLocaleTimeString([], options); - runTimeElement.textContent = `Ends at ${formattedEndTime}`; - } else { - runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; - } - const ratingTest = document.createElement("div"); - ratingTest.className = "rating-value"; - const imdbLogo = document.createElement("img"); - imdbLogo.className = "imdb-logo"; - imdbLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg"; - imdbLogo.alt = "IMDb Logo"; - imdbLogo.style.width = "30px"; - imdbLogo.style.height = "30px"; - ratingTest.appendChild(imdbLogo); - if (typeof rating === "number") { - const formattedRating = rating.toFixed(1); - ratingTest.innerHTML += `${formattedRating} ⭐`; - } else { - console.error("Rating is undefined or not a number:", rating); - ratingTest.innerHTML += `N/A ⭐`; - } - ratingTest.appendChild(createSeparator()); - const tomatoRatingDiv = document.createElement("div"); - tomatoRatingDiv.className = "tomato-rating"; - const criticRating = item.CriticRating; - const tomatoLogo = document.createElement("img"); - tomatoLogo.className = "tomato-logo"; - const criticLogo = document.createElement("img"); - criticLogo.className = "critic-logo"; - tomatoLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; - let valueElement; - if (typeof criticRating === "number") { - valueElement = document.createTextNode(`${criticRating}% `); - } else { - valueElement = document.createTextNode(`N/A `); - } - if (criticRating === undefined || criticRating <= 59) { - criticLogo.src = - "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; - criticLogo.alt = "Rotten Tomato"; - } else { - criticLogo.src = "https://i.imgur.com/iMfwDk7.png"; - criticLogo.alt = "Fresh Tomato"; - } - tomatoLogo.style.width = "15px"; - tomatoLogo.style.height = "17px"; - criticLogo.style.width = "15px"; - criticLogo.style.height = "15px"; - tomatoRatingDiv.appendChild(tomatoLogo); - tomatoRatingDiv.appendChild(valueElement); - tomatoRatingDiv.appendChild(criticLogo); - tomatoRatingDiv.appendChild(createSeparator()); - const ageRatingDiv = document.createElement("div"); - ageRatingDiv.className = "age-rating"; - if (item.OfficialRating) { - ageRatingDiv.innerHTML = `${item.OfficialRating}`; - } else { - ageRatingDiv.innerHTML = `N/A`; - } - const calender = "📅"; - const premiereDate = document.createElement("div"); - premiereDate.className = "date"; - const year = date ? new Date(date).getFullYear() : "N/A"; - premiereDate.textContent = isNaN(year) ? "N/A" : year; - ratingTest.appendChild(tomatoRatingDiv); - ratingTest.appendChild(document.createTextNode(calender)); - ratingTest.appendChild(premiereDate); - ratingTest.appendChild(createSeparator()); - ratingTest.appendChild(ageRatingDiv); - ratingTest.appendChild(createSeparator()); - ratingTest.appendChild(runTimeElement); - const genresArray = item.Genres; - function parseGenres(genresArray) { - if (genresArray && genresArray.length > 0) { - return genresArray.slice(0, 3).join(" 🔹 "); - } else { - return "No Genre Available"; - } - } - const genreElement = document.createElement("div"); - genreElement.className = "genre"; - genreElement.innerHTML = parseGenres(genresArray); - const infoContainer = document.createElement("div"); - infoContainer.className = "info-container"; - infoContainer.appendChild(ratingTest); - // Create a container for the buttons -const buttonContainer = document.createElement("div"); -buttonContainer.className = "button-container"; // Add a class for styling + const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); + const truncateText = (element, maxLength) => { + let text = element.innerText; + if (text.length > maxLength) + element.innerText = text.substring(0, maxLength) + "..."; + }; + const createSlideElement = (item, title) => { + const itemId = item.Id; + const plot = item.Overview || "No overview available"; + const rating = item.CommunityRating; + const tomato = item.CriticRating; + const runtime = item.RunTimeTicks; + const genre = item.Genres; + const youtube = item.RemoteTrailers; + const age = item.OfficialRating; + const date = item.PremiereDate; + const season = item.ChildCount; + function createSeparator() { + const separatorHtml = + ''; + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = separatorHtml; + return tempDiv.firstChild; + } + const slide = document.createElement("a"); + slide.className = "slide"; + slide.target = "_top"; + slide.rel = "noreferrer"; + slide.tabIndex = 0; + slide.style.display = "none"; + const backdrop = document.createElement("img"); + backdrop.className = "backdrop"; + backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; + backdrop.alt = "Backdrop"; + const backdropOverlay = document.createElement("div"); + backdropOverlay.className = "backdrop-overlay"; + const backdropContainer = document.createElement("div"); + backdropContainer.className = "backdrop-container"; + backdropContainer.appendChild(backdrop); + backdropContainer.appendChild(backdropOverlay); + const logo = document.createElement("img"); + logo.className = "logo"; + logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; + logo.alt = "Logo"; + const logoContainer = document.createElement("div"); + logoContainer.className = "logo-container"; + logoContainer.appendChild(logo); + const featuredContent = document.createElement("div"); + featuredContent.className = "featured-content"; + featuredContent.textContent = title; + const plotElement = document.createElement("div"); + plotElement.className = "plot"; + plotElement.textContent = plot; + truncateText(plotElement, 360); + const plotContainer = document.createElement("div"); + plotContainer.className = "plot-container"; + plotContainer.appendChild(plotElement); + const gradientOverlay = document.createElement("div"); + gradientOverlay.className = "gradient-overlay"; + const runTimeElement = document.createElement("div"); + runTimeElement.className = "runTime"; + if (season === undefined) { + const milliseconds = runtime / 10000; + const currentTime = new Date(); + const endTime = new Date(currentTime.getTime() + milliseconds); + const options = { hour: "2-digit", minute: "2-digit", hour12: !0 }; + const formattedEndTime = endTime.toLocaleTimeString([], options); + runTimeElement.textContent = `Ends at ${formattedEndTime}`; + } else { + runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; + } + const ratingTest = document.createElement("div"); + ratingTest.className = "rating-value"; + const imdbLogo = document.createElement("img"); + imdbLogo.className = "imdb-logo"; + imdbLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg"; + imdbLogo.alt = "IMDb Logo"; + imdbLogo.style.width = "30px"; + imdbLogo.style.height = "30px"; + ratingTest.appendChild(imdbLogo); + if (typeof rating === "number") { + const formattedRating = rating.toFixed(1); + ratingTest.innerHTML += `${formattedRating} ⭐`; + } else { + console.error("Rating is undefined or not a number:", rating); + ratingTest.innerHTML += `N/A ⭐`; + } + ratingTest.appendChild(createSeparator()); + const tomatoRatingDiv = document.createElement("div"); + tomatoRatingDiv.className = "tomato-rating"; + const criticRating = item.CriticRating; + const tomatoLogo = document.createElement("img"); + tomatoLogo.className = "tomato-logo"; + const criticLogo = document.createElement("img"); + criticLogo.className = "critic-logo"; + tomatoLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; + let valueElement; + if (typeof criticRating === "number") { + valueElement = document.createTextNode(`${criticRating}% `); + } else { + valueElement = document.createTextNode(`N/A `); + } + if (criticRating === undefined || criticRating <= 59) { + criticLogo.src = + "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; + criticLogo.alt = "Rotten Tomato"; + } else { + criticLogo.src = "https://i.imgur.com/iMfwDk7.png"; + criticLogo.alt = "Fresh Tomato"; + } + tomatoLogo.style.width = "15px"; + tomatoLogo.style.height = "17px"; + criticLogo.style.width = "15px"; + criticLogo.style.height = "15px"; + tomatoRatingDiv.appendChild(tomatoLogo); + tomatoRatingDiv.appendChild(valueElement); + tomatoRatingDiv.appendChild(criticLogo); + tomatoRatingDiv.appendChild(createSeparator()); + const ageRatingDiv = document.createElement("div"); + ageRatingDiv.className = "age-rating"; + if (item.OfficialRating) { + ageRatingDiv.innerHTML = `${item.OfficialRating}`; + } else { + ageRatingDiv.innerHTML = `N/A`; + } + const calender = "📅"; + const premiereDate = document.createElement("div"); + premiereDate.className = "date"; + const year = date ? new Date(date).getFullYear() : "N/A"; + premiereDate.textContent = isNaN(year) ? "N/A" : year; + ratingTest.appendChild(tomatoRatingDiv); + ratingTest.appendChild(document.createTextNode(calender)); + ratingTest.appendChild(premiereDate); + ratingTest.appendChild(createSeparator()); + ratingTest.appendChild(ageRatingDiv); + ratingTest.appendChild(createSeparator()); + ratingTest.appendChild(runTimeElement); + const genresArray = item.Genres; + function parseGenres(genresArray) { + if (genresArray && genresArray.length > 0) { + return genresArray.slice(0, 3).join(" 🔹 "); + } else { + return "No Genre Available"; + } + } + const genreElement = document.createElement("div"); + genreElement.className = "genre"; + genreElement.innerHTML = parseGenres(genresArray); + const infoContainer = document.createElement("div"); + infoContainer.className = "info-container"; + infoContainer.appendChild(ratingTest); + // Create a container for the buttons + const buttonContainer = document.createElement("div"); + buttonContainer.className = "button-container"; // Add a class for styling -// Create the Play button -const playButton = document.createElement("button"); -playButton.className = "play-button"; -playButton.innerHTML = ` + // Create the Play button + const playButton = document.createElement("button"); + playButton.className = "play-button"; + playButton.innerHTML = ` - Play Now + Play `; -playButton.onclick = async () => { - if (!window.ApiClient) { - console.error("Jellyfin API client is not available."); - return; - } - const apiClient = window.ApiClient; - const userId = apiClient.getCurrentUserId(); - if (!userId) { - console.error("User not found."); - return; - } - try { - const sessions = await apiClient.getJSON(apiClient.serverAddress() + "/Sessions"); - if (!sessions.length) { - console.error("No active Jellyfin sessions found."); - return; - } + playButton.onclick = async () => { + if (!window.PlaybackManager) { + console.error("PlaybackManager is not available."); + return; + } - // Find the session matching the current user - const userSession = sessions.find(session => session.UserId === userId); - if (!userSession) { - console.error("No active session found for the current user."); - return; - } + if (!window.ApiClient) { + console.error("Jellyfin API client is not available."); + return; + } - const sessionId = userSession.Id; - const playbackUrl = `${apiClient.serverAddress()}/Sessions/${sessionId}/Playing?StartPositionTicks=0&PlayCommand=PlayNow&StartIndex=0&ItemIds=${itemId}&api_key=${token}`; + const apiClient = window.ApiClient; + const userId = apiClient.getCurrentUserId(); - const response = await fetch(playbackUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `DeviceId="unique-device-id", Token="${token}"`, - }, - }); + if (!userId) { + console.error("User not found."); + return; + } - if (response.status === 204) { - console.log("Playback started successfully."); - } else { - const errorData = await response.json(); - console.error("Failed to start playback:", errorData); - } - } catch (error) { - console.error("Error fetching sessions:", error); - } -}; + try { + // Fetch the media item details + const item = await apiClient.getItem(userId, itemId); + if (!item) { + console.error("Media item not found."); + return; + } -// Create the Details button -const detailButton = document.createElement("button"); -detailButton.className = "detail-button"; -detailButton.innerHTML = ` + // Initiate playback using PlaybackManager + window.PlaybackManager.play({ + items: [item], + startPositionTicks: 0, // Start from beginning + isMuted: false, + isPaused: false, + }) + .then(() => { + console.log("Playback started successfully."); + }) + .catch((error) => { + console.error("Failed to start playback:", error); + }); + } catch (error) { + console.error("Error starting playback:", error); + } + }; + + // Create the Details button + const detailButton = document.createElement("button"); + detailButton.className = "detail-button"; + detailButton.innerHTML = ` - More Info + Info `; -detailButton.onclick = () => { - window.top.location.href = `/#!/details?id=${itemId}`; -}; + detailButton.onclick = () => { + window.top.location.href = `/#!/details?id=${itemId}`; + }; -// Append buttons to the button container -buttonContainer.appendChild(playButton); -buttonContainer.appendChild(detailButton); + // Append buttons to the button container + buttonContainer.appendChild(playButton); + buttonContainer.appendChild(detailButton); -// Append all elements to the slide -slide.append( - logoContainer, - backdropContainer, - gradientOverlay, - featuredContent, - plotContainer, - infoContainer, - genreElement, - buttonContainer // Add button container instead of individual buttons -); + // Append all elements to the slide + slide.append( + logoContainer, + backdropContainer, + gradientOverlay, + featuredContent, + plotContainer, + infoContainer, + genreElement, + buttonContainer + ); -return slide; - }; - const createSlideForItem = async (item, title) => { - const container = document.getElementById("slides-container"); - const itemId = item.Id; - const backdropUrl = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; - const logoUrl = `${window.location.origin}/Items/${itemId}/Images/Logo`; - const [backdropExists, logoExists] = await Promise.all([ - fetch(backdropUrl, { method: "HEAD" }).then((res) => res.ok), - fetch(logoUrl, { method: "HEAD" }).then((res) => res.ok), - ]); - if (backdropExists && logoExists) { - const slideElement = createSlideElement(item, title); - container.appendChild(slideElement); - console.log(`Added slide for item ${itemId}`); - if (container.children.length === 1) { - showSlide(0); - } - } else { - console.warn(`Skipping item ${itemId}: Missing backdrop or logo.`); - } - }; - const fetchItemDetails = async (itemId) => { - const response = await fetch( - `${window.location.origin}/Users/${userId}/Items/${itemId}`, - { - headers: { - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, - }, - } - ); - const item = await response.json(); - console.log("Item Title:", item.Name); - return item; - }; - const fetchItemIdsFromList = async () => { - try { - const response = await fetch(listFileName); - if (!response.ok) { - throw new Error("Failed to fetch list.txt"); - } - const text = await response.text(); - return text - .split("\n") - .map((id) => id.trim()) - .filter((id) => id); - } catch (error) { - console.error("Error fetching list.txt:", error); - return []; - } - }; - const fetchItemsFromServer = async () => { - try { - const response = await fetch( - `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, - { - headers: { - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, - }, - } - ); - const data = await response.json(); - const items = data.Items; - const movies = items.filter((item) => item.Type === "Movie"); - const tvShows = items.filter((item) => item.Type === "Series"); - const shuffledMovies = shuffleArray(movies); - const shuffledTvShows = shuffleArray(tvShows); - const selectedMovies = shuffledMovies.slice(0, 15); - const selectedTvShows = shuffledTvShows.slice(0, 15); - const allItems = []; - const maxLength = Math.max(selectedMovies.length, selectedTvShows.length); - for (let i = 0; i < maxLength; i++) { - if (i < selectedMovies.length) allItems.push(selectedMovies[i]); - if (i < selectedTvShows.length) allItems.push(selectedTvShows[i]); - } - return allItems; - } catch (error) { - console.error("Error fetching items:", error); - return []; - } - }; - const createSlidesForItems = async (items) => { - await Promise.all( - items.map((item) => - createSlideForItem(item, item.Type === "Movie" ? "Movie" : "TV Show") - ) - ); - }; - const createPaginationDots = () => { - const dotsContainer = document.createElement("div"); - dotsContainer.className = "dots-container"; - for (let i = 0; i < 5; i++) { - const dot = document.createElement("span"); - dot.className = "dot"; - dotsContainer.appendChild(dot); - } - const container = document.getElementById("slides-container"); - container.appendChild(dotsContainer); - }; - const showSlide = (index) => { - const slides = document.querySelectorAll(".slide"); - const dots = document.querySelectorAll(".dot"); - slides.forEach((slide, i) => { - if (i === index) { - slide.style.display = "block"; - slide.offsetHeight; - slide.style.opacity = "1"; - slide.classList.add("active"); - } else { - slide.style.opacity = "0"; - slide.classList.remove("active"); - setTimeout(() => { - slide.style.display = "none"; - }, 500); - } - }); - dots.forEach((dot, i) => { - dot.classList.toggle("active", i === index % 5); - }); - }; - const initializeSlideshow = () => { - const slides = document.querySelectorAll(".slide"); - createPaginationDots(); - const container = document.getElementById("slides-container"); - let currentSlideIndex = 0; - let focusedSlide = null; - let containerFocused = !1; - const updateCurrentSlide = (index) => { - if (isTransitioning) return; - isTransitioning = true; - currentSlideIndex = (index + slides.length) % slides.length; - showSlide(currentSlideIndex); - setTimeout(() => { - isTransitioning = false; - }, 500); + return slide; }; - const openActiveSlide = () => { - if (focusedSlide) { - window.location.href = focusedSlide.href; - } - }; - if (slides.length > 0) { - showSlide(currentSlideIndex); - container.style.display = "block"; - setTimeout(() => { - setInterval(() => { - updateCurrentSlide(currentSlideIndex + 1); - }, shuffleInterval); - }, 8000); - } - slides.forEach((slide) => { - slide.addEventListener( - "focus", - () => { - focusedSlide = slide; - container.classList.remove("disable-interaction"); - }, - !0 - ); - slide.addEventListener( - "blur", - () => { - if (focusedSlide === slide) { - focusedSlide = null; - } - }, - !0 - ); - }); - document.addEventListener("keydown", (event) => { - if (containerFocused) { - switch (event.keyCode) { - case 37: - updateCurrentSlide(currentSlideIndex - 1); - break; - case 39: - updateCurrentSlide(currentSlideIndex + 1); - break; - case 13: - case 32: - openActiveSlide(); - break; + const createSlideForItem = async (item, title) => { + const container = document.getElementById("slides-container"); + const itemId = item.Id; + const backdropUrl = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; + const logoUrl = `${window.location.origin}/Items/${itemId}/Images/Logo`; + const [backdropExists, logoExists] = await Promise.all([ + fetch(backdropUrl, { method: "HEAD" }).then((res) => res.ok), + fetch(logoUrl, { method: "HEAD" }).then((res) => res.ok), + ]); + if (backdropExists && logoExists) { + const slideElement = createSlideElement(item, title); + container.appendChild(slideElement); + console.log(`Added slide for item ${itemId}`); + if (container.children.length === 1) { + showSlide(0); + } + } else { + console.warn(`Skipping item ${itemId}: Missing backdrop or logo.`); } - } - }); - const leftArrow = document.createElement("div"); - const rightArrow = document.createElement("div"); - leftArrow.classList.add("arrow", "left-arrow"); - rightArrow.classList.add("arrow", "right-arrow"); - leftArrow.innerHTML = ''; - rightArrow.innerHTML = ''; - container.appendChild(leftArrow); - container.appendChild(rightArrow); - leftArrow.addEventListener("click", () => - updateCurrentSlide(currentSlideIndex - 1) - ); - rightArrow.addEventListener("click", () => - updateCurrentSlide(currentSlideIndex + 1) - ); - container.addEventListener("focus", () => { - containerFocused = !0; - }); - container.addEventListener("blur", () => { - containerFocused = !1; - }); - }; - const initializeSlides = async () => { - const itemIds = await fetchItemIdsFromList(); - let items; - if (itemIds.length > 0) { - const itemPromises = itemIds.map((id) => fetchItemDetails(id)); - items = await Promise.all(itemPromises); - } else { - const allItems = await fetchItemsFromServer(); - const itemPromises = allItems.map((item) => fetchItemDetails(item.Id)); - items = await Promise.all(itemPromises); - } - items = shuffleArray(items); - await createSlidesForItems(items); - initializeSlideshow(); - }; - initializeSlides(); + }; + const fetchItemDetails = async (itemId) => { + const response = await fetch( + `${window.location.origin}/Users/${userId}/Items/${itemId}`, + { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + } + ); + const item = await response.json(); + console.log("Item Title:", item.Name); + return item; + }; + const fetchItemIdsFromList = async () => { + try { + const response = await fetch(listFileName); + if (!response.ok) { + throw new Error("Failed to fetch list.txt"); + } + const text = await response.text(); + return text + .split("\n") + .map((id) => id.trim()) + .filter((id) => id); + } catch (error) { + console.error("Error fetching list.txt:", error); + return []; + } + }; + const fetchItemsFromServer = async () => { + try { + const response = await fetch( + `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, + { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + } + ); + const data = await response.json(); + const items = data.Items; + const movies = items.filter((item) => item.Type === "Movie"); + const tvShows = items.filter((item) => item.Type === "Series"); + const shuffledMovies = shuffleArray(movies); + const shuffledTvShows = shuffleArray(tvShows); + const selectedMovies = shuffledMovies.slice(0, 15); + const selectedTvShows = shuffledTvShows.slice(0, 15); + const allItems = []; + const maxLength = Math.max(selectedMovies.length, selectedTvShows.length); + for (let i = 0; i < maxLength; i++) { + if (i < selectedMovies.length) allItems.push(selectedMovies[i]); + if (i < selectedTvShows.length) allItems.push(selectedTvShows[i]); + } + return allItems; + } catch (error) { + console.error("Error fetching items:", error); + return []; + } + }; + const createSlidesForItems = async (items) => { + await Promise.all( + items.map((item) => + createSlideForItem(item, item.Type === "Movie" ? "Movie" : "TV Show") + ) + ); + }; + const createPaginationDots = () => { + const dotsContainer = document.createElement("div"); + dotsContainer.className = "dots-container"; + for (let i = 0; i < 5; i++) { + const dot = document.createElement("span"); + dot.className = "dot"; + dotsContainer.appendChild(dot); + } + const container = document.getElementById("slides-container"); + container.appendChild(dotsContainer); + }; + const showSlide = (index) => { + const slides = document.querySelectorAll(".slide"); + const dots = document.querySelectorAll(".dot"); + slides.forEach((slide, i) => { + if (i === index) { + slide.style.display = "block"; + slide.offsetHeight; + slide.style.opacity = "1"; + slide.classList.add("active"); + } else { + slide.style.opacity = "0"; + slide.classList.remove("active"); + setTimeout(() => { + slide.style.display = "none"; + }, 500); + } + }); + dots.forEach((dot, i) => { + dot.classList.toggle("active", i === index % 5); + }); + }; + const initializeSlideshow = () => { + const slides = document.querySelectorAll(".slide"); + createPaginationDots(); + const container = document.getElementById("slides-container"); + let currentSlideIndex = 0; + let focusedSlide = null; + let containerFocused = !1; + const updateCurrentSlide = (index) => { + if (isTransitioning) return; + isTransitioning = true; + currentSlideIndex = (index + slides.length) % slides.length; + showSlide(currentSlideIndex); + setTimeout(() => { + isTransitioning = false; + }, 500); + }; + const openActiveSlide = () => { + if (focusedSlide) { + window.location.href = focusedSlide.href; + } + }; + if (slides.length > 0) { + showSlide(currentSlideIndex); + container.style.display = "block"; + setTimeout(() => { + setInterval(() => { + updateCurrentSlide(currentSlideIndex + 1); + }, shuffleInterval); + }, 8000); + } + slides.forEach((slide) => { + slide.addEventListener( + "focus", + () => { + focusedSlide = slide; + container.classList.remove("disable-interaction"); + }, + !0 + ); + slide.addEventListener( + "blur", + () => { + if (focusedSlide === slide) { + focusedSlide = null; + } + }, + !0 + ); + }); + document.addEventListener("keydown", (event) => { + if (containerFocused) { + switch (event.keyCode) { + case 37: + updateCurrentSlide(currentSlideIndex - 1); + break; + case 39: + updateCurrentSlide(currentSlideIndex + 1); + break; + case 13: + case 32: + openActiveSlide(); + break; + } + } + }); + const leftArrow = document.createElement("div"); + const rightArrow = document.createElement("div"); + leftArrow.classList.add("arrow", "left-arrow"); + rightArrow.classList.add("arrow", "right-arrow"); + leftArrow.innerHTML = ''; + rightArrow.innerHTML = ''; + container.appendChild(leftArrow); + container.appendChild(rightArrow); + leftArrow.addEventListener("click", () => + updateCurrentSlide(currentSlideIndex - 1) + ); + rightArrow.addEventListener("click", () => + updateCurrentSlide(currentSlideIndex + 1) + ); + container.addEventListener("focus", () => { + containerFocused = !0; + }); + container.addEventListener("blur", () => { + containerFocused = !1; + }); + }; + const initializeSlides = async () => { + const itemIds = await fetchItemIdsFromList(); + let items; + if (itemIds.length > 0) { + const itemPromises = itemIds.map((id) => fetchItemDetails(id)); + items = await Promise.all(itemPromises); + } else { + const allItems = await fetchItemsFromServer(); + const itemPromises = allItems.map((item) => fetchItemDetails(item.Id)); + items = await Promise.all(itemPromises); + } + items = shuffleArray(items); + await createSlidesForItems(items); + initializeSlideshow(); + }; + initializeSlides(); }; document.addEventListener("DOMContentLoaded", () => { - document.body.addEventListener("click", (event) => { - const homeButton = event.target.matches( - ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" - ); - if (homeButton) { - event.preventDefault(); - window.location.href = "/web/index.html#/home.html"; - setTimeout(handleHomeNavigation, 300); - } - }); -}); + document.body.addEventListener("click", (event) => { + const homeButton = event.target.matches( + ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" + ); + if (homeButton) { + event.preventDefault(); + window.location.href = "/web/index.html#/home.html"; + setTimeout(handleHomeNavigation, 300); + } + }); +}); \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs index 4f45027..ced7f3e 100644 --- a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs +++ b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs @@ -48,6 +48,14 @@ namespace Jellyfin.Plugin.MediaBar.Services payloads.Add(payload); } + { + JObject payload = new JObject(); + payload.Add("id", "3d171ef1-a198-48ac-9a60-f6aa98e5fd6d"); + payload.Add("fileNamePattern", "main.jellyfin.bundle.js"); + payload.Add("tranformationEndpoint", "/MediaBar/Patch/MainJellyfinBundle"); + + payloads.Add(payload); + } string? publishedServerUrl = m_serverApplicationHost.GetType() .GetProperty("PublishedServerUrl", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(m_serverApplicationHost) as string; From 2f3b9d4f32c6466f77b4669506bbee58da6161a2 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 12 Feb 2025 13:42:26 +0000 Subject: [PATCH 058/108] Updated with additional diagnostic logging and corrected a typo. --- .../Services/StartupService.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs index ced7f3e..4b2ffe8 100644 --- a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs +++ b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs @@ -30,13 +30,15 @@ namespace Jellyfin.Plugin.MediaBar.Services public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { + m_logger.LogInformation($"MediaBar Startup. Registering file transformations."); + List payloads = new List(); { JObject payload = new JObject(); payload.Add("id", "0dfac9d7-d898-4944-900b-1c1837707279"); payload.Add("fileNamePattern", "index.html"); - payload.Add("tranformationEndpoint", "/MediaBar/Patch/IndexHtml"); + payload.Add("transformationEndpoint", "/MediaBar/Patch/IndexHtml"); payloads.Add(payload); } @@ -44,7 +46,7 @@ namespace Jellyfin.Plugin.MediaBar.Services JObject payload = new JObject(); payload.Add("id", "e6d32b76-d54b-4946-b73e-c5c9c50575c9"); payload.Add("fileNamePattern", "home-html\\.[a-zA-z0-9]+\\.chunk\\.js"); - payload.Add("tranformationEndpoint", "/MediaBar/Patch/HomeHtmlChunk"); + payload.Add("transformationEndpoint", "/MediaBar/Patch/HomeHtmlChunk"); payloads.Add(payload); } @@ -52,21 +54,26 @@ namespace Jellyfin.Plugin.MediaBar.Services JObject payload = new JObject(); payload.Add("id", "3d171ef1-a198-48ac-9a60-f6aa98e5fd6d"); payload.Add("fileNamePattern", "main.jellyfin.bundle.js"); - payload.Add("tranformationEndpoint", "/MediaBar/Patch/MainJellyfinBundle"); + payload.Add("transformationEndpoint", "/MediaBar/Patch/MainJellyfinBundle"); payloads.Add(payload); } string? publishedServerUrl = m_serverApplicationHost.GetType() .GetProperty("PublishedServerUrl", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(m_serverApplicationHost) as string; + m_logger.LogInformation($"Retrieved value for published server URL: {publishedServerUrl}"); + HttpClient client = new HttpClient(); + client.BaseAddress = new Uri(publishedServerUrl ?? $"http://localhost:{m_serverApplicationHost.HttpPort}"); + + m_logger.LogInformation($"Setting base address to: {client.BaseAddress}."); + m_logger.LogInformation($"Retrieving media bar payloads."); foreach (JObject payload in payloads) { - HttpClient client = new HttpClient(); - client.BaseAddress = new Uri(publishedServerUrl ?? $"http://localhost:{m_serverApplicationHost.HttpPort}"); - try { + m_logger.LogInformation($"Registering transformation '{payload.Value("id")}' with endpoint '{payload.Value("transformationEndpoint")}'"); + await client.PostAsync("/FileTransformation/RegisterTransformation", new StringContent(payload.ToString(Formatting.None), MediaTypeHeaderValue.Parse("application/json"))); From a5cfdfebd990e9a74fda973eae01e5adf4250dfc Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 12 Feb 2025 23:25:58 +0000 Subject: [PATCH 059/108] Added 5 second delay to the startup script for all plugins to ensure that file transformation would be ready to receive requests when it starts. --- src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs index 4b2ffe8..8b65539 100644 --- a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs +++ b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs @@ -1,4 +1,5 @@ -using System.Net.Http.Headers; +using System.Net; +using System.Net.Http.Headers; using System.Reflection; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; @@ -31,6 +32,8 @@ namespace Jellyfin.Plugin.MediaBar.Services public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { m_logger.LogInformation($"MediaBar Startup. Registering file transformations."); + m_logger.LogInformation("Delaying 5 seconds to ensure file transformation is ready to receive requests."); + await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); List payloads = new List(); From 59041dc81c4c1f7573de560eef6ea1b791869949 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 12 Feb 2025 23:41:14 +0000 Subject: [PATCH 060/108] Merged MakD:main into plugin version. --- .../Inject/media-bar.js | 81 +++++------ .../Inject/slideshowpure.js | 130 ++++++++++++------ 2 files changed, 122 insertions(+), 89 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js index ec47dd9..713367a 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js @@ -1,54 +1,35 @@ -function saveCredentialsToSessionStorage(credentials) { - try { - sessionStorage.setItem( - "json-credentials", - JSON.stringify(credentials) - ); - console.log("Credentials saved to sessionStorage."); - } catch (error) { - console.error("Error saving credentials:", error); - } -} -function saveApiKey(apiKey) { - try { - sessionStorage.setItem("api-key", apiKey); - console.log("API key saved to sessionStorage."); - } catch (error) { - console.error("Error saving API key:", error); - } -} -(function () { - var originalConsoleLog = console.log; - console.log = function (message) { - originalConsoleLog.apply(console, arguments); - if ( - typeof message === "string" && - message.startsWith("Stored JSON credentials:") - ) { - try { - var jsonString = message.substring( - "Stored JSON credentials: ".length - ); - var credentials = JSON.parse(jsonString); - saveCredentialsToSessionStorage(credentials); - } catch (error) { - console.error("Error parsing credentials:", error); - } - } - if ( - typeof message === "string" && - message.startsWith("opening web socket with url:") - ) { - try { - var url = message.split("url:")[1].trim(); - var urlParams = new URL(url).searchParams; - var apiKey = urlParams.get("api_key"); - if (apiKey) { - saveApiKey(apiKey); +(function () { + // List of CSS selectors for Home buttons + const buttonSelectors = [ + ".headerHomeButton.barsMenuButton", + ".css-17c09up", + ".mainDrawer-scrollContainer > a:nth-child(2)" + ]; + + // Polling interval to check for buttons + const intervalId = setInterval(function () { + buttonSelectors.forEach(selector => { + // Try to find the button + const homeButton = document.querySelector(selector); + + // If the button is found + if (homeButton) { + // Attach the click event listener if not already added + if (!homeButton.hasAttribute("data-home-handler")) { + homeButton.addEventListener("click", function (event) { + event.preventDefault(); // Prevent default behavior if necessary + window.location.href = "/web/index.html#/home.html"; + }); + + // Mark the button as handled + homeButton.setAttribute("data-home-handler", "true"); } - } catch (error) { - console.error("Error extracting API key:", error); } + }); + + // Stop polling if all buttons are found + if (buttonSelectors.every(selector => document.querySelector(selector))) { + clearInterval(intervalId); } - }; + }, 100); // Check every 100ms })(); \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js index 27dc4e0..39d5be6 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -1,34 +1,73 @@ -const slidesInit = () => { +/** + * Credentials for a given server + * @typedef {Object} ServerCredentials + * @property {string} Id + * @property {string} AccessToken + */ + +/** + * Credentials + * @typedef {Object} Credentials + * @property {ServerCredentials[]} Servers + */ + +/** + * Fetches the credentials inside of localstorage + * @returns {{token: string, userId: string}} Credential resource + */ +const getJellyfinCredentials = () => { + const jellyfinCreds = localStorage.getItem("jellyfin_credentials"); + + try { + /** + * @type {Credentials} + */ + const serverCredentials = JSON.parse(jellyfinCreds); + + const firstServer = serverCredentials.Servers[0]; + + if (!firstServer) { + console.error("Could not find credentials for the client"); + return; + } + + return { token: firstServer.AccessToken, userId: firstServer.UserId }; + } catch (e) { + console.error("Could not parse jellyfin credentials", e); + } +}; + +const slidesInit = () => { + if (window.hasInitializedSlideshow) { + console.log("Slideshow already initialized. Skipping re-init."); + return; + } + window.hasInitializedSlideshow = true; const shuffleInterval = 8000; let isTransitioning = false; const listFileName = `${window.location.origin}/web/avatars/list.txt`; - const jsonCredentials = sessionStorage.getItem("json-credentials"); - let userId = null; - let token = null; - //const deviceId = localStorage.getItem("_deviceId2"); - //console.log(deviceId); - if (jsonCredentials) { - const credentials = JSON.parse(jsonCredentials); - userId = credentials.Servers[0].UserId; - token = credentials.Servers[0].AccessToken; - } + + const { token, userId } = getJellyfinCredentials(); + const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); const truncateText = (element, maxLength) => { let text = element.innerText; if (text.length > maxLength) element.innerText = text.substring(0, maxLength) + "..."; }; + const createSlideElement = (item, title) => { const itemId = item.Id; const plot = item.Overview || "No overview available"; const rating = item.CommunityRating; - const tomato = item.CriticRating; + const criticRating = item.CriticRating; const runtime = item.RunTimeTicks; - const genre = item.Genres; + const genresArray = item.Genres; const youtube = item.RemoteTrailers; const age = item.OfficialRating; const date = item.PremiereDate; const season = item.ChildCount; + function createSeparator() { const separatorHtml = ''; @@ -46,33 +85,43 @@ backdrop.className = "backdrop"; backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; backdrop.alt = "Backdrop"; + const backdropOverlay = document.createElement("div"); backdropOverlay.className = "backdrop-overlay"; + const backdropContainer = document.createElement("div"); backdropContainer.className = "backdrop-container"; backdropContainer.appendChild(backdrop); backdropContainer.appendChild(backdropOverlay); + const logo = document.createElement("img"); logo.className = "logo"; logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; logo.alt = "Logo"; + const logoContainer = document.createElement("div"); logoContainer.className = "logo-container"; logoContainer.appendChild(logo); + const featuredContent = document.createElement("div"); featuredContent.className = "featured-content"; featuredContent.textContent = title; + const plotElement = document.createElement("div"); plotElement.className = "plot"; plotElement.textContent = plot; truncateText(plotElement, 360); + const plotContainer = document.createElement("div"); plotContainer.className = "plot-container"; plotContainer.appendChild(plotElement); + const gradientOverlay = document.createElement("div"); gradientOverlay.className = "gradient-overlay"; + const runTimeElement = document.createElement("div"); runTimeElement.className = "runTime"; + if (season === undefined) { const milliseconds = runtime / 10000; const currentTime = new Date(); @@ -83,8 +132,10 @@ } else { runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; } + const ratingTest = document.createElement("div"); ratingTest.className = "rating-value"; + const imdbLogo = document.createElement("img"); imdbLogo.className = "imdb-logo"; imdbLogo.src = @@ -93,6 +144,7 @@ imdbLogo.style.width = "30px"; imdbLogo.style.height = "30px"; ratingTest.appendChild(imdbLogo); + if (typeof rating === "number") { const formattedRating = rating.toFixed(1); ratingTest.innerHTML += `${formattedRating} ⭐`; @@ -101,21 +153,25 @@ ratingTest.innerHTML += `N/A ⭐`; } ratingTest.appendChild(createSeparator()); + const tomatoRatingDiv = document.createElement("div"); tomatoRatingDiv.className = "tomato-rating"; - const criticRating = item.CriticRating; + const tomatoLogo = document.createElement("img"); tomatoLogo.className = "tomato-logo"; - const criticLogo = document.createElement("img"); - criticLogo.className = "critic-logo"; tomatoLogo.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; + + const criticLogo = document.createElement("img"); + criticLogo.className = "critic-logo"; + let valueElement; if (typeof criticRating === "number") { valueElement = document.createTextNode(`${criticRating}% `); } else { valueElement = document.createTextNode(`N/A `); } + if (criticRating === undefined || criticRating <= 59) { criticLogo.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; @@ -128,22 +184,28 @@ tomatoLogo.style.height = "17px"; criticLogo.style.width = "15px"; criticLogo.style.height = "15px"; + tomatoRatingDiv.appendChild(tomatoLogo); tomatoRatingDiv.appendChild(valueElement); tomatoRatingDiv.appendChild(criticLogo); tomatoRatingDiv.appendChild(createSeparator()); + const ageRatingDiv = document.createElement("div"); ageRatingDiv.className = "age-rating"; + if (item.OfficialRating) { ageRatingDiv.innerHTML = `${item.OfficialRating}`; } else { ageRatingDiv.innerHTML = `N/A`; } + const calender = "📅"; const premiereDate = document.createElement("div"); premiereDate.className = "date"; + const year = date ? new Date(date).getFullYear() : "N/A"; premiereDate.textContent = isNaN(year) ? "N/A" : year; + ratingTest.appendChild(tomatoRatingDiv); ratingTest.appendChild(document.createTextNode(calender)); ratingTest.appendChild(premiereDate); @@ -151,7 +213,7 @@ ratingTest.appendChild(ageRatingDiv); ratingTest.appendChild(createSeparator()); ratingTest.appendChild(runTimeElement); - const genresArray = item.Genres; + function parseGenres(genresArray) { if (genresArray && genresArray.length > 0) { return genresArray.slice(0, 3).join(" 🔹 "); @@ -159,9 +221,11 @@ return "No Genre Available"; } } + const genreElement = document.createElement("div"); genreElement.className = "genre"; genreElement.innerHTML = parseGenres(genresArray); + const infoContainer = document.createElement("div"); infoContainer.className = "info-container"; infoContainer.appendChild(ratingTest); @@ -271,14 +335,11 @@ } }; const fetchItemDetails = async (itemId) => { - const response = await fetch( - `${window.location.origin}/Users/${userId}/Items/${itemId}`, - { - headers: { - Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, - }, - } - ); + const response = await fetch(`${window.location.origin}/Items/${itemId}`, { + headers: { + Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, + }, + }); const item = await response.json(); console.log("Item Title:", item.Name); return item; @@ -302,7 +363,7 @@ const fetchItemsFromServer = async () => { try { const response = await fetch( - `${window.location.origin}/Users/${userId}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&isPlayed=False&Limit=150`, + `${window.location.origin}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&SortBy=random&isPlayed=False&Limit=500`, { headers: { Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, @@ -352,6 +413,8 @@ const dots = document.querySelectorAll(".dot"); slides.forEach((slide, i) => { if (i === index) { + console.log(`Showing slide ${index}`); // Debugging + slide.style.removeProperty("display"); slide.style.display = "block"; slide.offsetHeight; slide.style.opacity = "1"; @@ -363,6 +426,7 @@ slide.style.display = "none"; }, 500); } + document.getElementById("slides-container").offsetHeight; }); dots.forEach((dot, i) => { dot.classList.toggle("active", i === index % 5); @@ -470,16 +534,4 @@ initializeSlideshow(); }; initializeSlides(); -}; -document.addEventListener("DOMContentLoaded", () => { - document.body.addEventListener("click", (event) => { - const homeButton = event.target.matches( - ".headerHomeButton, .css-17c09up, .mainDrawer-scrollContainer > a:nth-child(2)" - ); - if (homeButton) { - event.preventDefault(); - window.location.href = "/web/index.html#/home.html"; - setTimeout(handleHomeNavigation, 300); - } - }); -}); \ No newline at end of file +}; \ No newline at end of file From 33839f186ff48194480992e5eee4620619a4a951 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 12 Feb 2025 23:50:37 +0000 Subject: [PATCH 061/108] Add logo to install package directory. --- src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 8068433..29684b2 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -32,6 +32,7 @@ + From d71bc1ec3b02d9cee2946690b927c528b45393e0 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 17 Feb 2025 21:46:44 +0000 Subject: [PATCH 062/108] Updated to support Jellyfin Version 10.10.6.0 --- .../Jellyfin.Plugin.MediaBar.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 29684b2..ba43814 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -5,14 +5,14 @@ enable enable false - 2.0.0 + 2.1.0.0 - - - - + + + + From 3c8913e85e7e4ba380ed8773697fd73602b42407 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 24 Feb 2025 22:09:51 +0000 Subject: [PATCH 063/108] Updated js/css from upstream --- .../Inject/slideshowpure.css | 2705 ++++++++++++++--- .../Inject/slideshowpure.js | 182 +- 2 files changed, 2335 insertions(+), 552 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css index e3b74dd..b44b624 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css @@ -1,451 +1,105 @@ -@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap"); - -@media only screen and (min-width: 767px) { - div[data-role="page"].page { - overflow-y: scroll; - scrollbar-width: none; - -ms-overflow-style: none; +@import url(https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap); +.bar-loading { + z-index: 99999999; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0); + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} +.bar-loading h1 img { + opacity: 1; + display: block; + z-index: 999999999 !important; + visibility: visible; +} +.docspinner-loader { + width: 10px; + height: 10px; + margin: 50px auto 0; + position: relative; + border-radius: 50%; + border: 4px solid #fff0; + border-top-color: #fff; + animation: spin 1s linear infinite; + z-index: 999999999 !important; +} +@keyframes spin { + 0% { + transform: rotate(0deg); } - - div[data-role="page"].page::-webkit-scrollbar { - display: none; + 100% { + transform: rotate(360deg); } - +} +/* Mobile (Portrait) */ +@media only screen and (max-width: 480px) and (orientation: portrait) { .backdrop, .slide { position: absolute; } - - .featured-content, - .plot-container { - font-family: "Titillium Web", sans-serif; - left: 0; - } - - .plot, - body { - overflow: hidden; - } - .arrow { position: absolute; top: 50%; transform: translateY(-50%); font-size: 24px; cursor: pointer; - color: white; - z-index: 3; - display: flex; - justify-content: center; - align-items: center; - } - - .arrow::before { - content: ""; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 40px; - height: 40px; - background-color: rgba(12, 110, 162, 0.7); - border-radius: 50%; - z-index: -1; - } - - .left-arrow { - left: 20px; - } - - .right-arrow { - right: 20px; - } - - .dots-container { - position: absolute; - bottom: 5%; - left: 0; - right: 0; - z-index: 2; - display: flex; - justify-content: center; - align-items: center; - width: 100vw; - height: 22px; - background-color: #ffffff00; - gap: 2vw; - } - - .dot { - width: 15vw; - height: 4px; - background-color: #cecece; - border-radius: 2px; - cursor: pointer; - transition: background-color 0.3s; - } - - .dot.active { - background-color: rgba(12, 110, 162, 0.7) !important; - } - - body { - margin: 0; - padding: 0; - } - - .fade-in { - opacity: 1 !important; - } - - .fade-out { - opacity: 0 !important; - } - - #slides-container { - position: relative; - width: 92vw; - height: 650px; - overflow: hidden; - margin: 0 auto; - border-radius: 15px; - } - - .slide { - transition: opacity 0.5s ease-in-out; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; - } - - .slide.active { - opacity: 1; - display: block; - } - - .slide:focus { - outline: #fff solid 2px; - } - - .backdrop-container { - position: relative; - top: -40%; - right: -35%; - width: 70%; - height: 100%; - } - - .backdrop { - right: 0; - width: 100%; - height: 100%; - object-fit: cover; - object-position: center 20%; - border-radius: 5px; - z-index: 1; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } - - .backdrop-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.6); - border-radius: 5px; - z-index: 2; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% - ); - } - - .logo-container { - width: 45%; - height: 40%; - position: relative; - display: flex; - justify-content: left; - align-items: left; - } - - .logo { - position: relative; - max-height: 100%; - max-width: 100%; - height: auto; - width: auto; - object-fit: contain; - z-index: 3; - top: 5em; - left: 8em; - } - - .featured-content { - position: absolute; - top: 0; - width: 100%; - height: 50px; - background-color: transparent; - color: #d3d3d3; - font-size: 22px; + color: rgb(255 255 255 / 0.6); + z-index: 4; + -webkit-tap-highlight-color: #fff0; display: none; - align-items: center; - justify-content: flex-start; - z-index: 2; } - - .plot-container { - position: absolute; - bottom: 0; - color: #fff; - width: 90%; - height: 60%; - font-size: 15px; - padding: 10px 0 25px 15px; - border-radius: 5px; - z-index: 3; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - text-align: justify; - } - - .plot { - display: -webkit-box; - line-clamp: 4; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - left: 7.9em; - position: absolute; - top: 190px; - } - - .gradient-overlay { - position: absolute; - top: 0; - left: 0; - width: 70%; - height: 100%; - background: linear-gradient(to right, #000 49%, rgba(0, 0, 0, 0) 70%); - z-index: 2; - mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0.5) 10%, - rgba(0, 0, 0, 1) 12% - ); - -webkit-mask-image: linear-gradient( - to top, - rgba(0, 0, 0, 0) 8%, - rgba(0, 0, 0, 0.5) 10%, - rgba(0, 0, 0, 1) 12% - ); - } - .genre { - position: absolute; - top: calc(50% + 75px); - left: 8.4em; - text-align: center; - color: #fff; - font-family: "Titillium Web", sans-serif; - font-size: 14px; - z-index: 4; - } - .info-container { - position: absolute; - top: calc(50% + 0px); - display: flex; - align-items: center; - justify-content: center; - left: 2em !important; - color: #fff; - z-index: 4; - } - .rating-value { - font-family: "Titillium Web", sans-serif; - display: flex; - align-items: center; - margin: 0 6em; - z-index: 5; - position: relative; - top: 1.5em; - } - .runTime { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin: 0 8px; - margin-right: -3px; - } - .rating-value img { - margin-right: 6px; - } - .tomato-rating { - display: flex; - align-items: center; - margin-left: 0; - } - .tomato-rating img { - margin-left: 5px; - } - .age-rating { - display: flex; - align-items: center; - margin-left: 5px; - margin-right: 5px; - border-radius: 5px; - background: rgb(255 255 255 / 0.8); - color: #000; - border: none; - font-weight: 600; - white-space: nowrap; - padding: 0 0.6em; - } - .date { - font-family: "Titillium Web", sans-serif; - font-weight: 500; - display: flex; - align-items: center; - margin-right: 5px; - margin-left: 2px; - } - .separator-icon { - margin-left: 5px; - margin-right: 5px; - font-size: 10px; - color: aquamarine; - } - .button-container { - position: absolute; - top: calc(50% + 190px); /* Same as the previous button positions */ - left: 7.5em; - display: flex; - gap: 2em; /* Space between buttons */ - z-index: 5; /* Ensures it's above other elements */ - } - .play-button, - .detail-button { - display: inline-flex; /* Ensures icon and text are on the same line */ - align-items: center; - justify-content: center; - padding: 8px 16px; /* Slightly reduced padding for thinner buttons */ - border: solid 2px; - border-radius: 25px; /* Sleeker look */ - font-size: 14px; - white-space: nowrap; /* Prevents text wrapping */ - cursor: pointer; - transition: all 0.3s ease; - font-weight: bold; - gap: 6px; /* Adds space between icon and text */ - -webkit-tap-highlight-color: transparent; - } - - .play-button { - background-color: #ffffff; - color: #000; - } - - .detail-button { - background-color: rgba(255, 255, 255, 0); - color: #fff; - } - - .play-button:hover, - .detail-button:hover { - opacity: 0.8; - } - - .play-icon, - .detail-icon { - font-size: 18px; /* Ensures icons are aligned with text */ - display: flex; - align-items: center; - } -} - -@media only screen and (max-width: 767px) { - .backdrop, - .slide { - position: absolute; - } - - .arrow { - position: absolute; - bottom: 25px; - font-size: 24px; - cursor: pointer; - color: rgba(255, 255, 255, 0.6); - z-index: 4; - -webkit-tap-highlight-color: transparent; - } - .left-arrow { left: 10px; } - .right-arrow { right: 10px; } - .featured-content, .plot-container { font-family: "Titillium Web", sans-serif; left: 0; } - .plot, body { overflow: hidden; } - body { margin: 0; padding: 0; + height: 100%; } - .fade-in { opacity: 1 !important; } - .fade-out { opacity: 0 !important; } - + #homeTab { + top: -5em; + padding-bottom: 5em !important; + position: relative; + padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -11em; + } .dots-container { position: absolute; - bottom: 11%; + bottom: 20%; left: 50%; transform: translateX(-50%) scale(0.6); z-index: 2; display: flex; gap: 5px; - background-color: #ffffff00; + background-color: #ffffff13; border-radius: 50px; justify-content: center; align-items: center; @@ -453,7 +107,6 @@ height: 22px; transition: opacity 0.5s ease-in-out; } - .dot { display: inline-block; width: 9px; @@ -463,23 +116,21 @@ border-radius: 50%; transition: background-color 0.3s; } - .dot.active { - width: 32px; + width: 27px; height: 12px; - background-color: rgba(12, 110, 162, 0.7); + background-color: #fff; border-radius: 10px; } - #slides-container { position: relative; - width: 92vw; - height: 650px; + width: 100vw; + height: 800px; overflow: hidden; - margin: 0 auto; - border-radius: 10px; + margin: 0; + padding: 0%; + top: -3.5em; } - .slide { transition: opacity 0.5s ease-in-out; position: absolute; @@ -489,77 +140,82 @@ height: 100%; display: block; } - .slide.active { opacity: 1; display: block; } - .slide:focus { outline: #fff solid 2px; } .logo { position: absolute; - bottom: 28%; + top: 50%; left: 50%; - transform: translateX(-50%); - max-height: 60%; - max-width: 80%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; width: auto; + z-index: 4; + filter: brightness(1.5); + } + .featured-logo-blur { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + scale: 1.01; + filter: blur(1.1rem); z-index: 3; } - .backdrop { position: absolute; - top: 45px; + top: 0; left: 0; width: 100%; - height: calc(100% - 50px); + height: 100%; object-fit: cover; - object-position: center 20%; z-index: 1; - border-radius: 13px; mask-image: linear-gradient( to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% ); -webkit-mask-image: linear-gradient( to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% ); } - .gradient-overlay { position: absolute; - top: 45px; + top: 0; left: 0; width: 100%; - height: calc(100% - 50px); - background: rgba(0, 0, 0, 0.55); + height: 100%; + background: rgb(0 0 0 / 0.65); z-index: 2; - border-radius: 13px; pointer-events: none; mask-image: linear-gradient( to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% ); -webkit-mask-image: linear-gradient( to top, - rgba(0, 0, 0, 0) 10%, - rgba(0, 0, 0, 0.5) 15%, - rgba(0, 0, 0, 1) 30% + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% ); } - .genre { position: absolute; - bottom: 16%; + bottom: 30%; left: 50%; transform: translateX(-50%); text-align: center; @@ -580,7 +236,7 @@ display: flex; align-items: center; justify-content: center; - bottom: 20%; + bottom: 25%; left: 50%; transform: translateX(-50%); font-size: 1.1em; @@ -635,52 +291,2177 @@ .plot { display: none; } - .button-container { position: absolute; - bottom: 20px; + bottom: 13%; left: 50%; transform: translateX(-50%); display: flex; - gap: 15px; /* Adjust spacing between buttons */ + gap: 15px; z-index: 4; } - .play-button, .detail-button { - display: inline-flex; /* Ensures icon and text are on the same line */ + display: inline-flex; align-items: center; justify-content: center; - padding: 8px 16px; /* Slightly reduced padding for thinner buttons */ + padding: 8px 16px; border: none; - border-radius: 20px; /* Sleeker look */ + border-radius: 20px; font-size: 14px; - white-space: nowrap; /* Prevents text wrapping */ + white-space: nowrap; cursor: pointer; transition: all 0.3s ease; - font-weight: bold; - gap: 6px; /* Adds space between icon and text */ - -webkit-tap-highlight-color: transparent; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; } - .play-button { - background-color: #ffffff; - color: #000; + background-color: #0c6da2; + color: #fff; } - .detail-button { - background-color: rgba(255, 255, 255, 0.2); + background-color: rgb(12 109 162 / 0.5); + border: solid 1.5px rgb(12 109 162 / 0.8); color: #fff; } - .play-button:hover, .detail-button:hover { opacity: 0.8; } - .play-icon, .detail-icon { - font-size: 18px; /* Ensures icons are aligned with text */ + font-size: 18px; + display: flex; + align-items: center; + } +} + +/* Mobile (Landscape) */ +@media only screen and (max-width: 767px) and (orientation: landscape) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 11em); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.8; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 155vh; + overflow: hidden; + margin: 0 auto; + /*border-radius: 15px;*/ + top: -7.5em; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -15em; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 8em; + left: 5em; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 4.5em; + position: absolute; + top: 7.5em; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 35px); + left: 4.8em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: -1em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 10em); + left: 4.5em; + display: flex; + z-index: 5; + justify-content: space-between; + width: 16em; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/* Tablet (Portrait) */ +@media only screen and (min-width: 481px) and (max-width: 1024px) and (orientation: portrait) { + .backdrop, + .slide { + position: absolute; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: rgb(255 255 255 / 0.6); + z-index: 4; + -webkit-tap-highlight-color: #fff0; + display: none; + } + .left-arrow { + left: 10px; + } + .right-arrow { + right: 10px; + } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + body { + margin: 0; + padding: 0; + height: 100%; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #homeTab { + top: -5em; + padding-bottom: 5em !important; + position: relative; + padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -150px; + } + .dots-container { + position: absolute; + bottom: 20%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff13; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 27px; + height: 12px; + background-color: #fff; + border-radius: 10px; + } + #slides-container { + position: relative; + width: 100vw; + height: 800px; + overflow: hidden; + margin: 0; + padding: 0%; + top: -3.5em; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + z-index: 4; + filter: brightness(1.5); + } + .featured-logo-blur { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + scale: 1.01; + filter: blur(1.1rem); + z-index: 3; + } + .backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(0 0 0 / 0.65); + z-index: 2; + pointer-events: none; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .genre { + position: absolute; + bottom: 30%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Titillium Web", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 25%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } + .button-container { + position: absolute; + bottom: 13%; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 15px; + z-index: 4; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: none; + border-radius: 20px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + border: solid 1.5px rgb(12 109 162 / 0.8); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/* Tablet (Landscape) */ +@media only screen and (min-width: 768px) and (max-width: 1024px) and (orientation: landscape) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 9em); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.8; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 135vh; + overflow: hidden; + margin: 0 auto; + /*border-radius: 15px;*/ + top: -7.5em; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -15em; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 5em; + left: 5em; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 4.5em; + position: absolute; + top: 6.5em; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 1.5em); + left: 5em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + -1em); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 3em; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 7.5em); + left: 4em; + display: flex; + z-index: 5; + justify-content: space-between; + width: 15em; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/* Laptop (Small to Medium Screens) */ +@media only screen and (min-width: 1025px) and (max-width: 1440px) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 13em); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.8; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 95vh; + overflow: hidden; + margin: 0 auto; + /*border-radius: 15px;*/ + top: -7.8em; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -17em; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 10em; + left: 8em; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 7.9em; + position: absolute; + top: 190px; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 50px); + left: 8.4em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 180px); + left: 7.5em; + display: flex; + z-index: 5; + justify-content: space-between; + width: 16em; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/* Large Screens (2K) */ +@media only screen and (min-width: 1441px) and (max-width: 2560px) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 13em); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.8; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 95vh; + overflow: hidden; + margin: 0 auto; + /*border-radius: 15px;*/ + top: -6.8em; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -20em; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 10em; + left: 8em; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 7.9em; + position: absolute; + top: 190px; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 50px); + left: 8.4em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 180px); + left: 7.5em; + display: flex; + z-index: 5; + justify-content: space-between; + width: 14em; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/* Extra Large Screens (4K) */ +@media only screen and (min-width: 2561px) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Titillium Web", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: flex; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 13em); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.8; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 95vh; + overflow: hidden; + margin: 0 auto; + /*border-radius: 15px;*/ + top: -6.8em; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: -20em; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 10em; + left: 8em; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 7.9em; + position: absolute; + top: 190px; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + rgb(0 0 0) 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 50px); + left: 8.4em; + text-align: center; + color: #fff; + font-family: "Titillium Web", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 2em !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Titillium Web", sans-serif; + display: flex; + align-items: center; + margin: 0 6em; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Titillium Web", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 180px); + left: 7.5em; + display: flex; + z-index: 5; + justify-content: space-between; + width: 16em; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; display: flex; align-items: center; } diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js index 39d5be6..eae1aef 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -1,61 +1,63 @@ -/** - * Credentials for a given server - * @typedef {Object} ServerCredentials - * @property {string} Id - * @property {string} AccessToken - */ - -/** - * Credentials - * @typedef {Object} Credentials - * @property {ServerCredentials[]} Servers - */ - -/** - * Fetches the credentials inside of localstorage - * @returns {{token: string, userId: string}} Credential resource - */ -const getJellyfinCredentials = () => { +const getJellyfinCredentials = () => { const jellyfinCreds = localStorage.getItem("jellyfin_credentials"); - try { - /** - * @type {Credentials} - */ const serverCredentials = JSON.parse(jellyfinCreds); - const firstServer = serverCredentials.Servers[0]; - if (!firstServer) { console.error("Could not find credentials for the client"); return; } - return { token: firstServer.AccessToken, userId: firstServer.UserId }; } catch (e) { console.error("Could not parse jellyfin credentials", e); } }; -const slidesInit = () => { +const initLoadingScreen = () => { + const currentPath = window.location.href.toLowerCase(); + if ( + currentPath.includes("/web/#/home.html") || + currentPath.includes("/web/index.html#/home.html") || + currentPath.endsWith("/web/") + ) { + const loadingHTML = ` +
+

+ Server Logo +

+
+
+
+
`; + document.body.insertAdjacentHTML("beforeend", loadingHTML); + const interval = setInterval(() => { + if (document.querySelector(".manualLoginForm")) { + $(".bar-loading").fadeOut(700, () => $(".bar-loading").remove()); + clearInterval(interval); + } + }, 100); + } +}; +initLoadingScreen(); +const slidesInit = async () => { if (window.hasInitializedSlideshow) { console.log("Slideshow already initialized. Skipping re-init."); return; } - window.hasInitializedSlideshow = true; + + window.hasInitializedSlideshow = !0; const shuffleInterval = 8000; - let isTransitioning = false; + let isTransitioning = !1; const listFileName = `${window.location.origin}/web/avatars/list.txt`; - const { token, userId } = getJellyfinCredentials(); - const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); const truncateText = (element, maxLength) => { let text = element.innerText; if (text.length > maxLength) element.innerText = text.substring(0, maxLength) + "..."; }; - const createSlideElement = (item, title) => { const itemId = item.Id; const plot = item.Overview || "No overview available"; @@ -63,11 +65,9 @@ const slidesInit = () => { const criticRating = item.CriticRating; const runtime = item.RunTimeTicks; const genresArray = item.Genres; - const youtube = item.RemoteTrailers; const age = item.OfficialRating; const date = item.PremiereDate; const season = item.ChildCount; - function createSeparator() { const separatorHtml = ''; @@ -85,43 +85,41 @@ const slidesInit = () => { backdrop.className = "backdrop"; backdrop.src = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; backdrop.alt = "Backdrop"; - + backdrop.loading = "eager"; const backdropOverlay = document.createElement("div"); backdropOverlay.className = "backdrop-overlay"; - const backdropContainer = document.createElement("div"); backdropContainer.className = "backdrop-container"; backdropContainer.appendChild(backdrop); backdropContainer.appendChild(backdropOverlay); - const logo = document.createElement("img"); logo.className = "logo"; logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; logo.alt = "Logo"; - + logo.loading = "eager"; + const logoImgBlur = document.createElement("img"); + logoImgBlur.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; + logoImgBlur.alt = item.Name || "Title"; + logoImgBlur.loading = "eager"; + logoImgBlur.className = "featured-logo-blur"; const logoContainer = document.createElement("div"); logoContainer.className = "logo-container"; logoContainer.appendChild(logo); - + logoContainer.appendChild(logoImgBlur); const featuredContent = document.createElement("div"); featuredContent.className = "featured-content"; featuredContent.textContent = title; - const plotElement = document.createElement("div"); plotElement.className = "plot"; plotElement.textContent = plot; truncateText(plotElement, 360); - const plotContainer = document.createElement("div"); plotContainer.className = "plot-container"; plotContainer.appendChild(plotElement); - const gradientOverlay = document.createElement("div"); gradientOverlay.className = "gradient-overlay"; - const runTimeElement = document.createElement("div"); runTimeElement.className = "runTime"; - if (season === undefined) { const milliseconds = runtime / 10000; const currentTime = new Date(); @@ -132,10 +130,8 @@ const slidesInit = () => { } else { runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`; } - const ratingTest = document.createElement("div"); ratingTest.className = "rating-value"; - const imdbLogo = document.createElement("img"); imdbLogo.className = "imdb-logo"; imdbLogo.src = @@ -144,34 +140,27 @@ const slidesInit = () => { imdbLogo.style.width = "30px"; imdbLogo.style.height = "30px"; ratingTest.appendChild(imdbLogo); - if (typeof rating === "number") { const formattedRating = rating.toFixed(1); ratingTest.innerHTML += `${formattedRating} ⭐`; } else { - console.error("Rating is undefined or not a number:", rating); ratingTest.innerHTML += `N/A ⭐`; } ratingTest.appendChild(createSeparator()); - const tomatoRatingDiv = document.createElement("div"); tomatoRatingDiv.className = "tomato-rating"; - const tomatoLogo = document.createElement("img"); tomatoLogo.className = "tomato-logo"; tomatoLogo.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Rotten_Tomatoes_positive_audience.svg/1920px-Rotten_Tomatoes_positive_audience.svg.png"; - const criticLogo = document.createElement("img"); criticLogo.className = "critic-logo"; - let valueElement; if (typeof criticRating === "number") { valueElement = document.createTextNode(`${criticRating}% `); } else { valueElement = document.createTextNode(`N/A `); } - if (criticRating === undefined || criticRating <= 59) { criticLogo.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Rotten_Tomatoes_rotten.svg/1024px-Rotten_Tomatoes_rotten.svg.png"; @@ -184,28 +173,22 @@ const slidesInit = () => { tomatoLogo.style.height = "17px"; criticLogo.style.width = "15px"; criticLogo.style.height = "15px"; - tomatoRatingDiv.appendChild(tomatoLogo); tomatoRatingDiv.appendChild(valueElement); tomatoRatingDiv.appendChild(criticLogo); tomatoRatingDiv.appendChild(createSeparator()); - const ageRatingDiv = document.createElement("div"); ageRatingDiv.className = "age-rating"; - if (item.OfficialRating) { ageRatingDiv.innerHTML = `${item.OfficialRating}`; } else { ageRatingDiv.innerHTML = `N/A`; } - const calender = "📅"; const premiereDate = document.createElement("div"); premiereDate.className = "date"; - const year = date ? new Date(date).getFullYear() : "N/A"; premiereDate.textContent = isNaN(year) ? "N/A" : year; - ratingTest.appendChild(tomatoRatingDiv); ratingTest.appendChild(document.createTextNode(calender)); ratingTest.appendChild(premiereDate); @@ -213,7 +196,6 @@ const slidesInit = () => { ratingTest.appendChild(ageRatingDiv); ratingTest.appendChild(createSeparator()); ratingTest.appendChild(runTimeElement); - function parseGenres(genresArray) { if (genresArray && genresArray.length > 0) { return genresArray.slice(0, 3).join(" 🔹 "); @@ -221,23 +203,18 @@ const slidesInit = () => { return "No Genre Available"; } } - const genreElement = document.createElement("div"); genreElement.className = "genre"; genreElement.innerHTML = parseGenres(genresArray); - const infoContainer = document.createElement("div"); infoContainer.className = "info-container"; infoContainer.appendChild(ratingTest); - // Create a container for the buttons const buttonContainer = document.createElement("div"); - buttonContainer.className = "button-container"; // Add a class for styling - - // Create the Play button + buttonContainer.className = "button-container"; const playButton = document.createElement("button"); playButton.className = "play-button"; playButton.innerHTML = ` - + play_circle Play `; playButton.onclick = async () => { @@ -245,34 +222,27 @@ const slidesInit = () => { console.error("PlaybackManager is not available."); return; } - if (!window.ApiClient) { console.error("Jellyfin API client is not available."); return; } - const apiClient = window.ApiClient; const userId = apiClient.getCurrentUserId(); - if (!userId) { console.error("User not found."); return; } - try { - // Fetch the media item details const item = await apiClient.getItem(userId, itemId); if (!item) { console.error("Media item not found."); return; } - - // Initiate playback using PlaybackManager window.PlaybackManager.play({ items: [item], - startPositionTicks: 0, // Start from beginning - isMuted: false, - isPaused: false, + startPositionTicks: 0, + isMuted: !1, + isPaused: !1, }) .then(() => { console.log("Playback started successfully."); @@ -284,8 +254,6 @@ const slidesInit = () => { console.error("Error starting playback:", error); } }; - - // Create the Details button const detailButton = document.createElement("button"); detailButton.className = "detail-button"; detailButton.innerHTML = ` @@ -295,12 +263,8 @@ const slidesInit = () => { detailButton.onclick = () => { window.top.location.href = `/#!/details?id=${itemId}`; }; - - // Append buttons to the button container - buttonContainer.appendChild(playButton); buttonContainer.appendChild(detailButton); - - // Append all elements to the slide + buttonContainer.appendChild(playButton); slide.append( logoContainer, backdropContainer, @@ -311,7 +275,6 @@ const slidesInit = () => { genreElement, buttonContainer ); - return slide; }; const createSlideForItem = async (item, title) => { @@ -326,7 +289,6 @@ const slidesInit = () => { if (backdropExists && logoExists) { const slideElement = createSlideElement(item, title); container.appendChild(slideElement); - console.log(`Added slide for item ${itemId}`); if (container.children.length === 1) { showSlide(0); } @@ -341,7 +303,6 @@ const slidesInit = () => { }, }); const item = await response.json(); - console.log("Item Title:", item.Name); return item; }; const fetchItemIdsFromList = async () => { @@ -354,7 +315,8 @@ const slidesInit = () => { return text .split("\n") .map((id) => id.trim()) - .filter((id) => id); + .filter((id) => id) + .slice(1); } catch (error) { console.error("Error fetching list.txt:", error); return []; @@ -413,7 +375,6 @@ const slidesInit = () => { const dots = document.querySelectorAll(".dot"); slides.forEach((slide, i) => { if (i === index) { - console.log(`Showing slide ${index}`); // Debugging slide.style.removeProperty("display"); slide.style.display = "block"; slide.offsetHeight; @@ -441,11 +402,11 @@ const slidesInit = () => { let containerFocused = !1; const updateCurrentSlide = (index) => { if (isTransitioning) return; - isTransitioning = true; + isTransitioning = !0; currentSlideIndex = (index + slides.length) % slides.length; showSlide(currentSlideIndex); setTimeout(() => { - isTransitioning = false; + isTransitioning = !1; }, 500); }; const openActiveSlide = () => { @@ -505,6 +466,46 @@ const slidesInit = () => { rightArrow.innerHTML = ''; container.appendChild(leftArrow); container.appendChild(rightArrow); + let touchStartX = 0; + let touchEndX = 0; + const minSwipeDistance = 50; + let isTouchOnButton = false; + + container.addEventListener( + "touchstart", + (event) => { + touchStartX = event.touches[0].clientX; + isTouchOnButton = + event.target.closest(".play-button, .detail-button") !== null; + }, + { passive: false } + ); + container.addEventListener( + "touchmove", + (event) => { + touchEndX = event.touches[0].clientX; + }, + { passive: false } + ); + + container.addEventListener( + "touchend", + (event) => { + if (isTouchOnButton) return; + + const swipeDistance = touchEndX - touchStartX; + if (Math.abs(swipeDistance) > minSwipeDistance) { + event.preventDefault(); + event.stopPropagation(); + if (swipeDistance < 0) { + updateCurrentSlide(currentSlideIndex + 1); + } else { + updateCurrentSlide(currentSlideIndex - 1); + } + } + }, + { passive: false } + ); leftArrow.addEventListener("click", () => updateCurrentSlide(currentSlideIndex - 1) ); @@ -532,6 +533,7 @@ const slidesInit = () => { items = shuffleArray(items); await createSlidesForItems(items); initializeSlideshow(); + $(".bar-loading").fadeOut(700, () => $(".bar-loading").remove()); }; initializeSlides(); }; \ No newline at end of file From c8094cd5defd11bc76d32bae0c36484a06894a4e Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 24 Feb 2025 22:10:51 +0000 Subject: [PATCH 064/108] Added configuration page to enable and disable the plugin. Allows for future proofing in case there are optional configurations (like the normal mode before support for it was dropped) --- .../Configuration/PluginConfiguration.cs | 15 ++++ .../Configuration/config.html | 84 +++++++++++++++++++ .../Jellyfin.Plugin.MediaBar.csproj | 1 + .../MediaBarPlugin.cs | 5 +- 4 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs create mode 100644 src/Jellyfin.Plugin.MediaBar/Configuration/config.html diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs b/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs new file mode 100644 index 0000000..28117ae --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs @@ -0,0 +1,15 @@ +using MediaBrowser.Model.Plugins; + +namespace Jellyfin.Plugin.MediaBar.Configuration +{ + public enum MediaBarState + { + Disabled, + Enabled, + } + + public class PluginConfiguration : BasePluginConfiguration + { + public MediaBarState Enabled { get; set; } = MediaBarState.Enabled; + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html new file mode 100644 index 0000000..c3dfc64 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html @@ -0,0 +1,84 @@ + + + + Media Bar + + +
+
+
+
+

Media Bar

+ + + Help + +
+
+
+ +
+
+ Enabled: + + Whether the media bar should appear or not. +
+
+
+ +
+ + \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index ba43814..81c5ef6 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs index 172ac70..7ed4188 100644 --- a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs +++ b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs @@ -1,11 +1,12 @@ -using MediaBrowser.Common.Configuration; +using Jellyfin.Plugin.MediaBar.Configuration; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; namespace Jellyfin.Plugin.MediaBar { - public class MediaBarPlugin : BasePlugin + public class MediaBarPlugin : BasePlugin { public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448"); From 5f33f3cf46cdabee62c30bdc083e662ede97a5f7 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 24 Feb 2025 22:14:29 +0000 Subject: [PATCH 065/108] Forgot to add IHasPluginConfiguration --- src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs index 7ed4188..478a36f 100644 --- a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs +++ b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Serialization; namespace Jellyfin.Plugin.MediaBar { - public class MediaBarPlugin : BasePlugin + public class MediaBarPlugin : BasePlugin, IHasPluginConfiguration { public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448"); From 2ff38bcd22cb7b0c2724fda50bd755feff3cfbff Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 24 Feb 2025 22:18:25 +0000 Subject: [PATCH 066/108] And forgot the IHasWebPages --- src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs index 478a36f..c1d97fa 100644 --- a/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs +++ b/src/Jellyfin.Plugin.MediaBar/MediaBarPlugin.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Serialization; namespace Jellyfin.Plugin.MediaBar { - public class MediaBarPlugin : BasePlugin, IHasPluginConfiguration + public class MediaBarPlugin : BasePlugin, IHasPluginConfiguration, IHasWebPages { public override Guid Id => Guid.Parse("08f615ea-2107-4f04-89cc-091035f54448"); @@ -15,5 +15,16 @@ namespace Jellyfin.Plugin.MediaBar public MediaBarPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IServiceProvider serviceProvider) : base(applicationPaths, xmlSerializer) { } + + public IEnumerable GetPages() + { + string? prefix = GetType().Namespace; + + yield return new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = $"{prefix}.Configuration.config.html" + }; + } } } \ No newline at end of file From a09662569c67e52ee3d8eeb28503b679e4195610 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 24 Feb 2025 22:38:50 +0000 Subject: [PATCH 067/108] Forgot to add the save button to the settings lol --- .../Configuration/config.html | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html index c3dfc64..ed45d74 100644 --- a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html @@ -18,16 +18,22 @@
-
-
- Enabled: - - Whether the media bar should appear or not. +
+
+
+ Enabled: + + Whether the media bar should appear or not. +
-
+
+ +
\ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/index.html b/src/Jellyfin.Plugin.MediaBar/Inject/index.html index 41c07a8..b1c02f4 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/index.html +++ b/src/Jellyfin.Plugin.MediaBar/Inject/index.html @@ -1,4 +1,3 @@ - - + - + \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js b/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js deleted file mode 100644 index 713367a..0000000 --- a/src/Jellyfin.Plugin.MediaBar/Inject/media-bar.js +++ /dev/null @@ -1,35 +0,0 @@ -(function () { - // List of CSS selectors for Home buttons - const buttonSelectors = [ - ".headerHomeButton.barsMenuButton", - ".css-17c09up", - ".mainDrawer-scrollContainer > a:nth-child(2)" - ]; - - // Polling interval to check for buttons - const intervalId = setInterval(function () { - buttonSelectors.forEach(selector => { - // Try to find the button - const homeButton = document.querySelector(selector); - - // If the button is found - if (homeButton) { - // Attach the click event listener if not already added - if (!homeButton.hasAttribute("data-home-handler")) { - homeButton.addEventListener("click", function (event) { - event.preventDefault(); // Prevent default behavior if necessary - window.location.href = "/web/index.html#/home.html"; - }); - - // Mark the button as handled - homeButton.setAttribute("data-home-handler", "true"); - } - } - }); - - // Stop polling if all buttons are found - if (buttonSelectors.every(selector => document.querySelector(selector))) { - clearInterval(intervalId); - } - }, 100); // Check every 100ms -})(); \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css index b44b624..f877169 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css @@ -1,4 +1,4 @@ -@import url(https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap); +@import url(https://fonts.googleapis.com/css2?family=Archivo+Narrow:ital,wght@0,400..700;1,400..700&display=swap); .bar-loading { z-index: 99999999; position: fixed; @@ -6,7 +6,7 @@ left: 0; width: 100%; height: 100%; - background-color: rgb(0 0 0); + background-color: #000; display: flex; align-items: center; justify-content: center; @@ -37,8 +37,9 @@ transform: rotate(360deg); } } -/* Mobile (Portrait) */ -@media only screen and (max-width: 480px) and (orientation: portrait) { + +/*Mobiles S*/ +@media only screen and (max-width: 320px) and (orientation: portrait) { .backdrop, .slide { position: absolute; @@ -62,7 +63,7 @@ } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -80,16 +81,16 @@ .fade-out { opacity: 0 !important; } - #homeTab { - top: -5em; - padding-bottom: 5em !important; - position: relative; - padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; - } + /*#homeTab { + top: -5em; + padding-bottom: 5em !important; + position: relative; + padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; + }*/ .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -11em; + top: 71vh; } .dots-container { position: absolute; @@ -129,7 +130,7 @@ overflow: hidden; margin: 0; padding: 0%; - top: -3.5em; + /*top: -3.5em;*/ } .slide { transition: opacity 0.5s ease-in-out; @@ -182,13 +183,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .gradient-overlay { @@ -204,13 +205,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { @@ -225,7 +226,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-size: 1.1em; font-weight: 500; z-index: 4; @@ -245,13 +246,13 @@ width: 100%; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -337,15 +338,14 @@ } } -/* Mobile (Landscape) */ -@media only screen and (max-width: 767px) and (orientation: landscape) { +@media only screen and (max-width: 320px) and (orientation: landscape) { .backdrop, .slide { position: absolute; } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -360,7 +360,7 @@ cursor: pointer; color: #fff; z-index: 3; - display: flex; + display: none; justify-content: center; align-items: center; } @@ -428,16 +428,14 @@ #slides-container { position: relative; width: 100vw; - height: 155vh; + height: 190vh; overflow: hidden; margin: 0 auto; - /*border-radius: 15px;*/ - top: -7.5em; } .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -15em; + top: 125vh; } .slide { transition: opacity 0.5s ease-in-out; @@ -474,13 +472,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .backdrop-overlay { @@ -496,13 +494,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .logo-container { @@ -521,8 +519,8 @@ width: auto; object-fit: contain; z-index: 4; - top: 8em; - left: 5em; + top: 8vh; + left: 5vw; filter: brightness(1.5); } .featured-content { @@ -559,9 +557,9 @@ line-clamp: 2; -webkit-line-clamp: 2; -webkit-box-orient: vertical; - left: 4.5em; + left: 5vw; position: absolute; - top: 7.5em; + top: 8.5em; } .gradient-overlay { position: absolute; @@ -575,22 +573,22 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { position: absolute; top: calc(50% + 35px); - left: 4.8em; + left: 5vw; text-align: center; color: #fff; - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-size: 14px; z-index: 4; } @@ -605,15 +603,14 @@ z-index: 4; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; display: flex; align-items: center; - margin: 0 6em; z-index: 5; position: relative; } .runTime { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -645,7 +642,7 @@ padding: 0 0.6em; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -703,8 +700,8 @@ } } -/* Tablet (Portrait) */ -@media only screen and (min-width: 481px) and (max-width: 1024px) and (orientation: portrait) { +/*Mobiles M*/ +@media only screen and (min-width: 321px) and (max-width: 375px) and (orientation: portrait) { .backdrop, .slide { position: absolute; @@ -728,7 +725,7 @@ } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -746,16 +743,16 @@ .fade-out { opacity: 0 !important; } - #homeTab { - top: -5em; - padding-bottom: 5em !important; - position: relative; - padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; - } + /*#homeTab { + top: -5em; + padding-bottom: 5em !important; + position: relative; + padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; + }*/ .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -150px; + top: 91vh; } .dots-container { position: absolute; @@ -791,11 +788,11 @@ #slides-container { position: relative; width: 100vw; - height: 800px; + height: 120vh; overflow: hidden; margin: 0; padding: 0%; - top: -3.5em; + /*top: -3.5em;*/ } .slide { transition: opacity 0.5s ease-in-out; @@ -848,13 +845,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .gradient-overlay { @@ -870,13 +867,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { @@ -891,7 +888,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-size: 1.1em; font-weight: 500; z-index: 4; @@ -911,13 +908,13 @@ width: 100%; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -1003,15 +1000,14 @@ } } -/* Tablet (Landscape) */ -@media only screen and (min-width: 768px) and (max-width: 1024px) and (orientation: landscape) { +@media only screen and (min-width: 321px) and (max-width: 375px) and (orientation: landscape) { .backdrop, .slide { position: absolute; } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -1026,7 +1022,7 @@ cursor: pointer; color: #fff; z-index: 3; - display: flex; + display: none; justify-content: center; align-items: center; } @@ -1052,7 +1048,670 @@ } .dots-container { position: absolute; - top: calc(50% + 9em); + top: calc(50% + 22vh); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.6; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 135vh; + overflow: hidden; + margin: 0 auto; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: 75vh; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 50%; + max-width: 70%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 25vh; + left: 5vw; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 5vw; + position: absolute; + top: calc(50% + -20vh); + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 0vh); + left: 5vw; + text-align: center; + color: #fff; + font-family: "Archivo Narrow", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + -10vh); + display: flex; + align-items: center; + justify-content: center; + left: 5vw; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Archivo Narrow", sans-serif; + display: flex; + align-items: center; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 20vh); + left: 2vw; + display: flex; + z-index: 5; + justify-content: space-between; + width: 16em; + scale: 0.8; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/*Mobiles L*/ +@media only screen and (min-width: 376px) and (max-width: 425px) and (orientation: portrait) { + .backdrop, + .slide { + position: absolute; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: rgb(255 255 255 / 0.6); + z-index: 4; + -webkit-tap-highlight-color: #fff0; + display: none; + } + .left-arrow { + left: 10px; + } + .right-arrow { + right: 10px; + } + .featured-content, + .plot-container { + font-family: "Archivo Narrow", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + body { + margin: 0; + padding: 0; + height: 100%; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + /*#homeTab { + top: -5em; + padding-bottom: 5em !important; + position: relative; + padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; + }*/ + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: 73vh; + } + .dots-container { + position: absolute; + bottom: 20%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff13; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 27px; + height: 12px; + background-color: #fff; + border-radius: 10px; + } + #slides-container { + position: relative; + width: 100vw; + height: 95vh; + overflow: hidden; + margin: 0; + padding: 0%; + /*top: -3.5em;*/ + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + z-index: 4; + filter: brightness(1.5); + } + .featured-logo-blur { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + scale: 1.01; + filter: blur(1.1rem); + z-index: 3; + } + .backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(0 0 0 / 0.65); + z-index: 2; + pointer-events: none; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .genre { + position: absolute; + bottom: 30%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Archivo Narrow", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 25%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } + .button-container { + position: absolute; + bottom: 13%; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 15px; + z-index: 4; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: none; + border-radius: 20px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + border: solid 1.5px rgb(12 109 162 / 0.8); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +@media only screen and (min-width: 376px) and (max-width: 425px) and (orientation: landscape) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Archivo Narrow", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: none; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 11em); right: 3%; z-index: 2; display: flex; @@ -1094,16 +1753,14 @@ #slides-container { position: relative; width: 100vw; - height: 135vh; + height: 190vh; overflow: hidden; margin: 0 auto; - /*border-radius: 15px;*/ - top: -7.5em; } .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -15em; + top: 125vh; } .slide { transition: opacity 0.5s ease-in-out; @@ -1140,13 +1797,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .backdrop-overlay { @@ -1162,13 +1819,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .logo-container { @@ -1187,7 +1844,7 @@ width: auto; object-fit: contain; z-index: 4; - top: 5em; + top: 8em; left: 5em; filter: brightness(1.5); } @@ -1227,7 +1884,7 @@ -webkit-box-orient: vertical; left: 4.5em; position: absolute; - top: 6.5em; + top: 35vh; } .gradient-overlay { position: absolute; @@ -1241,45 +1898,44 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { position: absolute; - top: calc(50% + 1.5em); - left: 5em; + top: calc(50% + 35px); + left: 4.8em; text-align: center; color: #fff; - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-size: 14px; z-index: 4; } .info-container { position: absolute; - top: calc(50% + -1em); + top: calc(50% + 0px); display: flex; align-items: center; justify-content: center; - left: 2em !important; + left: -1em !important; color: #fff; z-index: 4; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; display: flex; align-items: center; - margin: 0 3em; z-index: 5; position: relative; } .runTime { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -1311,7 +1967,1324 @@ padding: 0 0.6em; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 10em); + left: 4.5em; + display: flex; + z-index: 5; + justify-content: space-between; + width: 16em; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +/*Mobiles UL*/ +@media only screen and (min-width: 426px) and (max-width: 768px) and (orientation: portrait) { + .backdrop, + .slide { + position: absolute; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: rgb(255 255 255 / 0.6); + z-index: 4; + -webkit-tap-highlight-color: #fff0; + display: none; + } + .left-arrow { + left: 10px; + } + .right-arrow { + right: 10px; + } + .featured-content, + .plot-container { + font-family: "Archivo Narrow", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + body { + margin: 0; + padding: 0; + height: 100%; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: 73vh; + } + .dots-container { + position: absolute; + bottom: 24%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff13; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 27px; + height: 12px; + background-color: #fff; + border-radius: 10px; + } + #slides-container { + position: relative; + width: 100vw; + height: 100vh; + overflow: hidden; + margin: 0; + padding: 0%; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + z-index: 4; + filter: brightness(1.5); + } + .featured-logo-blur { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + scale: 1.01; + filter: blur(1.1rem); + z-index: 3; + } + .backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(0 0 0 / 0.65); + z-index: 2; + pointer-events: none; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .genre { + position: absolute; + bottom: 33%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Archivo Narrow", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 28%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } + .button-container { + position: absolute; + bottom: 18vh; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 15px; + z-index: 4; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: none; + border-radius: 20px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + border: solid 1.5px rgb(12 109 162 / 0.8); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +@media only screen and (min-width: 426px) and (max-width: 768px) and (orientation: landscape) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Archivo Narrow", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: none; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 34vh); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.6; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 145vh; + overflow: hidden; + margin: 0 auto; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: 90vh; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 8em; + left: 4vw; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 4vw; + position: absolute; + top: 33vh; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 10vh); + left: 4vw; + text-align: center; + color: #fff; + font-family: "Archivo Narrow", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + 0px); + display: flex; + align-items: center; + justify-content: center; + left: 4vw; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Archivo Narrow", sans-serif; + display: flex; + align-items: center; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-right: 5px; + margin-left: 2px; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 10px; + color: aquamarine; + } + .button-container { + position: absolute; + top: calc(50% + 32vh); + left: 0.5vw; + display: flex; + z-index: 5; + justify-content: space-between; + width: 16em; + scale: 0.8; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: solid 2px rgb(12 109 162 / 0.7); + border-radius: 25px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} +/*upwards changed lanscape width*/ + +@media only screen and (min-width: 769px) and (max-width: 1024px) and (orientation: portrait) { + .backdrop, + .slide { + position: absolute; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 24px; + cursor: pointer; + color: rgb(255 255 255 / 0.6); + z-index: 4; + -webkit-tap-highlight-color: #fff0; + display: none; + } + .left-arrow { + left: 10px; + } + .right-arrow { + right: 10px; + } + .featured-content, + .plot-container { + font-family: "Archivo Narrow", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + body { + margin: 0; + padding: 0; + height: 100%; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + /*#homeTab { + top: -5em; + padding-bottom: 5em !important; + position: relative; + padding-bottom: calc(env(safe-area-inset-bottom) + 5em) !important; + }*/ + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: 73vh; + } + .dots-container { + position: absolute; + bottom: 20%; + left: 50%; + transform: translateX(-50%) scale(0.6); + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff13; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 27px; + height: 12px; + background-color: #fff; + border-radius: 10px; + } + #slides-container { + position: relative; + width: 100vw; + height: 95vh; + overflow: hidden; + margin: 0; + padding: 0%; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .logo { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + z-index: 4; + filter: brightness(1.5); + } + .featured-logo-blur { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-height: 50%; + max-width: 70%; + width: auto; + scale: 1.01; + filter: blur(1.1rem); + z-index: 3; + } + .backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(0 0 0 / 0.65); + z-index: 2; + pointer-events: none; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .genre { + position: absolute; + bottom: 30%; + left: 50%; + transform: translateX(-50%); + text-align: center; + color: #fff; + width: 90%; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: "Archivo Narrow", sans-serif; + font-size: 1.1em; + font-weight: 500; + z-index: 4; + box-sizing: border-box; + } + .info-container { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + bottom: 25%; + left: 50%; + transform: translateX(-50%); + font-size: 1.1em; + color: #fff; + z-index: 5; + width: 100%; + } + .rating-value { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + } + .date { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin-left: 2px; + } + .tomato-rating { + display: flex; + align-items: center; + } + .tomato-logo { + margin-right: 3px; + } + .critic-logo { + margin-left: 3px; + } + .imdb-logo { + margin-right: 3px; + } + .age-rating { + display: flex; + align-items: center; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.4em; + } + .separator-icon { + margin-left: 5px; + margin-right: 5px; + font-size: 1px; + color: aquamarine; + } + .featured-content, + .plot { + display: none; + } + .button-container { + position: absolute; + bottom: 13%; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 15px; + z-index: 4; + } + .play-button, + .detail-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + border: none; + border-radius: 20px; + font-size: 14px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 700; + gap: 6px; + -webkit-tap-highlight-color: #fff0; + } + .play-button { + background-color: #0c6da2; + color: #fff; + } + .detail-button { + background-color: rgb(12 109 162 / 0.5); + border: solid 1.5px rgb(12 109 162 / 0.8); + color: #fff; + } + .play-button:hover, + .detail-button:hover { + opacity: 0.8; + } + .play-icon, + .detail-icon { + font-size: 18px; + display: flex; + align-items: center; + } +} + +@media only screen and (min-width: 769px) and (max-width: 1024px) and (orientation: landscape) { + .backdrop, + .slide { + position: absolute; + } + .featured-content, + .plot-container { + font-family: "Archivo Narrow", sans-serif; + left: 0; + } + .plot, + body { + overflow: hidden; + } + .arrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + cursor: pointer; + color: #fff; + z-index: 3; + display: none; + justify-content: center; + align-items: center; + } + .arrow::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background-color: rgb(12 110 162 / 0.7); + border-radius: 50%; + z-index: -1; + } + .left-arrow { + left: 20px; + font-size: 12px; + } + .right-arrow { + right: 20px; + font-size: 12px; + } + .dots-container { + position: absolute; + top: calc(50% + 9em); + right: 3%; + z-index: 2; + display: flex; + gap: 5px; + background-color: #ffffff3d; + border-radius: 50px; + justify-content: center; + align-items: center; + width: 140px; + height: 22px; + transition: opacity 0.5s ease-in-out; + scale: 0.6; + } + .dot { + display: inline-block; + width: 9px; + height: 9px; + margin: 0 5px; + background-color: #cecece; + border-radius: 50%; + transition: background-color 0.3s; + } + .dot.active { + width: 22px; + height: 12px; + background-color: #fff; + border-radius: 6px; + } + body { + margin: 0; + padding: 0; + } + .fade-in { + opacity: 1 !important; + } + .fade-out { + opacity: 0 !important; + } + #slides-container { + position: relative; + width: 100vw; + height: 135vh; + overflow: hidden; + margin: 0 auto; + } + .sections.homeSectionsContainer { + position: relative; + z-index: 8; + top: 83vh; + } + .slide { + transition: opacity 0.5s ease-in-out; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: block; + } + .slide.active { + opacity: 1; + display: block; + } + .slide:focus { + outline: #fff solid 2px; + } + .backdrop-container { + position: relative; + top: -40%; + right: -35%; + width: 70%; + height: 100%; + } + .backdrop { + right: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 20%; + border-radius: 5px; + z-index: 1; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .backdrop-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(0 0 0 / 0.6); + border-radius: 5px; + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .logo-container { + width: 45%; + height: 40%; + position: relative; + display: flex; + justify-content: left; + align-items: left; + } + .logo { + position: relative; + max-height: 70%; + max-width: 100%; + height: auto; + width: auto; + object-fit: contain; + z-index: 4; + top: 20vh; + left: 5vw; + filter: brightness(1.5); + } + .featured-content { + position: absolute; + top: 0; + width: 100%; + height: 50px; + background-color: #fff0; + color: #d3d3d3; + font-size: 22px; + display: none; + align-items: center; + justify-content: flex-start; + z-index: 2; + } + .plot-container { + position: absolute; + bottom: 0; + color: #fff; + width: 90%; + height: 60%; + font-size: 15px; + padding: 10px 0 25px 15px; + border-radius: 5px; + z-index: 3; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + text-align: justify; + } + .plot { + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + left: 5vw; + position: absolute; + top: 25vh; + } + .gradient-overlay { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 100%; + background: linear-gradient(to right, #000 49%, #fff0 70%); + z-index: 2; + mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + -webkit-mask-image: linear-gradient( + to top, + #fff0 2%, + rgb(0 0 0 / 0.5) 6%, + #000000 8% + ); + } + .genre { + position: absolute; + top: calc(50% + 3.5vh); + left: 5vw; + text-align: center; + color: #fff; + font-family: "Archivo Narrow", sans-serif; + font-size: 14px; + z-index: 4; + } + .info-container { + position: absolute; + top: calc(50% + -6vh); + display: flex; + align-items: center; + justify-content: center; + left: 5vw !important; + color: #fff; + z-index: 4; + } + .rating-value { + font-family: "Archivo Narrow", sans-serif; + display: flex; + align-items: center; + z-index: 5; + position: relative; + } + .runTime { + font-family: "Archivo Narrow", sans-serif; + font-weight: 500; + display: flex; + align-items: center; + margin: 0 8px; + margin-right: -3px; + } + .rating-value img { + margin-right: 6px; + } + .tomato-rating { + display: flex; + align-items: center; + margin-left: 0; + } + .tomato-rating img { + margin-left: 5px; + } + .age-rating { + display: flex; + align-items: center; + margin-left: 5px; + margin-right: 5px; + border-radius: 5px; + background: rgb(255 255 255 / 0.8); + color: #000; + border: none; + font-weight: 600; + white-space: nowrap; + padding: 0 0.6em; + } + .date { + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -1327,7 +3300,7 @@ .button-container { position: absolute; top: calc(50% + 7.5em); - left: 4em; + left: 5vw; display: flex; z-index: 5; justify-content: space-between; @@ -1369,7 +3342,6 @@ } } -/* Laptop (Small to Medium Screens) */ @media only screen and (min-width: 1025px) and (max-width: 1440px) { .backdrop, .slide { @@ -1377,7 +3349,7 @@ } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -1418,7 +3390,7 @@ } .dots-container { position: absolute; - top: calc(50% + 13em); + top: calc(50% + 21vh); right: 3%; z-index: 2; display: flex; @@ -1463,13 +3435,11 @@ height: 95vh; overflow: hidden; margin: 0 auto; - /*border-radius: 15px;*/ - top: -7.8em; } .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -17em; + top: 65vh; } .slide { transition: opacity 0.5s ease-in-out; @@ -1506,13 +3476,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .backdrop-overlay { @@ -1528,13 +3498,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .logo-container { @@ -1554,7 +3524,7 @@ object-fit: contain; z-index: 4; top: 10em; - left: 8em; + left: 6vw; filter: brightness(1.5); } .featured-content { @@ -1591,9 +3561,9 @@ line-clamp: 2; -webkit-line-clamp: 2; -webkit-box-orient: vertical; - left: 7.9em; + left: 6vw; position: absolute; - top: 190px; + top: 20vh; } .gradient-overlay { position: absolute; @@ -1607,22 +3577,22 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { position: absolute; top: calc(50% + 50px); - left: 8.4em; + left: 6vw; text-align: center; color: #fff; - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-size: 14px; z-index: 4; } @@ -1632,20 +3602,19 @@ display: flex; align-items: center; justify-content: center; - left: 2em !important; + left: 6vw !important; color: #fff; z-index: 4; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; display: flex; align-items: center; - margin: 0 6em; z-index: 5; position: relative; } .runTime { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -1677,7 +3646,7 @@ padding: 0 0.6em; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -1692,12 +3661,12 @@ } .button-container { position: absolute; - top: calc(50% + 180px); - left: 7.5em; + top: calc(50% + 20vh); + left: 5vw; display: flex; z-index: 5; justify-content: space-between; - width: 16em; + width: 15em; } .play-button, .detail-button { @@ -1734,8 +3703,6 @@ align-items: center; } } - -/* Large Screens (2K) */ @media only screen and (min-width: 1441px) and (max-width: 2560px) { .backdrop, .slide { @@ -1743,7 +3710,7 @@ } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -1784,7 +3751,7 @@ } .dots-container { position: absolute; - top: calc(50% + 13em); + top: calc(50% + 20vh); right: 3%; z-index: 2; display: flex; @@ -1826,16 +3793,14 @@ #slides-container { position: relative; width: 100vw; - height: 95vh; + height: 98vh; overflow: hidden; margin: 0 auto; - /*border-radius: 15px;*/ - top: -6.8em; } .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -20em; + top: 65vh; } .slide { transition: opacity 0.5s ease-in-out; @@ -1872,13 +3837,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .backdrop-overlay { @@ -1894,13 +3859,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .logo-container { @@ -1919,8 +3884,8 @@ width: auto; object-fit: contain; z-index: 4; - top: 10em; - left: 8em; + top: 15vh; + left: 5vw; filter: brightness(1.5); } .featured-content { @@ -1957,9 +3922,9 @@ line-clamp: 2; -webkit-line-clamp: 2; -webkit-box-orient: vertical; - left: 7.9em; + left: 5vw; position: absolute; - top: 190px; + top: 20vh; } .gradient-overlay { position: absolute; @@ -1973,45 +3938,44 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { position: absolute; - top: calc(50% + 50px); - left: 8.4em; + top: calc(50% + 4.5vh); + left: 5vw; text-align: center; color: #fff; - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-size: 14px; z-index: 4; } .info-container { position: absolute; - top: calc(50% + 0px); + top: calc(50% + -2vh); display: flex; align-items: center; justify-content: center; - left: 2em !important; + left: 5vw !important; color: #fff; z-index: 4; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; display: flex; align-items: center; - margin: 0 6em; z-index: 5; position: relative; } .runTime { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -2043,7 +4007,7 @@ padding: 0 0.6em; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -2058,8 +4022,8 @@ } .button-container { position: absolute; - top: calc(50% + 180px); - left: 7.5em; + top: calc(50% + 19vh); + left: 5vw; display: flex; z-index: 5; justify-content: space-between; @@ -2100,8 +4064,6 @@ align-items: center; } } - -/* Extra Large Screens (4K) */ @media only screen and (min-width: 2561px) { .backdrop, .slide { @@ -2109,7 +4071,7 @@ } .featured-content, .plot-container { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; left: 0; } .plot, @@ -2150,7 +4112,7 @@ } .dots-container { position: absolute; - top: calc(50% + 13em); + top: calc(50% + 21vh); right: 3%; z-index: 2; display: flex; @@ -2195,13 +4157,11 @@ height: 95vh; overflow: hidden; margin: 0 auto; - /*border-radius: 15px;*/ - top: -6.8em; } .sections.homeSectionsContainer { position: relative; z-index: 8; - top: -20em; + top: 65vh; } .slide { transition: opacity 0.5s ease-in-out; @@ -2238,13 +4198,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .backdrop-overlay { @@ -2260,13 +4220,13 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .logo-container { @@ -2286,7 +4246,7 @@ object-fit: contain; z-index: 4; top: 10em; - left: 8em; + left: 5vw; filter: brightness(1.5); } .featured-content { @@ -2322,10 +4282,11 @@ display: -webkit-box; line-clamp: 2; -webkit-line-clamp: 2; + font-size: x-large; -webkit-box-orient: vertical; - left: 7.9em; + left: 5vw; position: absolute; - top: 190px; + top: 20vh; } .gradient-overlay { position: absolute; @@ -2339,45 +4300,45 @@ to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); -webkit-mask-image: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, - rgb(0 0 0) 8% + #000000 8% ); } .genre { position: absolute; - top: calc(50% + 50px); - left: 8.4em; + top: calc(50% + 5vh); + left: 5vw; text-align: center; color: #fff; - font-family: "Titillium Web", sans-serif; - font-size: 14px; + font-family: "Archivo Narrow", sans-serif; + font-size: x-large; z-index: 4; } .info-container { position: absolute; - top: calc(50% + 0px); + top: calc(50% + 0vh); + font-size: x-large; display: flex; align-items: center; justify-content: center; - left: 2em !important; + left: 5vw !important; color: #fff; z-index: 4; } .rating-value { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; display: flex; align-items: center; - margin: 0 6em; z-index: 5; position: relative; } .runTime { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -2409,7 +4370,7 @@ padding: 0 0.6em; } .date { - font-family: "Titillium Web", sans-serif; + font-family: "Archivo Narrow", sans-serif; font-weight: 500; display: flex; align-items: center; @@ -2424,12 +4385,13 @@ } .button-container { position: absolute; - top: calc(50% + 180px); - left: 7.5em; + top: calc(50% + 20vh); + left: 12vh; display: flex; z-index: 5; justify-content: space-between; - width: 16em; + width: 15em; + scale: 1.3; } .play-button, .detail-button { diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js index eae1aef..a2f9a73 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js +++ b/src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js @@ -1,17 +1,30 @@ const getJellyfinCredentials = () => { const jellyfinCreds = localStorage.getItem("jellyfin_credentials"); + const deviceID = localStorage.getItem("_deviceId2"); + if (!jellyfinCreds) { + console.error("No Jellyfin credentials found in localStorage"); + return; + } try { const serverCredentials = JSON.parse(jellyfinCreds); - const firstServer = serverCredentials.Servers[0]; - if (!firstServer) { - console.error("Could not find credentials for the client"); + const firstServer = serverCredentials?.Servers?.[0]; + if (!firstServer || !firstServer.AccessToken || !firstServer.UserId) { + console.error("Invalid Jellyfin credentials structure"); return; } - return { token: firstServer.AccessToken, userId: firstServer.UserId }; + return { + token: firstServer.AccessToken, + userId: firstServer.UserId, + deviceID + }; } catch (e) { - console.error("Could not parse jellyfin credentials", e); + console.error("Could not parse Jellyfin credentials", e); } }; +const credentials = getJellyfinCredentials(); +if (credentials) { + console.log("Token:", credentials.token, "UserId:", credentials.userId, "DeviceID:", credentials.deviceID); +} const initLoadingScreen = () => { const currentPath = window.location.href.toLowerCase(); @@ -32,8 +45,15 @@ const initLoadingScreen = () => { `; document.body.insertAdjacentHTML("beforeend", loadingHTML); + const interval = setInterval(() => { - if (document.querySelector(".manualLoginForm")) { + if ( + document.querySelector(".manualLoginForm") || + ( + document.querySelector(".homeSectionsContainer") && + document.querySelector(".slide.active") + ) + ) { $(".bar-loading").fadeOut(700, () => $(".bar-loading").remove()); clearInterval(interval); } @@ -41,12 +61,33 @@ const initLoadingScreen = () => { } }; initLoadingScreen(); -const slidesInit = async () => { + +const observer = new MutationObserver((mutations) => { + for (let mutation of mutations) { + if (window.location.hash.includes("#/home.html")) { + let container = document.getElementById("slides-container"); + + if (!container) { + container = document.createElement("div"); + container.id = "slides-container"; + document.body.appendChild(container); + slidesInit(); + } + container.style.display = "block"; + } else { + const container = document.getElementById("slides-container"); + if (container) container.style.display = "none"; + } + } +}); +observer.observe(document.body, { childList: true, subtree: true }); + + +const slidesInit = () => { if (window.hasInitializedSlideshow) { console.log("Slideshow already initialized. Skipping re-init."); return; } - window.hasInitializedSlideshow = !0; const shuffleInterval = 8000; let isTransitioning = !1; @@ -97,15 +138,9 @@ const slidesInit = async () => { logo.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; logo.alt = "Logo"; logo.loading = "eager"; - const logoImgBlur = document.createElement("img"); - logoImgBlur.src = `${window.location.origin}/Items/${itemId}/Images/Logo`; - logoImgBlur.alt = item.Name || "Title"; - logoImgBlur.loading = "eager"; - logoImgBlur.className = "featured-logo-blur"; const logoContainer = document.createElement("div"); logoContainer.className = "logo-container"; logoContainer.appendChild(logo); - logoContainer.appendChild(logoImgBlur); const featuredContent = document.createElement("div"); featuredContent.className = "featured-content"; featuredContent.textContent = title; @@ -142,9 +177,9 @@ const slidesInit = async () => { ratingTest.appendChild(imdbLogo); if (typeof rating === "number") { const formattedRating = rating.toFixed(1); - ratingTest.innerHTML += `${formattedRating} ⭐`; + ratingTest.innerHTML += `${formattedRating}`; } else { - ratingTest.innerHTML += `N/A ⭐`; + ratingTest.innerHTML += `N/A`; } ratingTest.appendChild(createSeparator()); const tomatoRatingDiv = document.createElement("div"); @@ -159,7 +194,10 @@ const slidesInit = async () => { if (typeof criticRating === "number") { valueElement = document.createTextNode(`${criticRating}% `); } else { - valueElement = document.createTextNode(`N/A `); + const naSpan = document.createElement("span"); + naSpan.textContent = "N/A "; + naSpan.style.color = "#fff9"; + valueElement = naSpan; } if (criticRating === undefined || criticRating <= 59) { criticLogo.src = @@ -184,13 +222,19 @@ const slidesInit = async () => { } else { ageRatingDiv.innerHTML = `N/A`; } - const calender = "📅"; const premiereDate = document.createElement("div"); premiereDate.className = "date"; - const year = date ? new Date(date).getFullYear() : "N/A"; - premiereDate.textContent = isNaN(year) ? "N/A" : year; + const year = date ? new Date(date).getFullYear() : NaN; + if (isNaN(year)) { + const naSpan = document.createElement("span"); + naSpan.textContent = "N/A"; + naSpan.style.color = "#fff9"; + premiereDate.innerHTML = ""; + premiereDate.appendChild(naSpan); + } else { + premiereDate.textContent = year; + } ratingTest.appendChild(tomatoRatingDiv); - ratingTest.appendChild(document.createTextNode(calender)); ratingTest.appendChild(premiereDate); ratingTest.appendChild(createSeparator()); ratingTest.appendChild(ageRatingDiv); @@ -214,44 +258,62 @@ const slidesInit = async () => { const playButton = document.createElement("button"); playButton.className = "play-button"; playButton.innerHTML = ` - play_circle + play_circle_fill Play `; playButton.onclick = async () => { - if (!window.PlaybackManager) { - console.error("PlaybackManager is not available."); - return; - } - if (!window.ApiClient) { - console.error("Jellyfin API client is not available."); - return; - } const apiClient = window.ApiClient; const userId = apiClient.getCurrentUserId(); - if (!userId) { - console.error("User not found."); + const mediabrowser = apiClient.appName(); + const appVersion = apiClient.appVersion(); + const deviceName = apiClient.deviceName(); + const deviceId = apiClient.deviceId(); + const accessToken = apiClient.accessToken(); + const baseUrl = apiClient.serverAddress(); + if (!userId || !mediabrowser || !deviceName || !deviceId || !appVersion || !accessToken || !baseUrl) { + console.error("Details not found."); return; } + let sessionId = null; try { - const item = await apiClient.getItem(userId, itemId); - if (!item) { - console.error("Media item not found."); + const response = await fetch(`${baseUrl}/Sessions?deviceId=${encodeURIComponent(deviceId)}`, { + headers: { + 'X-Emby-Authorization': `MediaBrowser Client="${mediabrowser}", Device="${deviceName}", DeviceId="${deviceId}", Version="${appVersion}", Token="${accessToken}"` + } + }); + if (!response.ok) { + throw new Error(`Failed to fetch session data: ${response.statusText}`); + } + const sessions = await response.json(); + if (sessions.length === 0) { + console.warn("No sessions found for deviceId:", deviceId); return; } - window.PlaybackManager.play({ - items: [item], - startPositionTicks: 0, - isMuted: !1, - isPaused: !1, - }) - .then(() => { - console.log("Playback started successfully."); - }) - .catch((error) => { - console.error("Failed to start playback:", error); - }); + const session = sessions[0]; + sessionId = session.Id; + console.log("Found sessionId:", sessionId); } catch (error) { - console.error("Error starting playback:", error); + console.error("Error fetching session data:", error); + return; + } + if (!sessionId) { + console.error("Session ID not found."); + return; + } + const playUrl = `${baseUrl}/Sessions/${sessionId}/Playing?playCommand=PlayNow&itemIds=${itemId}`; + try { + const playResponse = await fetch(playUrl, { + method: 'POST', + headers: { + 'X-Emby-Authorization': `MediaBrowser Client="${mediabrowser}", Device="${deviceName}", DeviceId="${deviceId}", Version="${appVersion}", Token="${accessToken}"` + } + }); + if (!playResponse.ok) { + throw new Error(`Failed to send play command: ${playResponse.statusText}`); + } + console.log("Play command sent successfully to session:", sessionId); + } catch (error) { + console.error("Error sending play command:", error); } }; const detailButton = document.createElement("button"); @@ -261,8 +323,8 @@ const slidesInit = async () => { Info `; detailButton.onclick = () => { - window.top.location.href = `/#!/details?id=${itemId}`; - }; + Emby.Page.show(`/details?id=${itemId}`); + } buttonContainer.appendChild(detailButton); buttonContainer.appendChild(playButton); slide.append( @@ -280,20 +342,10 @@ const slidesInit = async () => { const createSlideForItem = async (item, title) => { const container = document.getElementById("slides-container"); const itemId = item.Id; - const backdropUrl = `${window.location.origin}/Items/${itemId}/Images/Backdrop/0`; - const logoUrl = `${window.location.origin}/Items/${itemId}/Images/Logo`; - const [backdropExists, logoExists] = await Promise.all([ - fetch(backdropUrl, { method: "HEAD" }).then((res) => res.ok), - fetch(logoUrl, { method: "HEAD" }).then((res) => res.ok), - ]); - if (backdropExists && logoExists) { - const slideElement = createSlideElement(item, title); - container.appendChild(slideElement); - if (container.children.length === 1) { - showSlide(0); - } - } else { - console.warn(`Skipping item ${itemId}: Missing backdrop or logo.`); + const slideElement = createSlideElement(item, title); + container.appendChild(slideElement); + if (container.children.length === 1) { + showSlide(0); } }; const fetchItemDetails = async (itemId) => { @@ -325,7 +377,7 @@ const slidesInit = async () => { const fetchItemsFromServer = async () => { try { const response = await fetch( - `${window.location.origin}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&SortBy=random&isPlayed=False&Limit=500`, + `${window.location.origin}/Items?IncludeItemTypes=Movie,Series&Recursive=true&hasOverview=true&imageTypes=Logo,Backdrop&SortBy=random&isPlayed=False&Limit=100`, { headers: { Authorization: `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"`, @@ -394,6 +446,15 @@ const slidesInit = async () => { }); }; const initializeSlideshow = () => { + var slideInterval = null; + class SlideTimer { + constructor(fnctn, time) { + var timerObject = setInterval(fnctn, time); + this.stop = function () { if (timerObject) { clearInterval(timerObject); timerObject = null; } return this; }; + this.start = function () { if (!timerObject) { this.stop(); timerObject = setInterval(fnctn, time); } return this; }; + this.restart = function () { return this.stop().start(); }; + } + } const slides = document.querySelectorAll(".slide"); createPaginationDots(); const container = document.getElementById("slides-container"); @@ -405,6 +466,7 @@ const slidesInit = async () => { isTransitioning = !0; currentSlideIndex = (index + slides.length) % slides.length; showSlide(currentSlideIndex); + slideInterval.restart(); setTimeout(() => { isTransitioning = !1; }, 500); @@ -417,11 +479,10 @@ const slidesInit = async () => { if (slides.length > 0) { showSlide(currentSlideIndex); container.style.display = "block"; - setTimeout(() => { - setInterval(() => { + slideInterval = + new SlideTimer(function () { updateCurrentSlide(currentSlideIndex + 1); }, shuffleInterval); - }, 8000); } slides.forEach((slide) => { slide.addEventListener( @@ -533,7 +594,6 @@ const slidesInit = async () => { items = shuffleArray(items); await createSlidesForItems(items); initializeSlideshow(); - $(".bar-loading").fadeOut(700, () => $(".bar-loading").remove()); }; initializeSlides(); }; \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 81c5ef6..16f6c4c 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -18,13 +18,9 @@
- - - - diff --git a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs index a45eaa6..0552d13 100644 --- a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs +++ b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs @@ -57,26 +57,6 @@ namespace Jellyfin.Plugin.MediaBar.Services payloads.Add(payload); } - { - JObject payload = new JObject(); - payload.Add("id", "e6d32b76-d54b-4946-b73e-c5c9c50575c9"); - payload.Add("fileNamePattern", "home-html\\.[a-zA-z0-9]+\\.chunk\\.js"); - payload.Add("callbackAssembly", GetType().Assembly.FullName); - payload.Add("callbackClass", typeof(TransformationPatches).FullName); - payload.Add("callbackMethod", nameof(TransformationPatches.HomeHtmlChunk)); - - payloads.Add(payload); - } - { - JObject payload = new JObject(); - payload.Add("id", "3d171ef1-a198-48ac-9a60-f6aa98e5fd6d"); - payload.Add("fileNamePattern", "main.jellyfin.bundle.js"); - payload.Add("callbackAssembly", GetType().Assembly.FullName); - payload.Add("callbackClass", typeof(TransformationPatches).FullName); - payload.Add("callbackMethod", nameof(TransformationPatches.MainBundle)); - - payloads.Add(payload); - } { JObject payload = new JObject(); payload.Add("id", "8d374d6b-3c5b-464a-a2a2-96e92fa81345"); From 6f432ec95c930f92344674e16da8bc398cae208f Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 10 Mar 2025 22:08:42 +0000 Subject: [PATCH 077/108] Updated README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f26a04c..ef1c461 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Jellyfin-Media-Bar - Now with Play Now Function 1. Add `https://www.iamparadox.dev/jellyfin/plugins/manifest.json` to your repositories -2. Install "File Transformation" 2.0.0 -3. Install "Media Bar" 2.0.0 +2. Install "File Transformation" at least 2.2.1.0 +3. Install latest "Media Bar" 4. Restart Jellyfin
From f4851d4d1c222bd48d095515e5189d6bfefd378e Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 19 Mar 2025 22:29:55 +0000 Subject: [PATCH 078/108] Changed plugin to use js and css from jsdelivr rather than embedded directly. Added setting for user to specify which release should be used (or "latest", default) --- .../Configuration/PluginConfiguration.cs | 2 + .../Configuration/config.html | 8 + .../Helpers/TransformationPatches.cs | 6 +- .../Inject/index.html | 5 +- .../Inject/slideshowpure.css | 4430 ----------------- .../Inject/slideshowpure.js | 599 --- .../Jellyfin.Plugin.MediaBar.csproj | 4 - 7 files changed, 17 insertions(+), 5037 deletions(-) delete mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.css delete mode 100644 src/Jellyfin.Plugin.MediaBar/Inject/slideshowpure.js diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs b/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs index e9fbbd7..3757b2c 100644 --- a/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs @@ -12,6 +12,8 @@ namespace Jellyfin.Plugin.MediaBar.Configuration { public MediaBarState Enabled { get; set; } = MediaBarState.Enabled; + public string VersionString { get; set; } = "latest"; + public bool UseAvatarsFile { get; set; } = true; public string AvatarsPlaylist { get; set; } = string.Empty; diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html index 16baa58..ce6fefd 100644 --- a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html @@ -28,6 +28,12 @@ Whether the media bar should appear or not. +
+ + + +
Which version of the jsdelivr'd plugin should be used.
+
From 4f95f28e47c7901668af7797e8bedf6e144ef962 Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Sun, 20 Jul 2025 11:18:36 +0100 Subject: [PATCH 084/108] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index ef1c461..42f3fde 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,11 @@ 3. Install latest "Media Bar" 4. Restart Jellyfin +## Reporting Issues +If you issues relating to the visuals or behaviour of buttons added by the Media Bar please report them on MakD's repo (the one this is forked from). This plugin pulls the content from their repo directly and is only in control of adding it without the need for modifying your JF install files. + +Any issues with plugin's settings (including using a playlist as your avatar's playlist) should be made here. +
Original README.md from MakD From c3455668a9d82db4ea3e3402677e33ece856ea78 Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Sun, 20 Jul 2025 11:19:01 +0100 Subject: [PATCH 085/108] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 42f3fde..8874fc3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ 4. Restart Jellyfin ## Reporting Issues -If you issues relating to the visuals or behaviour of buttons added by the Media Bar please report them on MakD's repo (the one this is forked from). This plugin pulls the content from their repo directly and is only in control of adding it without the need for modifying your JF install files. +If you face issues relating to the visuals or behaviour of buttons added by the Media Bar please report them on MakD's repo (the one this is forked from). This plugin pulls the content from their repo directly and is only in control of adding it without the need for modifying your JF install files. Any issues with plugin's settings (including using a playlist as your avatar's playlist) should be made here. From 40c1246a318de8a5127e5613c8146b3bdf45044b Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Wed, 20 Aug 2025 15:29:44 +0100 Subject: [PATCH 086/108] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8874fc3..903728e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ If you face issues relating to the visuals or behaviour of buttons added by the Any issues with plugin's settings (including using a playlist as your avatar's playlist) should be made here. +## Development Update - 20th August 2025 + +Hey all! Things are changing with my plugins are more and more people start to use them and report issues. In order to make it easier for me to manage I'm splitting bugs and features into different areas. For feature requests please head over to https://features.iamparadox.dev/ where you'll be able to signin with GitHub and make a feature request. For bugs please report them on the relevant GitHub repo and they will be added to the project board when I've seen them. I've found myself struggling to know when issues are made and such recently so I'm also planning to create a system that will monitor a particular view for new issues that come up and send me a notification which should hopefully allow me to keep more up to date and act faster on various issues. + +As with a lot of devs, I am very momentum based in my personal life coding and there are often times when these projects may appear dormant, I assure you now that I don't plan to let these projects go stale for a long time, there just might be times where there isn't an update or response for a couple weeks, but I'll try to keep that better than it has been. With all new releases to Jellyfin I will be updating as soon as possible, I have already made a start on 10.11.0 and will release an update to my plugins hopefully not long after that version is officially released! +
Original README.md from MakD From 482f6a595da113d18253b02cee88f4f110b99bc3 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Fri, 22 Aug 2025 23:41:48 +0100 Subject: [PATCH 087/108] Started work to support 10.11 --- .../Jellyfin.Plugin.MediaBar.csproj | 10 +++++----- .../Services/StartupService.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 231bb94..53b520d 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable enable false @@ -9,10 +9,10 @@ - - - - + + + + diff --git a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs index 0552d13..30b25d1 100644 --- a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs +++ b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs @@ -92,7 +92,7 @@ namespace Jellyfin.Plugin.MediaBar.Services { yield return new TaskTriggerInfo() { - Type = TaskTriggerInfo.TriggerStartup + Type = TaskTriggerInfoType.StartupTrigger }; } } From 982c0f9b6df8bed63567102f36e5514d4bc271ab Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sat, 23 Aug 2025 01:35:59 +0100 Subject: [PATCH 088/108] Adding a framework for being able to support multiple jellyfin versions simultaneously without having to stop support for previous versions when a new one releases. --- .../Attributes/JellyfinVersionAttribute.cs | 20 +++++++++++++++++++ .../Jellyfin.Plugin.MediaBar.csproj | 9 +++++---- .../Properties/AssemblyInfo.cs | 5 ++++- 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 src/Jellyfin.Plugin.MediaBar/Attributes/JellyfinVersionAttribute.cs diff --git a/src/Jellyfin.Plugin.MediaBar/Attributes/JellyfinVersionAttribute.cs b/src/Jellyfin.Plugin.MediaBar/Attributes/JellyfinVersionAttribute.cs new file mode 100644 index 0000000..360b97b --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/Attributes/JellyfinVersionAttribute.cs @@ -0,0 +1,20 @@ +using System.Reflection; + +namespace Jellyfin.Plugin.MediaBar.Attributes +{ + [AttributeUsage(AttributeTargets.Assembly)] + public class JellyfinVersionAttribute : Attribute + { + public string Version { get; set; } + + public JellyfinVersionAttribute(string version) + { + Version = version; + } + + public static string? GetVersion() + { + return Assembly.GetExecutingAssembly().GetCustomAttribute()?.Version; + } + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 231bb94..9b2e284 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -6,13 +6,14 @@ enable false 2.3.0.0 + 10.10.7 - - - - + + + + diff --git a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs index fe547c2..769af65 100644 --- a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs +++ b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs @@ -1,6 +1,9 @@ using System.Reflection; +using Jellyfin.Plugin.MediaBar.Attributes; + [assembly: AssemblyCompany("IAmParadox27")] [assembly: AssemblyProduct("Jellyfin.Plugins.MediaBar")] [assembly: AssemblyDescription("")] [assembly: AssemblyTitle("Jellyfin.Plugins.MediaBar")] -[assembly: AssemblyVersion("2.3.0.0")] \ No newline at end of file +[assembly: AssemblyVersion("2.3.0.0")] +[assembly: JellyfinVersion("10.10.7")] \ No newline at end of file From ad2abb106060779936eb09e7c78f641ae1a740c8 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sat, 23 Aug 2025 02:12:35 +0100 Subject: [PATCH 089/108] Added dynamic target framework since 10.11 updates to net9.0 --- src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index fe50e46..b428962 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -1,12 +1,13 @@  - net9.0 + 10.11.0-rc5 + net9.0 + net8.0 enable enable false 2.3.0.0 - 10.11.0-rc5 From fa4338965e5f617bb4a982dc42594995a6755ddc Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sat, 23 Aug 2025 11:49:43 +0100 Subject: [PATCH 090/108] Changes required to support multiple versions at the same time --- .../Jellyfin.Plugin.MediaBar.csproj | 12 ++++++++++++ .../10.10.7/StartupServiceHelper.cs | 15 +++++++++++++++ .../10.11.0-rc5/StartupServiceHelper.cs | 15 +++++++++++++++ .../Services/StartupService.cs | 9 ++------- 4 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.10.7/StartupServiceHelper.cs create mode 100644 src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0-rc5/StartupServiceHelper.cs diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index b428962..ddd7301 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -18,6 +18,18 @@ + + + + + + + + + + + + diff --git a/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.10.7/StartupServiceHelper.cs b/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.10.7/StartupServiceHelper.cs new file mode 100644 index 0000000..e7b5ed1 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.10.7/StartupServiceHelper.cs @@ -0,0 +1,15 @@ +using MediaBrowser.Model.Tasks; + +namespace Jellyfin.Plugin.MediaBar.JellyfinVersionSpecific +{ + public static class StartupServiceHelper + { + public static IEnumerable GetDefaultTriggers() + { + yield return new TaskTriggerInfo() + { + Type = TaskTriggerInfo.TriggerStartup + }; + } + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0-rc5/StartupServiceHelper.cs b/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0-rc5/StartupServiceHelper.cs new file mode 100644 index 0000000..30cba08 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0-rc5/StartupServiceHelper.cs @@ -0,0 +1,15 @@ +using MediaBrowser.Model.Tasks; + +namespace Jellyfin.Plugin.MediaBar.JellyfinVersionSpecific +{ + public static class StartupServiceHelper + { + public static IEnumerable GetDefaultTriggers() + { + yield return new TaskTriggerInfo() + { + Type = TaskTriggerInfoType.StartupTrigger + }; + } + } +} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs index 30b25d1..91fa6e7 100644 --- a/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs +++ b/src/Jellyfin.Plugin.MediaBar/Services/StartupService.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Runtime.Loader; using System.Text; using Jellyfin.Plugin.MediaBar.Helpers; +using Jellyfin.Plugin.MediaBar.JellyfinVersionSpecific; using Jellyfin.Plugin.MediaBar.Model; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; @@ -88,12 +89,6 @@ namespace Jellyfin.Plugin.MediaBar.Services return Task.CompletedTask; } - public IEnumerable GetDefaultTriggers() - { - yield return new TaskTriggerInfo() - { - Type = TaskTriggerInfoType.StartupTrigger - }; - } + public IEnumerable GetDefaultTriggers() => StartupServiceHelper.GetDefaultTriggers(); } } \ No newline at end of file From 10a9e121f48308a30d108f3dd3f22312cfebd08d Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sat, 23 Aug 2025 11:50:05 +0100 Subject: [PATCH 091/108] Missed file --- src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs index 769af65..86043cc 100644 --- a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs +++ b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs @@ -6,4 +6,4 @@ using Jellyfin.Plugin.MediaBar.Attributes; [assembly: AssemblyDescription("")] [assembly: AssemblyTitle("Jellyfin.Plugins.MediaBar")] [assembly: AssemblyVersion("2.3.0.0")] -[assembly: JellyfinVersion("10.10.7")] \ No newline at end of file +[assembly: JellyfinVersion("10.11.0-rc5")] \ No newline at end of file From 13c2884d612ce307ff8b1cbcc8bd51d16578ab04 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sat, 23 Aug 2025 19:22:17 +0100 Subject: [PATCH 092/108] Changed TargetFramework logic for 10.11 to use StartsWith functionality instead of == to allow for more RC to be released and just update by updating `` instead of lots of places --- src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index ddd7301..e9c9e61 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -2,7 +2,7 @@ 10.11.0-rc5 - net9.0 + net9.0 net8.0 enable enable From 523f9e113a917f194ead327241f91f4d90011468 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sun, 24 Aug 2025 22:05:18 +0100 Subject: [PATCH 093/108] Updated media bar to support 10.11 --- slideshowpure.js | 11 +++++++---- .../Jellyfin.Plugin.MediaBar.csproj | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/slideshowpure.js b/slideshowpure.js index 0d20a96..1b22a53 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -155,9 +155,11 @@ const initJellyfinData = (callback) => { const initLoadingScreen = () => { const currentPath = window.location.href.toLowerCase(); const isHomePage = - currentPath.includes("/web/#/home.html") || - currentPath.includes("/web/index.html#/home.html") || - currentPath.endsWith("/web/"); + currentPath.equals("/web/#/home.html") || + currentPath.equals("/web/#/home") || + currentPath.equals("/web/index.html#/home.html") || + currentPath.equals("/web/index.html#/home") || + currentPath.equals("/web/"); if (!isHomePage) return; @@ -732,7 +734,8 @@ const VisibilityObserver = { if (!container) return; const isVisible = - window.location.hash.includes("#/home.html") && + (window.location.hash.equals("#/home.html") || + window.location.hash.equals("#/home")) && activeTab.getAttribute("data-index") === "0"; container.style.display = isVisible ? "block" : "none"; diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index e9c9e61..bb93bfe 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -40,6 +40,8 @@ + + From 6559962c437bebd59f7c53cdea4d58bdd250a95e Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sun, 24 Aug 2025 22:17:50 +0100 Subject: [PATCH 094/108] Changed src for assets to be directly from this fork rather than the original - this allows for changes to be made here before they are made in the original repo and still allows for syncing from the original repo for updates --- src/Jellyfin.Plugin.MediaBar/Inject/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/index.html b/src/Jellyfin.Plugin.MediaBar/Inject/index.html index f5ab064..fa7afc9 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/index.html +++ b/src/Jellyfin.Plugin.MediaBar/Inject/index.html @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file From 1074209f1937ae246af7c667120a8eaa64086d0b Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Sun, 24 Aug 2025 22:25:47 +0100 Subject: [PATCH 095/108] Apparently .equals doesn't exist and it was using href not href minus origin :) fixed both those with working code --- slideshowpure.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/slideshowpure.js b/slideshowpure.js index 1b22a53..5ff235e 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -153,13 +153,13 @@ const initJellyfinData = (callback) => { */ const initLoadingScreen = () => { - const currentPath = window.location.href.toLowerCase(); + const currentPath = window.location.href.toLowerCase().replace(window.location.origin, ""); const isHomePage = - currentPath.equals("/web/#/home.html") || - currentPath.equals("/web/#/home") || - currentPath.equals("/web/index.html#/home.html") || - currentPath.equals("/web/index.html#/home") || - currentPath.equals("/web/"); + currentPath === "/web/#/home.html" || + currentPath === "/web/#/home" || + currentPath === "/web/index.html#/home.html" || + currentPath === "/web/index.html#/home" || + currentPath === "/web/"; if (!isHomePage) return; @@ -734,8 +734,8 @@ const VisibilityObserver = { if (!container) return; const isVisible = - (window.location.hash.equals("#/home.html") || - window.location.hash.equals("#/home")) && + (window.location.hash === "#/home.html" || + window.location.hash === "#/home") && activeTab.getAttribute("data-index") === "0"; container.style.display = isVisible ? "block" : "none"; From 3af80c6c7e1ac8b6129bac5602dd40a6be081829 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 25 Aug 2025 02:18:58 +0100 Subject: [PATCH 096/108] Adding auto syncing for fork using wei/pull --- .github/pull.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/pull.yml diff --git a/.github/pull.yml b/.github/pull.yml new file mode 100644 index 0000000..5d5fefe --- /dev/null +++ b/.github/pull.yml @@ -0,0 +1,14 @@ +version: "1" +rules: + - base: main + upstream: MakD:Jellyfin-Media-Bar + mergeMethod: merge + mergeUnstable: false + assignees: + - IAmParadox27 + reviewers: + - IAmParadox27 + conflictReviewers: + - IAmParadox27 +label: ":arrow_heading_down: pull" +conflictLabel: "merge-conflict" \ No newline at end of file From 8656479c3691d61ae4506dd258d42564c33f752c Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Tue, 26 Aug 2025 08:09:32 +0100 Subject: [PATCH 097/108] Force the plugin to use "main" instead of "latest" for the version string since changing CDN --- .../Configuration/PluginConfiguration.cs | 2 +- src/Jellyfin.Plugin.MediaBar/Configuration/config.html | 5 +++++ .../Helpers/TransformationPatches.cs | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs b/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs index 3757b2c..675121e 100644 --- a/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs @@ -12,7 +12,7 @@ namespace Jellyfin.Plugin.MediaBar.Configuration { public MediaBarState Enabled { get; set; } = MediaBarState.Enabled; - public string VersionString { get; set; } = "latest"; + public string VersionString { get; set; } = "main"; public bool UseAvatarsFile { get; set; } = true; diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html index 8618f3c..af3cc69 100644 --- a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html @@ -94,6 +94,11 @@ document.querySelector("[data-id=enabledSelect]").value = config.Enabled; document.querySelector("[data-id=chkUseAvatarsFile]").checked = config.UseAvatarsFile; document.querySelector("[data-id=txtAvatarsPlaylist]").value = config.AvatarsPlaylist; + + if (config.VersionString === 'latest') { + config.VersionString = 'main'; + } + document.querySelector("[data-id=txtVersionString]").value = config.VersionString; }) .catch(function (error) { diff --git a/src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs b/src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs index ad5257e..ab40461 100644 --- a/src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs +++ b/src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs @@ -94,6 +94,11 @@ namespace Jellyfin.Plugin.MediaBar.Helpers Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(MediaBarPlugin).Namespace}.Inject.index.html")!; using TextReader reader = new StreamReader(stream); + if (MediaBarPlugin.Instance.Configuration.VersionString == "latest") + { + MediaBarPlugin.Instance.Configuration.VersionString = "main"; + } + string importedHtml = reader .ReadToEnd() .Replace("{{Config.VersionString}}", MediaBarPlugin.Instance.Configuration.VersionString); From 388a703184ba3eb74b183770f78e70fa7f8123c5 Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:34:08 +0100 Subject: [PATCH 098/108] Update README.md --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 903728e..027f6d8 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,60 @@ -# Jellyfin-Media-Bar - Now with Play Now Function - -1. Add `https://www.iamparadox.dev/jellyfin/plugins/manifest.json` to your repositories -2. Install "File Transformation" at least 2.2.1.0 -3. Install latest "Media Bar" -4. Restart Jellyfin +

Media Bar

+

A Jellyfin Plugin

+

+ Logo +
+
+ + DBAD + + + Current Release + +

## Reporting Issues -If you face issues relating to the visuals or behaviour of buttons added by the Media Bar please report them on MakD's repo (the one this is forked from). This plugin pulls the content from their repo directly and is only in control of adding it without the need for modifying your JF install files. + +If you face issues relating to the visuals or behaviour of buttons added by the Media Bar please report them on MakD's repo (the one this is forked from). This plugin pulls the content from their repo directly and is only in control of adding it without the need for modifying your JF install files. Any issues with plugin's settings (including using a playlist as your avatar's playlist) should be made here. - ## Development Update - 20th August 2025 Hey all! Things are changing with my plugins are more and more people start to use them and report issues. In order to make it easier for me to manage I'm splitting bugs and features into different areas. For feature requests please head over to https://features.iamparadox.dev/ where you'll be able to signin with GitHub and make a feature request. For bugs please report them on the relevant GitHub repo and they will be added to the project board when I've seen them. I've found myself struggling to know when issues are made and such recently so I'm also planning to create a system that will monitor a particular view for new issues that come up and send me a notification which should hopefully allow me to keep more up to date and act faster on various issues. As with a lot of devs, I am very momentum based in my personal life coding and there are often times when these projects may appear dormant, I assure you now that I don't plan to let these projects go stale for a long time, there just might be times where there isn't an update or response for a couple weeks, but I'll try to keep that better than it has been. With all new releases to Jellyfin I will be updating as soon as possible, I have already made a start on 10.11.0 and will release an update to my plugins hopefully not long after that version is officially released! +## Installation + +### Prerequisites +- This plugin is based on Jellyfin Version `10.10.7` +- The following plugins are required to also be installed, please following their installation guides: + - File Transformation (https://github.com/IAmParadox27/jellyfin-plugin-file-transformation) at least v2.2.1.0 + +### Installation +1. Add `https://www.iamparadox.dev/jellyfin/plugins/manifest.json` to your plugin repositories. +2. Install `Media Bar` from the Catalogue. +3. Restart Jellyfin. +4. Force refresh your webpage (or app) and you should see your new Media Bar at the top of the home page. +## Upcoming Features/Known Issues +If you find an issue with any of the sections or usage of the plugin, please open an issue on GitHub. + +### FAQ + +#### I've installed the plugins and the media bar doesn't appear. How do I fix? +This is common, particularly on a fresh install. The first thing you should try is the following +1. Launch your browsers developer tools + +![image](https://github.com/user-attachments/assets/e8781a69-464e-430e-a07c-5172a620ef84) + +3. Open the **Network** tab across the top bar +4. Check the **Disable cache** checkbox +5. Refresh the page **while the dev tools are still open** + +![image](https://github.com/user-attachments/assets/6f8c3fc7-89a3-4475-b8a6-cd4a58d51b84) + +## Credits +Credits for this plugin go to @MakD for his original work and to @BobHasNoSoul and @SethBacon for their influence to MakD. For full credits see below in the original README content + +## Original README
Original README.md from MakD From fa13c9eecda35fbc8141eb936f2d2b16fd848632 Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:35:46 +0100 Subject: [PATCH 099/108] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 027f6d8..cedc389 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ DBAD - + Current Release

From 19bbcfeef1c77b5f45bdc7970300727bf4dac15c Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 27 Aug 2025 19:13:45 +0100 Subject: [PATCH 100/108] Preparing for a seamless upgrade to 10.11.0 proper --- .../Jellyfin.Plugin.MediaBar.csproj | 6 ++++++ .../10.11.0/StartupServiceHelper.cs | 15 +++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0/StartupServiceHelper.cs diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index bb93bfe..175e78b 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -31,6 +31,12 @@ + + + + + + diff --git a/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0/StartupServiceHelper.cs b/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0/StartupServiceHelper.cs new file mode 100644 index 0000000..30cba08 --- /dev/null +++ b/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0/StartupServiceHelper.cs @@ -0,0 +1,15 @@ +using MediaBrowser.Model.Tasks; + +namespace Jellyfin.Plugin.MediaBar.JellyfinVersionSpecific +{ + public static class StartupServiceHelper + { + public static IEnumerable GetDefaultTriggers() + { + yield return new TaskTriggerInfo() + { + Type = TaskTriggerInfoType.StartupTrigger + }; + } + } +} \ No newline at end of file From 1f832402c3a40c7a83a9b9fbc7a3109e5c604117 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Fri, 3 Oct 2025 21:18:22 +0100 Subject: [PATCH 101/108] Changed 10.11 pre-release code to target rc8 not rc5 --- .../Jellyfin.Plugin.MediaBar.csproj | 8 ++++---- .../{10.11.0-rc5 => 10.11.0-rc8}/StartupServiceHelper.cs | 0 src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/{10.11.0-rc5 => 10.11.0-rc8}/StartupServiceHelper.cs (100%) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 175e78b..7324d96 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -1,7 +1,7 @@  - 10.11.0-rc5 + 10.11.0-rc8 net9.0 net8.0 enable @@ -26,9 +26,9 @@ - - - + + + diff --git a/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0-rc5/StartupServiceHelper.cs b/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0-rc8/StartupServiceHelper.cs similarity index 100% rename from src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0-rc5/StartupServiceHelper.cs rename to src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0-rc8/StartupServiceHelper.cs diff --git a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs index 86043cc..c2933ad 100644 --- a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs +++ b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs @@ -6,4 +6,4 @@ using Jellyfin.Plugin.MediaBar.Attributes; [assembly: AssemblyDescription("")] [assembly: AssemblyTitle("Jellyfin.Plugins.MediaBar")] [assembly: AssemblyVersion("2.3.0.0")] -[assembly: JellyfinVersion("10.11.0-rc5")] \ No newline at end of file +[assembly: JellyfinVersion("10.11.0-rc8")] \ No newline at end of file From 28c280ad577267753c4069224b626bba7ac7b221 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Wed, 15 Oct 2025 22:37:22 +0100 Subject: [PATCH 102/108] Added support for customising the media bar's configuration variables from the admin plugin page --- .../Configuration/PluginConfiguration.cs | 40 ++++ .../Configuration/config.html | 203 +++++++++++++++--- .../Controllers/MediaBarController.cs | 7 + .../Inject/index.html | 68 +++++- 4 files changed, 284 insertions(+), 34 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs b/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs index 675121e..d74bcdf 100644 --- a/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/PluginConfiguration.cs @@ -17,5 +17,45 @@ namespace Jellyfin.Plugin.MediaBar.Configuration public bool UseAvatarsFile { get; set; } = true; public string AvatarsPlaylist { get; set; } = string.Empty; + + public WebConfig WebConfig { get; set; } = new WebConfig(); + } + + public class WebConfig + { + public ImageSvgs ImageSvgs { get; set; } = new ImageSvgs(); + + public int ShuffleInterval { get; set; } = -1; + + public int RetryInterval { get; set; } = -1; + + public int MinSwipeDistance { get; set; } = -1; + + public int LoadingCheckInterval { get; set; } = -1; + + public int MaxPlotLength { get; set; } = -1; + + public int MaxMovies { get; set; } = -1; + + public int MaxTvShows { get; set; } = -1; + + public int MaxItems { get; set; } = -1; + + public int PreloadCount { get; set; } = -1; + + public int FadeTransitionDuration { get; set; } = -1; + + public bool SlideAnimationEnabled { get; set; } = true; + } + + public class ImageSvgs + { + public string? ImdbLogo { get; set; } = null; + + public string? TomatoLogo { get; set; } = null; + + public string? FreshTomato { get; set; } = null; + + public string? RottenTomato { get; set; } = null; } } \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html index af3cc69..a5c4e49 100644 --- a/src/Jellyfin.Plugin.MediaBar/Configuration/config.html +++ b/src/Jellyfin.Plugin.MediaBar/Configuration/config.html @@ -16,41 +16,122 @@ -
+
+
+ + +
-
-
- Enabled: - - Whether the media bar should appear or not. +
+
+
+ Enabled: + + Whether the media bar should appear or not. +
+
+ + + +
Which version of the jsdelivr'd plugin should be used.
+
+
+ +
If checked, the plugin will look for a list.txt file in the avatars directory at the root of jellyfin-web.
+
+
+ + + +
If populated, and "Use physical avatars file" is disabled, this is the name of the playlist which is used to populate the media bar.
+ If empty, the media bar will randomly populate.
+
-
- - - -
Which version of the jsdelivr'd plugin should be used.
-
-
- -
If checked, the plugin will look for a list.txt file in the avatars directory at the root of jellyfin-web.
-
-
- - - -
If populated, and "Use physical avatars file" is disabled, this is the name of the playlist which is used to populate the media bar.
- If empty, the media bar will randomly populate.
+
+
@@ -67,7 +148,30 @@ configurationWrapper: document.querySelector("#configurationWrapper"), btnSave: document.querySelector("#saveConfig"), - + + setupTabs: function() { + const tabs = document.querySelectorAll('.jellyfin-tab-button'); + const tabContents = document.querySelectorAll('.jellyfin-tab-content'); + tabs.forEach(tab => { + tab.addEventListener('click', () => { + tabs.forEach(t => { + t.classList.remove('active'); + t.style.color = '#ccc'; + t.style.borderBottom = '2px solid transparent'; + }); + tab.classList.add('active'); + tab.style.color = 'var(--primary-accent-color, #fff)'; + tab.style.borderBottom = '2px solid var(--primary-accent-color, #fff)'; + tabContents.forEach(content => { + content.classList.remove('active'); + content.style.display = 'none'; + }); + const activeTab = document.getElementById(tab.dataset.tab); + activeTab.classList.add('active'); + activeTab.style.display = 'block'; + }); + }); + }, saveConfig: function (e) { e.preventDefault(); Dashboard.showLoadingMsg(); @@ -76,6 +180,25 @@ UseAvatarsFile: document.querySelector("[data-id=chkUseAvatarsFile]").checked, AvatarsPlaylist: document.querySelector("[data-id=txtAvatarsPlaylist]").value, VersionString: document.querySelector("[data-id=txtVersionString]").value, + WebConfig: { + ImageSvgs: { + ImdbLogo: null, + TomatoLogo: null, + FreshTomato: null, + RottenTomato: null, + }, + ShuffleInterval: parseInt(document.querySelector("[data-id=numShuffleInterval]").value, 10), + RetryInterval: parseInt(document.querySelector("[data-id=numRetryInterval]").value, 10), + MinSwipeDistance: parseInt(document.querySelector("[data-id=numMinSwipeDistance]").value, 10), + LoadingCheckInterval: parseInt(document.querySelector("[data-id=numLoadingCheckInterval]").value, 10), + MaxPlotLength: parseInt(document.querySelector("[data-id=numMaxPlotLength]").value, 10), + MaxMovies: parseInt(document.querySelector("[data-id=numMaxMovies]").value, 10), + MaxTvShows: parseInt(document.querySelector("[data-id=numMaxTvShows]").value, 10), + MaxItems: parseInt(document.querySelector("[data-id=numMaxItems]").value, 10), + PreloadCount: parseInt(document.querySelector("[data-id=numPreloadCount]").value, 10), + FadeTransitionDuration: parseInt(document.querySelector("[data-id=numFadeTransitionDuration]").value, 10), + SlideAnimationEnabled: document.querySelector("[data-id=chkSlideAnimationEnabled]").checked, + } }; window.ApiClient.updatePluginConfiguration(MediaBarConfig.pluginId, config) @@ -100,6 +223,18 @@ } document.querySelector("[data-id=txtVersionString]").value = config.VersionString; + + document.querySelector("[data-id=numShuffleInterval]").value = config.WebConfig.ShuffleInterval; + document.querySelector("[data-id=numRetryInterval]").value = config.WebConfig.RetryInterval; + document.querySelector("[data-id=numMinSwipeDistance]").value = config.WebConfig.MinSwipeDistance; + document.querySelector("[data-id=numLoadingCheckInterval]").value = config.WebConfig.LoadingCheckInterval; + document.querySelector("[data-id=numMaxPlotLength]").value = config.WebConfig.MaxPlotLength; + document.querySelector("[data-id=numMaxMovies]").value = config.WebConfig.MaxMovies; + document.querySelector("[data-id=numMaxTvShows]").value = config.WebConfig.MaxTvShows; + document.querySelector("[data-id=numMaxItems]").value = config.WebConfig.MaxItems; + document.querySelector("[data-id=numPreloadCount]").value = config.WebConfig.PreloadCount; + document.querySelector("[data-id=numFadeTransitionDuration]").value = config.WebConfig.FadeTransitionDuration; + document.querySelector("[data-id=chkSlideAnimationEnabled]").checked = config.WebConfig.SlideAnimationEnabled; }) .catch(function (error) { console.error(error); @@ -109,6 +244,8 @@ }); }, init: function () { + MediaBarConfig.setupTabs(); + MediaBarConfig.btnSave.addEventListener("click", MediaBarConfig.saveConfig); MediaBarConfig.loadConfig(); } diff --git a/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs index 12da879..183a7ab 100644 --- a/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs +++ b/src/Jellyfin.Plugin.MediaBar/Controllers/MediaBarController.cs @@ -1,5 +1,6 @@ using System.Reflection; using Jellyfin.Extensions; +using Jellyfin.Plugin.MediaBar.Configuration; using Jellyfin.Plugin.MediaBar.Helpers; using Jellyfin.Plugin.MediaBar.Model; using MediaBrowser.Controller.Entities; @@ -49,5 +50,11 @@ namespace Jellyfin.Plugin.MediaBar.Controllers return Content(content, "text/plain"); } + + [HttpGet("WebConfig")] + public ActionResult GetWebConfig() + { + return Ok(MediaBarPlugin.Instance.Configuration.WebConfig); + } } } \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Inject/index.html b/src/Jellyfin.Plugin.MediaBar/Inject/index.html index fa7afc9..96b23a4 100644 --- a/src/Jellyfin.Plugin.MediaBar/Inject/index.html +++ b/src/Jellyfin.Plugin.MediaBar/Inject/index.html @@ -1,2 +1,68 @@  - \ No newline at end of file + + \ No newline at end of file From ca193daf59c0ea65d9a58112437788b0798fa462 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 20 Oct 2025 10:36:46 +0100 Subject: [PATCH 103/108] Update to 10.11 proper --- .../Jellyfin.Plugin.MediaBar.csproj | 10 ++-------- .../10.11.0-rc8/StartupServiceHelper.cs | 15 --------------- .../Properties/AssemblyInfo.cs | 4 ++-- 3 files changed, 4 insertions(+), 25 deletions(-) delete mode 100644 src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0-rc8/StartupServiceHelper.cs diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 7324d96..a468b97 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -1,13 +1,13 @@  - 10.11.0-rc8 + 10.11.0 net9.0 net8.0 enable enable false - 2.3.0.0 + 2.4.0.0 @@ -25,12 +25,6 @@ - - - - - - diff --git a/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0-rc8/StartupServiceHelper.cs b/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0-rc8/StartupServiceHelper.cs deleted file mode 100644 index 30cba08..0000000 --- a/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0-rc8/StartupServiceHelper.cs +++ /dev/null @@ -1,15 +0,0 @@ -using MediaBrowser.Model.Tasks; - -namespace Jellyfin.Plugin.MediaBar.JellyfinVersionSpecific -{ - public static class StartupServiceHelper - { - public static IEnumerable GetDefaultTriggers() - { - yield return new TaskTriggerInfo() - { - Type = TaskTriggerInfoType.StartupTrigger - }; - } - } -} \ No newline at end of file diff --git a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs index c2933ad..8b03811 100644 --- a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs +++ b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs @@ -5,5 +5,5 @@ using Jellyfin.Plugin.MediaBar.Attributes; [assembly: AssemblyProduct("Jellyfin.Plugins.MediaBar")] [assembly: AssemblyDescription("")] [assembly: AssemblyTitle("Jellyfin.Plugins.MediaBar")] -[assembly: AssemblyVersion("2.3.0.0")] -[assembly: JellyfinVersion("10.11.0-rc8")] \ No newline at end of file +[assembly: AssemblyVersion("2.4.0.0")] +[assembly: JellyfinVersion("10.11.0")] \ No newline at end of file From 462ddcf418c6463af04512ce91235a71336911e3 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Tue, 21 Oct 2025 00:40:31 +0100 Subject: [PATCH 104/108] Fixed issue for now where if the user is using 10.11 they should use a different URL for the banner image and as such, if they're configured to use the default `main` branch, then we'll change it to the `10.11` branch to solve it until it's solved properly in the js --- .../Helpers/TransformationPatches.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs b/src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs index ab40461..7fc4f43 100644 --- a/src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs +++ b/src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs @@ -1,6 +1,7 @@ using System.Reflection; using System.Text.RegularExpressions; using Jellyfin.Extensions; +using Jellyfin.Plugin.MediaBar.Attributes; using Jellyfin.Plugin.MediaBar.Configuration; using Jellyfin.Plugin.MediaBar.Model; using MediaBrowser.Controller.Entities; @@ -98,6 +99,13 @@ namespace Jellyfin.Plugin.MediaBar.Helpers { MediaBarPlugin.Instance.Configuration.VersionString = "main"; } + + if (MediaBarPlugin.Instance.Configuration.VersionString == "main" && + JellyfinVersionAttribute.GetVersion() == "10.11.0") + { + // Force 10.11 branch instead of main for now + MediaBarPlugin.Instance.Configuration.VersionString = "10.11"; + } string importedHtml = reader .ReadToEnd() From e3acc3b1c888b1eb61ac027cb4a659655d64c856 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 27 Oct 2025 22:57:38 +0000 Subject: [PATCH 105/108] Updated plugin to 10.11.1 --- .../Jellyfin.Plugin.MediaBar.csproj | 19 +++++++++++++------ .../StartupServiceHelper.cs | 0 2 files changed, 13 insertions(+), 6 deletions(-) rename src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/{10.11.0 => 10.11}/StartupServiceHelper.cs (100%) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index a468b97..16affc7 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -1,8 +1,11 @@  - 10.11.0 - net9.0 + 10.11.1 + $(JellyfinVersion) + 10.11.*-* + 10.11.0 + net9.0 net8.0 enable enable @@ -11,10 +14,10 @@ - - - - + + + + @@ -30,6 +33,10 @@ + + + + diff --git a/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0/StartupServiceHelper.cs b/src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11/StartupServiceHelper.cs similarity index 100% rename from src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11.0/StartupServiceHelper.cs rename to src/Jellyfin.Plugin.MediaBar/JellyfinVersionSpecific/10.11/StartupServiceHelper.cs From 5f671b0cdde2ccbd9e6fbd360d00d3fcd561b531 Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Tue, 28 Oct 2025 00:48:13 +0000 Subject: [PATCH 106/108] Lil' ol' whoopsy on the versioning --- src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs b/src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs index 7fc4f43..dc0240a 100644 --- a/src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs +++ b/src/Jellyfin.Plugin.MediaBar/Helpers/TransformationPatches.cs @@ -101,7 +101,7 @@ namespace Jellyfin.Plugin.MediaBar.Helpers } if (MediaBarPlugin.Instance.Configuration.VersionString == "main" && - JellyfinVersionAttribute.GetVersion() == "10.11.0") + JellyfinVersionAttribute.GetVersion() == "10.11") { // Force 10.11 branch instead of main for now MediaBarPlugin.Instance.Configuration.VersionString = "10.11"; From d43d4298d0013a7cdf3a44fd1cce050043a129d7 Mon Sep 17 00:00:00 2001 From: Zach Ross-Clyne <78486554+IAmParadox27@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:19:43 +0000 Subject: [PATCH 107/108] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index cedc389..206feb2 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ If you find an issue with any of the sections or usage of the plugin, please ope ### FAQ +#### I've updated Jellyfin to latest version but I can't see the plugin available in the catalogue + +The likelihood is the plugin hasn't been updated for that version of Jellyfin and the plugins are strictly 1 version compatible. Please wait until an update has been pushed. If you can see the version number in the release assets then please make an issue, but if its not in the assets, please wait. I know Jellyfin has updated, I'll update when I can. + #### I've installed the plugins and the media bar doesn't appear. How do I fix? This is common, particularly on a fresh install. The first thing you should try is the following 1. Launch your browsers developer tools From b2447485bf0deba8fb3f14d5b4c16073d703610d Mon Sep 17 00:00:00 2001 From: IAmParadox27 Date: Mon, 3 Nov 2025 22:24:54 +0000 Subject: [PATCH 108/108] Updated plugin to support 10.11.2 --- .../Jellyfin.Plugin.MediaBar.csproj | 10 ++++++---- .../Properties/AssemblyInfo.cs | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj index 16affc7..be752d5 100644 --- a/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj +++ b/src/Jellyfin.Plugin.MediaBar/Jellyfin.Plugin.MediaBar.csproj @@ -1,9 +1,10 @@  - 10.11.1 + 10.11.2 $(JellyfinVersion) - 10.11.*-* + $(JellyfinVersion) + 10.11.0 10.11.0 net9.0 net8.0 @@ -51,7 +52,8 @@ - - + + + diff --git a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs index 8b03811..d8d2df8 100644 --- a/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs +++ b/src/Jellyfin.Plugin.MediaBar/Properties/AssemblyInfo.cs @@ -6,4 +6,4 @@ using Jellyfin.Plugin.MediaBar.Attributes; [assembly: AssemblyDescription("")] [assembly: AssemblyTitle("Jellyfin.Plugins.MediaBar")] [assembly: AssemblyVersion("2.4.0.0")] -[assembly: JellyfinVersion("10.11.0")] \ No newline at end of file +[assembly: JellyfinVersion("10.11.2")] \ No newline at end of file