Compare commits

...

29 Commits

Author SHA1 Message Date
d26c7667c9 some fixes 2025-02-25 01:47:27 +01:00
062c22bfa8 add language specific series descriptors 2025-02-22 00:44:33 +01:00
632a27983e updated version 2025-02-21 13:42:27 +01:00
de1ef45c41 no more hard coded api key 2025-02-21 01:51:49 +01:00
353e7714bf add chunk content for both 2024-12-31 20:21:51 +01:00
1b828c511a add new (unused) style 2024-12-31 20:20:28 +01:00
252ca17a34 adjusted skip/back button mobile 2024-12-31 20:17:30 +01:00
5e8eede1cd remove comment 2024-12-31 20:09:35 +01:00
55a19ce7e5 fix mobile css 2024-12-31 20:08:19 +01:00
e6384c096b fix hover error 2024-12-29 04:06:46 +01:00
d80c8c01ee fix showonotherpages 2024-12-28 02:12:16 +01:00
cdceb555f0 mod1 2024-12-28 01:48:25 +01:00
548aafb019 .. 2024-12-27 03:43:34 +01:00
50756f3c2f fix first load issue 2024-12-27 03:40:51 +01:00
3bdd185a53 fix skip intro typo 2024-12-27 03:15:54 +01:00
5362f15b80 add enable skip intro 2024-12-27 03:07:03 +01:00
361698e84b add intro skipping 2024-12-27 03:04:34 +01:00
d6214d0441 var name changed 2024-12-27 01:23:43 +01:00
232f55923f remove dep function, typo 2024-12-27 00:52:32 +01:00
3105ed3088 add muteon in list.txt 2024-12-27 00:37:01 +01:00
7b2f5acb21 del old files 2024-12-27 00:33:57 +01:00
8018010aae .. 2024-12-27 00:32:26 +01:00
d9e638ec9e add new commits in my way 2024-12-26 22:08:46 +01:00
ef5f893637 new version test 2024-12-25 13:57:49 +01:00
a4a8b4e7f2 fix mobile height in home-html 2024-11-18 23:47:40 +01:00
bcac8a402a add list.txt 2024-11-15 11:32:31 +01:00
d9b216ce49 start video muted 2024-11-15 00:59:39 +01:00
161fad5fc4 readme 2024-11-15 00:55:55 +01:00
753b821480 readme and pictures 2024-11-15 00:55:44 +01:00
30 changed files with 1183 additions and 286 deletions

View File

@@ -15,17 +15,17 @@ Thanks to [SethBacon](https://forum.jellyfin.org/u-sethbacon) & [BobHasNoSoul](h
Testet on Jellyfin 10.10.0
![clips](/images/all_clips.gif)
---
![overview](/images/demo1.gif)
## How to install
1. Download [spotlight.html]
1. Download [spotlight.html](/spotlight.html), [script.js](/script.js) and [styles.css](/styles.css)
2. Go to your ```jellyfin-web``` folder and create a folder named ```ui``` and drop ```spotlight.html, script.js and styles.css``` in that folder
3. In your Jellyfin Dashboard, under ```API Keys``` create an API key for Spotlight, copy the key, and insert it as the value for ```token``` in ```script.js```. You can also set the corresponding values for list name, random selection, show it only on main page, show title, plot length, etc.
3. In your Jellyfin Dashboard, under ```API Keys``` create an API key for Spotlight, copy the key, and insert it as the value for the ```token``` variable in ```script.js```. You can also set the corresponding values for list name, random selection, show it only on main page, plot length, etc.
4. ```Important: Use Notepad++ for this``` In the jellyfin-web folder, open the file ```home-html.RANDOMSTRINGHERE.chunk.js```
@@ -42,10 +42,13 @@ Testet on Jellyfin 10.10.0
9. That's it.
![feat17](https://github.com/user-attachments/assets/af916d90-ec7c-4af0-b6e8-0f6f94ef1f07)
![clips](/images/all_clips.gif)
# Web View (Movie and series)
![mobile](/images/desktop.png)
# Mobile View (Landscape / Portrait)
![mobile](https://i.imgur.com/Y0wEa81.png)
![mobile](/images/mobile.png)
# How to feature specific content in the bar
@@ -61,9 +64,9 @@ Simply delete Step 5's snippet added to ```home-html.chunk.js``` then refresh yo
No changes here from my side...
![feat8](https://github.com/user-attachments/assets/d6855e23-8c08-4a8b-b05d-6ba9c9895672)
![fullscreen](/images/fullscreen.gif)
Same as above except use [this version of spotlight.html](https://github.com/tedhinklater/Jellyfin-Featured-Content-Bar/blob/main/fullscreen/spotlight.html)
Same as above except use [this version of spotlight.html](/fullscreen/spotlight.html)
insert this into home-html.RANDOMSTRINGHERE.chunk.js after ```data-backdroptype="movie,series,book">```

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,9 @@
.layout-desktop .page.homePage.libraryPage.allLibraryPage.backdropPage.pageWithAbsoluteTabs.withTabs.mainAnimatedPage { margin-top:-4.5em;}
.layout-desktop .overflowBackdropCard, .overflowSmallBackdropCard { width: 12.7vw !important; padding-right: 1.85em;}
.layout-desktop .skinHeader-withBackground {background-color: transparent; backdrop-filter: blur(0px);}
.layout-desktop #homeTab .section0 .sectionTitle.sectionTitle-cards.padded-left { display: none !important;}
.layout-desktop #homeTab .verticalSection.section1.emby-scroller-container { position: relative; top: -27em; left: 73em; width: 44vw; margin-bottom: -17em;}
.layout-desktop #homeTab .verticalSection.section2.emby-scroller-container::after { content: ''; position: fixed; top: 0; left: 0; width: 100%; height: 100vw; background: black; z-index: -1;}
[dir="ltr"] #homeTab .verticalSection.section0.emby-scroller-container .emby-scrollbuttons {right: -5em; top: -2em;}
.layout-desktop #homeTab .verticalSection.section0 .cardText-first {display: none !important;}
.layout-desktop #homeTab .sections.homeSectionsContainer { margin-top: 2em;}

View File

@@ -0,0 +1 @@
<style>.featurediframe { width: 100vw; height: 100vh; display: block; border: 0px solid #000; margin: 0 auto; margin-bottom: 40px} @media (max-width:1000px) and (orientation:portrait) {.featurediframe {height: 46vh; width: 95vw;}} @media (max-width:1000px) and (orientation:landscape) {.featurediframe {height: 98vh; width: 95vw;}} @media (min-width: 2000px) { .featurediframe {height:102vh;}}</style><iframe class="featurediframe" src="/web/ui/spotlight.html"></iframe>

476
fullscreen/spotlight.html Normal file
View File

@@ -0,0 +1,476 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<title>Jellyfin Spotlight v2.3.2</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<style>
@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');
body { margin: 0; padding: 0; overflow: hidden; }
.slide { position: relative; width: 100vw; height: 100vh; box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);}
.slide:focus { outline: 2px solid #fff; }
.backdrop { position: absolute; top: 3.15em; left: 0; width: 100%; height: 100%; object-fit: cover; object-position: center 30%; z-index: 1; transition: width 0.5s ease, filter 0.8s ease, scale 2s ease; transform-origin: top; animation: none;}
.logo { position: relative; transform: translateX(-50%) translateY(-50%); top: 56%; left: 14.5%; z-index: 3; text-shadow: -2px 2px 4px rgba(0, 0, 0, 0.5); filter: drop-shadow(1px 1px 1px); pointer-events: none; transition: transform 0.3s ease, max-height 0.3s ease, max-width 0.3s ease;}
.heading { position: absolute; top: 0; left: 0; width: 100%; height: 2.3em; background-color: transparent; font-family: "Titillium Web", sans-serif; color: #D3D3D3; font-size: 22px; display: flex; align-items: center; justify-content: flex-start; z-index: 2; padding: 10px; padding-left: 0px; box-sizing: border-box; text-shadow: 1px 1px 4px rgba(0, 0, 0, 1); }
.next-button { position: absolute; top: 0.4em; right: 0.25em; font-size: 1.5em; color: #D3D3D3; cursor: pointer; z-index: 10; font-family: "Titillium Web", sans-serif; font-size: 1.35em; text-shadow: 1px 1px 4px rgba(0, 0, 0, 1);}
.skip-button {font-size: 1.62em; top: 0.5em; opacity: 0.5; transition: opacity 0.3s ease, background 0.3s ease; padding-left: 0.15em; padding-right: 0.15em; padding-top: 0.1em; border-radius: 50%; box-sizing: border-box; font-style: normal; font-weight: 400; letter-spacing: normal; line-height: 1; text-transform: none; word-wrap: normal; direction: inherit; white-space: nowrap; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; -moz-osx-font-smoothing: grayscale; -webkit-font-feature-settings: "liga"; font-feature-settings: "liga";}
.skip-button:hover {background: #ffffff70; opacity: 1;}
.video-container { position: absolute; right: 0; top: 50px; width: 0; height: calc(100% - 50px); overflow: hidden; z-index: 2; transition: width 0.5s ease; z-index: 6; border-top-right-radius: 0.5em; border-bottom-right-radius: 0.5em; transform-origin: right;}
.video-player { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 7; }
.clickable-overlay { position: absolute; top: 50px; left: 0; width: 100%; height: calc(100%); z-index: 2; pointer-events: none; background: transparent; transition: background 0.8s ease, backdrop-filter 0.8s ease;}
.clickable-overlay::before { content: ''; position: absolute; top: 25%; left: 0; width: 100%; height: 75%; background: linear-gradient(0deg, rgb(0% 0% 0%) 0%, rgba(0, 0, 0, 0.99) 6.25%, rgba(0, 0, 0, 0.96) 12.5%, rgba(0, 0, 0, 0.92) 18.75%, rgba(0, 0, 0, 0.86) 25%, rgba(0, 0, 0, 0.78) 31.25%, rgba(0, 0, 0, 0.69) 37.5%, rgba(0, 0, 0, 0.6) 43.75%, rgba(0, 0, 0, 0.5) 50%, rgba(0, 0, 0, 0.4) 56.25%, rgba(0, 0, 0, 0.31) 62.5%, rgba(0, 0, 0, 0.22) 68.75%, rgba(0, 0, 0, 0.14) 75%, rgba(0, 0, 0, 0.08) 81.25%, rgba(0, 0, 0, 0.04) 87.5%, rgba(0, 0, 0, 0.01) 93.75%, rgba(0, 0, 0, 0) 100% ); opacity: 0.15; transition: opacity 0.3s ease; z-index: 1;}
.clickable-overlay::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(180deg, rgb(0% 0% 0% / 0.9) 0%, rgb(0% 0% 0% / 0.6952285766601562) 6.25%, rgb(0% 0% 0% / 0.5275634765625) 12.5%, rgb(0% 0% 0% / 0.39222564697265627) 18.75%, rgb(0% 0% 0% / 0.284765625) 25%, rgb(0% 0% 0% / 0.20106353759765627) 31.25%, rgb(0% 0% 0% / 0.1373291015625) 37.5%, rgb(0% 0% 0% / 0.09010162353515627) 43.75%, rgb(0% 0% 0% / 0.05625000000000002) 50%, rgb(0% 0% 0% / 0.03297271728515627) 56.25%, rgb(0% 0% 0% / 0.017797851562500022) 62.5%, rgb(0% 0% 0% / 0.00858306884765625) 68.75%, rgb(0% 0% 0% / 0.003515625000000022) 75%, rgb(0% 0% 0% / 0.0011123657226562722) 81.25%, rgb(0% 0% 0% / 0.0002197265625000222) 87.5%, rgb(0% 0% 0% / 0.000013732910156272204) 93.75%, rgb(0% 0% 0% / 0) 100% ); opacity: 0.25; transition: opacity 0.3s ease, visibility 0.8s ease; z-index: 3; border-radius: 0.25em; }
.lorem-ipsum {position: relative; bottom: -44em; left: 59em; text-align: left; font-family: "Titillium Web", sans-serif; font-size: 92%; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); opacity: 1; color: #fff; z-index: 4; box-sizing: border-box; background: transparent; max-width: fit-content; padding-left: 0.3em; padding-right: 0.3em; display: flex; flex-direction: row; max-height: 1.9em; padding-top: 0.15em; white-space: nowrap;}
.lorem-ipsum span { margin-right: 0.2em;}
.age-rating {position: absolute; top: 3.5em; left: 0em; text-align: left; font-family: "Titillium Web", sans-serif; font-size: 95%; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); opacity: 1; color: #fff; z-index: 4; box-sizing: border-box; background: transparent; max-width: fit-content; padding-left: 0.3em; padding-right: 0.3em; display: flex; flex-direction: row; max-height: 1.9em; padding-top: 0.15em; white-space: nowrap;}
.plot { position: absolute; bottom: 0; left: -0.25em; right: 0; color: #fff; font-family: "Titillium Web", sans-serif; font-size: 0.9em; font-weight: 500; background: transparent; padding: 10px; z-index: 7; box-sizing: border-box; padding-bottom: 3px; padding-top: 0.5em; line-height: 1.2em; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; border-bottom-left-radius: 0.45em; transition: opacity 0.1s ease, max-height 0.8s ease; opacity: 0; max-width: 31%; opacity: 1; -webkit-line-clamp: 3; max-height: 7.8em;}
.genres { position: relative; bottom: -44.1em; color: #fff; z-index: 2; font-family: "Titillium Web", sans-serif; font-size: 0.79em; opacity: 1; transition: opacity 0.3s ease; max-width: 39em; overflow: hidden; height: 1.95em; padding-top: 0.25em; line-height: 2em; white-space: nowrap;}
.premiere-year {position: absolute; top: 5.75em; left: 0.5em; font-size: 0.9em; font-family: "Titillium Web", sans-serif; color: #fff; z-index: 3; opacity: 0; transition: opacity 0.3s; text-align: center; width: 3.5em;}
.additional-info {position: absolute; top: 7.3em; left: 31em; font-size: 0.9em; font-family: "Titillium Web", sans-serif; color: #fff; z-index: 3; opacity: 1; transition: opacity 0.3s; text-align: right; width: 5em;}
.community-rating {position: absolute; top: 5.75em; right: 69.5%; font-size: 0.9em; font-family: "Titillium Web", sans-serif; color: #fff; z-index: 3; opacity: 1; transition: opacity 0.3s; text-align: center; width: 3.6em; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);}
.critic-rating {position: absolute; top: 5.75em; right: 73%; font-size: 0.9em; font-family: "Titillium Web", sans-serif; color: #fff; z-index: 3; opacity: 1; transition: opacity 0.3s; text-align: center; width: 3.9em; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);}
@keyframes objectPositionAnimation { 0% { object-position: center 30%; } 40% { object-position: center 80%; } 80% { object-position: center 20%; } 100% { object-position: center 30%; }}
.star-icon { color: gold; font-size: 0.9em; padding-right: 0.2em; margin-left: -0.1em;}
@media (max-width: 1000px) { .age-rating {position: absolute; top: 3em; left: 0em; font-size: 0.7em;} .lorem-ipsum {font-size: 0.7em; transform: translateX(50%); right: 50vw !important; padding-left: 0; padding-right: 1.5em;} .lorem-ipsum span {background:transparent !important; border: none !important;}.logo { transform: translateX(-50%) translateY(-50%); top: 10.5em; left: 50% !important; max-width: 70%; max-height: 45%; transform-origin: 50% 50%;} .plot {display: none; opacity: 1 !important; font-size: 90%; bottom: 0.5em; -webkit-line-clamp: 2; max-height: 3.3em; overflow: hidden; width: 100% !important; max-width: 100%; line-height: 1.45em;} .lorem-ipsum::before { opacity:0; padding-bottom: 0.25em; } .clickable-overlay { background: linear-gradient(0deg, rgb(0% 0% 0%) 0%, rgb(0% 0% 0% / 0.9990234375) 6.25%, rgba(0, 0, 0, 0.99) 12.5%, rgba(0, 0, 0, 0.97) 18.75%, rgba(0, 0, 0, 0.94) 25%, rgba(0, 0, 0, 0.88) 31.25%, rgba(0, 0, 0, 0.79) 37.5%, rgba(0, 0, 0, 0.67) 43.75%, rgba(0, 0, 0, 0.5) 50%, rgba(0, 0, 0, 0.33) 56.25%, rgba(0, 0, 0, 0.21) 62.5%, rgba(0, 0, 0, 0.12) 68.75%, rgba(0, 0, 0, 0.06) 75%, rgba(0, 0, 0, 0.03) 81.25%, rgba(0, 0, 0, 0.01) 87.5%, rgba(0, 0, 0, 0) 93.75%, rgba(0, 0, 0, 0) 100% ); opacity: 0.8;} .backdrop {left: 0% !important; width: 100% !important; top: 2.95em;} .lorem-ipsum .full-content { opacity: 1; max-width: 100%;} .lorem-ipsum::before { opacity:1; backdrop-filter: blur(2px); transform: translateX(0);} .clickable-overlay::before { opacity: 0.98; backdrop-filter: blur(0px);} .slide:hover .clickable-overlay::before { opacity: 0.98;} .skip-button {opacity: 1 !important; font-size: 0.9em; top: 1.1em; color: #fff;} .genres {bottom: 4em; left: unset !important; overflow: hidden; max-width: 100vw !important; white-space: nowrap; left: 0; z-index: 8; color: #fff; font-size: 0.7em;} .genres span {background: transparent !important; border: none !important;} .additional-info {left: 18.5%; top: 5em; text-align: right;} .community-rating {left: 86vw !important; top: 19.45em !important; text-align: left; z-index: 8; font-size: 0.85em !important;} .critic-rating {font-size: 0.85em !important; left: 70vw !important; top: 19.45em !important; z-index: 8;} .additional-info {left: 18.5%; top: 5em; text-align: left;} .heading {z-index: 0;} .heading::before {display: none;} .video-container {max-width: 0 !important;} .watch-trailer-button {position: absolute; border: none; border-radius: 5px; padding: 0.5em 1em; padding-right: 1.5em; font-family: "Titillium Web", sans-serif; cursor: pointer; z-index: 10; transition: background-color 0.3s ease; align-items: center; justify-content: center; display: flex !important; top: unset !important; bottom: 0.4em; font-size: 0.7em !important; background: #fff9 !important; color: #000e !important; font-weight: 600; transform: translateX(-100%) !important; left: 47% !important;} .slide { box-shadow: none;} .text-container::before { display: none !important;} .buttons-container {top: unset !important; display: flex; position: absolute; bottom: 0.6em; left: 0em !important; width: 100vw; z-index: 10;} .details-button { background: #fff9 !important; color: 000e !important; position: absolute; border: none; border-radius: 5px; padding: 0.5em 1.5em; font-family: "Titillium Web", sans-serif; cursor: pointer; z-index: 10; transition: background-color 0.3s ease; align-items: center; justify-content: center; bottom: -0.4em; font-size: 0.7em !important; background: #fff9; color: #000e; font-weight: 600; transform: translateX(100%) !important; right: 47%; display: flex; padding-right: 2em;} .details-button:hover { background-color: #fffc;} .watch-trailer-button:hover { background-color: #fffc !important;} .details-button:hover { background-color: #fffc !important;} .fas {padding: 0.5em;}}
@media (max-width:1000px) and (orientation:portrait) {.back-button {right:58vw;} .skip-button {right: 0em; top: 0.25em !important;} .genres {right: 50vw; position: absolute; max-width: 95vw !important; overflow:hidden; top: unset; transform: translateX(50%); bottom: 6.5em;} .lorem-ipsum {position: absolute; left: unset; bottom: 4.25em;}}
@media (max-width:1000px) and (orientation:landscape) {.community-rating {left:89vw !important;} .critic-rating {left:76vw !important;} .back-button {right:70vw;} .skip-button {right: 0em; top: 0.25em !important;} .genres {position: absolute; bottom: 6.5em; top: unset; transform: translateX(50%); right: 50vw;} .lorem-ipsum {position: absolute; left: unset; bottom: 4.25em;}}
@media (min-width: 1001px) { .material-icons {font-size: 2em;}.details-button { color: white; background: #101010e3; font-size: 1.75em; padding: 0.25em; padding-left: 0.5em; padding-right: 0.75em; transition: background-color 0.3s ease; cursor: pointer; border-radius: 0.5em;} .lorem-ipsum { bottom: -47em; left: 59em; font-size: 1.1em; transform: translateX(-100%); transition: left 0.5s;} clickable-overlay {pointer-events: none;} .logo {max-height: 23em; position: absolute; top: 29em; transition: transform 0.1s ease, max-height 0.3s ease; max-width: 47em; left: 34em; transform: translateX(-50%) translateY(-50%); transition: left 0.5s, max-width 0.5s;} @media screen and (max-width:1400px){ .logo {max-width: 37em; left: 30em;} .lorem-ipsum {left: 55em;}} .watch-trailer-button {display: none;} .backdrop {filter: brightness(95%) contrast(105%) saturate(105%); left: 0%; width: 100%; transition: width 0.5s ease, filter 0.8s ease, transform 2s ease; filter: brightness(95%) contrast(105%) saturate(105%);} .slide {margin-top: -3.15em; height: 100vh;} .skip-button {top: 17.5em; right: 0.5em; font-size: 2em;} .video-container {top: 9em; height: 18.62em; right: 4em; border-radius: 0em; transform-origin: 100% 0; transition: transform 0.5s ease; transition-delay: 1s;} .plot {position: relative; bottom: -33.5em; left: 4em; font-size: 1.25em; max-width: 49em !important; width: 49em !important; -webkit-line-clamp: 3;} .genres {z-index: 3; bottom: unset; top: 10.5em; left: 5.1em; font-size: 1.1em; max-width: 74em;} .age-rating {font-size: 1.3em !important; position: absolute; left: 4em; top: 6.2em; white-space: nowrap;} .community-rating {top: 44em; left: 50em; font-size: 1.2em;} .critic-rating {top: 44em; left: 43em; font-size: 1.2em;} .heading::before {content: ''; position: absolute; top: 2.27em; left: 0; width: 100%; background: linear-gradient(0deg, rgb(0% 0% 0%) 0%, rgb(0% 0% 0% / 0.9903926402016152) 6.25%, rgb(0% 0% 0% / 0.9619397662556434) 12.5%, rgb(0% 0% 0% / 0.9157348061512727) 18.75%, rgb(0% 0% 0% / 0.8535533905932737) 25%, rgb(0% 0% 0% / 0.7777851165098011) 31.25%, rgb(0% 0% 0% / 0.6913417161825449) 37.5%, rgb(0% 0% 0% / 0.5975451610080642) 43.75%, rgb(0% 0% 0% / 0.5) 50%, rgb(0% 0% 0% / 0.4024548389919359) 56.25%, rgb(0% 0% 0% / 0.3086582838174552) 62.5%, rgb(0% 0% 0% / 0.22221488349019902) 68.75%, rgb(0% 0% 0% / 0.14644660940672627) 75%, rgb(0% 0% 0% / 0.08426519384872733) 81.25%, rgb(0% 0% 0% / 0.03806023374435663) 87.5%, rgb(0% 0% 0% / 0.009607359798384785) 93.75%, rgb(0% 0% 0% / 0) 100% ); height: 100vh; z-index:6; opacity: 1; bottom: 0;} .skip-button { position: absolute; top: 0.4em; right: 0.25em; font-size: 1.5em; color: #D3D3D3; cursor: pointer; z-index: 10; font-family: "Titillium Web", sans-serif; font-size: 1.35em; text-shadow: 1px 1px 4px rgba(0, 0, 0, 1);}}
@media (max-width: 500px) { .community-rating {left: 0em; top: 5.6em;} .critic-rating {left: 0em; top: 7.6em;} .additional-info {left: 18.5%; top: 5em; text-align: right;} .heading::before {display: none;} .logo {max-width: 60%; max-height: 35%; left: 50% !important;}}
.age-rating { font-weight: 600; font-size: 1.1em;} .additional-info {display: none;}
.age-rating span.gb-u, .age-rating span.u { background-color: #078c6d; color: white; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);}
.age-rating span.gb-pg, .age-rating span.pg { background-color: #d7a203; color: white; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);}
.age-rating span.gb-12a, .age-rating span.\31 2A { background-color: #ee7600; color: white; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);}
.age-rating span.gb-12, .age-rating span.\31 2, .age-rating span.gb-15, .age-rating span.\31 2 { background-color: #e19887; color: #e2002d; border: 0.09em solid white !important; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);}
.age-rating span.pg-13 { background-color: #157c0d; color: white; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);}
.age-rating span.tv-y7, .age-rating span.tv-13, .age-rating span.tv-14, .age-rating span.\31 6, .age-rating span.tv-ma, .age-rating span.tv-y, .age-rating span.tv-g, .age-rating span.tv-pg { background-color: #157c0d; color: white; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);}
.age-rating span.nc-17, .age-rating span.gb-18, .age-rating span.\31 8, .age-rating span.r { background-color: #c10f1f; color: white; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);}
.age-rating span.au-g, .age-rating span.nz-g, .age-rating span.eu-pg, .age-rating span.ca-g, .age-rating span.jp-g, .age-rating span.de-0 { background-color: #078c6d; color: white; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); }
.age-rating span.au-pg, .age-rating span.nz-pg, .age-rating span.eu-12, .age-rating span.ca-pg, .age-rating span.jp-pg12, .age-rating span.de-6 { background-color: #d7a203; color: white; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); }
.age-rating span.au-m, .age-rating span.nz-m, .age-rating span.eu-16, .age-rating span.ca-14a, .age-rating span.jp-r15, .age-rating span.de-12 { background-color: #07b1e0; color: white; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); }
.age-rating span.au-ma15, .age-rating span.nz-r15, .age-rating span.eu-18, .age-rating span.ca-18a, .age-rating span.jp-r18, .age-rating span.de-16 { background-color: #fc9712; color: white; border: 0.09em solid white !important; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); }
.age-rating span.au-r18, .age-rating span.nz-r18, .age-rating span.ca-r, .age-rating span.jp-r18+, .age-rating span.de-18 { background-color: #c10f1f; color: white; text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); }
.video-container {overflow: visible;}
.watch-trailer-button { }
.watch-trailer-button .fa-play { margin-right: 0.5em;}
.slide { opacity: 1; transition: opacity 0.31s ease-in-out, font-size 1s ease; }
.fade-in { opacity: 0; visibility: hidden; transition: opacity 0.31s ease-in-out;}
.fade-in-active { opacity: 1; visibility: visible;}
.fade-out { opacity: 0;}
.backdrop { transition: opacity 0.3s ease-in-out, width 0.5s ease, transform 1.5s ease, filter 1s ease;}
#video-overlay { position: fixed; top: 13vh; left: 0vw; width: 100%; height: 140vh; background-color: rgb(0, 0, 0); z-index: 1000; display: flex; background-size: cover; padding: 6vh; padding-top: 10vh; margin-top: -10vh;}
#video-overlay-content { position: relative; width: 90%; max-width: 100%; height: 60%; background-color: #000;}
#close-overlay { position: absolute; top: -1em; right: 0em; font-size: 1.5em; color: #7b7b7b; cursor: pointer;}
#countdown-bar-container { display: none; position: absolute; left: 0%; bottom: 0; width: 110%; height: 0.15em !important; background-color: rgba(0,0,0,0); z-index: 10;}
#countdown-bar { height: 100%; width: 0%; background-color: #ffffff; opacity: 0.5; transition: width 0.1s linear; border-radius: 1em;}
.video-player { mask-image: linear-gradient(45deg, rgb(0% 0% 0% / 0) 0%, rgb(0% 0% 0% / 0.1) 25%, rgb(0% 0% 0% / 0.7) 50%, rgb(0% 0% 0% / 1) 100% ); -webkit-mask-image: linear-gradient(45deg, rgb(0% 0% 0% / 0) 0%, rgb(0% 0% 0% / 0.1) 25%, rgb(0% 0% 0% / 0.7) 50%, rgb(0% 0% 0% / 1) 100% ); transition: mask-image 5s ease, -webkit-mask-image 5s ease; }
.video-container:hover { mask-image: none; -webkit-mask-image: none; transform:scale(1.335); transition-delay: 0s;}
.video-container:hover .video-player { mask-image: none; -webkit-mask-image: none;}
.skip-button {font-size: 2em; top: 50vh; position: absolute; z-index: 20; cursor: pointer; opacity: 0.5; transition: opacity 0.3s ease;} .skip-button:hover {opacity: 1;}
.buttons-container {display: flex; position: absolute; top: 53.6em; left: 8em; z-index: 10;}
.details-button:hover { background-color: #444;}
@media (min-width: 2000px) { .slide {font-size: 0.85vw;} .backdrop {top: 2em;}}
@media (min-width: 3000px) { .slide {.backdrop {top: 0em;} .slide {margin-top: -4vh;}}
@media screen and (min-aspect-ratio: 21/9) { .slide {font-size: 1.6vh; }}
.watch-trailer-button { position: absolute; border: none; border-radius: 5px; padding: 0.5em 1.5em; font-size: 0.9em; font-family: "Titillium Web", sans-serif; cursor: pointer; z-index: 10; transition: background-color 0.3s ease; align-items: center; justify-content: center; padding-right: 2em;}
.material-symbols-outlined { vertical-align: middle; font-size: 0.85em; opacity: 0.75; }
</style>
</head>
<body>
<div id="slides-container"></div>
<div id="video-overlay" style="display: none;">
<div id="video-overlay-content">
<span id="close-overlay" class="fas fa-times"></span>
<iframe id="trailer-video" width="100%" height="100%" frameborder="0" allowfullscreen></iframe>
</div>
</div>
<script>
let title = 'Spotlight', moviesSeriesBoth = 3, shuffleInterval = 15000, plotMaxLength = 350, token = 'YOURAPIKEYHERE', useTrailers = true;
let isChangingSlide = false, player = null, slideChangeTimeout = null, isHomePageActive = false;
let currentLocation = window.top.location.href;
let movieList = [], currentMovieIndex = 0;
const createElem = (tag, className, textContent, src, alt) => {
const elem = document.createElement(tag);
if (className) elem.className = className;
if (textContent) elem.textContent = textContent;
if (src) elem.src = src;
if (alt) elem.alt = alt;
return elem;
};
// Check for screen size below 1000px
function isMobile() {
return window.innerWidth <= 1000;
}
const truncateText = (text, maxLength) => text.length > maxLength ? text.substr(0, maxLength) + '...' : text;
const cleanup = () => {
if (player) { player.destroy(); player = null; }
clearTimeout(slideChangeTimeout);
const container = document.getElementById('slides-container');
if (container) container.innerHTML = '';
};
const createSlideElement = (movie, hasVideo = false) => {
cleanup();
const container = document.getElementById('slides-container');
const slide = createElem('div', 'slide');
['backdrop', 'logo'].forEach(type => slide.appendChild(createElem('img', type, null, `/Items/${movie.Id}/Images/${type.charAt(0).toUpperCase() + type.slice(1)}${type === 'backdrop' ? '/0' : ''}`, type)));
slide.appendChild(createElem('div', 'heading', title));
const textContainer = createElem('div', 'text-container');
const premiereYear = movie.PremiereDate ? new Date(movie.PremiereDate).getFullYear() : 'Unknown';
const additionalInfo = movie.Type === 'Series' ?
(movie.ChildCount ? `${movie.ChildCount} Season${movie.ChildCount > 1 ? 's' : ''}` : 'Unknown Seasons') :
(movie.RunTimeTicks ? `${Math.round(movie.RunTimeTicks / 600000000)} min` : 'Unknown Runtime');
let loremText = `
<span style="border: 1px solid #fff8; background: #101010e3; border-radius: 0.5em; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.05em; padding-bottom: 0.05em; margin-left: 1em;">${additionalInfo}</span>
<span style="border: 1px solid #fff8; background: #101010e3; border-radius: 0.5em; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.05em; padding-bottom: 0.05em; margin-left: 1em;">${premiereYear}</span> `;
if (movie.CommunityRating) {
loremText += `<span style="border: 1px solid #fff8; background: #101010e3; border-radius: 0.5em; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.05em; padding-bottom: 0.05em; margin-left: 1em;">
<i class="star-icon fas fa-star"></i> ${movie.CommunityRating.toFixed(1)}
</span> `;
}
if (movie.CriticRating) {
loremText += `<span style="border: 1px solid #fff8; background: #101010e3; border-radius: 0.5em; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.05em; padding-bottom: 0.05em; margin-left: 1em;">
<img src="https://i.imgur.com/rMvyQMt.png" alt="Rotten Tomatoes" style="width: 1.05em; height: 1.25em; font-size: 0.9em; padding-right: 0.1em; margin-left: -0.1em; vertical-align: middle;">
${movie.CriticRating}%</span>`;
}
// Age Rating
const ageRating = movie.OfficialRating ? movie.OfficialRating : 'NR';
const ratingClass = ageRating.toLowerCase().replace(/ /g, '-');
const ageRatingDiv = createElem('div', 'age-rating');
const ageRatingSpan = createElem('span', ratingClass, ageRating);
ageRatingSpan.style.cssText = 'border: 0.09em solid currentColor; border-radius: .1em; padding: 0.2em; display: inline-block; text-align: center; line-height: 0.8em;';
ageRatingDiv.appendChild(ageRatingSpan);
slide.appendChild(ageRatingDiv);
// Genres Section
const genresDiv = createElem('div', 'genres');
if (movie.Genres && movie.Genres.length > 0) {
movie.Genres.forEach(genre => {
const genreElem = createElem('span', 'genre-item');
genreElem.textContent = genre;
genreElem.style.backgroundColor = '#101010e3';
genreElem.style.paddingLeft = '0.5em';
genreElem.style.paddingRight = '0.5em';
genreElem.style.paddingTop = '0.1em';
genreElem.style.paddingBottom = '0.1em';
genreElem.style.color = 'white';
genreElem.style.borderRadius = '0em';
genreElem.style.marginRight = '1em';
genreElem.style.border = '1px solid #fff8';
genreElem.style.borderRadius = '0.5em';
genresDiv.appendChild(genreElem);
});
} else {
genresDiv.textContent = 'Genres: N/A';
}
slide.appendChild(genresDiv);
const loremDiv = createElem('div', 'lorem-ipsum');
loremDiv.innerHTML = loremText;
textContainer.appendChild(loremDiv);
textContainer.appendChild(createElem('div', 'plot', truncateText(movie.Overview, plotMaxLength)));
slide.appendChild(textContainer);
// Create Details buttons
const buttonsContainer = createElem('div', 'buttons-container');
// Details Button
const detailsButton = createElem('button', 'details-button');
const playIcon = createElem('i', 'fas fa-play');
detailsButton.appendChild(playIcon);
detailsButton.appendChild(document.createTextNode(' Play'));
detailsButton.onclick = (e) => {
e.stopPropagation();
window.top.location.href = `/#!/details?id=${movie.Id}`; // Navigate to details page
};
// Append buttons to the container
buttonsContainer.appendChild(detailsButton);
slide.appendChild(buttonsContainer);
const backButton = createElem('div', 'back-button');
const backIcon = createElem('i', 'material-icons');
backIcon.textContent = 'chevron_left';
const skipButton = createElem('div', 'skip-button');
const skipIcon = createElem('i', 'material-icons');
skipIcon.textContent = 'chevron_right';
backButton.appendChild(backIcon);
skipButton.appendChild(skipIcon);
skipIcon.onclick = (e) => { e.stopPropagation(); fetchRandomMovie(); };
slide.appendChild(backButton);
slide.appendChild(skipButton);
const overlay = createElem('div', 'clickable-overlay');
overlay.onclick = () => window.top.location.href = `/#!/details?id=${movie.Id}`;
slide.appendChild(overlay);
if (hasVideo && movie.RemoteTrailers && movie.RemoteTrailers.length > 0) {
const trailerUrl = movie.RemoteTrailers[0].Url;
const watchTrailerButton = createElem('button', 'watch-trailer-button');
const playIcon = document.createElement('i');
playIcon.className = 'fas fa-film';
watchTrailerButton.appendChild(playIcon);
watchTrailerButton.appendChild(document.createTextNode(' Trailer'));
watchTrailerButton.onclick = (e) => {
e.stopPropagation();
if (isMobile()) {
// Show the video in an overlay for mobile
showVideoOverlay(trailerUrl);
} else {
// Open the trailer in a new tab for desktop
window.open(trailerUrl, '_blank');
}
};
slide.appendChild(watchTrailerButton);
}
if (useTrailers && hasVideo && movie.RemoteTrailers?.length > 0) {
const videoId = new URL(movie.RemoteTrailers[0].Url).searchParams.get('v');
const videoContainer = createElem('div', 'video-container');
const videoElement = createElem('div', 'video-player');
videoContainer.appendChild(videoElement);
slide.appendChild(videoContainer);
player = new YT.Player(videoElement, {
height: '100%', width: '100%', videoId,
events: {
'onReady': event => {
event.target.playVideo();
['backdrop', 'plot', 'lorem-ipsum'].forEach(cls => {
const element = document.querySelector(`.${cls}`);
if (element) element.style.width = '100%';
});
videoContainer.style.width = '27.6vw';
},
'onStateChange': event => { if (event.data === YT.PlayerState.ENDED) setTimeout(fetchRandomMovie, 100); },
'onError': () => {
console.error(`YouTube prevented playback of '${movie.Name}'`);
if (player) { player.destroy(); player = null; }
['backdrop', 'plot', 'lorem-ipsum'].forEach(cls => {
const element = document.querySelector(`.${cls}`);
if (element) element.style.width = '100%';
});
videoContainer.style.width = '0';
startSlideChangeTimer();
}
}
});
} else startSlideChangeTimer();
container.innerHTML = '';
container.appendChild(slide);
};
function addSwipeListeners(slide) {
let startX, startY, distX, distY;
const threshold = 50;
const restraint = 100;
slide.addEventListener('touchstart', e => {
const touch = e.touches[0];
startX = touch.clientX;
startY = touch.clientY;
});
slide.addEventListener('touchmove', e => {
const touch = e.touches[0];
distX = touch.clientX - startX;
distY = touch.clientY - startY;
});
slide.addEventListener('touchend', () => {
if (Math.abs(distX) > threshold && Math.abs(distY) < restraint) {
if (distX > 0) {
console.log('Swipe Right');
} else {
console.log('Swipe Left');
fetchRandomMovie();
}
}
distX = distY = 0;
});
}
// Show the video overlay
function showVideoOverlay(trailerUrl) {
const videoOverlay = document.getElementById('video-overlay');
const videoFrame = document.getElementById('trailer-video');
const closeOverlay = document.getElementById('close-overlay');
// Extract video ID from trailer URL
const videoId = new URL(trailerUrl).searchParams.get('v');
const embedUrl = `https://www.youtube.com/embed/${videoId}?autoplay=1`;
// Set iframe's source to trailer URL
videoFrame.src = embedUrl;
// Show the overlay
videoOverlay.style.display = 'block';
// Pause the slide timer when the video overlay is open
clearSlideChangeTimeout();
// Close the overlay when the X is clicked
closeOverlay.onclick = () => {
videoOverlay.style.display = 'none';
videoFrame.src = ''; // Stop the video
};
// Close the overlay when clicking outside content
window.onclick = (event) => {
if (event.target === videoOverlay) {
videoOverlay.style.display = 'none';
videoFrame.src = '';
}
};
}
// Close video overlay and restart slide timer
function closeVideoOverlay() {
const videoOverlay = document.getElementById('video-overlay');
const videoFrame = document.getElementById('trailer-video');
// Hide overlay
videoOverlay.style.display = 'none';
// Reset the iframe source
videoFrame.src = '';
// Restart slide change timer when video overlay is closed
startSlideChangeTimer();
}
function clearSlideChangeTimeout() {
if (slideChangeTimeout) {
clearTimeout(slideChangeTimeout);
slideChangeTimeout = null;
}
}
const startSlideChangeTimer = () => { clearTimeout(slideChangeTimeout); slideChangeTimeout = setTimeout(fetchRandomMovie, shuffleInterval); };
const checkBackdropAndLogo = movie => {
Promise.all(['/Images/Backdrop/0', '/Images/Logo'].map(url =>
fetch(`/Items/${movie.Id}${url}`, { method: 'HEAD' }).then(response => response.ok)
)).then(([backdropExists, logoExists]) =>
backdropExists && logoExists ? createSlideElement(movie, true) : fetchRandomMovie()
).catch(() => fetchRandomMovie());
};
const readCustomList = () =>
fetch('list.txt?' + new Date().getTime())
.then(response => response.ok ? response.text() : null)
.then(text => {
if (!text) return null;
const lines = text.split('\n').filter(Boolean);
title = lines.shift() || title;
return lines.map(line => line.trim().substring(0, 32));
})
.catch(() => null);
const fetchRandomMovie = () => {
if (isChangingSlide) return;
isChangingSlide = true;
if (movieList.length === 0) {
readCustomList().then(list => {
if (list) { movieList = list; currentMovieIndex = 0; }
fetchNextMovie();
});
} else fetchNextMovie();
};
const fetchNextMovie = () => {
const fetchCurrentUserId = () =>
fetch('/Sessions', {
headers: { 'Authorization': `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"` }
})
.then(response => response.json())
.then(sessions => {
const currentSession = sessions.find(session => session.UserId);
return currentSession ? currentSession.UserId : null;
})
.catch(() => null);
fetchCurrentUserId().then(currentUserId => {
if (!currentUserId) {
console.error('Could not retrieve the current user ID.');
return;
}
const headers = { 'Authorization': `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"` };
if (movieList.length > 0) {
if (currentMovieIndex >= movieList.length) currentMovieIndex = 0;
const movieId = movieList[currentMovieIndex];
currentMovieIndex++;
fetch(`/Users/${currentUserId}/Items/${movieId}?Fields=Overview,RemoteTrailers,PremiereDate,RunTimeTicks,ChildCount,Genres`, { headers })
.then(response => response.json())
.then(checkBackdropAndLogo)
.catch(() => startSlideChangeTimer())
.finally(() => { isChangingSlide = false; });
} else {
const itemTypes = moviesSeriesBoth === 1 ? 'Movie' : (moviesSeriesBoth === 2 ? 'Series' : 'Movie,Series');
fetch(`/Users/${currentUserId}/Items?IncludeItemTypes=${itemTypes}&Recursive=true&Limit=1&SortBy=random&Fields=Id,Overview,RemoteTrailers,PremiereDate,RunTimeTicks,ChildCount,Genres`, { headers })
.then(response => response.json())
.then(data => { if (data.Items[0]) checkBackdropAndLogo(data.Items[0]); })
.catch(() => startSlideChangeTimer())
.finally(() => { isChangingSlide = false; });
}
});
};
const checkNavigation = () => {
const newLocation = window.top.location.href;
if (newLocation !== currentLocation) {
currentLocation = newLocation;
const isHomePage = url => url.includes('/home') || url.endsWith('/web/') || url.endsWith('/web/index.html');
if (isHomePage(newLocation)) {
if (!isHomePageActive) {
console.log("Returning to homepage, reactivating slideshow");
isHomePageActive = true;
cleanup();
fetchRandomMovie();
}
} else if (isHomePageActive) {
console.log("Leaving homepage, cleaning up slideshow");
isHomePageActive = false;
cleanup();
}
}
};
setInterval(checkNavigation, 100);
document.addEventListener('DOMContentLoaded', () => {
if (window.innerWidth < 1001) useTrailers = false;
const isHomePage = url => url.includes('/home') || url.endsWith('/web/') || url.endsWith('/web/index.html');
if (isHomePage(window.top.location.href)) {
isHomePageActive = true;
readCustomList().then(list => {
if (list) { movieList = list; currentMovieIndex = 0; }
fetchRandomMovie();
});
}
});
window.addEventListener('unload', cleanup);
window.addEventListener('popstate', checkNavigation);
</script>
<script src="https://www.youtube.com/iframe_api"></script>
</body>
</html>

View File

@@ -1,4 +1,4 @@
<style> .featurediframe {width: 95vw; height: 24em; display: block; border: 0; margin: -1em auto 0;} @media (min-width: 2100px) {.featurediframe {height: 33em;}} @media (max-width: 1599px) {.featurediframe {margin-top: 1.2em;}} @media (max-width: 800px) {.featurediframe {margin-top: 0.8em;}} </style> <iframe class="featurediframe" src="/web/ui/spotlight.html"></iframe>
<style> .featurediframe {width: 95vw; height: 24em; display: block; border: 0; margin: -1em auto 0;} @media (min-width: 2100px) {.featurediframe {height: 33em;}} @media (max-width: 1599px) {.featurediframe {margin-top: 1.2em;}} @media (max-width: 800px) {.featurediframe {margin-top: 0.8em; height: 25em;}} </style> <iframe class="featurediframe" src="/web/ui/spotlight.html"></iframe>
<style>
@@ -25,7 +25,40 @@
@media (max-width: 800px) {
.featurediframe {
margin-top: 0.8em;
height: 25em;
}
}
</style>
<iframe class="featurediframe" src="/web/ui/spotlight.html"></iframe>
"use strict"; (self.webpackChunk = self.webpackChunk || []).push([[8372], { 5939: function (a, e, t) { t.r(e), e.default = '<div id="indexPage" style="outline:0" data-role="page" data-dom-cache="true" class="page homePage libraryPage allLibraryPage backdropPage pageWithAbsoluteTabs withTabs" data-backdroptype="movie,series,book"><style> .featurediframe {width: 95vw; height: 24em; display: block; border: 0; margin: -1em auto 0;} @media (min-width: 2100px) {.featurediframe {height: 33em;}} @media (max-width: 1599px) {.featurediframe {margin-top: 1.2em;}} @media (max-width: 800px) {.featurediframe {margin-top: 0.8em; height: 25em;}} </style> <iframe class="featurediframe" src="/web/ui/spotlight.html"></iframe> <style>:root { --save-gut: max(env(safe-area-inset-left), .3%) } .requestIframe { margin: 0 .4em; padding: 0 var(--save-gut); width: calc(100% - (.4em * 2) - (var(--save-gut) * 2)); height: 90vh; border: none; position: absolute; top: 5.3em } @media (max-width: 1599px) { .requestIframe { height: 83vh; top: 8.2em; } }</style><script>setTimeout(() => { createRequestTab() }, 500)</script> <div class="tabContent pageTabContent" id="homeTab" data-index="0"> <div class="sections"></div> </div> <div class="tabContent pageTabContent" id="favoritesTab" data-index="1"> <div class="sections"></div> </div><div class="tabContent pageTabContent" id="requestsTab" data-index="2"> <div class="sections"><iframe class="requestIframe" src="https://jellyseerr.mahom03-spacecloud.de"></iframe></div> </div> </div> ' } }]);
NEU (aber nicht genutzt):
<style> .featurediframe {width: 95vw; height: 23.5em; display: block; border: 0px solid #000; margin: 0 auto; margin-bottom: 0em; margin-top: 1em;} @media (min-width: 3158px) {.featurediframe {height: 50em;} } @media (min-width: 2601px) and (max-width: 3157px) {.featurediframe {height: 33em;} } @media (min-width: 2000px) and (max-width: 2600px) {.featurediframe {height: 27em; font-size: 133%;} .layout-desktop #homeTab .sections.homeSectionsContainer {margin-top: -3em !important;} } @media (max-width: 1000px) and (orientation: portrait) {.featurediframe {height: 25em; margin-bottom: -3em;} } @media (max-width: 1000px) and (orientation: landscape) {.featurediframe {height: 26em; margin-bottom: -7em;} } @media (max-width: 400px) and (orientation: portrait) {.featurediframe {height: 45vh; margin-bottom: 0em;} } @media (max-height: 400px) and (orientation: landscape) {.featurediframe {height: 100vh;} } @media screen and (aspect-ratio: 4/3) {.featurediframe {height: 25em;} } @media screen and (aspect-ratio: 3/4) {.featurediframe {height: 25em; margin-bottom: -5em;} } @media screen and (aspect-ratio: 16/10) and (max-height: 1200px) {.featurediframe {height: 34em; margin-bottom: -5em;} } @media screen and (aspect-ratio: 10/16) and (max-height: 1280px) {.featurediframe {height: 25em; margin-bottom: -5em;} } @media (min-aspect-ratio: 21/9) and (min-width: 3000px) {.featurediframe { height: 50em;} } </style> <iframe class="featurediframe" src="/web/ui/spotlight.html"></iframe>
<style>
.featurediframe {width: 95vw; height: 23.5em; display: block; border: 0px solid #000; margin: 0 auto; margin-bottom: 0em; margin-top: 1em;}
@media (min-width: 3158px) {.featurediframe {height: 50em;} }
@media (min-width: 2601px) and (max-width: 3157px) {.featurediframe {height: 33em;} }
@media (min-width: 2000px) and (max-width: 2600px) {.featurediframe {height: 27em; font-size: 133%;} .layout-desktop #homeTab .sections.homeSectionsContainer {margin-top: -3em !important;} }
@media (max-width: 1000px) and (orientation: portrait) {.featurediframe {height: 25em; margin-bottom: -3em;} }
@media (max-width: 1000px) and (orientation: landscape) {.featurediframe {height: 26em; margin-bottom: -7em;} }
@media (max-width: 500px) and (orientation: portrait) {.featurediframe {height: 45vh; margin-bottom: 0em;} }
@media (max-height: 500px) and (orientation: landscape) {.featurediframe {height: 100vh;} }
@media screen and (aspect-ratio: 4/3) {.featurediframe {height: 25em;} }
@media screen and (aspect-ratio: 3/4) {.featurediframe {height: 25em; margin-bottom: -5em;} }
@media screen and (aspect-ratio: 16/10) and (max-height: 1200px) {.featurediframe {height: 34em; margin-bottom: -5em;} }
@media screen and (aspect-ratio: 10/16) and (max-height: 1280px) {.featurediframe {height: 25em; margin-bottom: -5em;} }
@media (min-aspect-ratio: 21/9) and (min-width: 3000px) {.featurediframe { height: 50em;} }
</style>
<iframe class="featurediframe" src="/web/ui/spotlight.html"></iframe>
"use strict"; (self.webpackChunk = self.webpackChunk || []).push([[8372], { 5939: function (a, e, t) { t.r(e), e.default = '<div id="indexPage" style="outline:0" data-role="page" data-dom-cache="true" class="page homePage libraryPage allLibraryPage backdropPage pageWithAbsoluteTabs withTabs" data-backdroptype="movie,series,book"><style> .featurediframe {width: 95vw; height: 23.5em; display: block; border: 0px solid #000; margin: 0 auto; margin-bottom: 0em; margin-top: 1em;} @media (min-width: 3158px) {.featurediframe {height: 50em;}} @media (min-width: 2601px) and (max-width: 3157px) {.featurediframe {height: 33em;}} @media (min-width: 2000px) and (max-width: 2600px) {.featurediframe {height: 27em; font-size: 133%;} .layout-desktop #homeTab .sections.homeSectionsContainer {margin-top: -3em !important;}} @media (max-width: 1000px) and (orientation: portrait) {.featurediframe {height: 25em; margin-bottom: -3em;}} @media (max-width: 1000px) and (orientation: landscape) {.featurediframe {height: 26em; margin-bottom: -7em;}} @media (max-width: 400px) and (orientation: portrait) {.featurediframe {height: 45vh; margin-bottom: 0em;}} @media (max-height: 400px) and (orientation: landscape) {.featurediframe {height: 100vh;}} @media screen and (aspect-ratio: 4/3) {.featurediframe {height: 25em;}} @media screen and (aspect-ratio: 3/4) {.featurediframe {height: 25em; margin-bottom: -5em;}} @media screen and (aspect-ratio: 16/10) and (max-height: 1200px) {.featurediframe {height: 34em; margin-bottom: -5em;}} @media screen and (aspect-ratio: 10/16) and (max-height: 1280px) {.featurediframe {height: 25em; margin-bottom: -5em;}} @media (min-aspect-ratio: 21/9) and (min-width: 3000px) {.featurediframe { height: 50em;}}</style><iframe class="featurediframe" src="/web/ui/spotlight.html"></iframe> <style>:root { --save-gut: max(env(safe-area-inset-left), .3%) } .requestIframe { margin: 0 .4em; padding: 0 var(--save-gut); width: calc(100% - (.4em * 2) - (var(--save-gut) * 2)); height: 90vh; border: none; position: absolute; top: 5.3em } @media (max-width: 1599px) { .requestIframe { height: 83vh; top: 8.2em; } }</style><script>setTimeout(() => { createRequestTab() }, 500)</script> <div class="tabContent pageTabContent" id="homeTab" data-index="0"> <div class="sections"></div> </div> <div class="tabContent pageTabContent" id="favoritesTab" data-index="1"> <div class="sections"></div> </div><div class="tabContent pageTabContent" id="requestsTab" data-index="2"> <div class="sections"><iframe class="requestIframe" src="https://jellyseerr.mahom03-spacecloud.de"></iframe></div> </div> </div> ' } }]);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
images/desktop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
images/fullscreen.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 MiB

BIN
images/mobile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

9
list.txt Normal file
View File

@@ -0,0 +1,9 @@
Spotlight MuteOn
f81c9b854e3edc62fb049e252c488115 Hunger Games
7d515072462d6cedd74de8e2df71888f Interstellar
df4f3da3066455404e6b874d22ba86aa Fast X
0eab5162a26ed05546215e762dde8544 Greatest Showman
820d0a5c3eaee93e8d3bfbb670a9a41e Mission Impossible 7
b948bde88c4e1f48e84f26fec46df211 Oppenheimer
daeab01d279b7df4f6726892a09d3846 Im Westen nichts neues
5c70c7506a20814c6cfa15d139b4c0b5 Lupin

541
script.js
View File

@@ -1,17 +1,148 @@
let title = 'Spotlight'; // Title of the slideshow
let title = 'Spotlight'; // Title of the slideshow TBD
let listFileName = 'list.txt'; // Name of the file containing the list of movie IDs
let token = 'YOURAPIKEYHERE'; // Your Jellyfin API key
let moviesSeriesBoth = 3; // 1 for movies, 2 for series, 3 for both
let shuffleInterval = 15000; // Time in milliseconds before the next slide is shown, unless trailer is playing
let useTrailers = true; // Set to false to disable trailers
let setRandomMovie = true; // Set to false to disable random movie selection from the list
let showOnOtherPages = false; // Set to true to show the slideshow on all pages eg. favorites tab, requests tab, etc.
let showTitle = false; // Set to false to hide the title
//let showTitle = false; // Set to false to hide the title TBD
let disableTrailerControls = false; // Set to false to enable trailer controls
let setMutedHover = true; // Set to false to disable unmuting the video on hover
let unmutedVolume = 20; // Set the volume level when the video is unmuted
let useSponsorBlock = true; // Set to true to use SponsorBlock data to skip intro/outro segments of trailers
let skipIntro = true; // Set to true to skip the intro segment of the trailer
let plotMaxLength = 550; // Maximum number of characters in the plot
let trailerMaxLength = 0; // Default value 0; length measured in ms, set to 0 to disable, could be used instead of SponsorBlock
let startTrailerMuted = true; // Default value true; set to false to start the video unmuted
// Language specific strings
// currently implemented: en, de, fr, es, it, pl, nl
const seasonTerms = ["Season", "Staffel", "Saison", "Temporada", "Stagione", "Sezon", "Seizoen"];
const seasonsTerms = ["Seasons", "Staffeln", "Saisons", "Temporadas", "Stagioni", "Sezony", "Seizoenen"];
const unknownSeasonsTerms = ["Unknown seasons", "Unbekannte Staffeln","Saisons inconnues", "Temporadas desconocidas", "Stagioni sconosciute", "Nieznane sezony", "Onbekende seizoenen"];
const movieTerms = ["Movie", "Film", "Film", "Película", "Film", "Film", "Film"];
const moviesTerms = ["Movies", "Filme", "Films", "Películas", "Film", "Filmy", "Films"];
const unknownMoviesTerms = ["Unknown movies", "Unbekannte Filme", "Films inconnus", "Películas desconocidas", "Film sconosciuti", "Nieznane filmy", "Onbekende films"];
const unknownYearTerms = ["Unknown year", "Unbekanntes Jahr", "Année inconnue", "Año desconocido", "Anno sconosciuto", "Nieznany rok", "Onbekend jaar"];
const unknownRuntimeTerms = ["Unknown Runtime", "Unbekannte Länge", "Durée inconnue", "Duración desconocida", "Durata sconosciuta", "Nieznany czas trwania", "Onbekende duur"];
// get the Jellyfin credentials from the local storage (api token and user id)
const getJellyfinCredentials = () => {
const jellyfinCreds = localStorage.getItem("jellyfin_credentials");
try {
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 { token, userId } = getJellyfinCredentials();
// variables
let isChangingSlide = false, player = null, slideChangeTimeout = null, isHomePageActive = false;
let currentLocation = window.top.location.href;
let movieList = [], currentMovieIndex = 0;
let previousMovies = [];
let forwardMovies = [];
let monitorOutroInterval = null; // Global interval variable to monitor the outro segment of the trailer
let isMuted = startTrailerMuted; userInteracted = false;
// Get the current browser language
const getBrowserLanguage = () => {
const language = navigator.language || navigator.userLanguage;
return language.split('-')[0]; // Return the language code (e.g., 'en', 'de')
};
const browserLanguage = getBrowserLanguage();
// set corresponding language index
const setLanguage = (language) => {
switch (language) {
case 'de':
return 1;
case 'fr':
return 2;
case 'es':
return 3;
case 'it':
return 4;
case 'pl':
return 5;
case 'nl':
return 6;
default:
return 0;
}
};
const languageIndex = setLanguage(browserLanguage);
// Get SponsorBlock-Data for the outro/intro segment of the trailer
const fetchSponsorBlockData = async (videoId) => {
try {
const response = await fetch(`https://sponsor.ajay.app/api/skipSegments?videoID=${videoId}&categories=["intro","outro"]`);
const segments = await response.json();
let intro = null;
let outro = null;
segments.forEach(segment => {
if (segment.category === "intro" && Array.isArray(segment.segment)) {
intro = segment.segment; // [start, end] of the intro
} else if (segment.category === "outro" && Array.isArray(segment.segment)) {
outro = segment.segment; // [start, end] of the outro
}
});
return { intro, outro };
} catch (error) {
console.error('Error fetching SponsorBlock data:', error);
return { intro: null, outro: null };
}
};
// Monitor the video player for the outro segment
function monitorOutro(player, outroSegment) {
if (monitorOutroInterval) { // Clear the interval if it's already running
clearInterval(monitorOutroInterval);
}
monitorOutroInterval = setInterval(() => {
if (!outroSegment || !player) {
console.log('Invalid outro segment or player not initialized');
clearInterval(monitorOutroInterval);
return;
}
const currentTime = player.getCurrentTime();
if (currentTime >= outroSegment[0] && currentTime < outroSegment[1]) {
clearInterval(monitorOutroInterval);
player.stopVideo(); // stop video
setTimeout(fetchRandomMovie, 100); // fetch next movie
}
}, 500); // check every 500ms
}
const clearMonitorOutroInterval = () => {
if (monitorOutroInterval) {
clearInterval(monitorOutroInterval);
monitorOutroInterval = null;
}
};
const createElem = (tag, className, textContent, src, alt) => {
const elem = document.createElement(tag);
@@ -30,24 +161,50 @@ function isMobile() {
const truncateText = (text, maxLength) => text.length > maxLength ? text.substr(0, maxLength) + '...' : text;
const cleanup = () => {
if (player) { player.destroy(); player = null; }
if (player && typeof player.destroy === 'function') {
player.destroy();
}
player = null;
clearTimeout(slideChangeTimeout);
slideChangeTimeout = null;
clearMonitorOutroInterval();
const container = document.getElementById('slides-container');
if (container) container.innerHTML = '';
};
const createSlideElement = (movie, hasVideo = false) => {
cleanup();
isMuted = startTrailerMuted;
const container = document.getElementById('slides-container');
const slide = createElem('div', 'slide');
if (movie && (!previousMovies.length || previousMovies[previousMovies.length - 1].Id !== movie.Id)) {
previousMovies.push(movie);
}
if (previousMovies.length > 50) {
previousMovies.shift();
}
['backdrop', 'logo'].forEach(type => slide.appendChild(createElem('img', type, null, `/Items/${movie.Id}/Images/${type.charAt(0).toUpperCase() + type.slice(1)}${type === 'backdrop' ? '/0' : ''}`, type)));
slide.appendChild(createElem('div', 'heading', title));
const textContainer = createElem('div', 'text-container');
const premiereYear = movie.PremiereDate ? new Date(movie.PremiereDate).getFullYear() : 'Unknown';
const additionalInfo = movie.Type === 'Series' ?
(movie.ChildCount ? `${movie.ChildCount} Season${movie.ChildCount > 1 ? 's' : ''}` : 'Unknown Seasons') :
(movie.RunTimeTicks ? `${Math.round(movie.RunTimeTicks / 600000000)} min` : 'Unknown Runtime');
const premiereYear = movie.PremiereDate ? new Date(movie.PremiereDate).getFullYear() : unknownYearTerms[languageIndex];
let additionalInfo;
if (movie.Type === 'Series') {
additionalInfo = movie.ChildCount
? `${movie.ChildCount} ${movie.ChildCount > 1 ? seasonsTerms[languageIndex] : seasonTerms[languageIndex]}`
: unknownSeasonsTerms[languageIndex];
} else if (movie.Type === 'BoxSet') {
additionalInfo = movie.ChildCount
? `${movie.ChildCount} ${movie.ChildCount > 1 ? moviesTerms[languageIndex] : movieTerms[languageIndex]}`
: unknownMoviesTerms[languageIndex];
} else {
additionalInfo = movie.RunTimeTicks
? `${Math.round(movie.RunTimeTicks / 600000000)} min`
: unknownRuntimeTerms[languageIndex];
}
let loremText = `
<span style="background: transparent; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.05em; padding-bottom: 0.05em; margin-left: 1em;">${additionalInfo}</span>
@@ -60,7 +217,7 @@ const createSlideElement = (movie, hasVideo = false) => {
}
if (movie.CriticRating) {
loremText += `<span style="background: transparent; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.05em; padding-bottom: 0.05em; margin-left: 1em;">
<img src="https://i.imgur.com/rMvyQMt.png" alt="Rotten Tomatoes" style="width: 1.05em; height: 1.25em; font-size: 0.9em; padding-right: 0.1em; margin-left: -0.1em; vertical-align: bottom;">
<img src="https://i.imgur.com/aoLXyXx.png" alt="Rotten Tomatoes" style="width: 1.05em; height: 1.25em; font-size: 0.83em; padding-right: 0.4em; margin-left: -0.1em; vertical-align: middle; padding-bottom: 0.3em;">
${movie.CriticRating}%</span>`;
}
@@ -111,15 +268,22 @@ const createSlideElement = (movie, hasVideo = false) => {
const backButton = createElem('div', 'back-button');
const backIcon = createElem('i', 'material-icons');
backIcon.textContent = 'chevron_left';
backButton.appendChild(backIcon);
backButton.onclick = (e) => {
e.stopPropagation();
navigateBack();
};
// Skip button logic
const skipButton = createElem('div', 'skip-button');
const skipIcon = createElem('i', 'material-icons');
skipIcon.textContent = 'chevron_right';
backButton.appendChild(backIcon);
skipButton.appendChild(skipIcon);
skipButton.onclick = (e) => {
e.stopPropagation();
navigateForward();
};
skipIcon.onclick = (e) => { e.stopPropagation(); fetchRandomMovie(); };
backIcon.onclick = (e) => { e.stopPropagation(); previousMovie(); };
slide.appendChild(backButton);
slide.appendChild(skipButton);
@@ -177,36 +341,93 @@ const createSlideElement = (movie, hasVideo = false) => {
height: '100%',
width: '100%',
videoId,
playerVars: {
mute: isMuted ? 1 : 0, // Mute the video
controls: disableTrailerControls ? 0 : 1, // Hide the controls
disablekb: 1, // Disable keyboard controls
iv_load_policy: 3, // Disable annotations
cc_load_policy: 3, // Disable captions
},
events: {
'onReady': event => {
event.target.playVideo();
'onReady': () => {
if (useSponsorBlock) {
fetchSponsorBlockData(videoId).then(({ intro, outro }) => {
if (intro && skipIntro) {
console.log(`SponsorBlock intro segment: Start - ${intro[0]}s, End - ${intro[1]}s`);
if (player && typeof player.seekTo === 'function') {
player.seekTo(intro[1], true);
}
} else {
console.log('No intro segment found/provided or intro skipping is disabled');
}
if (outro) {
console.log(`SponsorBlock outro segment: Start - ${outro[0]}s, End - ${outro[1]}s`);
monitorOutro(player, outro);
} else {
console.log('No outro segment found/provided');
}
}).catch(error => {
console.error('Error reading SponsorBlock data:', error);
});
}
if (trailerMaxLength > 0) {
clearTimeout(slideChangeTimeout);
slideChangeTimeout = setTimeout(() => {
clearMonitorOutroInterval();
if (player) {
player.stopVideo();
player.destroy();
player = null;
}
fetchRandomMovie();
}, trailerMaxLength);
}
if (isMuted) {
player.mute(); // Mute the video if the setting is MuteOn
} else {
player.unMute();
}
player.playVideo();
player.setVolume(unmutedVolume); // Set the volume, value between 0 and 100
console.log(`Playing trailer for '${movie.Name}'`);
},
'onStateChange': event => {
if (event.data === YT.PlayerState.PLAYING) {
'onStateChange': () => {
if (player.getPlayerState() === YT.PlayerState.PLAYING) {
// Only show when YT video is successfully playing
const backdrop = document.querySelector('.backdrop');
if (backdrop) {
backdrop.style.width = 'calc(100% - 23vw)';
backdrop.style.left = '0vw';
}
const plot = document.querySelector('.plot');
if (plot) plot.style.width = 'calc(100% - 36.4vw)';
if (plot) {
plot.style.left = '33vw';
plot.style.maxWidth = '63vw';
}
const genres = document.querySelector('.genres');
if (genres) {
genres.style.left = 'calc(50% - 17vw)';
}
const loremIpsum = document.querySelector('.lorem-ipsum');
if (loremIpsum) loremIpsum.style.paddingRight = '32.4vw';
if (loremIpsum) loremIpsum.style.left = 'calc(50% - 17vw)';
const logo = document.querySelector('.logo');
if (logo) logo.style.left = 'calc(50% - 14.2vw)';
if (logo) logo.style.left = 'calc(50% - 17vw)';
videoContainer.style.width = '34.4vw';
} else if (event.data === YT.PlayerState.ENDED) {
setTimeout(fetchRandomMovie, 100);
} else if (player.getPlayerState() === YT.PlayerState.ENDED) {
clearMonitorOutroInterval();
setTimeout(fetchRandomMovie, 20);
}
},
'onError': () => {
console.error(`YouTube prevented playback of '${movie.Name}'`);
if (player) {
clearMonitorOutroInterval();
player.destroy();
player = null;
}
@@ -230,6 +451,7 @@ const createSlideElement = (movie, hasVideo = false) => {
}
}
});
} else {
startSlideChangeTimer();
}
@@ -239,17 +461,6 @@ const createSlideElement = (movie, hasVideo = false) => {
container.appendChild(slide);
};
// Fetch the previous movie in the list
function previousMovie() {
if (isChangingSlide) return;
isChangingSlide = true;
// Reset index or set to the end of the list if we are at the first element
currentMovieIndex = (currentMovieIndex - 2 + movieList.length) % movieList.length;
fetchNextMovie();
}
function addSwipeListeners(slide) {
let startX, startY, distX, distY;
const threshold = 50;
@@ -280,6 +491,33 @@ function addSwipeListeners(slide) {
});
}
const navigateBack = () => {
if (previousMovies.length < 2) {
console.log("No previous slides in history.");
return;
}
const currentMovie = previousMovies.pop();
forwardMovies.unshift(currentMovie);
const previousMovie = previousMovies[previousMovies.length - 1];
createSlideElement(previousMovie, true);
};
const navigateForward = () => {
if (forwardMovies.length === 0) {
console.log("No forward slides in history.");
fetchRandomMovie();
return;
}
const currentMovie = forwardMovies.shift();
previousMovies.push(currentMovie);
createSlideElement(currentMovie, true);
};
// Show the video overlay
function showVideoOverlay(trailerUrl) {
const videoOverlay = document.getElementById('video-overlay');
@@ -350,132 +588,144 @@ const readCustomList = () =>
fetch(listFileName + '?' + new Date().getTime())
.then(response => response.ok ? response.text() : null)
.then(text => {
if (!text) return null;
if (!text || !text.trim()) {
console.warn('List.txt is empty or could not be loaded.');
return null; // Fallback to random selection
}
const lines = text.split('\n').filter(Boolean);
title = lines.shift() || title;
return lines.map(line => line.trim().substring(0, 32));
const firstLine = lines.shift().trim();
const [parsedTitle, muteSetting] = firstLine.split(/\s+/);
title = parsedTitle || title;
// Check for mute
isMuted = muteSetting === "MuteOn";
// Remaining lines are media IDs
const mediaList = lines.map(line => line.trim().substring(0, 32));
if (setRandomMovie) {
return shuffleArray(mediaList); // Shuffle the list before returning it if set
}
return mediaList; // return exact list
})
.catch(() => null);
.catch(() => {
console.error('Error reading List.txt. Falling back to random selection.');
return null;
});
// using Fisher-Yates shuffle algorithm if list is available and setRandomMovie is set to true
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
// Generate a random index between 0 and i
const j = Math.floor(Math.random() * (i + 1));
// Swap elements at indices i and j
[array[i], array[j]] = [array[j], array[i]];
//var temp = array[i];
//array[i] = array[j];
//array[j] = temp;
}
return array;
};
//const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); //better use Fisher-Yates shuffle algorithm
const fetchRandomMovie = () => {
if (isChangingSlide) return;
isChangingSlide = true;
if (movieList.length === 0) {
readCustomList().then(list => {
if (list) {
if (list && list.length > 0) {
movieList = list;
//// Shuffle the list if it was set by the user
//if (setRandomMovie) {
// shuffleArray(movieList);
//}
currentMovieIndex = 0;
fetchNextMovie();
} else {
console.warn("Fallback to random selection.");
fetchNextMovie(true);
}
fetchNextMovie();
});
} else fetchNextMovie();
} else {
fetchNextMovie();
}
};
const fetchNextMovie = () => {
const fetchCurrentUserId = () =>
fetch('/Sessions', {
headers: { 'Authorization': `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"` }
})
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
};
const fetchNextMovie = (useRandom = false) => {
if (!userId) {
console.error('Could not retrieve the current user ID.');
isChangingSlide = false;
return;
}
const headers = { 'Authorization': `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"` };
if (!useRandom && movieList.length > 0) {
if (currentMovieIndex >= movieList.length) currentMovieIndex = 0;
const movieId = movieList[currentMovieIndex];
currentMovieIndex++;
fetch(`/Users/${userId}/Items/${movieId}?Fields=Overview,RemoteTrailers,PremiereDate,RunTimeTicks,ChildCount,Genres`, { headers })
.then(response => response.json())
.then(sessions => {
const currentSession = sessions.find(session => session.UserId);
return currentSession ? currentSession.UserId : null;
})
.catch(() => null);
fetchCurrentUserId().then(currentUserId => {
if (!currentUserId) {
console.error('Could not retrieve the current user ID.');
return;
}
const headers = { 'Authorization': `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"` };
if (movieList.length > 0) {
if (currentMovieIndex >= movieList.length) currentMovieIndex = 0;
const movieId = movieList[currentMovieIndex];
currentMovieIndex++;
fetch(`/Users/${currentUserId}/Items/${movieId}?Fields=Overview,RemoteTrailers,PremiereDate,RunTimeTicks,ChildCount,Genres`, { headers })
.then(response => response.json())
.then(checkBackdropAndLogo)
.catch(() => startSlideChangeTimer())
.finally(() => { isChangingSlide = false; });
} else {
const itemTypes = moviesSeriesBoth === 1 ? 'Movie' : (moviesSeriesBoth === 2 ? 'Series' : 'Movie,Series');
fetch(`/Users/${currentUserId}/Items?IncludeItemTypes=${itemTypes}&Recursive=true&Limit=1&SortBy=random&Fields=Id,Overview,RemoteTrailers,PremiereDate,RunTimeTicks,ChildCount,Genres`, { headers })
.then(response => response.json())
.then(data => { if (data.Items[0]) checkBackdropAndLogo(data.Items[0]); })
.catch(() => startSlideChangeTimer())
.finally(() => { isChangingSlide = false; });
}
});
.then(checkBackdropAndLogo)
.catch(() => startSlideChangeTimer())
.finally(() => { isChangingSlide = false; });
} else {
const itemTypes = moviesSeriesBoth === 1 ? 'Movie' : (moviesSeriesBoth === 2 ? 'Series' : 'Movie,Series');
fetch(`/Users/${userId}/Items?IncludeItemTypes=${itemTypes}&Recursive=true&Limit=1&SortBy=random&Fields=Id,Overview,RemoteTrailers,PremiereDate,RunTimeTicks,ChildCount,Genres`, { headers })
.then(response => response.json())
.then(data => { if (data.Items[0]) checkBackdropAndLogo(data.Items[0]); })
.catch(() => startSlideChangeTimer())
.finally(() => { isChangingSlide = false; });
}
};
const checkNavigation = () => {
const newLocation = window.top.location.href;
// Check if the user is navigating to or from the homepage
if (newLocation !== currentLocation) {
currentLocation = newLocation;
const isHomePage = url => url.includes('/home') || url.endsWith('/web/') || url.endsWith('/web/index.html');
if (isHomePage(newLocation)) {
if (!isHomePageActive) {
console.log("Returning to homepage, reactivating slideshow");
isHomePageActive = true;
cleanup();
fetchRandomMovie();
}
} else if (isHomePageActive) {
console.log("Leaving homepage, cleaning up slideshow");
if (!isHomePage(newLocation) && isHomePageActive) {
console.log("Leaving homepage, cleaning up slideshow and stopping video");
isHomePageActive = false;
stopBackgroundVideo();
cleanup();
}
if (isHomePage(newLocation) && !isHomePageActive) {
console.log("Returning to homepage, reactivating slideshow");
isHomePageActive = true;
fetchRandomMovie();
}
}
// Check if parent is available and if the iframe is in an embedded environment
if (!showOnOtherPages && window.parent && window.parent !== window) {
const homeTab = window.parent.document.getElementById('homeTab');
const isHomeTabActive = homeTab && homeTab.classList.contains('is-active');
const isHomePage = url => url.includes('/home') || url.endsWith('/web/') || url.endsWith('/web/index.html');
if (isHomePage(newLocation)) {
const homeTab = window.parent.document.getElementById('homeTab');
const isHomeTabActive = homeTab && homeTab.classList.contains('is-active');
// Check if the user is switching tabs while on the homepage to eg. favorites or requests, if so, stop the slideshow
if (isHomeTabActive && !isHomePageActive) {
console.log("HomeTab is active, reactivating slideshow");
isHomePageActive = true;
window.parent.document.querySelector('.featurediframe').style.display = 'block';
cleanup();
fetchRandomMovie();
} else if (!isHomeTabActive && isHomePageActive) {
console.log("Leaving HomeTab, cleaning up slideshow");
isHomePageActive = false;
window.parent.document.querySelector('.featurediframe').style.display = 'none';
cleanup();
// Check if the user is switching tabs while on the homepage to eg. favorites or requests, if so, stop the slideshow
if (isHomeTabActive && !isHomePageActive) {
console.log("HomeTab is active, reactivating slideshow");
isHomePageActive = true;
window.parent.document.querySelector('.featurediframe').style.display = 'block';
fetchRandomMovie();
} else if (!isHomeTabActive && isHomePageActive) {
console.log("Leaving HomeTab, cleaning up slideshow");
isHomePageActive = false;
window.parent.document.querySelector('.featurediframe').style.display = 'none';
stopBackgroundVideo();
cleanup();
}
}
} else {
console.error("Spotlight iframe is not in an embedded environment, has a different domain or showOnOtherPages is set to true");
}
};
const stopBackgroundVideo = () => {
if (player && typeof player.stopVideo === 'function') {
player.stopVideo();
player.destroy();
}
player = null;
};
setInterval(checkNavigation, 60);
document.addEventListener('DOMContentLoaded', () => {
@@ -483,17 +733,46 @@ document.addEventListener('DOMContentLoaded', () => {
const isHomePage = url => url.includes('/home') || url.endsWith('/web/') || url.endsWith('/web/index.html');
if (isHomePage(window.top.location.href)) {
isHomePageActive = true;
console.log("Homepage detected, starting slideshow");
readCustomList().then(list => {
if (list) {
movieList = list;
// Shuffle the list if it was set by the user
if (setRandomMovie) {
shuffleArray(movieList);
}
currentMovieIndex = 0;
}
if (list) { movieList = list; currentMovieIndex = 0; }
fetchRandomMovie();
});
if (setMutedHover) {
const parentBody = window.parent.document.body;
const hoverContainer = document.getElementById('slides-container');
// prevent error: Unmuting failed and the element was paused instead because the user didn't interact with the document before.
const onUserInteraction = () => {
userInteracted = true;
console.log('User interacted with the page');
parentBody.removeEventListener('click', onUserInteraction);
parentBody.removeEventListener('keydown', onUserInteraction);
hoverContainer.removeEventListener('click', onUserInteraction);
hoverContainer.removeEventListener('keydown', onUserInteraction);
};
parentBody.addEventListener('click', onUserInteraction);
parentBody.addEventListener('keydown', onUserInteraction);
hoverContainer.addEventListener('click', onUserInteraction);
hoverContainer.addEventListener('keydown', onUserInteraction);
hoverContainer.addEventListener('mouseenter', () => {
if (userInteracted && player && typeof player.unMute === 'function') {
player.unMute();
isMuted = false;
}
});
hoverContainer.addEventListener('mouseleave', () => {
if (userInteracted && player && typeof player.mute === 'function') {
player.mute();
isMuted = true;
}
});
}
}
});

View File

@@ -1,21 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<title>Jellyfin Spotlight v2.3.2 Fork v1.0</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css">
<title>Jellyfin Spotlight v3.2.0</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="slides-container"></div>
<div id="video-overlay" style="display: none;">
<div id="slides-container"></div>
<div id="video-overlay" style="display: none;">
<div id="video-overlay-content">
<span id="close-overlay" class="fas fa-times"></span>
<iframe id="trailer-video" width="100%" height="100%" frameborder="0" allowfullscreen></iframe>
</div>
</div>
<script src="script.js"></script>
<script src="https://www.youtube.com/iframe_api"></script>
<script src="script.js"></script>
</body>
</html>
</html>

View File

@@ -30,7 +30,7 @@ body {
transition: width 0.5s ease, filter 0.8s ease, scale 2s ease;
transform-origin: top;
animation: objectPositionAnimation 45s ease-in-out infinite;
border-radius: 1em;
border-radius: 2.1em;
}
.logo {
@@ -40,7 +40,7 @@ body {
max-height: 9em;
max-width: 29em;
width: auto;
z-index: 3;
z-index: 7;
text-shadow: -2px 2px 4px rgba(0, 0, 0, 0.5);
filter: drop-shadow(1px 1px 1px);
pointer-events: none;
@@ -79,61 +79,19 @@ body {
text-shadow: 1px 1px 4px rgba(0, 0, 0, 1);
}
.details-button {
display: none;
}
/* MARK: modified
css for back and skip button
*/
.back-button,
.skip-button {
position: absolute;
color: #D3D3D3;
color: #fff;
cursor: pointer;
z-index: 10;
font-family: "Titillium Web", sans-serif;
text-shadow: 1px 1px 4px rgba(0, 0, 0, 1);
font-style: normal;
font-weight: 400;
letter-spacing: normal;
line-height: 1;
text-transform: none;
white-space: nowrap;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
-moz-osx-font-smoothing: grayscale;
-webkit-font-feature-settings: "liga";
font-feature-settings: "liga";
border-radius: 50%;
box-sizing: border-box;
opacity: 0.5;
transition: opacity 0.3s ease, background 0.3s ease;
font-size: 1.62em;
width: 1.7em;
height: 1.7em;
display: flex;
align-items: center;
justify-content: center;
font-size: 2em;
}
.back-button:hover,
.skip-button:hover {
background: #ffffff70;
opacity: 1;
}
.skip-button {
right: 0.5em;
top: 50%;
transform: translateY(-50%);
}
.back-button {
left: 0.5em;
top: 50%;
transform: translateY(-50%);
/* display: none; */ /* Optional, if activated later */
.details-button {
display: none;
}
.material-icons {
@@ -203,7 +161,7 @@ css for back and skip button
.age-rating {
position: absolute;
top: 0.1em;
top: 0.5em;
left: 2em;
text-align: left;
font-family: "Titillium Web", sans-serif;
@@ -226,9 +184,9 @@ css for back and skip button
.plot {
position: absolute;
bottom: 0.1em;
left: 2.2em;
right: 0;
top: -0.3em;
left: 50vw;
transform: translateX(-50%);
color: #fff;
font-family: "Titillium Web", sans-serif;
font-size: 0.95em;
@@ -245,13 +203,14 @@ css for back and skip button
-webkit-box-orient: vertical;
overflow: hidden;
border-bottom-left-radius: 0.45em;
transition: opacity 0.1s ease, max-height 0.8s ease, width 0.3s ease;
transition: opacity 0.1s ease, max-height 0.8s ease, width 0.3s ease, left 0.3s ease;
opacity: 0;
max-width: 96vw;
opacity: 1;
-webkit-line-clamp: 2;
line-clamp: 2;
max-height: 7.8em;
text-align: center;
}
.genres {
@@ -349,19 +308,17 @@ css for back and skip button
transform: scale(1);
}
/* .slide:hover .clickable-overlay {} */
/*.slide:hover .clickable-overlay {}*/
.slide:hover .clickable-overlay::before {
opacity: 0;
backdrop-filter: blur(0px);
}
/*
.slide:hover .logo {}
.slide:hover .plot {}
*/
.slide:hover .lorem-ipsum::before {
opacity: 1;
backdrop-filter: blur(2px);
@@ -393,7 +350,7 @@ css for back and skip button
.logo {
transform: translateX(-50%) translateY(-50%);
top: 10.5em;
top: 6.5em;
left: 50% !important;
max-width: 70%;
max-height: 45%;
@@ -567,12 +524,13 @@ css for back and skip button
@media (max-width:1000px) and (orientation:portrait) {
.back-button {
left: 0em;
top: 5em !important;
left: 0.5em;
}
.skip-button {
right: 0em;
/*top: 50% !important;*/
top: 5em !important;
right: 0.5em;
}
.genres {
@@ -593,6 +551,10 @@ css for back and skip button
}
@media (max-width:1000px) and (orientation:landscape) {
.slide {
height: 22em;
}
.community-rating {
left: 89vw !important;
}
@@ -602,12 +564,13 @@ css for back and skip button
}
.back-button {
left: 0em;
top: 5em !important;
left: 0.5em;
}
.skip-button {
right: 0em;
/*top: 50% !important;*/
top: 5em !important;
right: 0.5em;
}
.genres {
@@ -628,74 +591,124 @@ css for back and skip button
@media (min-width: 1001px) {
.logo {
max-height: 45%;
top: 50vh;
top: 34vh;
max-width: 47em;
left: 50%;
transition: transform 0.1s ease, max-height 0.3s ease;
transition: transform 0.1s ease, left 0.3s ease, max-height 0.3s ease;
}
}
.watch-trailer-button {
display: none;
}
.lorem-ipsum {
left: 50vw;
transition: left 0.3s ease;
transform: translateX(-50%);
top: unset;
bottom: 3.2em;
padding-left: unset;
}
.backdrop {
left: 0em;
width: 100%;
transition: width 0.5s ease, left 0.5s ease, filter 0.8s ease, transform 2s ease;
}
.watch-trailer-button {
display: none;
}
.genres {
left: 8em;
z-index: 9;
max-width: 39em;
}
.backdrop {
left: 0em;
width: 100%;
transition: width 0.5s ease, left 0.5s ease, filter 0.8s ease, transform 2s ease;
}
.community-rating {
top: 16.45em;
left: 31em;
font-size: 1em;
}
.genres {
left: 50vw;
top: unset;
bottom: 5em;
transition: left 0.3s;
z-index: 9;
max-width: 39em;
transform: translateX(-50%);
}
.critic-rating {
position: absolute;
top: 16.45em;
left: 26.5em;
font-size: 1em;
}
.community-rating {
top: 16.45em;
left: 31em;
font-size: 1em;
}
.text-container::before {
content: '';
z-index: 5;
display: flex;
position: absolute;
width: 100vw;
top: -19.3em;
height: 3em;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.7) 6.25%, rgba(0, 0, 0, 0.68) 12.5%, rgba(0, 0, 0, 0.66) 18.75%, rgba(0, 0, 0, 0.64) 25%, rgba(0, 0, 0, 0.6) 31.25%, rgba(0, 0, 0, 0.56) 37.5%, rgba(0, 0, 0, 0.51) 43.75%, rgba(0, 0, 0, 0.45) 50%, rgba(0, 0, 0, 0.38) 56.25%, rgba(0, 0, 0, 0.31) 62.5%, rgba(0, 0, 0, 0.22) 68.75%, rgba(0, 0, 0, 0.14) 75%, rgba(0, 0, 0, 0.13) 81.25%, rgba(0, 0, 0, 0) 100%);
opacity: 0.8;
backdrop-filter: blur(1px);
border-top-right-radius: 1em;
border-top-left-radius: 1em;
-webkit-mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 3%, rgba(0, 0, 0, 0.5) 7%, rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0.9) 25%, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 100%);
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 3%, rgba(0, 0, 0, 0.5) 7%, rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0.9) 25%, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 100%);
}
.critic-rating {
position: absolute;
top: 16.45em;
left: 26.5em;
font-size: 1em;
}
.text-container::after {
content: '';
background: linear-gradient(0deg, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.7) 6.25%, rgba(0, 0, 0, 0.68) 12.5%, rgba(0, 0, 0, 0.66) 18.75%, rgba(0, 0, 0, 0.64) 25%, rgba(0, 0, 0, 0.6) 31.25%, rgba(0, 0, 0, 0.56) 37.5%, rgba(0, 0, 0, 0.51) 43.75%, rgba(0, 0, 0, 0.45) 50%, rgba(0, 0, 0, 0.38) 56.25%, rgba(0, 0, 0, 0.31) 62.5%, rgba(0, 0, 0, 0.22) 68.75%, rgba(0, 0, 0, 0.14) 75%, rgba(0, 0, 0, 0.13) 81.25%, rgba(0, 0, 0, 0) 100%);
z-index: 5;
display: flex;
position: absolute;
width: 100vw;
border-top-right-radius: 1em;
border-top-left-radius: 1em;
bottom: 0em;
height: 3em;
opacity: 0.8;
backdrop-filter: blur(1px);
-webkit-mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 3%, rgba(0, 0, 0, 0.5) 7%, rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0.9) 25%, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 100%);
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 3%, rgba(0, 0, 0, 0.5) 7%, rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0.9) 25%, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 100%);
.back-button {
display: inline-flex;
justify-content: center;
align-items: center;
width: 1.2em;
height: 1.2em;
border-radius: 50%;
overflow: hidden;
transition: background 0.3s, color 0.3s;
color: #ffffffa1;
left: 0.5em;
top: 5em;
}
.skip-button {
display: inline-flex;
justify-content: center;
align-items: center;
width: 1.2em;
height: 1.2em;
border-radius: 50%;
overflow: hidden;
transition: background 0.3s, color 0.3s;
color: #ffffffa1;
right: 0.5em;
top: 5em;
}
.text-container::before {
content: '';
z-index: 5;
display: flex;
position: absolute;
width: 100vw;
top: -19.3em;
height: 3em;
background: transparent;
backdrop-filter: blur(0px);
border-top-right-radius: 1em;
border-top-left-radius: 1em;
-webkit-mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 3%, rgba(0, 0, 0, 0.5) 7%, rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0.9) 25%, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 100%);
mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 3%, rgba(0, 0, 0, 0.5) 7%, rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0.9) 25%, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 100%);
}
.text-container::after {
content: '';
background: linear-gradient(0deg, rgb(0% 0% 0% / 0.98) 0%, rgb(0% 0% 0% / 0.9705847873975829) 6.25%, rgb(0% 0% 0% / 0.9427009709305305) 12.5%, rgb(0% 0% 0% / 0.8974201100282472) 18.75%, rgb(0% 0% 0% / 0.8364823227814083) 25%, rgb(0% 0% 0% / 0.7622294141796051) 31.25%, rgb(0% 0% 0% / 0.6775148818588941) 37.5%, rgb(0% 0% 0% / 0.5855942577879029) 43.75%, rgb(0% 0% 0% / 0.49000000000000005) 50%, rgb(0% 0% 0% / 0.39440574221209723) 56.25%, rgb(0% 0% 0% / 0.3024851181411061) 62.5%, rgb(0% 0% 0% / 0.217770585820395) 68.75%, rgb(0% 0% 0% / 0.14351767721859177) 75%, rgb(0% 0% 0% / 0.0825798899717528) 81.25%, rgb(0% 0% 0% / 0.03729902906946947) 87.5%, rgb(0% 0% 0% / 0.009415212602417067) 93.75%, rgb(0% 0% 0% / 0) 100%);
z-index: 5;
display: flex;
position: absolute;
width: 100vw;
border-bottom-left-radius: 1em;
bottom: 0em;
height: 22.5em;
opacity: 1;
backdrop-filter: blur(0px);
-webkit-mask-image: linear-gradient(0deg, rgb(0% 0% 0% / 0.98) 0%, rgb(0% 0% 0% / 0.9705847873975829) 6.25%, rgb(0% 0% 0% / 0.9427009709305305) 12.5%, rgb(0% 0% 0% / 0.8974201100282472) 18.75%, rgb(0% 0% 0% / 0.8364823227814083) 25%, rgb(0% 0% 0% / 0.7622294141796051) 31.25%, rgb(0% 0% 0% / 0.6775148818588941) 37.5%, rgb(0% 0% 0% / 0.5855942577879029) 43.75%, rgb(0% 0% 0% / 0.49000000000000005) 50%, rgb(0% 0% 0% / 0.39440574221209723) 56.25%, rgb(0% 0% 0% / 0.3024851181411061) 62.5%, rgb(0% 0% 0% / 0.217770585820395) 68.75%, rgb(0% 0% 0% / 0.14351767721859177) 75%, rgb(0% 0% 0% / 0.0825798899717528) 81.25%, rgb(0% 0% 0% / 0.03729902906946947) 87.5%, rgb(0% 0% 0% / 0.009415212602417067) 93.75%, rgb(0% 0% 0% / 0) 100%);
mask-image: linear-gradient(0deg, rgb(0% 0% 0% / 0.98) 0%, rgb(0% 0% 0% / 0.9705847873975829) 6.25%, rgb(0% 0% 0% / 0.9427009709305305) 12.5%, rgb(0% 0% 0% / 0.8974201100282472) 18.75%, rgb(0% 0% 0% / 0.8364823227814083) 25%, rgb(0% 0% 0% / 0.7622294141796051) 31.25%, rgb(0% 0% 0% / 0.6775148818588941) 37.5%, rgb(0% 0% 0% / 0.5855942577879029) 43.75%, rgb(0% 0% 0% / 0.49000000000000005) 50%, rgb(0% 0% 0% / 0.39440574221209723) 56.25%, rgb(0% 0% 0% / 0.3024851181411061) 62.5%, rgb(0% 0% 0% / 0.217770585820395) 68.75%, rgb(0% 0% 0% / 0.14351767721859177) 75%, rgb(0% 0% 0% / 0.0825798899717528) 81.25%, rgb(0% 0% 0% / 0.03729902906946947) 87.5%, rgb(0% 0% 0% / 0.009415212602417067) 93.75%, rgb(0% 0% 0% / 0) 100%);
pointer-events: none;
}
.skip-button:hover {
background: #101010a8;
color: #fff;
}
.back-button:hover {
background: #101010a8;
color: #fff;
}
}
.age-rating {
@@ -876,18 +889,11 @@ css for back and skip button
font-size: 133%;
}
/* .backdrop {}
/*
.backdrop {}
.age-rating {}
.back-button {
top: 38vh;
}
.skip-button {
top: 38vh;
}
.video-container {}
.text-container::before {}
@@ -896,9 +902,6 @@ css for back and skip button
.lorem-ipsum {}
*/
.logo {
top: 38vh;
}
}
@media (min-width: 3000px) {
@@ -923,10 +926,11 @@ css for back and skip button
#video-overlay {
position: fixed;
top: 13vh;
top: 10vh;
border-radius: 1em;
left: 0vw;
width: 100%;
height: 140vh;
width: 94.5vw;
height: 55vh;
background-color: rgb(0, 0, 0);
z-index: 1000;
display: flex;
@@ -938,12 +942,30 @@ css for back and skip button
#video-overlay-content {
position: relative;
width: 90%;
width: 100%;
max-width: 100%;
height: 60%;
height: 100%;
background-color: #000;
}
@media (orientation: portrait) {
#video-overlay {
position: fixed;
top: 10vh;
border-radius: 1em;
left: 0vw;
width: 87.5vw;
height: 68vh;
background-color: rgb(0, 0, 0);
z-index: 1000;
display: flex;
background-size: cover;
padding: 6vh;
padding-top: 10vh;
margin-top: -10vh;
}
}
#close-overlay {
position: absolute;
top: -1em;
@@ -998,4 +1020,64 @@ css for back and skip button
vertical-align: middle;
font-size: 0.85em;
opacity: 0.75;
}
.logo {
opacity: 0;
animation: fadeIn 1.5s ease-in forwards;
animation-delay: 1s;
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
.plot {
opacity: 0;
animation: fadeIn 1s ease-in forwards;
animation-delay: 2s;
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
.genres {
opacity: 0;
animation: fadeIn 1s ease-in forwards;
animation-delay: 1.5s;
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
.lorem-ipsum {
opacity: 0;
animation: fadeIn 1s ease-in forwards;
animation-delay: 1.5s;
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
.text-container::after {
opacity: 0.7;
animation: fadeIn 1.5s ease-in forwards;
animation-delay: 1s;
}
@keyframes fadeIn {
to {
opacity: 1;
}
}