diff --git a/slideshowpure.css b/slideshowpure.css index 49394a3..6089fc8 100644 --- a/slideshowpure.css +++ b/slideshowpure.css @@ -1,67 +1,49 @@ -/* - * Jellyfin Slideshow by M0RPH3US v6.1.0 - */ - :root { - --slideshow-height: 100vh; - --slideshow-content-top: 65vh; - --slideshow-dots-reserve: max(2.5vh, 1.75rem); - --slideshow-gutter: 4vw; --slideshow-font: "Archivo Narrow", "Noto Sans", sans-serif; - --plate: rgb(28 30 34); --plate-accent: rgb(150 156 166); - --slideshow-bottom-fade: linear-gradient( to top, #fff0 2%, rgb(0 0 0 / 0.5) 6%, #000 8% ); - --slideshow-video-side-fade: linear-gradient( to right, transparent 10%, black 30% ); } - .backdrop.animate { animation: frostedGlass 1.2s cubic-bezier(0.4, 0, 0.2, 1), kenBurnsZoomIn 12s ease-out forwards; } - .logo.animate { animation: frostedGlass 1.2s cubic-bezier(0.4, 0, 0.2, 1); } - @keyframes frostedGlass { from { filter: blur(8px); opacity: 0.7; } - to { filter: blur(0); opacity: 1; } } - @keyframes kenBurnsZoomIn { from { transform: scale3d(1, 1, 1); } - to { transform: scale3d(1.1, 1.1, 1); } } - .bar-loading { z-index: 99999999 !important; position: fixed; @@ -78,11 +60,9 @@ overflow: hidden; will-change: opacity; } - .bar-loading.hide { opacity: 0; } - .loader-content { position: relative; display: flex; @@ -92,7 +72,6 @@ width: 250px; height: auto; } - .bar-loading h1 { width: 250px; margin: 0 auto; @@ -101,7 +80,6 @@ justify-content: center; align-items: center; } - .bar-loading h1 div { width: 250px; max-height: 250px; @@ -110,7 +88,6 @@ opacity: 0; transition: opacity 0.5s ease-in-out; } - .progress-container { width: 200px; height: 6px; @@ -118,21 +95,18 @@ align-items: center; position: relative; } - .progress-bar { height: 5px; - background: white; + background: #fff; border-radius: 2px; transition: width 0.2s ease-in-out; } - .progress-gap { width: 6px; height: 5px; - background: transparent; + background: #fff0; flex-shrink: 0; } - .unfilled-bar { height: 5px; background: #686868; @@ -140,11 +114,9 @@ flex-grow: 1; transition: width 0.2s ease-in-out; } - .bar-loading .splashLogo { height: 12%; } - .video-container { position: absolute; inset: 0; @@ -154,20 +126,15 @@ overflow: hidden; opacity: 0; transition: opacity 0.5s ease-in-out; - mask-image: var(--slideshow-video-side-fade), var(--slideshow-bottom-fade); - - -webkit-mask-image: var(--slideshow-video-side-fade), - var(--slideshow-bottom-fade); - + -webkit-mask-image: + var(--slideshow-video-side-fade), var(--slideshow-bottom-fade); mask-composite: intersect; -webkit-mask-composite: source-in; } - .video-container.active { opacity: 1; } - .video-player { position: absolute; top: 50%; @@ -178,7 +145,6 @@ min-width: 177.78cqh; min-height: 100cqh; } - .video-player iframe, .video-player .local-trailer { width: 100%; @@ -186,33 +152,28 @@ pointer-events: none; border: 0; } - .video-player .local-trailer { object-fit: cover; display: block; background: #000; } - .volume-toggle { position: absolute; top: 5rem; right: 3.5rem; cursor: pointer; - color: white; + color: #fff; z-index: 10; opacity: 0.3; transition: opacity 0.3s ease; - text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8); + text-shadow: 1px 1px 3px rgb(0 0 0 / 0.8); } - .volume-toggle:hover { opacity: 0.9; } - .volume-toggle i { font-size: 1.5rem; } - @media (max-width: 1599px) { .volume-toggle { top: 8rem; @@ -221,7 +182,6 @@ font-size: 2rem; } } - .backdrop.low-quality { filter: blur(0.5px); transform: scale(1.01); @@ -229,7 +189,6 @@ filter 0.3s ease-in-out, transform 0.3s ease-in-out; } - .backdrop.high-quality { filter: blur(0); transform: scale(1); @@ -237,26 +196,21 @@ filter 0.3s ease-in-out, transform 0.3s ease-in-out; } - .logo.low-quality { filter: brightness(1) blur(0.5px); transition: filter 0.3s ease-in-out; } - .logo.high-quality { filter: brightness(1.1) blur(0); transition: filter 0.3s ease-in-out; } - .homeSectionsContainer { position: relative; - margin-top: calc( - var(--slideshow-content-top) - var(--slideshow-page-offset, 0px) + var(--slideshow-content-top) - var(--slideshow-page-offset, 0) ); z-index: 6; } - #slides-container { position: absolute; top: 0; @@ -265,7 +219,6 @@ height: var(--slideshow-height); overflow: hidden; } - .arrow { position: absolute; top: 50%; @@ -288,71 +241,59 @@ background-color 0.2s ease; -webkit-tap-highlight-color: #fff0; } - .arrow i { font-size: 2rem; } - .arrow:hover { background: rgb(0 0 0 / 0.7); } - #slides-container:hover .arrow, #slides-container:focus-within .arrow { opacity: 1; visibility: visible; } - .left-arrow { left: 20px; } - .right-arrow { right: 20px; } - .pause-button { position: absolute; top: 5rem; right: 0.8rem; cursor: pointer; - color: white; + color: #fff; z-index: 10; opacity: 0.3; transition: opacity 0.3s ease; - text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8); + text-shadow: 1px 1px 3px rgb(0 0 0 / 0.8); } - .pause-button i { font-size: 1.5rem; } - .pause-button:hover { opacity: 0.9; } - @media (max-width: 1599px) { .pause-button { top: 8rem; } - .pause-button i { font-size: 2rem; } } - @media (max-width: 768px) { .pause-button i { font-size: 2.5rem; } } - .dots-container { position: absolute; right: 3%; bottom: calc( - var(--slideshow-height) - var(--slideshow-content-top) + - var(--slideshow-dots-reserve) + var(--slideshow-height) - + var(--slideshow-content-top)+var(--slideshow-dots-reserve) ); z-index: 5; display: flex; @@ -362,7 +303,6 @@ height: auto; transition: opacity 0.3s ease-in-out; } - .dot { display: inline-block; width: 0.5em; @@ -375,12 +315,10 @@ transform 0.5s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.5s ease-in-out; } - .dot.active { background-color: #fff; transform: scale(1.7); } - .slide { opacity: 0; position: absolute; @@ -392,13 +330,11 @@ z-index: 0; pointer-events: none; } - .slide.active { opacity: 1; z-index: 1; pointer-events: auto; } - .backdrop-container { position: absolute; top: 0%; @@ -408,7 +344,6 @@ mask-image: var(--slideshow-bottom-fade); -webkit-mask-image: var(--slideshow-bottom-fade); } - .backdrop { right: 0; width: 100%; @@ -424,46 +359,40 @@ opacity 0.45s ease-in-out; will-change: width; } - .backdrop.with-video { width: 40%; - mask-image: var(--slideshow-bottom-fade), + mask-image: + var(--slideshow-bottom-fade), linear-gradient(to right, black 30%, transparent 85%); - - -webkit-mask-image: var(--slideshow-bottom-fade), + -webkit-mask-image: + var(--slideshow-bottom-fade), linear-gradient(to right, black 30%, transparent 85%); - mask-composite: intersect; -webkit-mask-composite: source-in; } - .backdrop { background-size: cover; background-position: center 20%; background-repeat: no-repeat; } - .backdrop.awaiting-image { opacity: 0; } - .backdrop.image-failed { opacity: 0 !important; } - .backdrop-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; - background-color: rgba(0, 0, 0, 0.2); + background-color: rgb(0 0 0 / 0.2); border-radius: 5px; z-index: 4; mask-image: var(--slideshow-bottom-fade); -webkit-mask-image: var(--slideshow-bottom-fade); } - .gradient-overlay { position: absolute; top: 0; @@ -472,21 +401,20 @@ height: 100%; background: linear-gradient( 130deg, - rgba(29, 29, 29, 0.65) 10%, - rgba(29, 29, 29, 0.35) 30%, - rgba(29, 29, 29, 0) 100% + rgb(29 29 29 / 0.65) 10%, + rgb(29 29 29 / 0.35) 30%, + #fff0 100% ); z-index: 4; mask-image: var(--slideshow-bottom-fade); -webkit-mask-image: var(--slideshow-bottom-fade); } - .slide-content { position: absolute; left: var(--slideshow-gutter); bottom: calc( - var(--slideshow-height) - var(--slideshow-content-top) + - var(--slideshow-dots-reserve) + var(--slideshow-height) - + var(--slideshow-content-top)+var(--slideshow-dots-reserve) ); width: min(46%, 46rem); display: flex; @@ -495,29 +423,24 @@ gap: 0.9rem; z-index: 5; color: #fff; - max-height: calc(var(--slideshow-content-top) - 5vh); overflow: hidden; } - .logo-container { display: flex; align-items: flex-end; width: 100%; height: 22vh; - flex: 0 1 auto; min-height: 0; margin-bottom: 0.4rem; } - .info-container, .genre, .plot-container, .button-container { flex: 0 0 auto; } - .logo { max-height: 100%; max-width: 100%; @@ -528,18 +451,15 @@ filter: brightness(1.5); font-size: 4rem; } - .plot-container { width: 100%; box-sizing: border-box; transition: width 0.5s ease-in-out; } - .slide-content:has(~ .video-container.active) .plot-container, .plot-container.with-video { width: 90%; } - .plot { display: -webkit-box; line-clamp: 3; @@ -548,7 +468,17 @@ overflow: hidden; margin: 0; } - +.poster-container, +.tagline, +.action-label { + display: none; +} +.poster-container .poster { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} .genre { display: flex; gap: 5px; @@ -557,21 +487,18 @@ font-family: var(--slideshow-font); flex-wrap: wrap; } - .button-container { display: flex; align-items: center; gap: 15px; margin-top: 0.4rem; } - .play-button { display: inline-flex; flex-direction: row; align-items: center; justify-content: center; height: 50px; - padding-top: 0; padding-bottom: 0; border: none; @@ -589,11 +516,9 @@ -webkit-tap-highlight-color: #fff0; border-radius: 8px; } - .play-button:hover { background: rgb(255 255 255 / 0.85); } - .detail-button, .favorite-button { display: inline-flex; @@ -615,38 +540,31 @@ color 0.2s ease; -webkit-tap-highlight-color: #fff0; } - .detail-button:hover, .favorite-button:hover { background: rgb(0 0 0 / 0.7); border-color: rgb(255 255 255 / 0.7); } - .favorite-button.favorited { color: #ff4d4d; border-color: rgb(255 77 77 / 0.6); } - .favorite-button::before { content: "favorite_outline"; font-family: "Material Icons"; } - .favorite-button.favorited::before { content: "favorite"; font-family: "Material Icons"; } - .play-button::before { content: "play_arrow"; font-family: "Material Icons"; } - .detail-button::before { content: "info_outline"; font-family: "Material Icons"; } - .play-button::before, .detail-button::before, .favorite-button::before, @@ -657,7 +575,6 @@ color: inherit; vertical-align: middle; } - .play-button:focus-visible, .detail-button:focus-visible, .favorite-button:focus-visible, @@ -667,7 +584,6 @@ outline: 3px solid #fff; outline-offset: 3px; } - .info-container { display: flex; align-items: center; @@ -675,7 +591,6 @@ flex-wrap: wrap; font-weight: 500; } - .misc-info { font-family: var(--slideshow-font); display: flex; @@ -683,7 +598,6 @@ flex-wrap: wrap; gap: 10px; } - .runTime { font-family: "Archivo Narrow", sans-serif; display: flex; @@ -693,7 +607,6 @@ flex-wrap: wrap; font-weight: 500; } - .community-rating-star { color: #f2b01e; font-size: 1.4em; @@ -701,12 +614,10 @@ margin-right: 0.125em; width: auto !important; } - .star-rating-container { display: flex; align-items: center; } - .critic-rating { -webkit-align-items: center; align-items: center; @@ -714,7 +625,6 @@ min-height: 1.2em; gap: 0.25em; } - .age-rating { display: flex; align-items: center; @@ -726,7 +636,6 @@ white-space: nowrap; padding: 0 0.5em; } - .date { font-family: "Archivo Narrow", sans-serif; font-weight: 500; @@ -736,39 +645,32 @@ align-content: center; justify-content: center; } - .separator-icon { font-size: 10px; color: rgb(255 255 255 / 0.55); } - .featured-content { display: none; } - @media only screen and (max-width: 767px) and (orientation: portrait) { .plot-container { display: none; } - .backdrop-container { position: absolute; right: 0%; width: 100%; height: 100%; } - .video-container, .arrow, .volume-toggle { display: none !important; } - .backdrop.with-video { width: 100% !important; mask-image: none !important; } - .backdrop { position: absolute; top: 0; @@ -791,7 +693,6 @@ #000000 8% ); } - .gradient-overlay { position: absolute; top: 0; @@ -814,104 +715,82 @@ #000000 8% ); } - .slide-content { left: 50%; right: auto; transform: translateX(-50%); - bottom: calc( - var(--slideshow-height) - var(--slideshow-content-top) + 7vh - ); + bottom: calc(var(--slideshow-height) - var(--slideshow-content-top)+7vh); width: 92%; align-items: center; text-align: center; } - .logo-container { height: 20vh; justify-content: center; align-items: center; } - .logo { object-position: center; } - .genre, .info-container, .misc-info { justify-content: center; } - .button-container { justify-content: center; transform: scale(0.95); } - .dots-container { top: auto; - bottom: calc( - var(--slideshow-height) - var(--slideshow-content-top) + 1.5vh - ); + bottom: calc(var(--slideshow-height) - var(--slideshow-content-top)+1.5vh); left: 50%; right: auto; transform: translateX(-50%) scale(0.8); - background-color: #ffffff00; + background-color: #fff0; } - .dot.active { transform: scale(1.6); } } - @media only screen and (max-width: 1024px) and (max-height: 767px) and (orientation: landscape) { :root { - --slideshow-content-top: 68vh; --slideshow-gutter: 5vw; } - .slide-content { gap: 0.35rem; } - .logo-container { height: 13vh; margin-bottom: 0.15rem; } - .plot { -webkit-line-clamp: 1; line-clamp: 1; } - .button-container { transform: scale(0.8); transform-origin: left center; margin-top: 0; } - .dots-container { transform: scale(0.6); } } - @media only screen and (min-width: 2560px) { .slide-content { gap: 1.2rem; } - .logo-container { height: 26vh; } } - @media (prefers-reduced-motion: reduce) { .backdrop.animate, .logo.animate { animation: none !important; } - .slide, .backdrop, .dot, @@ -929,21 +808,15 @@ transition: none !important; } } - #slides-container.layout-plate { --slideshow-gutter: clamp(1.25rem, 3.4vw, 4.5rem); - --plate-width: clamp(19rem, 32vw, 34rem); - --plate-fade: clamp(3rem, 9vw, 11rem); - --plate-text: clamp(0.82rem, 0.62rem + 0.5vw, 1.05rem); --plate-logo-height: clamp(4.5rem, 13vh, 11rem); --plate-control: clamp(2.5rem, 2.1rem + 1.1vw, 3.25rem); - --plate-focal: 68%; } - #slides-container.layout-plate .plate { position: absolute; inset: 0 auto 0 0; @@ -960,68 +833,56 @@ -webkit-mask-image: var(--slideshow-bottom-fade); transition: background 0.6s ease-in-out; } - #slides-container.layout-plate .slide-content { width: calc(var(--plate-width) - var(--slideshow-gutter) * 2); gap: clamp(0.5rem, 1.4vh, 1.1rem); font-size: var(--plate-text); } - #slides-container.layout-plate .logo-container { height: var(--plate-logo-height); } - #slides-container.layout-plate .backdrop { object-position: var(--plate-focal, 68%) 22%; } - #slides-container.layout-plate .info-container, #slides-container.layout-plate .misc-info, #slides-container.layout-plate .genre, #slides-container.layout-plate .plot { font-size: var(--plate-text); } - #slides-container.layout-plate .plot { color: rgb(255 255 255 / 0.76); } - #slides-container.layout-plate .separator-icon { color: var(--plate-accent); opacity: 0.8; } - #slides-container.layout-plate .gradient-overlay { background: linear-gradient( 130deg, rgb(29 29 29 / 0.25) 10%, rgb(29 29 29 / 0.12) 34%, - rgb(29 29 29 / 0) 100% + #fff0 100% ); } - #slides-container.layout-plate .video-container.active { left: var(--plate-width); } - #slides-container.layout-plate .backdrop.with-video { width: 100%; opacity: 0; mask-image: var(--slideshow-bottom-fade); -webkit-mask-image: var(--slideshow-bottom-fade); } - #slides-container.layout-plate .plot-container.with-video { width: 100%; } - #slides-container.layout-plate .dots-container { left: var(--slideshow-gutter); right: auto; bottom: calc(var(--slideshow-height) - var(--slideshow-content-top) - 2.2vh); gap: 0; } - #slides-container.layout-plate .dot { width: 0.4rem; height: 0.4rem; @@ -1032,7 +893,6 @@ width 0.45s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.45s ease-in-out; } - #slides-container.layout-plate .dot.active { width: 1.5rem; height: 0.4rem; @@ -1040,24 +900,20 @@ background-color: #fff; transform: none; } - #slides-container.layout-plate .play-button, #slides-container.layout-plate .detail-button, #slides-container.layout-plate .favorite-button { height: var(--plate-control); } - #slides-container.layout-plate .detail-button, #slides-container.layout-plate .favorite-button { width: var(--plate-control); border-color: rgb(255 255 255 / 0.28); } - #slides-container.layout-plate .detail-button:hover, #slides-container.layout-plate .favorite-button:hover { border-color: var(--plate-accent); } - .layout-tv #slides-container.layout-plate { --slideshow-gutter: 5vw; --plate-width: clamp(26rem, 34vw, 44rem); @@ -1065,26 +921,21 @@ --plate-logo-height: clamp(8rem, 17vh, 15rem); --plate-control: clamp(4rem, 4.5vw, 5.25rem); } - .layout-tv #slides-container.layout-plate .slide-content { gap: clamp(1rem, 2vh, 1.9rem); bottom: calc(var(--slideshow-height) - var(--slideshow-content-top) + 5vh); } - .layout-tv #slides-container.layout-plate .dots-container { bottom: calc(var(--slideshow-height) - var(--slideshow-content-top) + 0.6vh); } - .layout-tv #slides-container.layout-plate .dot { width: 0.75rem; height: 0.75rem; } - .layout-tv #slides-container.layout-plate .dot.active { width: 2.75rem; height: 0.75rem; } - .layout-tv #slides-container.layout-plate .play-button:focus-visible, .layout-tv #slides-container.layout-plate .detail-button:focus-visible, .layout-tv #slides-container.layout-plate .favorite-button:focus-visible { @@ -1093,7 +944,6 @@ 0 0 0 0.35rem rgb(255 255 255 / 0.95), 0 0 0 0.7rem rgb(0 0 0 / 0.6); } - @media (max-width: 1100px) and (orientation: landscape) { #slides-container.layout-plate { --plate-width: clamp(17rem, 42vw, 26rem); @@ -1101,18 +951,14 @@ --plate-logo-height: clamp(3.5rem, 15vh, 7rem); } } - @media (max-width: 767px) and (orientation: portrait) { #slides-container.layout-plate { --plate-fade: 20vh; - --plate-focal: 50%; } - #slides-container.layout-plate .backdrop { object-position: var(--plate-focal) 16%; } - #slides-container.layout-plate .plate { inset: auto 0 0 0; width: auto; @@ -1124,7 +970,6 @@ transparent 100% ); } - #slides-container.layout-plate .slide-content { left: var(--slideshow-gutter); right: var(--slideshow-gutter); @@ -1133,51 +978,41 @@ align-items: flex-start; text-align: left; } - #slides-container.layout-plate .logo-container { justify-content: flex-start; } - #slides-container.layout-plate .logo { object-position: left bottom; } - #slides-container.layout-plate .genre, #slides-container.layout-plate .info-container, #slides-container.layout-plate .misc-info, #slides-container.layout-plate .button-container { justify-content: flex-start; } - #slides-container.layout-plate .button-container { transform: none; } - #slides-container.layout-plate .play-button { flex: 1 1 auto; } - #slides-container.layout-plate .dots-container { left: var(--slideshow-gutter); right: auto; transform: none; } } - #slides-container.layout-plate .slide-content { - top: auto; bottom: calc( - var(--slideshow-height) - var(--slideshow-content-top) + - var(--slideshow-dots-reserve) + var(--slideshow-height) - + var(--slideshow-content-top)+var(--slideshow-dots-reserve) ); - max-height: calc( var(--slideshow-rows-top, var(--slideshow-content-top)) - 3.5rem - 3vh ); gap: clamp(0.7rem, 1.9vh, 1.6rem); } - #slides-container.layout-plate .plate-eyebrow { font-family: "IBM Plex Mono", ui-monospace, "Archivo Narrow", monospace; font-size: clamp(0.6rem, 0.5rem + 0.24vw, 0.78rem); @@ -1187,18 +1022,15 @@ flex: 0 0 auto; font-variant-numeric: tabular-nums; } - #slides-container.layout-plate .plot-container { width: 100%; } - #slides-container.layout-plate .plot { -webkit-line-clamp: 3; line-clamp: 3; color: rgb(255 255 255 / 0.72); line-height: 1.55; } - #slides-container.layout-plate .plate-specs { display: flex; flex-direction: column; @@ -1208,7 +1040,6 @@ min-height: 0; overflow: hidden; } - #slides-container.layout-plate .spec-row { display: flex; align-items: baseline; @@ -1216,15 +1047,14 @@ gap: 1rem; padding-bottom: clamp(0.3rem, 0.7vh, 0.55rem); margin-bottom: clamp(0.3rem, 0.7vh, 0.55rem); - border-bottom: 1px solid color-mix(in srgb, var(--plate-accent) 30%, transparent); + border-bottom: 1px solid + color-mix(in srgb, var(--plate-accent) 30%, transparent); } - #slides-container.layout-plate .spec-row.is-last { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; } - #slides-container.layout-plate .spec-label { font-family: "IBM Plex Mono", ui-monospace, "Archivo Narrow", monospace; font-size: clamp(0.58rem, 0.48rem + 0.22vw, 0.74rem); @@ -1234,7 +1064,6 @@ margin: 0; white-space: nowrap; } - #slides-container.layout-plate .spec-value { font-family: var(--slideshow-font); font-size: var(--plate-text); @@ -1247,17 +1076,14 @@ text-overflow: ellipsis; white-space: nowrap; } - #slides-container.layout-plate .button-container { flex: 0 0 auto; margin-top: clamp(0.2rem, 1vh, 0.7rem); } - #slides-container.layout-plate .dots-container { top: auto; bottom: calc(var(--slideshow-height) - var(--slideshow-content-top) + 1.5vh); } - .layout-tv #slides-container.layout-plate .slide-content { top: clamp(5rem, 15vh, 11rem); bottom: auto; @@ -1265,28 +1091,23 @@ var(--slideshow-content-top) - clamp(5rem, 15vh, 11rem) - 6vh ); } - .layout-tv #slides-container.layout-plate .plate-eyebrow { font-size: clamp(1rem, 1vw, 1.3rem); } - .layout-tv #slides-container.layout-plate .spec-label { font-size: clamp(0.95rem, 0.95vw, 1.2rem); } - @media (max-width: 767px) and (orientation: portrait) { #slides-container.layout-plate .slide-content { top: auto; bottom: calc(var(--slideshow-height) - var(--slideshow-content-top) + 7vh); max-height: none; } - #slides-container.layout-plate .plate-specs { flex-direction: row; flex-wrap: wrap; gap: 0 1.1rem; } - #slides-container.layout-plate .spec-row { border-bottom: 0; padding-bottom: 0; @@ -1294,95 +1115,75 @@ gap: 0.4rem; justify-content: flex-start; } - #slides-container.layout-plate .plot { -webkit-line-clamp: 2; line-clamp: 2; } } - :root.sspure-plate { --slideshow-content-top: 74vh; } - #slides-container.layout-plate .logo-container { flex: 0 1 auto; min-height: 3.5rem; } - #slides-container.layout-plate .plot-container { flex: 0 1 auto; min-height: 0; overflow: hidden; } - #slides-container.layout-plate { --plate-logo-height: clamp(5rem, 17vh, 13rem); } - #slides-container.layout-plate .plate { mask-image: none; -webkit-mask-image: none; } - .layout-tv #slides-container.layout-plate { --plate-logo-height: clamp(9rem, 20vh, 17rem); } - @media (max-width: 1100px) and (orientation: landscape) { #slides-container.layout-plate { --plate-logo-height: clamp(3.5rem, 16vh, 7rem); } - #slides-container.layout-plate .plot-container { display: none; } } - @media (max-width: 767px) and (orientation: portrait) { #slides-container.layout-plate { --plate-logo-height: clamp(4.5rem, 12vh, 8rem); } - #slides-container.layout-plate .plate { mask-image: var(--slideshow-bottom-fade); -webkit-mask-image: var(--slideshow-bottom-fade); } } - #slides-container.layout-plate .backdrop.with-video { opacity: 1; width: 100%; } - #slides-container.layout-plate .slide:has(.video-container.active) .backdrop { opacity: 0; } - #slides-container.layout-plate .dots-container { top: auto; bottom: calc( - 100% - var(--slideshow-content-top) + var(--slideshow-page-offset, 0px) + - 3vh + 100% - var(--slideshow-content-top)+var(--slideshow-page-offset, 0)+3vh ); } - .layout-tv #slides-container.layout-plate .dots-container { bottom: calc( - 100% - var(--slideshow-content-top) + var(--slideshow-page-offset, 0px) + - 4vh + 100% - var(--slideshow-content-top)+var(--slideshow-page-offset, 0)+4vh ); } - @media (max-width: 767px) and (orientation: portrait) { #slides-container.layout-plate .dots-container { bottom: calc( - 100% - var(--slideshow-content-top) + var(--slideshow-page-offset, 0px) + - 2vh + 100% - var(--slideshow-content-top)+var(--slideshow-page-offset, 0)+2vh ); } } - #slides-container.layout-marquee { --slideshow-gutter: clamp(1.25rem, 3.6vw, 4.5rem); --mq-logo-height: clamp(4.5rem, 15vh, 12rem); @@ -1390,17 +1191,14 @@ --mq-spec: clamp(0.62rem, 0.5rem + 0.32vw, 0.82rem); --mq-rule-width: clamp(16rem, 32vw, 34rem); } - #slides-container.layout-marquee .slide-content { width: min(52%, 50rem); gap: clamp(0.55rem, 1.5vh, 1.15rem); } - #slides-container.layout-marquee .logo-container { height: var(--mq-logo-height); flex: 0 0 auto; } - #slides-container.layout-marquee .spec-rail { position: relative; width: var(--mq-rule-width); @@ -1410,14 +1208,12 @@ flex: 0 0 auto; margin-top: 0.35rem; } - #slides-container.layout-marquee .spec-progress { position: absolute; inset: 0 auto 0 0; width: 0; background: #fff; } - #slides-container.layout-marquee .spec-line { display: flex; align-items: center; @@ -1431,22 +1227,18 @@ font-variant-numeric: tabular-nums; flex: 0 0 auto; } - #slides-container.layout-marquee .spec-item { padding: 0 0.85em; } - #slides-container.layout-marquee .spec-item:first-child { padding-left: 0; } - #slides-container.layout-marquee .spec-divider { width: 1px; height: 0.95em; background: rgb(255 255 255 / 0.3); flex: 0 0 auto; } - #slides-container.layout-marquee .spec-cert { margin-left: 0.85em; border: 1px solid rgb(255 255 255 / 0.5); @@ -1455,42 +1247,35 @@ font-weight: 600; letter-spacing: 0.06em; } - #slides-container.layout-marquee .plot { -webkit-line-clamp: 2; line-clamp: 2; color: rgb(255 255 255 / 0.74); } - #slides-container.layout-marquee .plot-container { width: 100%; flex: 0 1 auto; min-height: 0; overflow: hidden; } - #slides-container.layout-marquee .button-container { flex: 0 0 auto; margin-top: clamp(0.2rem, 0.9vh, 0.6rem); } - #slides-container.layout-marquee .play-button, #slides-container.layout-marquee .detail-button, #slides-container.layout-marquee .favorite-button { height: var(--mq-control); } - #slides-container.layout-marquee .detail-button, #slides-container.layout-marquee .favorite-button { width: var(--mq-control); } - #slides-container.layout-marquee .dots-container { left: var(--slideshow-gutter); right: auto; gap: 0; } - #slides-container.layout-marquee .dot { width: 0.4rem; height: 0.4rem; @@ -1501,7 +1286,6 @@ width 0.45s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.45s ease-in-out; } - #slides-container.layout-marquee .dot.active { width: 1.5rem; height: 0.4rem; @@ -1509,7 +1293,6 @@ background-color: #fff; transform: none; } - .layout-tv #slides-container.layout-marquee { --slideshow-gutter: 5vw; --mq-logo-height: clamp(9rem, 20vh, 17rem); @@ -1517,21 +1300,17 @@ --mq-spec: clamp(1.1rem, 1.1vw, 1.4rem); --mq-rule-width: clamp(28rem, 42vw, 52rem); } - .layout-tv #slides-container.layout-marquee .spec-rail { height: 2px; } - .layout-tv #slides-container.layout-marquee .dot { width: 0.75rem; height: 0.75rem; } - .layout-tv #slides-container.layout-marquee .dot.active { width: 2.75rem; height: 0.75rem; } - .layout-tv #slides-container.layout-marquee .play-button:focus-visible, .layout-tv #slides-container.layout-marquee .detail-button:focus-visible, .layout-tv #slides-container.layout-marquee .favorite-button:focus-visible { @@ -1540,27 +1319,22 @@ 0 0 0 0.35rem rgb(255 255 255 / 0.95), 0 0 0 0.7rem rgb(0 0 0 / 0.6); } - @media (max-width: 1100px) and (orientation: landscape) { #slides-container.layout-marquee { --mq-logo-height: clamp(3.5rem, 16vh, 7rem); --mq-rule-width: clamp(14rem, 42vw, 22rem); } - #slides-container.layout-marquee .plot-container { display: none; } - #slides-container.layout-marquee .slide-content { width: min(62%, 34rem); } } - @media (max-width: 767px) and (orientation: portrait) { #slides-container.layout-marquee { --mq-logo-height: clamp(4rem, 11vh, 7rem); } - #slides-container.layout-marquee .slide-content { left: var(--slideshow-gutter); right: var(--slideshow-gutter); @@ -1574,61 +1348,50 @@ align-items: stretch; text-align: left; } - #slides-container.layout-marquee .logo-container { justify-content: flex-start; } - #slides-container.layout-marquee .logo { object-position: left center; } - #slides-container.layout-marquee .spec-rail { width: 100%; } - #slides-container.layout-marquee .spec-line { letter-spacing: 0.09em; font-size: clamp(0.6rem, 2.4vw, 0.72rem); } - #slides-container.layout-marquee .plot { -webkit-line-clamp: 2; line-clamp: 2; } - #slides-container.layout-marquee .button-container { justify-content: flex-start; transform: none; } - #slides-container.layout-marquee .play-button { flex: 1 1 auto; } - #slides-container.layout-marquee .dots-container { left: 50%; right: auto; transform: translateX(-50%); } } - #slides-container.layout-marquee .slide-content { top: auto; bottom: calc( - var(--slideshow-height) - var(--slideshow-content-top) + - var(--slideshow-dots-reserve) + var(--slideshow-height) - + var(--slideshow-content-top)+var(--slideshow-dots-reserve) ); max-height: calc( var(--slideshow-rows-top, var(--slideshow-content-top)) - 8vh ); } - #slides-container.layout-marquee .play-button, #slides-container.layout-plate .play-button { border-radius: 999px; } - @media (max-width: 767px) and (orientation: portrait) { #slides-container.layout-marquee .detail-button, #slides-container.layout-marquee .favorite-button, @@ -1636,104 +1399,86 @@ #slides-container.layout-plate .favorite-button { flex: 0 0 auto; } - #slides-container.layout-marquee .button-container, #slides-container.layout-plate .button-container { width: 100%; } } - @media (max-width: 767px) and (orientation: portrait) { #slides-container.layout-marquee .slide-content { transform: none; } } - @media (max-width: 359px) and (orientation: portrait) { #slides-container.layout-marquee .plot-container, #slides-container.layout-plate .plot-container { display: none; } } - @media (max-width: 767px) and (orientation: portrait) { #slides-container.layout-marquee .plot-container, #slides-container.layout-plate .plot-container { display: block; } - #slides-container.layout-marquee .plot, #slides-container.layout-plate .plot { -webkit-line-clamp: 2; line-clamp: 2; } } - @media (max-width: 359px) and (orientation: portrait) { #slides-container.layout-marquee .plot-container, #slides-container.layout-plate .plot-container { display: none; } } - #slides-container.layout-marquee .spec-line { min-height: 1.62em; align-items: center; } - #slides-container.layout-marquee .spec-line { min-height: 0; } - #slides-container.layout-marquee .spec-item, #slides-container.layout-marquee .spec-cert { padding-top: 0.1em; padding-bottom: 0.1em; - border-top: 1px solid transparent; - border-bottom: 1px solid transparent; + border-top: 1px solid #fff0; + border-bottom: 1px solid #fff0; line-height: 1.2; } - #slides-container.layout-marquee .spec-cert { border-color: rgb(255 255 255 / 0.5); } - #slides-container.layout-marquee .spec-item + .spec-item, #slides-container.layout-marquee .spec-item + .spec-cert { border-left: 1px solid rgb(255 255 255 / 0.3); } - #slides-container.layout-marquee .spec-divider { display: none; } - #slides-container.layout-marquee .spec-item .community-rating-star { font-size: 1.25em; color: #f2b01e; margin-right: 0.3em; vertical-align: -0.22em; } - #slides-container.layout-marquee .spec-item svg { height: 1.15em; width: auto; margin-right: 0.3em; vertical-align: -0.22em; } - #slides-container.layout-marquee .spec-critics, #slides-container.layout-marquee .spec-rating { display: inline-flex; align-items: center; } - @media (max-width: 767px) and (orientation: portrait) { - #slides-container.layout-marquee .spec-line { flex-wrap: nowrap; overflow: hidden; } - #slides-container.layout-marquee .spec-genre { min-width: 0; overflow: hidden; @@ -1743,7 +1488,6 @@ line-height: 1.7; } } - #slides-container.layout-marquee .spec-item, #slides-container.layout-marquee .spec-cert { display: inline-flex; @@ -1751,16 +1495,13 @@ height: 1.7em; box-sizing: border-box; } - #slides-container.layout-marquee .spec-line { align-items: center; } - #slides-container.layout-marquee .spec-item + .spec-cert { position: relative; border-left-color: rgb(255 255 255 / 0.5); } - #slides-container.layout-marquee .spec-item + .spec-cert::before { content: ""; position: absolute; @@ -1770,40 +1511,31 @@ width: 1px; background: rgb(255 255 255 / 0.3); } - @media (max-width: 767px) and (orientation: portrait) { - :root.sspure-marquee, :root.sspure-plate { - --slideshow-height: 66vh; --slideshow-content-top: 54vh; } - #slides-container.layout-marquee .backdrop { object-position: center 28%; } } - @media (min-width: 768px) and (max-height: 700px) { #slides-container.layout-plate .plot-container { display: none; } - #slides-container.layout-plate { --plate-logo-height: clamp(3.5rem, 11vh, 7rem); } - #slides-container.layout-plate .slide-content { gap: clamp(0.4rem, 1vh, 0.7rem); } } - #slides-container.layout-plate .spec-row.is-placeholder { visibility: hidden; - border-bottom-color: transparent; + border-bottom-color: #fff0; } - #slides-container .arrow { width: 2.25rem; height: 2.25rem; @@ -1817,86 +1549,70 @@ background-color 0.2s ease, border-color 0.2s ease; } - #slides-container .arrow i { font-size: 1.25rem; } - #slides-container .arrow:hover { background: rgb(10 12 18 / 0.72); border-color: rgb(255 255 255 / 0.6); } - #slides-container .left-arrow { left: auto; right: calc(var(--slideshow-gutter) + 2.25rem + 0.5rem); } - #slides-container .right-arrow { right: var(--slideshow-gutter); } - #slides-container.layout-plate .arrow, #slides-container.layout-marquee .arrow { top: auto; bottom: 3.5vh; transform: none; } - .layout-tv #slides-container .arrow { width: 3.5rem; height: 3.5rem; } - .layout-tv #slides-container .arrow i { font-size: 2rem; } - .layout-tv #slides-container .left-arrow { right: calc(var(--slideshow-gutter) + 3.5rem + 0.75rem); } - .ss-settings-toggle { position: absolute; top: 5rem; right: 6.2rem; cursor: pointer; - color: white; + color: #fff; z-index: 10; opacity: 0.3; transition: opacity 0.3s ease; - text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8); + text-shadow: 1px 1px 3px rgb(0 0 0 / 0.8); } - .ss-settings-toggle:hover, .ss-settings-toggle:focus-visible { opacity: 0.9; } - .ss-settings-toggle i { font-size: 1.5rem; } - @media (max-width: 1599px) { .ss-settings-toggle { top: 8rem; } - .ss-settings-toggle i { font-size: 2rem; } } - @media (max-width: 767px) { .ss-settings-toggle { right: 3.5rem; } - .ss-settings-toggle i { font-size: 2.5rem; } } - .ss-settings { position: fixed; top: 4.5rem; @@ -1914,7 +1630,6 @@ box-shadow: 0 1.5rem 3rem rgb(0 0 0 / 0.5); color: #fff; font-family: "Archivo Narrow", "Noto Sans", sans-serif; - opacity: 0; visibility: hidden; transform: translateY(-0.5rem); @@ -1923,13 +1638,11 @@ visibility 0.2s ease, transform 0.2s ease; } - .ss-settings.is-open { opacity: 1; visibility: visible; transform: none; } - .ss-settings-head { display: flex; align-items: center; @@ -1938,7 +1651,6 @@ padding: 0.9rem 0.7rem 0.9rem 1.1rem; border-bottom: 1px solid rgb(255 255 255 / 0.1); } - .ss-settings-head h2 { margin: 0; font-size: 0.72rem; @@ -1947,7 +1659,6 @@ text-transform: uppercase; color: rgb(255 255 255 / 0.72); } - .ss-settings-close { display: flex; align-items: center; @@ -1957,27 +1668,23 @@ padding: 0; border: 0; border-radius: 50%; - background: transparent; + background: #fff0; color: rgb(255 255 255 / 0.6); cursor: pointer; } - .ss-settings-close:hover { background: rgb(255 255 255 / 0.1); color: #fff; } - .ss-settings-close i { font-size: 1.15rem; } - .ss-settings-body { padding: 0.35rem 1.1rem 0.6rem; overflow-y: auto; display: flex; flex-direction: column; } - .ss-set-row { display: flex; align-items: center; @@ -1986,26 +1693,21 @@ padding: 0.72rem 0; border-bottom: 1px solid rgb(255 255 255 / 0.07); } - .ss-set-row:last-child { border-bottom: 0; } - .ss-set-row.is-stacked { flex-direction: column; align-items: stretch; gap: 0.5rem; } - .ss-set-row.is-inactive { opacity: 0.4; } - .ss-set-label { font-size: 0.88rem; color: rgb(255 255 255 / 0.9); } - .ss-set-value { margin-left: auto; font-family: "IBM Plex Mono", ui-monospace, monospace; @@ -2013,14 +1715,12 @@ font-variant-numeric: tabular-nums; color: rgb(255 255 255 / 0.55); } - .ss-set-note { flex-basis: 100%; font-size: 0.68rem; letter-spacing: 0.04em; color: rgb(255 255 255 / 0.42); } - .ss-choice { display: grid; grid-auto-flow: column; @@ -2030,12 +1730,11 @@ border-radius: 0.55rem; background: rgb(255 255 255 / 0.07); } - .ss-choice-btn { padding: 0.4rem 0.3rem; border: 0; border-radius: 0.4rem; - background: transparent; + background: #fff0; color: rgb(255 255 255 / 0.62); font-family: inherit; font-size: 0.8rem; @@ -2044,17 +1743,53 @@ background-color 0.18s ease, color 0.18s ease; } - .ss-choice-btn:hover:not(:disabled):not(.is-active) { color: #fff; } - .ss-choice-btn.is-active { background: #fff; color: #10131a; font-weight: 600; } - +.ss-multi { + display: flex; + flex-direction: column; + gap: 0.1rem; + max-height: 9rem; + overflow-y: auto; + padding: 0.2rem; + border-radius: 0.55rem; + background: rgb(255 255 255 / 0.07); +} +.ss-multi-item { + display: flex; + align-items: center; + gap: 0.55rem; + padding: 0.35rem 0.45rem; + border-radius: 0.4rem; + color: rgb(255 255 255 / 0.72); + font-size: 0.8rem; + cursor: pointer; + transition: + background-color 0.18s ease, + color 0.18s ease; +} +.ss-multi-item:hover:not(:has(.ss-multi-box:disabled)) { + background: rgb(255 255 255 / 0.06); + color: #fff; +} +.ss-multi-item:has(.ss-multi-box:disabled) { + cursor: default; + opacity: 0.5; +} +.ss-multi-box { + flex: none; + width: 0.95rem; + height: 0.95rem; + margin: 0; + accent-color: #fff; + cursor: inherit; +} .ss-switch { appearance: none; -webkit-appearance: none; @@ -2068,7 +1803,6 @@ cursor: pointer; transition: background-color 0.2s ease; } - .ss-switch::after { content: ""; position: absolute; @@ -2080,38 +1814,32 @@ background: #fff; transition: transform 0.2s ease; } - .ss-switch:checked { background: #2f81f7; } - .ss-switch:checked::after { transform: translateX(1.05rem); } - .ss-range { flex: 0 0 8rem; accent-color: #2f81f7; cursor: pointer; } - .ss-settings input:disabled, .ss-settings button:disabled { cursor: not-allowed; opacity: 0.45; } - .ss-settings-foot { padding: 0.7rem 1.1rem 0.9rem; border-top: 1px solid rgb(255 255 255 / 0.1); } - .ss-settings-reset { width: 100%; padding: 0.55rem; border: 1px solid rgb(255 255 255 / 0.2); border-radius: 0.5rem; - background: transparent; + background: #fff0; color: rgb(255 255 255 / 0.75); font-family: inherit; font-size: 0.8rem; @@ -2120,17 +1848,14 @@ background-color 0.18s ease, color 0.18s ease; } - .ss-settings-reset:hover { background: rgb(255 255 255 / 0.1); color: #fff; } - .ss-settings :focus-visible { outline: 2px solid #fff; outline-offset: 2px; } - @media (max-width: 767px) { .ss-settings { top: auto; @@ -2143,7 +1868,6 @@ transform: translateY(1rem); } } - @media (prefers-reduced-motion: reduce) { .ss-settings, .ss-switch, @@ -2152,7 +1876,6 @@ transition: none; } } - @media (hover: hover) { #slides-container .ss-chrome { opacity: 0; @@ -2161,7 +1884,6 @@ opacity 0.25s ease, visibility 0.25s ease; } - #slides-container:hover .ss-chrome, #slides-container:focus-within .ss-chrome, #slides-container.is-paused .ss-chrome, @@ -2169,14 +1891,12 @@ opacity: 0.3; visibility: visible; } - #slides-container .ss-chrome:hover, #slides-container .ss-chrome:focus-visible { opacity: 0.9; visibility: visible; } } - @media (prefers-reduced-motion: reduce) { #slides-container .ss-chrome { transition: none; diff --git a/slideshowpure.js b/slideshowpure.js index da71a68..0919ec9 100644 --- a/slideshowpure.js +++ b/slideshowpure.js @@ -1,7 +1,3 @@ -/* - * Jellyfin Slideshow by M0RPH3US v6.1.0 - */ - const CONFIG = { IMAGE_SVG: { freshTomato: @@ -9,45 +5,23 @@ const CONFIG = { rottenTomato: '', }, - // ms each slide is shown before advancing. 12000 = 12s. shuffleInterval: 12000, - // ms between retries when waiting on Jellyfin. 250-1000. retryInterval: 500, - // px of horizontal travel before a swipe counts. Lower = twitchier. minSwipeDistance: 50, - // ms between checks for the app being ready (loading screen only). loadingCheckInterval: 100, - // legacy character cap for the synopsis. The CSS line-clamp does the real work. maxPlotLength: 360, - // how many titles to pull from the library. Items without logo art are dropped after this. maxItems: 50, - - // movies per run when quotas are on. 0 = off (or none, if maxSeries > 0). maxMovies: 0, - // series per run when quotas are on. 0 = off (or none, if maxMovies > 0). maxSeries: 0, - - // library display names the bar may draw from, e.g. ["Movies","4K Movies"]. Empty = all. libraries: [], - - // library display names allowed to autoplay trailers. Empty = wherever trailers are otherwise enabled. trailerLibraries: [], - // slides built ahead of the current one. 1 = next only; >1 also builds the previous. preloadCount: 1, - - // upper bound on pagination dots. Fewer titles than this show one dot each. maxDots: 5, - - // leading slides that must include every item type present. 0 = off (plain proportional shuffle). mixTypesInFirst: 3, - // ms of the crossfade between slides. Must match the CSS transition. fadeTransitionDuration: 500, - // true|false. Ken Burns zoom and blur-in on the backdrop and logo. - slideAnimationEnabled: true, - // true|false. Master switch for trailer playback, local and YouTube. - enableTrailers: true, - - // name fragments that demote a trailer (sign language, vertical crops, etc). + slideAnimationEnabled: !0, + enableTrailers: !0, + allowTrailersOnTouch: !1, trailerAlternateCutTerms: [ "sign language", "asl trailer", @@ -56,81 +30,34 @@ const CONFIG = { "described audio", "vertical", ], - - // true|false. Mirror the slide onto Jellyfin's page backdrop. Intrusive: it deletes Jellyfin's own backdrop layers while active. - syncPageBackdrop: false, - // ms to wait for the YouTube iframe API before giving up and carrying on. + syncPageBackdrop: !1, youtubeApiLoadTimeoutMs: 8000, - - // ms the backdrop is shown before the trailer starts. 0 = immediately. trailerStartDelayMs: 3500, - - // ms to wait for playback to actually begin before resuming the slideshow. Guards a silent failure. trailerPlaybackWatchdogMs: 4000, - // 0-100. Volume once unmuted. Trailers always start muted. trailerVolume: 50, - - // px. At or below this width the layout is swipe-only: no arrows, no trailer, no volume. touchLayoutMaxWidth: 767, - - // true|false. Black overlay hiding the app until the slideshow is ready. Not needed since the early start. - showLoadingScreen: false, - - // ms before the overlay lifts regardless. Prevents a permanent black screen. + showLoadingScreen: !1, loadingScreenTimeoutMs: 10000, - - // ms to wait for a signed-in user before giving up entirely. authWaitTimeoutMs: 30000, - - // ms between sign-in checks during the first moments. Cheap, so it is tight. authFastPollIntervalMs: 30, - // ms of fast polling before backing off to retryInterval. authFastPollWindowMs: 8000, - - // true|false. Start from Jellyfin's stored credentials instead of waiting for window.ApiClient (~2.4s earlier). - earlyStartFromStoredCredentials: true, - - // true|false. Paint a blurred approximation of the backdrop while the real image loads. Costs no request. - useBlurHashPlaceholder: true, - - // "marquee" | "plate" | "classic". marquee = spec line + progress rule, panel on phone. plate = type on a tinted panel. classic = the original. + earlyStartFromStoredCredentials: !0, + useBlurHashPlaceholder: !0, layout: "marquee", - - // 0-1. Plate layout only. Target lightness of the panel. 0.035 holds white text near 12:1. plateLuminance: 0.035, - // 0-1. Plate layout only. Lightness of the accent taken from the artwork. plateAccentLuminance: 0.42, - - // CSS colour. Plate panel when an item has no BlurHash to derive from. plateFallback: "rgb(28 30 34)", - // CSS colour. Accent when an item has no BlurHash. plateAccentFallback: "rgb(150 156 166)", - - // max rows in the plate layout's spec table. Fields are added in priority order. plateSpecRows: 4, - - // true|false. Prefer a trailer file on the server over the YouTube embed. Avoids embed blocks and autoplay refusals. - preferLocalTrailers: true, - - // true|false. Hold the rotation while the pointer is over the stage. Resumes mid-period, does not restart. - pauseOnHover: true, - - // true|false. Keep the shuffled order and position for the browser session, so returning to home resumes. - rememberOrderForSession: true, - - // true|false. Skip trailers when the browser reports a metered connection. - respectDataSaver: true, - - // true|false. Also time Jellyfin's own rows for comparison. Costs a body-wide observer; read marks via performance.getEntriesByType('mark'). - measureStartup: false, - - // true|false. Look up trailer intros via sponsor.ajay.app. Sends the video id to a third party, so off by default. - enableSponsorBlock: false, + preferLocalTrailers: !0, + pauseOnHover: !0, + rememberOrderForSession: !0, + respectDataSaver: !0, + measureStartup: !1, + enableSponsorBlock: !1, }; - const STORED_CONFIG_KEY = "mediabar.config"; const STORED_CONFIG_VERSION = 1; - const CONFIG_RULES = { shuffleInterval: [2000, 600000], retryInterval: [50, 10000], @@ -160,7 +87,6 @@ const CONFIG_RULES = { plateFallback: "colour", plateAccentFallback: "colour", }; - const STRUCTURAL_CONFIG_KEYS = new Set([ "layout", "maxItems", @@ -178,84 +104,71 @@ const STRUCTURAL_CONFIG_KEYS = new Set([ "libraries", "trailerLibraries", ]); - const lockedConfigKeys = new Set(); - const validateConfigValue = (key, value) => { const current = CONFIG[key]; const rule = CONFIG_RULES[key]; - if (typeof current === "boolean") { if (typeof value === "boolean") return value; - - if (value === "true" || value === "1") return true; - if (value === "false" || value === "0") return false; + if (value === "true" || value === "1") return !0; + if (value === "false" || value === "0") return !1; return null; } - if (typeof current === "number") { const parsed = typeof value === "number" ? value : Number(value); if (!Number.isFinite(parsed)) return null; if (!Array.isArray(rule)) return parsed; return Math.min(rule[1], Math.max(rule[0], parsed)); } - if (typeof current === "string") { const text = String(value); if (Array.isArray(rule)) return rule.includes(text) ? text : null; - if (rule === "colour") { return window.CSS?.supports("color", text) ? text : null; } return text; } - if (Array.isArray(current)) { return Array.isArray(value) ? value.map(String) : null; } - return null; }; - const applyImageSvgOverrides = (svgs) => { - if (!svgs || typeof svgs !== "object") return false; - - let changed = false; + if (!svgs || typeof svgs !== "object") return !1; + let changed = !1; for (const [rawKey, value] of Object.entries(svgs)) { const key = rawKey.charAt(0).toLowerCase() + rawKey.slice(1); if (!(key in CONFIG.IMAGE_SVG)) continue; if (typeof value !== "string" || !/^\s*]/i.test(value)) continue; CONFIG.IMAGE_SVG[key] = value; - changed = true; + changed = !0; } return changed; }; - const applyConfig = (source, options = {}) => { if (!source || typeof source !== "object") return []; - - const { trusted = false, sentinel = null, label = "override" } = options; + const { trusted = !1, sentinel = null, label = "override" } = options; const changed = []; - if (trusted && Array.isArray(source.lock)) { source.lock.forEach((key) => lockedConfigKeys.add(String(key))); } - for (const [rawKey, value] of Object.entries(source)) { if (rawKey === "lock" || rawKey === "__v") continue; - - if (rawKey === "IMAGE_SVG" || rawKey === "ImageSvgs" || rawKey === "imageSvgs") { + if ( + rawKey === "IMAGE_SVG" || + rawKey === "ImageSvgs" || + rawKey === "imageSvgs" + ) { if (trusted && applyImageSvgOverrides(value)) changed.push("IMAGE_SVG"); continue; } - const key = - rawKey in CONFIG ? rawKey : rawKey.charAt(0).toLowerCase() + rawKey.slice(1); - + rawKey in CONFIG + ? rawKey + : rawKey.charAt(0).toLowerCase() + rawKey.slice(1); if (!(key in CONFIG)) continue; if (!trusted && lockedConfigKeys.has(key)) continue; if (sentinel !== null && value === sentinel) continue; - const clean = validateConfigValue(key, value); if (clean === null) { console.warn( @@ -267,35 +180,28 @@ const applyConfig = (source, options = {}) => { ); continue; } - if (CONFIG[key] !== clean) { CONFIG[key] = clean; changed.push(key); } } - return changed; }; - const readStoredConfig = () => { try { const raw = localStorage.getItem(STORED_CONFIG_KEY); if (!raw) return null; - const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== "object") return null; - if (parsed.__v !== STORED_CONFIG_VERSION) { localStorage.removeItem(STORED_CONFIG_KEY); return null; } return parsed; } catch { - return null; } }; - const saveStoredConfig = (patch) => { const next = { ...(readStoredConfig() || {}), @@ -304,12 +210,9 @@ const saveStoredConfig = (patch) => { }; try { localStorage.setItem(STORED_CONFIG_KEY, JSON.stringify(next)); - } catch { - - } + } catch {} return next; }; - const readUrlConfig = () => { const found = {}; try { @@ -317,7 +220,6 @@ const readUrlConfig = () => { const hashQuery = hash.includes("?") ? hash.slice(hash.indexOf("?") + 1) : ""; - for (const params of [ new URLSearchParams(window.location.search), new URLSearchParams(hashQuery), @@ -331,38 +233,29 @@ const readUrlConfig = () => { } return found; }; - const setConfig = (patch) => { saveStoredConfig(patch); const changed = applyConfig(patch, { label: "runtime" }); - if (changed.some((key) => STRUCTURAL_CONFIG_KEYS.has(key))) { resetSlideshowState(); bootstrap(); } return changed; }; - const clearStoredConfig = () => { try { localStorage.removeItem(STORED_CONFIG_KEY); - } catch { - - } + } catch {} }; - applyConfig(window.MediaBarConfig, { - trusted: true, + trusted: !0, sentinel: -1, label: "plugin", }); -applyConfig(window.SlideshowConfig, { trusted: true, label: "index.html" }); - +applyConfig(window.SlideshowConfig, { trusted: !0, label: "index.html" }); const CONFIG_SERVER_DEFAULTS = JSON.parse(JSON.stringify(CONFIG)); - applyConfig(readStoredConfig(), { label: "localStorage" }); applyConfig(readUrlConfig(), { label: "url" }); - const STATE = { jellyfinData: { userId: null, @@ -372,74 +265,66 @@ const STATE = { deviceId: null, accessToken: null, serverAddress: null, - views: null, }, slideshow: { - hasInitialized: false, - isBootstrapping: false, - signOutWatcherAttached: false, - listenersAttached: false, - isTransitioning: false, - isPaused: false, + hasInitialized: !1, + isBootstrapping: !1, + signOutWatcherAttached: !1, + listenersAttached: !1, + isTransitioning: !1, + isPaused: !1, currentSlideIndex: 0, - containerFocused: false, + containerFocused: !1, slideInterval: null, itemIds: [], loadedItems: {}, createdSlides: {}, - slideVideoIds: {}, trailerStartTimer: null, trailerWatchdog: null, - trailerLibraryIds: null, - firstSlideShown: false, - hoverHeld: false, + firstSlideShown: !1, + hoverHeld: !1, resumeIndex: 0, - startedFromStoredCredentials: false, - credentialsRejected: false, - homeRenderWatched: false, + startedFromStoredCredentials: !1, + credentialsRejected: !1, + homeRenderWatched: !1, totalItems: 0, - isLoading: false, + isLoading: !1, players: {}, ytPromise: null, - isMuted: true, - isVideoPlaying: false, + isMuted: !0, + isVideoPlaying: !1, }, }; - const loadYouTubeAPI = () => { if (STATE.slideshow.ytPromise) return STATE.slideshow.ytPromise; - STATE.slideshow.ytPromise = new Promise((resolve) => { if (window.YT && window.YT.Player) { resolve(window.YT); return; } - let timeout; - let settled = false; + let settled = !1; const previousReady = window.onYouTubeIframeAPIReady; const finish = (YT = null) => { if (settled) return; - settled = true; + settled = !0; clearTimeout(timeout); resolve(YT && YT.Player ? YT : null); }; - window.onYouTubeIframeAPIReady = () => { if (typeof previousReady === "function") { previousReady(); } finish(window.YT); }; - let tag = document.querySelector('script[src*="youtube.com/iframe_api"]'); if (!tag) { tag = document.createElement("script"); tag.src = "https://www.youtube.com/iframe_api"; - tag.async = true; + tag.async = !0; tag.onerror = () => { console.warn( "YouTube iframe API failed to load; continuing without trailers.", @@ -449,7 +334,6 @@ const loadYouTubeAPI = () => { const firstScriptTag = document.getElementsByTagName("script")[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); } - timeout = setTimeout(() => { console.warn( "Timed out loading YouTube iframe API; continuing without trailers.", @@ -459,84 +343,70 @@ const loadYouTubeAPI = () => { }); return STATE.slideshow.ytPromise; }; - const mark = (name) => { try { performance.mark(`sspure:${name}`); - } catch (error) { - - } + } catch (error) {} }; - const markJellyfinHomeRender = () => { - if (!CONFIG.measureStartup) return; if (STATE.slideshow.homeRenderWatched) return; - STATE.slideshow.homeRenderWatched = true; - + STATE.slideshow.homeRenderWatched = !0; const alreadyRendered = () => - document.querySelector(".homeSectionsContainer .sectionTitle, .homeSectionsContainer .card"); - + document.querySelector( + ".homeSectionsContainer .sectionTitle, .homeSectionsContainer .card", + ); if (alreadyRendered()) { mark("jellyfin-home-rows"); return; } - const observer = new MutationObserver(() => { if (!alreadyRendered()) return; mark("jellyfin-home-rows"); observer.disconnect(); }); - observer.observe(document.body, { childList: true, subtree: true }); - + observer.observe(document.body, { childList: !0, subtree: !0 }); setTimeout(() => observer.disconnect(), 30000); }; - const isUserLoggedIn = () => { try { const apiClient = window.ApiClient; return Boolean( apiClient && - typeof apiClient.isLoggedIn === "function" && - apiClient.isLoggedIn() && - apiClient.accessToken() && - apiClient.getCurrentUserId(), + typeof apiClient.isLoggedIn === "function" && + apiClient.isLoggedIn() && + apiClient.accessToken() && + apiClient.getCurrentUserId(), ); } catch (error) { console.error("Error checking login status:", error); - return false; + return !1; } }; - const hasUsableCredentials = () => { try { const apiClient = window.ApiClient; return Boolean( apiClient && - typeof apiClient.accessToken === "function" && - apiClient.accessToken() && - apiClient.serverAddress() && - apiClient.getCurrentUserId(), + typeof apiClient.accessToken === "function" && + apiClient.accessToken() && + apiClient.serverAddress() && + apiClient.getCurrentUserId(), ); } catch (error) { - return false; + return !1; } }; - const readStoredCredentials = () => { if (!CONFIG.earlyStartFromStoredCredentials) return null; - try { const raw = localStorage.getItem("jellyfin_credentials"); if (!raw) return null; - const server = JSON.parse(raw)?.Servers?.[0]; if (!server?.AccessToken || !server?.UserId) return null; - const serverAddress = server.ManualAddress || server.LocalAddress || window.location.origin; if (!serverAddress) return null; - return { userId: server.UserId, accessToken: server.AccessToken, @@ -547,10 +417,8 @@ const readStoredCredentials = () => { return null; } }; - const initJellyfinData = () => { try { - if (hasUsableCredentials()) { const apiClient = window.ApiClient; STATE.jellyfinData = { @@ -563,12 +431,10 @@ const initJellyfinData = () => { serverId: apiClient.serverId() || null, serverAddress: apiClient.serverAddress() || null, }; - STATE.slideshow.startedFromStoredCredentials = false; + STATE.slideshow.startedFromStoredCredentials = !1; } else { - const stored = readStoredCredentials(); - if (!stored) return false; - + if (!stored) return !1; STATE.jellyfinData = { userId: stored.userId, appName: "Jellyfin Web", @@ -579,20 +445,18 @@ const initJellyfinData = () => { serverId: stored.serverId, serverAddress: stored.serverAddress, }; - STATE.slideshow.startedFromStoredCredentials = true; + STATE.slideshow.startedFromStoredCredentials = !0; } - return Boolean( STATE.jellyfinData.userId && - STATE.jellyfinData.accessToken && - STATE.jellyfinData.serverAddress, + STATE.jellyfinData.accessToken && + STATE.jellyfinData.serverAddress, ); } catch (error) { console.error("Error reading Jellyfin client data:", error); - return false; + return !1; } }; - const initLocalization = async () => { try { const locale = await LocalizationUtils.getCurrentLocale(); @@ -602,19 +466,15 @@ const initLocalization = async () => { console.error("Error initializing localization:", error); } }; - const initLoadingScreen = () => { if (!CONFIG.showLoadingScreen) return; - const currentPath = window.location.href.toLowerCase(); const isHomePage = currentPath.includes("/web/#/home.html") || currentPath.includes("/web/#/home") || currentPath.includes("/web/index.html#/home.html") || currentPath.endsWith("/web/"); - if (!isHomePage) return; - const loadingDiv = document.createElement("div"); loadingDiv.className = "bar-loading"; loadingDiv.id = "page-loader"; @@ -631,17 +491,13 @@ const initLoadingScreen = () => { `; document.body.appendChild(loadingDiv); - requestAnimationFrame(() => { document.querySelector(".bar-loading h1 div").style.opacity = "1"; }); - const progressBar = document.getElementById("progress-bar"); const unfilledBar = document.getElementById("unfilled-bar"); - let progress = 0; let lastIncrement = 5; - const progressInterval = setInterval(() => { if (progress < 95) { lastIncrement = Math.max(0.5, lastIncrement * 0.98); @@ -649,28 +505,22 @@ const initLoadingScreen = () => { const increment = lastIncrement * randomFactor; progress += increment; progress = Math.min(progress, 95); - progressBar.style.width = `${progress}%`; unfilledBar.style.width = `${100 - progress}%`; } }, 150); - const checkInterval = setInterval(() => { const loginFormLoaded = document.querySelector(".manualLoginForm"); const activeTab = document.querySelector(".pageTabContent.is-active"); - if (loginFormLoaded) { finishLoading(); return; } - if (activeTab) { const tabIndex = activeTab.getAttribute("data-index"); - if (tabIndex === "0") { const homeSections = document.querySelector(".homeSectionsContainer"); const slidesContainer = document.querySelector("#slides-container"); - if (homeSections && slidesContainer) { finishLoading(); } @@ -684,29 +534,23 @@ const initLoadingScreen = () => { } } }, CONFIG.loadingCheckInterval); - - let finished = false; + let finished = !1; const finishLoading = () => { if (finished) return; - finished = true; - + finished = !0; clearInterval(progressInterval); clearInterval(checkInterval); clearTimeout(safetyTimeout); - progressBar.style.transition = "width 300ms ease-in-out"; progressBar.style.width = "100%"; unfilledBar.style.width = "0%"; - const loader = document.getElementById("page-loader"); if (!loader) return; - setTimeout(() => { loader.style.opacity = "0"; setTimeout(() => loader.remove(), 300); }, 300); }; - const safetyTimeout = setTimeout(() => { console.warn( "Loading screen timed out after " + @@ -716,39 +560,31 @@ const initLoadingScreen = () => { finishLoading(); }, CONFIG.loadingScreenTimeoutMs); }; - const resetSlideshowState = () => { console.log("🔄 Resetting slideshow state..."); - if (STATE.slideshow.slideInterval) { STATE.slideshow.slideInterval.stop(); } - destroyAllPlayers(); - PageBackdrop.clear(); - const container = document.getElementById("slides-container"); if (container) { container.remove(); } - - STATE.slideshow.hasInitialized = false; - STATE.slideshow.isTransitioning = false; - STATE.slideshow.isPaused = false; + STATE.slideshow.hasInitialized = !1; + STATE.slideshow.isTransitioning = !1; + STATE.slideshow.isPaused = !1; STATE.slideshow.currentSlideIndex = 0; - STATE.slideshow.containerFocused = false; + STATE.slideshow.containerFocused = !1; STATE.slideshow.slideInterval = null; STATE.slideshow.itemIds = []; STATE.slideshow.loadedItems = {}; STATE.slideshow.createdSlides = {}; STATE.slideshow.totalItems = 0; - STATE.slideshow.isVideoPlaying = false; + STATE.slideshow.isVideoPlaying = !1; STATE.slideshow.trailerLibraryIds = null; - STATE.jellyfinData.views = null; }; - const destroyAllPlayers = () => { Object.keys(STATE.slideshow.players).forEach((itemId) => { const player = STATE.slideshow.players[itemId]; @@ -761,29 +597,23 @@ const destroyAllPlayers = () => { } }); STATE.slideshow.players = {}; - STATE.slideshow.isVideoPlaying = false; + STATE.slideshow.isVideoPlaying = !1; }; - const waitForSignIn = () => { if (isUserLoggedIn()) return Promise.resolve(); - return new Promise((resolve, reject) => { const deadline = Date.now() + CONFIG.authWaitTimeoutMs; const started = Date.now(); - const tick = () => { - if (isUserLoggedIn() || readStoredCredentials()) { mark("credentials-available"); resolve(); return; } - if (Date.now() > deadline) { reject(new Error("Timed out waiting for sign-in")); return; } - const elapsed = Date.now() - started; const interval = elapsed < CONFIG.authFastPollWindowMs @@ -791,42 +621,33 @@ const waitForSignIn = () => { : CONFIG.retryInterval; setTimeout(tick, interval); }; - tick(); }); }; - const bootstrap = async () => { if (STATE.slideshow.hasInitialized || STATE.slideshow.isBootstrapping) return; - STATE.slideshow.isBootstrapping = true; - + STATE.slideshow.isBootstrapping = !0; mark("bootstrap-start"); markJellyfinHomeRender(); initLoadingScreen(); watchForSignOut(); - try { await waitForSignIn(); mark("signed-in"); - if (!initJellyfinData()) { console.warn("Slideshow: ApiClient data incomplete, not starting."); return; } - await initLocalization(); mark("localization-ready"); - await slidesInit(); - if ( STATE.slideshow.credentialsRejected && STATE.slideshow.startedFromStoredCredentials ) { - STATE.slideshow.credentialsRejected = false; + STATE.slideshow.credentialsRejected = !1; resetSlideshowState(); - STATE.slideshow.isBootstrapping = false; - + STATE.slideshow.isBootstrapping = !1; await new Promise((resolve) => { const waitForApiClient = () => { if (hasUsableCredentials()) resolve(); @@ -834,21 +655,18 @@ const bootstrap = async () => { }; waitForApiClient(); }); - console.log("Slideshow: retrying with ApiClient credentials."); await bootstrap(); } } catch (error) { console.warn("Slideshow: not starting —", error.message); } finally { - STATE.slideshow.isBootstrapping = false; + STATE.slideshow.isBootstrapping = !1; } }; - const watchForSignOut = () => { if (STATE.slideshow.signOutWatcherAttached) return; - STATE.slideshow.signOutWatcherAttached = true; - + STATE.slideshow.signOutWatcherAttached = !0; window.addEventListener("hashchange", () => { if (STATE.slideshow.hasInitialized && !isUserLoggedIn()) { console.log("Slideshow: signed out, tearing down."); @@ -858,11 +676,8 @@ const watchForSignOut = () => { } }); }; - bootstrap(); - const SlideUtils = { - shuffleArray(array) { const newArray = [...array]; for (let i = newArray.length - 1; i > 0; i--) { @@ -871,23 +686,18 @@ const SlideUtils = { } return newArray; }, - mixTypesInHead(items, windowSize) { if (!windowSize || items.length <= windowSize) return items; - const result = [...items]; const presentTypes = new Set(result.map((item) => item.Type)); if (presentTypes.size < 2) return result; - for (const type of presentTypes) { const head = result.slice(0, windowSize); if (head.some((item) => item.Type === type)) continue; - const from = result.findIndex( (item, index) => index >= windowSize && item.Type === type, ); if (from === -1) continue; - const counts = {}; head.forEach((item) => { counts[item.Type] = (counts[item.Type] || 0) + 1; @@ -896,23 +706,18 @@ const SlideUtils = { .map((item, index) => (counts[item.Type] > 1 ? index : -1)) .filter((index) => index !== -1); if (!spare.length) break; - const to = spare[Math.floor(Math.random() * spare.length)]; [result[from], result[to]] = [result[to], result[from]]; } - return result; }, - createSeparator() { const separator = document.createElement("i"); separator.className = "material-icons fiber_manual_record separator-icon"; return separator; }, - createElement(tag, attributes = {}, content = null) { const element = document.createElement(tag); - Object.entries(attributes).forEach(([key, value]) => { if (key === "style" && typeof value === "object") { Object.entries(value).forEach(([prop, val]) => { @@ -927,13 +732,11 @@ const SlideUtils = { } else if (key.startsWith("on") && typeof value === "function") { element.addEventListener(key.slice(2), value); } else if (typeof value === "boolean") { - element[key] = value; } else { element.setAttribute(key, value); } }); - if (content) { if (typeof content === "string") { element.textContent = content; @@ -941,33 +744,25 @@ const SlideUtils = { element.appendChild(content); } } - return element; }, - getOrCreateSlidesContainer() { let container = document.getElementById("slides-container"); if (!container) { container = this.createElement("div", { id: "slides-container" }); document.body.appendChild(container); } - container.classList.toggle("layout-plate", CONFIG.layout === "plate"); container.classList.toggle("layout-marquee", CONFIG.layout === "marquee"); - const root = document.documentElement; root.classList.toggle("sspure-plate", CONFIG.layout === "plate"); root.classList.toggle("sspure-marquee", CONFIG.layout === "marquee"); - return container; }, - buildGenres(genresArray) { if (!Array.isArray(genresArray) || genresArray.length === 0) return null; - const genres = genresArray.slice(0, 3).filter(Boolean); if (!genres.length) return null; - const fragment = document.createDocumentFragment(); genres.forEach((genre, index) => { if (index > 0) fragment.appendChild(this.createSeparator()); @@ -978,16 +773,14 @@ const SlideUtils = { return fragment; }, }; - const isDataSaverOn = () => { - if (!CONFIG.respectDataSaver) return false; + if (!CONFIG.respectDataSaver) return !1; try { return Boolean(navigator.connection?.saveData); } catch (error) { - return false; + return !1; } }; - const LIST_FILTER_PARAMS = { genre: "Genres", tag: "Tags", @@ -996,40 +789,32 @@ const LIST_FILTER_PARAMS = { person: "Person", rating: "OfficialRatings", }; - const EMPTY_LIST = Object.freeze({ ids: [], filters: [] }); - const parseListFilter = (line) => { const colon = line.indexOf(":"); if (colon < 1) return null; - const key = line.slice(0, colon).trim().toLowerCase(); const param = LIST_FILTER_PARAMS[key]; if (!param) return null; - const value = line .slice(colon + 1) .split(",") .map((part) => part.trim()) .filter(Boolean) .join("|"); - if (!value) return null; return { key, param, value }; }; - const trailersAllowedFor = (item) => { const allowed = STATE.slideshow.trailerLibraryIds; - if (!allowed || !allowed.size) return true; - if (!item.LibraryId) return true; + if (!allowed || !allowed.size) return !0; + if (!item.LibraryId) return !0; return allowed.has(item.LibraryId); }; - const createLocalPlayer = (video) => ({ - isLocal: true, + isLocal: !0, element: video, playVideo() { - const started = video.play(); if (started && typeof started.catch === "function") { started.catch(() => { @@ -1043,15 +828,13 @@ const createLocalPlayer = (video) => ({ seekTo(seconds) { try { video.currentTime = seconds || 0; - } catch (error) { - - } + } catch (error) {} }, mute() { - video.muted = true; + video.muted = !0; }, unMute() { - video.muted = false; + video.muted = !1; }, setVolume(percent) { video.volume = Math.max(0, Math.min(1, (percent || 0) / 100)); @@ -1065,16 +848,12 @@ const createLocalPlayer = (video) => ({ video.removeAttribute("src"); video.load(); video.remove(); - } catch (error) { - - } + } catch (error) {} }, }); - const BlurHash = { DIGITS: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~", - decode83(str, start, end) { let value = 0; for (let i = start; i < end; i++) { @@ -1084,19 +863,16 @@ const BlurHash = { } return value; }, - toLinear(value) { const v = value / 255; return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); }, - fromLinear(value) { const v = Math.max(0, Math.min(1, value)); return Math.round( (v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055) * 255, ); }, - decodeDC(value) { return [ this.toLinear(value >> 16), @@ -1104,7 +880,6 @@ const BlurHash = { this.toLinear(value & 255), ]; }, - decodeAC(value, maxValue) { const quant = (v) => { const n = Math.floor(v); @@ -1116,16 +891,13 @@ const BlurHash = { quant(value % 19), ]; }, - toDataURL(hash, width = 32, height = 18) { try { if (typeof hash !== "string" || hash.length < 6) return null; - const sizeFlag = this.decode83(hash, 0, 1); const numX = (sizeFlag % 9) + 1; const numY = Math.floor(sizeFlag / 9) + 1; if (hash.length !== 4 + 2 * numX * numY) return null; - const maxValue = (this.decode83(hash, 1, 2) + 1) / 166; const colours = new Array(numX * numY); colours[0] = this.decodeDC(this.decode83(hash, 2, 6)); @@ -1136,16 +908,13 @@ const BlurHash = { maxValue, ); } - const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const context = canvas.getContext("2d"); if (!context) return null; - const imageData = context.createImageData(width, height); const pixels = imageData.data; - for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { let r = 0; @@ -1169,23 +938,19 @@ const BlurHash = { pixels[index + 3] = 255; } } - context.putImageData(imageData, 0, 0); return canvas.toDataURL(); } catch (error) { return null; } }, - plateColours(item) { const hashes = item?.ImageBlurHashes?.Backdrop; const hash = hashes ? Object.values(hashes)[0] : null; if (typeof hash !== "string" || hash.length < 6) return null; - try { const dc = this.decodeDC(this.decode83(hash, 2, 6)); if (dc.some((channel) => Number.isNaN(channel))) return null; - const luminance = 0.2126 * dc[0] + 0.7152 * dc[1] + 0.0722 * dc[2]; const pin = (target) => { const scale = target / (luminance || 1e-6); @@ -1193,7 +958,6 @@ const BlurHash = { }; const toCss = (linear) => `rgb(${linear.map((channel) => this.fromLinear(channel)).join(" ")})`; - return { plate: toCss(pin(CONFIG.plateLuminance)), accent: toCss(pin(CONFIG.plateAccentLuminance)), @@ -1202,7 +966,6 @@ const BlurHash = { return null; } }, - forItem(item, imageType, tag) { const hashes = item?.ImageBlurHashes?.[imageType]; if (!hashes) return null; @@ -1211,39 +974,30 @@ const BlurHash = { return typeof first === "string" ? first : null; }, }; - const LocalizationUtils = { translations: {}, locale: null, isLoading: {}, cachedLocale: null, chunkUrlCache: {}, - async getCurrentLocale() { if (this.cachedLocale) { return this.cachedLocale; } - let locale = document.documentElement.getAttribute("lang"); - if (!locale) { const navLang = navigator.language || navigator.userLanguage; locale = navLang || "en-us"; } - locale = locale.toLowerCase(); - this.cachedLocale = locale; return locale; }, - findTranslationChunkUrl(locale) { const localePrefix = locale.split("-")[0]; - if (this.chunkUrlCache[localePrefix]) { return this.chunkUrlCache[localePrefix]; } - if (window.performance && window.performance.getEntriesByType) { try { const resources = window.performance.getEntriesByType("resource"); @@ -1262,34 +1016,28 @@ const LocalizationUtils = { console.warn("Error checking performance entries:", e); } } - this.chunkUrlCache[localePrefix] = null; return null; }, - async loadTranslations(locale) { if (this.translations[locale]) return; if (this.isLoading[locale]) { await this.isLoading[locale]; return; } - const loadPromise = (async () => { try { const chunkUrl = this.findTranslationChunkUrl(locale); if (!chunkUrl) { return; } - const response = await fetch(chunkUrl); if (!response.ok) { throw new Error( `Failed to fetch translations: ${response.statusText}`, ); } - const chunkText = await response.text(); - const replaceEscaped = (text) => text .replace(/\\"/g, '"') @@ -1357,10 +1105,9 @@ const LocalizationUtils = { }; const ApiUtils = { - itemFieldsQuery() { return [ - "fields=Overview,Genres,RemoteTrailers,ChildCount,LocalTrailerCount", + "fields=Overview,Genres,Taglines,RemoteTrailers,ChildCount,LocalTrailerCount", "enableUserData=true", "enableImageTypes=Backdrop,Logo,Primary", "enableTotalRecordCount=false", @@ -1382,7 +1129,9 @@ const ApiUtils = { if (response.status === 401 || response.status === 403) { STATE.slideshow.credentialsRejected = true; } - console.warn(`Slideshow: could not load listed items (${response.status}).`); + console.warn( + `Slideshow: could not load listed items (${response.status}).`, + ); return []; } @@ -1395,20 +1144,16 @@ const ApiUtils = { }, async getSkipSegments(videoId) { - if (!CONFIG.enableSponsorBlock) return 0; try { - const categories = '["intro"]'; const response = await fetch( `https://sponsor.ajay.app/api/skipSegments?videoID=${videoId}&categories=${categories}`, ); - if (response.status === 200) { const segments = await response.json(); const introSegment = segments.find((s) => s.segment[0] < 5); - if (introSegment) { console.log( `[SponsorBlock] Skipping intro for ${videoId}. Start at: ${introSegment.segment[1]}`, @@ -1421,14 +1166,11 @@ const ApiUtils = { return 0; } }, - async fetchListEntries() { try { const listFileName = `${STATE.jellyfinData.serverAddress}/web/avatars/list.txt?userId=${STATE.jellyfinData.userId}`; const response = await fetch(listFileName); - if (!response.ok) { - if (response.status !== 404) { console.warn( `Slideshow: list.txt returned ${response.status}; using library items.`, @@ -1436,7 +1178,6 @@ const ApiUtils = { } return EMPTY_LIST; } - const contentType = response.headers.get("content-type") || ""; if (/html|xml/i.test(contentType)) { console.warn( @@ -1445,7 +1186,6 @@ const ApiUtils = { ); return EMPTY_LIST; } - const text = await response.text(); if (/^\s* line.trim()) - .filter((line) => line && !line.startsWith("#")) .forEach((line) => { - if (/^[0-9a-f-]{32,36}$/i.test(line)) { ids.push(line); return; } - const filter = parseListFilter(line); if (filter) { filters.push(filter); return; } - console.warn( `Slideshow: ignoring list.txt line "${line}". Expected an item id ` + `or one of ${Object.keys(LIST_FILTER_PARAMS).join(", ")} ` + `followed by ":". Prefix a line with # to comment it out.`, ); }); - return { ids, filters }; } catch (error) { console.error("Error fetching list.txt:", error); return EMPTY_LIST; } }, - async fetchItemsByFilter(filter) { try { const response = await fetch( @@ -1499,14 +1231,12 @@ const ApiUtils = { `&${this.itemFieldsQuery()}`, { headers: this.getAuthHeaders() }, ); - if (!response.ok) { console.warn( `Slideshow: ${filter.key}:${filter.value} failed (${response.status}).`, ); return []; } - const { Items: items = [] } = await response.json(); if (!items.length) { console.warn( @@ -1519,13 +1249,11 @@ const ApiUtils = { return []; } }, - async fetchListedItems(entries) { const [byId, ...byFilter] = await Promise.all([ entries.ids.length ? this.fetchItemsByIds(entries.ids) : [], ...entries.filters.map((filter) => this.fetchItemsByFilter(filter)), ]); - const seen = new Set(); const merged = []; byId.concat(...byFilter).forEach((item) => { @@ -1533,10 +1261,8 @@ const ApiUtils = { seen.add(item.Id); merged.push(item); }); - return merged; }, - async fetchItemPage(types, limit, library = null) { const response = await fetch( `${STATE.jellyfinData.serverAddress}/Items` + @@ -1544,43 +1270,33 @@ const ApiUtils = { `&imageTypes=Logo,Backdrop&SortBy=Random&isPlayed=False` + `&Limit=${limit}&${this.itemFieldsQuery()}` + (library ? `&ParentId=${encodeURIComponent(library.Id)}` : ""), - { - headers: this.getAuthHeaders(), - }, + { headers: this.getAuthHeaders() }, ); - if (!response.ok) { - if (response.status === 401 || response.status === 403) { - STATE.slideshow.credentialsRejected = true; + STATE.slideshow.credentialsRejected = !0; console.warn( "Slideshow: stored credentials rejected; waiting for ApiClient.", ); return []; } - console.error( `Failed to fetch items: ${response.status} ${response.statusText}`, ); return []; } - const { Items: items = [] } = await response.json(); - - const withLogos = items.filter((item) => item.ImageTags && item.ImageTags.Logo); - + const withLogos = items.filter( + (item) => item.ImageTags && item.ImageTags.Logo, + ); if (library) withLogos.forEach((item) => (item.LibraryId = library.Id)); - return withLogos; }, - async fetchAcrossLibraries(types, limit, libraries) { if (!libraries.length) return await this.fetchItemPage(types, limit); - const pages = await Promise.all( libraries.map((library) => this.fetchItemPage(types, limit, library)), ); - const merged = []; const longest = Math.max(0, ...pages.map((page) => page.length)); for (let i = 0; i < longest; i++) { @@ -1588,13 +1304,10 @@ const ApiUtils = { if (i < page.length) merged.push(page[i]); }); } - return merged.slice(0, limit); }, - async fetchViews() { if (STATE.jellyfinData.views) return STATE.jellyfinData.views; - try { const response = await fetch( `${STATE.jellyfinData.serverAddress}/Users/${STATE.jellyfinData.userId}/Views`, @@ -1614,15 +1327,11 @@ const ApiUtils = { return []; } }, - async resolveLibraries(names) { if (!names.length) return []; - const views = await this.fetchViews(); if (!views.length) return []; - const matched = []; - names.forEach((name) => { const wanted = String(name).trim().toLowerCase(); const view = views.find((v) => (v.Name || "").toLowerCase() === wanted); @@ -1634,27 +1343,21 @@ const ApiUtils = { ); } }); - return matched; }, - async fetchItemsFromServer() { try { if (!STATE.jellyfinData.accessToken) { console.warn("Access token not available. Skipping API request."); return []; } - if (!STATE.jellyfinData.serverAddress) { console.warn("Server address not available. Skipping API request."); return []; } - const movieQuota = Math.max(0, CONFIG.maxMovies || 0); const seriesQuota = Math.max(0, CONFIG.maxSeries || 0); - let libraries = await this.resolveLibraries(CONFIG.libraries); - const trailerLibraries = await this.resolveLibraries( CONFIG.trailerLibraries, ); @@ -1664,7 +1367,6 @@ const ApiUtils = { if (!libraries.length && trailerLibraries.length) { libraries = await this.fetchViews(); } - if (!movieQuota && !seriesQuota) { return await this.fetchAcrossLibraries( "Movie,Series", @@ -1672,7 +1374,6 @@ const ApiUtils = { libraries, ); } - const [movies, series] = await Promise.all([ movieQuota ? this.fetchAcrossLibraries("Movie", movieQuota, libraries) @@ -1681,12 +1382,9 @@ const ApiUtils = { ? this.fetchAcrossLibraries("Series", seriesQuota, libraries) : [], ]); - const merged = movies.concat(series); if (merged.length) return merged; - if (STATE.slideshow.credentialsRejected) return []; - console.warn( "Slideshow: the configured quota matched no items; using the pooled query.", ); @@ -1700,7 +1398,6 @@ const ApiUtils = { return []; } }, - async fetchLocalTrailerUrl(itemId) { try { const response = await fetch( @@ -1708,13 +1405,10 @@ const ApiUtils = { { headers: this.getAuthHeaders() }, ); if (!response.ok) return null; - const trailers = await response.json(); const trailer = Array.isArray(trailers) ? trailers[0] : null; if (!trailer?.Id) return null; - const mediaSourceId = trailer.MediaSources?.[0]?.Id || trailer.Id; - return ( `${STATE.jellyfinData.serverAddress}/Videos/${trailer.Id}/stream` + `?static=true&mediaSourceId=${encodeURIComponent(mediaSourceId)}` + @@ -1725,58 +1419,47 @@ const ApiUtils = { return null; } }, - getAuthHeaders() { return { Authorization: `MediaBrowser Client="${STATE.jellyfinData.appName}", Device="${STATE.jellyfinData.deviceName}", DeviceId="${STATE.jellyfinData.deviceId}", Version="${STATE.jellyfinData.appVersion}", Token="${STATE.jellyfinData.accessToken}"`, }; }, - async playItem(itemId) { try { const sessionId = await this.getSessionId(); if (!sessionId) { console.error("Session ID not found."); - return false; + return !1; } - const playUrl = `${STATE.jellyfinData.serverAddress}/Sessions/${sessionId}/Playing?playCommand=PlayNow&itemIds=${itemId}`; const playResponse = await fetch(playUrl, { method: "POST", headers: this.getAuthHeaders(), }); - if (!playResponse.ok) { throw new Error( `Failed to send play command: ${playResponse.statusText}`, ); } - console.log("Play command sent successfully to session:", sessionId); - return true; + return !0; } catch (error) { console.error("Error sending play command:", error); - return false; + return !1; } }, - async getSessionId() { try { const response = await fetch( `${ STATE.jellyfinData.serverAddress }/Sessions?deviceId=${encodeURIComponent(STATE.jellyfinData.deviceId)}`, - { - headers: this.getAuthHeaders(), - }, + { headers: this.getAuthHeaders() }, ); - if (!response.ok) { throw new Error(`Failed to fetch session data: ${response.statusText}`); } - const sessions = await response.json(); - if (!sessions || sessions.length === 0) { console.warn( "No sessions found for deviceId:", @@ -1784,21 +1467,17 @@ const ApiUtils = { ); return null; } - return sessions[0].Id; } catch (error) { console.error("Error fetching session data:", error); return null; } }, - async toggleFavorite(itemId, button) { try { const isFavorite = button.classList.contains("favorited"); - const url = `${STATE.jellyfinData.serverAddress}/UserFavoriteItems/${itemId}`; const method = isFavorite ? "DELETE" : "POST"; - const response = await fetch(url, { method, headers: { @@ -1806,13 +1485,10 @@ const ApiUtils = { "Content-Type": "application/json", }, }); - if (!response.ok) { throw new Error(`Failed to toggle favorite: ${response.statusText}`); } - button.classList.toggle("favorited", !isFavorite); - const cached = STATE.slideshow.loadedItems[itemId]; if (cached) { cached.UserData = cached.UserData || {}; @@ -1823,20 +1499,16 @@ const ApiUtils = { } }, }; - class SlideTimer { - constructor(callback, interval) { this.callback = callback; this.interval = interval; this.timerId = null; - this.resumeId = null; this.startedAt = null; this.remaining = interval; this.start(); } - stop() { if (this.timerId) { clearInterval(this.timerId); @@ -1850,7 +1522,6 @@ class SlideTimer { this.startedAt = null; return this; } - start() { if (this.timerId || this.resumeId) return this; this.startedAt = Date.now(); @@ -1858,13 +1529,10 @@ class SlideTimer { this.timerId = setInterval(this.callback, this.interval); return this; } - pause() { if (!this.timerId && !this.resumeId) return this; - const elapsed = this.startedAt ? Date.now() - this.startedAt : 0; this.remaining = Math.max(0, this.interval - (elapsed % this.interval)); - if (this.timerId) { clearInterval(this.timerId); this.timerId = null; @@ -1875,12 +1543,9 @@ class SlideTimer { } return this; } - resume() { if (this.timerId || this.resumeId) return this; - if (this.remaining >= this.interval) return this.start(); - this.startedAt = Date.now() - (this.interval - this.remaining); this.resumeId = setTimeout(() => { this.resumeId = null; @@ -1891,51 +1556,39 @@ class SlideTimer { }, this.remaining); return this; } - restart() { return this.stop().start(); } } - const VisibilityObserver = { - wasVisible: false, - pending: false, + wasVisible: !1, + pending: !1, lastDisplay: null, - scheduleUpdate() { if (this.pending) return; - this.pending = true; - + this.pending = !0; const run = () => { if (!this.pending) return; - this.pending = false; + this.pending = !1; this.updateVisibility(); }; - requestAnimationFrame(run); setTimeout(run, 250); }, - updateVisibility() { const container = document.getElementById("slides-container"); if (!container) return; - const activeTab = document.querySelector(".emby-tab-button-active"); const hash = window.location.hash; const onHome = hash === "#/home.html" || hash === "#/home"; - const isVisible = - onHome && - (activeTab ? activeTab.getAttribute("data-index") === "0" : true); - + onHome && (activeTab ? activeTab.getAttribute("data-index") === "0" : !0); const display = isVisible ? "block" : "none"; if (display !== this.lastDisplay) { container.style.display = display; this.lastDisplay = display; } - if (isVisible === this.wasVisible) return; - if (isVisible) { SlideshowManager.updateCurrentSlide(STATE.slideshow.currentSlideIndex); } else { @@ -1947,10 +1600,8 @@ const VisibilityObserver = { STATE.slideshow.slideVideoIds = {}; PageBackdrop.clear(); } - this.wasVisible = isVisible; }, - handleClick(event) { const target = event.target; if ( @@ -1961,23 +1612,54 @@ const VisibilityObserver = { VisibilityObserver.scheduleUpdate(); } }, - init() { const observer = new MutationObserver(() => this.scheduleUpdate()); - observer.observe(document.body, { childList: true, subtree: true }); + observer.observe(document.body, { childList: !0, subtree: !0 }); document.body.addEventListener("click", this.handleClick.bind(this)); window.addEventListener("hashchange", () => this.scheduleUpdate()); - this.updateVisibility(); }, }; - const PageBackdrop = { LAYER_CLASS: "slideshow-page-backdrop", observer: null, - isWriting: false, + isWriting: !1, currentItemId: null, - + surface: null, + prevInlineImage: null, + addedWithBackdrop: !1, + getSurface() { + const bg = document.querySelector(".backgroundContainer"); + if (!bg) return null; + if (document.querySelector(".backdropContainer .backdropImage")) + return null; + return bg; + }, + updateSurface(surface, itemId, src) { + if (this.surface !== surface) { + this.surface = surface; + this.prevInlineImage = surface.style.backgroundImage || ""; + this.addedWithBackdrop = !surface.classList.contains("withBackdrop"); + } + if (this.currentItemId === itemId) return; + surface.style.backgroundImage = `url("${src.replace(/"/g, "%22")}")`; + surface.classList.add("withBackdrop"); + this.currentItemId = itemId; + }, + restoreSurface() { + if (!this.surface) return; + if (this.prevInlineImage) { + this.surface.style.backgroundImage = this.prevInlineImage; + } else { + this.surface.style.removeProperty("background-image"); + } + if (this.addedWithBackdrop) { + this.surface.classList.remove("withBackdrop"); + } + this.surface = null; + this.prevInlineImage = null; + this.addedWithBackdrop = !1; + }, getOrCreateContainer() { let container = document.querySelector(".backdropContainer"); if (!container) { @@ -1988,7 +1670,6 @@ const PageBackdrop = { } return container; }, - getOrCreateLayer(container) { let layer = container.querySelector(`.${this.LAYER_CLASS}`); if (!layer || !layer.isConnected) { @@ -1999,7 +1680,6 @@ const PageBackdrop = { } return layer; }, - removeRotatorLayers(container) { const layers = container.querySelectorAll(".backdropImage"); layers.forEach((layer) => { @@ -2007,44 +1687,33 @@ const PageBackdrop = { layer.remove(); } }); - const slideshowLayer = container.querySelector(`.${this.LAYER_CLASS}`); if (slideshowLayer && slideshowLayer !== container.lastElementChild) { container.appendChild(slideshowLayer); } }, - startObserver(container) { if (this.observer) return; - this.observer = new MutationObserver(() => { - if (this.isWriting) return; - - this.isWriting = true; + this.isWriting = !0; try { this.removeRotatorLayers(container); } finally { - this.isWriting = false; + this.isWriting = !1; } }); - - this.observer.observe(container, { childList: true }); + this.observer.observe(container, { childList: !0 }); }, - stopObserver() { if (!this.observer) return; this.observer.disconnect(); this.observer = null; }, - update(itemId) { if (!CONFIG.syncPageBackdrop) return; - const item = STATE.slideshow.loadedItems[itemId]; - if (!item) return; - const src = SlideCreator.buildImageUrl( item, "Backdrop", @@ -2053,53 +1722,50 @@ const PageBackdrop = { 60, ); if (!src) return; - + const surface = this.getSurface(); + if (surface) { + this.updateSurface(surface, itemId, src); + return; + } const container = this.getOrCreateContainer(); - - this.isWriting = true; + this.isWriting = !0; try { const layer = this.getOrCreateLayer(container); - if (this.currentItemId !== itemId || layer.style.backgroundImage === "") { layer.style.backgroundImage = `url("${src.replace(/"/g, "%22")}")`; layer.classList.remove("backdropImageFadeIn"); - void layer.offsetWidth; layer.classList.add("backdropImageFadeIn"); this.currentItemId = itemId; } - this.removeRotatorLayers(container); } finally { - this.isWriting = false; + this.isWriting = !1; } - document .querySelector(".backgroundContainer") ?.classList.add("withBackdrop"); this.startObserver(container); }, - clear() { this.stopObserver(); this.currentItemId = null; - + if (this.surface) { + this.restoreSurface(); + return; + } const slideshowLayer = document.querySelector(`.${this.LAYER_CLASS}`); if (!slideshowLayer) return; - slideshowLayer.remove(); document .querySelector(".backgroundContainer") ?.classList.remove("withBackdrop"); }, }; - const SlideCreator = { - buildImageUrl(item, imageType, index, serverAddress, quality) { const itemId = item.Id; let tag = null; - if (imageType === "Backdrop") { if ( item.BackdropImageTags && @@ -2119,14 +1785,12 @@ const SlideCreator = { tag = item.ImageTags[imageType]; } } - let baseUrl; if (index !== undefined) { baseUrl = `${serverAddress}/Items/${itemId}/Images/${imageType}/${index}`; } else { baseUrl = `${serverAddress}/Items/${itemId}/Images/${imageType}`; } - if (tag) { const qualityParam = quality !== undefined ? `&quality=${quality}` : ""; return `${baseUrl}?tag=${tag}${qualityParam}`; @@ -2135,18 +1799,15 @@ const SlideCreator = { return `${baseUrl}?quality=${qualityParam}`; } }, - selectTrailerVideoId(remoteTrailers) { if (!Array.isArray(remoteTrailers) || remoteTrailers.length === 0) { return null; } - const rankName = (name) => { const text = (name || "").toLowerCase(); const isAlternateCut = CONFIG.trailerAlternateCutTerms.some((term) => text.includes(term), ); - let rank; if (text.includes("official trailer")) rank = 5; else if (text.includes("final trailer") || text.includes("main trailer")) @@ -2154,12 +1815,9 @@ const SlideCreator = { else if (text.includes("trailer")) rank = 3; else if (text.includes("teaser")) rank = 2; else rank = 1; - return isAlternateCut ? rank - 0.5 : rank; }; - let best = null; - for (const trailer of remoteTrailers) { let videoId = null; try { @@ -2176,65 +1834,52 @@ const SlideCreator = { videoId = urlObj.searchParams.get("v"); } } catch (e) {} - if (!videoId) continue; - const rank = rankName(trailer.Name); if (!best || rank > best.rank) { best = { videoId, rank }; } } - return best ? best.videoId : null; }, - createSlideElement(item, title) { if (!item || !item.Id) { console.error("Invalid item data:", item); return null; } - const itemId = item.Id; const serverAddress = STATE.jellyfinData.serverAddress; - const slide = SlideUtils.createElement("div", { className: "slide", "data-item-id": itemId, }); - let videoId = null; - let hasLocalTrailer = false; + let hasLocalTrailer = !1; let trailerContainer = null; - if ( CONFIG.enableTrailers && - !isTouchLayout() && + (CONFIG.allowTrailersOnTouch || !isTouchLayout()) && !isDataSaverOn() && trailersAllowedFor(item) ) { - hasLocalTrailer = CONFIG.preferLocalTrailers && (item.LocalTrailerCount || 0) > 0; videoId = hasLocalTrailer ? null : this.selectTrailerVideoId(item.RemoteTrailers); - if (hasLocalTrailer || videoId) { trailerContainer = SlideUtils.createElement("div", { className: "video-container", id: `trailer-${item.Id}`, }); - const playerDiv = SlideUtils.createElement("div", { className: "video-player", id: `yt-player-${item.Id}`, }); - trailerContainer.appendChild(playerDiv); slide.appendChild(trailerContainer); } } - if (isPlateLayout()) { const plate = BlurHash.plateColours(item); slide.style.setProperty("--plate", plate?.plate || CONFIG.plateFallback); @@ -2243,24 +1888,19 @@ const SlideCreator = { plate?.accent || CONFIG.plateAccentFallback, ); } - const backdrop = SlideUtils.createElement("img", { className: "backdrop high-quality", src: this.buildImageUrl(item, "Backdrop", 0, serverAddress, 60), alt: "", - "aria-hidden": "true", loading: "eager", }); - const backdropOverlay = SlideUtils.createElement("div", { className: "backdrop-overlay", }); - const backdropContainer = SlideUtils.createElement("div", { className: "backdrop-container", }); - if (CONFIG.useBlurHashPlaceholder) { const backdropTag = item.BackdropImageTags?.[0] || item.ImageTags?.Backdrop; @@ -2268,110 +1908,126 @@ const SlideCreator = { BlurHash.forItem(item, "Backdrop", backdropTag), ); if (placeholder) { - backdrop.style.backgroundImage = `url("${placeholder}")`; backdrop.classList.add("awaiting-image"); const reveal = () => backdrop.classList.remove("awaiting-image"); if (backdrop.complete) reveal(); else { - backdrop.addEventListener("load", reveal, { once: true }); - + backdrop.addEventListener("load", reveal, { once: !0 }); backdrop.addEventListener( "error", () => backdrop.classList.add("image-failed"), - { once: true }, + { once: !0 }, ); } } } - backdropContainer.append(backdrop, backdropOverlay); - const logo = SlideUtils.createElement("img", { className: "logo high-quality", src: this.buildImageUrl(item, "Logo", undefined, serverAddress, 40), alt: item.Name, loading: "eager", }); - const logoContainer = SlideUtils.createElement("div", { className: "logo-container", }); logoContainer.appendChild(logo); - const featuredContent = SlideUtils.createElement( "div", - { - className: "featured-content", - }, + { className: "featured-content" }, title, ); - const plotElement = SlideUtils.createElement( "div", { className: "plot" }, item.Overview || "", ); - const plotContainer = SlideUtils.createElement("div", { className: "plot-container", }); plotContainer.appendChild(plotElement); - + const posterContainer = SlideUtils.createElement("div", { + className: "poster-container", + }); + if (item.ImageTags && item.ImageTags.Primary) { + const poster = SlideUtils.createElement("img", { + className: "poster high-quality", + src: this.buildImageUrl(item, "Primary", undefined, serverAddress, 70), + alt: item.Name, + loading: "eager", + }); + posterContainer.appendChild(poster); + } else { + posterContainer.style.display = "none"; + } + const taglineText = + Array.isArray(item.Taglines) && item.Taglines.length + ? item.Taglines[0] + : ""; + const taglineElement = SlideUtils.createElement( + "div", + { className: "tagline" }, + taglineText, + ); + if (!taglineText) taglineElement.style.display = "none"; const gradientOverlay = SlideUtils.createElement("div", { className: "gradient-overlay", }); - const infoContainer = SlideUtils.createElement("div", { className: "info-container", }); - const ratingInfo = this.createRatingInfo(item); infoContainer.appendChild(ratingInfo); - const genreElement = SlideUtils.createElement("div", { className: "genre", }); const genres = SlideUtils.buildGenres(item.Genres); if (genres) genreElement.appendChild(genres); else genreElement.style.display = "none"; - const buttonContainer = SlideUtils.createElement("div", { className: "button-container", }); - const playButton = this.createPlayButton(itemId); const detailButton = this.createDetailButton(itemId); const favoriteButton = this.createFavoriteButton(item); - + detailButton.appendChild( + SlideUtils.createElement( + "span", + { className: "action-label" }, + LocalizationUtils.getLocalizedString("Details", "Details"), + ), + ); + favoriteButton.appendChild( + SlideUtils.createElement( + "span", + { className: "action-label" }, + LocalizationUtils.getLocalizedString("Favorite", "Favorite"), + ), + ); if (isMarqueeLayout() || isPlateLayout()) { buttonContainer.append(playButton, detailButton, favoriteButton); } else { buttonContainer.append(detailButton, playButton, favoriteButton); } - const content = SlideUtils.createElement("div", { className: "slide-content", }); - if (isMarqueeLayout()) { - - const rail = SlideUtils.createElement("div", { - className: "spec-rail", - }); + const rail = SlideUtils.createElement("div", { className: "spec-rail" }); rail.appendChild( SlideUtils.createElement("span", { className: "spec-progress" }), ); - content.append( + posterContainer, logoContainer, + taglineElement, rail, this.createSpecLine(item), plotContainer, buttonContainer, ); } else if (isPlateLayout()) { - const position = STATE.slideshow.itemIds.indexOf(itemId); const eyebrow = SlideUtils.createElement( "div", @@ -2380,7 +2036,6 @@ const SlideCreator = { ? `${LocalizationUtils.getLocalizedString("Featured", "Featured")} · ${String(position + 1).padStart(2, "0")} / ${String(STATE.slideshow.totalItems).padStart(2, "0")}` : LocalizationUtils.getLocalizedString("Featured", "Featured"), ); - const specs = this.createSpecTable(item); content.append(eyebrow, logoContainer, plotContainer); if (specs) content.appendChild(specs); @@ -2394,28 +2049,23 @@ const SlideCreator = { buttonContainer, ); } - slide.append(backdropContainer); - if (isPlateLayout()) { - slide.appendChild(SlideUtils.createElement("div", { className: "plate" })); + slide.appendChild( + SlideUtils.createElement("div", { className: "plate" }), + ); } - slide.append(gradientOverlay, featuredContent, content); - return { slide, videoId, hasLocalTrailer, trailerContainer }; }, - createSpecTable(item) { const table = SlideUtils.createElement("dl", { className: "plate-specs" }); - const rows = []; const add = (label, value) => { if (value !== null && value !== undefined && value !== "") { rows.push([label, value]); } }; - if (typeof item.CommunityRating === "number") { add( LocalizationUtils.getLocalizedString("Rating", "Rating"), @@ -2434,7 +2084,6 @@ const SlideCreator = { add(LocalizationUtils.getLocalizedString("Year", "Year"), String(year)); } } - if (item.ChildCount) { const seasonText = item.ChildCount <= 1 @@ -2453,7 +2102,6 @@ const SlideCreator = { hours ? `${hours}h ${minutes}m` : `${minutes}m`, ); } - if (Array.isArray(item.Genres) && item.Genres.length) { add( LocalizationUtils.getLocalizedString("Genres", "Genre"), @@ -2466,9 +2114,7 @@ const SlideCreator = { item.OfficialRating, ); } - rows.length = Math.min(rows.length, CONFIG.plateSpecRows); - rows.forEach(([label, value], index) => { const row = SlideUtils.createElement("div", { className: index === rows.length - 1 ? "spec-row is-last" : "spec-row", @@ -2481,7 +2127,6 @@ const SlideCreator = { ); table.appendChild(row); }); - for (let i = rows.length; i < CONFIG.plateSpecRows; i++) { const filler = SlideUtils.createElement("div", { className: "spec-row is-placeholder", @@ -2495,18 +2140,14 @@ const SlideCreator = { ); table.appendChild(filler); } - return table; }, - createSpecLine(item) { const line = SlideUtils.createElement("div", { className: "spec-line" }); - const parts = []; const push = (kind, value, html) => { if (value) parts.push([kind, value, Boolean(html)]); }; - if (typeof item.PremiereDate === "string") { const year = new Date(item.PremiereDate).getFullYear(); if (!Number.isNaN(year)) push("year", String(year)); @@ -2516,18 +2157,16 @@ const SlideCreator = { "rating", `` + item.CommunityRating.toFixed(1), - true, + !0, ); } if (typeof item.CriticRating === "number") { - const tomato = item.CriticRating < 60 ? CONFIG.IMAGE_SVG.rottenTomato : CONFIG.IMAGE_SVG.freshTomato; - push("critics", `${tomato}${item.CriticRating.toFixed(0)}%`, true); + push("critics", `${tomato}${item.CriticRating.toFixed(0)}%`, !0); } - if (item.ChildCount) { const seasonText = item.ChildCount <= 1 @@ -2538,17 +2177,14 @@ const SlideCreator = { ); push("runtime", `${item.ChildCount} ${seasonText}`); } else if (typeof item.RunTimeTicks === "number" && item.RunTimeTicks > 0) { - const totalMinutes = Math.round(item.RunTimeTicks / 600000000); const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; push("runtime", hours ? `${hours}h ${minutes}m` : `${minutes}m`); } - if (Array.isArray(item.Genres) && item.Genres.length) { push("genre", item.Genres.slice(0, 2).join(" · ")); } - parts.forEach(([kind, text, isHtml]) => { const attrs = { className: `spec-item spec-${kind}` }; if (isHtml) attrs.innerHTML = text; @@ -2556,7 +2192,6 @@ const SlideCreator = { SlideUtils.createElement("span", attrs, isHtml ? null : text), ); }); - if (typeof item.OfficialRating === "string" && item.OfficialRating) { line.appendChild( SlideUtils.createElement( @@ -2566,10 +2201,8 @@ const SlideCreator = { ), ); } - return line; }, - createRatingInfo(item) { const { CommunityRating: communityRating, @@ -2579,13 +2212,10 @@ const SlideCreator = { RunTimeTicks: runtime, ChildCount: seasonCount, } = item; - const miscInfo = SlideUtils.createElement("div", { className: "misc-info", }); - const fields = []; - if (typeof communityRating === "number") { fields.push( SlideUtils.createElement("div", { @@ -2596,7 +2226,6 @@ const SlideCreator = { }), ); } - if (typeof criticRating === "number") { const svgIcon = criticRating < 60 @@ -2609,27 +2238,20 @@ const SlideCreator = { }), ); } - if (typeof premiereDate === "string") { const year = new Date(premiereDate).getFullYear(); if (!Number.isNaN(year)) { fields.push( - SlideUtils.createElement( - "div", - { className: "date" }, - String(year), - ), + SlideUtils.createElement("div", { className: "date" }, String(year)), ); } } - if (typeof ageRating === "string" && ageRating) { fields.push( SlideUtils.createElement( "div", { className: "age-rating mediaInfoOfficialRating", - "aria-label": `Content rated ${ageRating}`, title: `Rating: ${ageRating}`, }, @@ -2637,7 +2259,6 @@ const SlideCreator = { ), ); } - if (seasonCount) { const seasonText = seasonCount <= 1 @@ -2671,17 +2292,13 @@ const SlideCreator = { ), ); } - fields.forEach((field, index) => { if (index > 0) miscInfo.appendChild(SlideUtils.createSeparator()); miscInfo.appendChild(field); }); - return miscInfo; }, - createPlayButton(itemId) { - const button = SlideUtils.createElement("button", { className: "detailButton btnPlay play-button", tabIndex: "0", @@ -2691,7 +2308,6 @@ const SlideCreator = { ApiUtils.playItem(itemId); }, }); - button.appendChild( SlideUtils.createElement( "span", @@ -2699,10 +2315,8 @@ const SlideCreator = { LocalizationUtils.getLocalizedString("Play", "Play"), ), ); - return button; }, - createDetailButton(itemId) { return SlideUtils.createElement("button", { className: "detailButton detail-button", @@ -2720,10 +2334,8 @@ const SlideCreator = { }, }); }, - createFavoriteButton(item) { - const isFavorite = item.UserData && item.UserData.IsFavorite === true; - + const isFavorite = item.UserData && item.UserData.IsFavorite === !0; const button = SlideUtils.createElement("button", { className: `favorite-button ${isFavorite ? "favorited" : ""}`, tabIndex: "0", @@ -2733,53 +2345,41 @@ const SlideCreator = { await ApiUtils.toggleFavorite(item.Id, button); }, }); - return button; }, - async createSlideForItemId(itemId) { try { if (STATE.slideshow.createdSlides[itemId]) { return document.querySelector(`.slide[data-item-id="${itemId}"]`); } - const container = SlideUtils.getOrCreateSlidesContainer(); - const item = STATE.slideshow.loadedItems[itemId]; if (!item || !item.Id) { console.warn(`Slideshow: no item data for ${itemId}, skipping slide.`); return null; } - const slideParts = this.createSlideElement( item, item.Type === "Movie" ? "Movie" : "TV Show", ); if (!slideParts) return null; - const { slide, videoId, hasLocalTrailer, trailerContainer } = slideParts; - container.appendChild(slide); - STATE.slideshow.createdSlides[itemId] = true; - + STATE.slideshow.createdSlides[itemId] = !0; STATE.slideshow.slideVideoIds[itemId] = videoId || null; - if (hasLocalTrailer) { const trailerUrl = await ApiUtils.fetchLocalTrailerUrl(itemId); - if (trailerUrl && document.getElementById(`trailer-${itemId}`)) { const video = SlideUtils.createElement("video", { className: "local-trailer", src: trailerUrl, - muted: "", playsinline: "", preload: "metadata", disablepictureinpicture: "", tabIndex: "-1", }); - video.muted = true; - + video.muted = !0; video.addEventListener("playing", () => SlideshowManager.onTrailerPlaying(itemId, trailerContainer), ); @@ -2793,22 +2393,18 @@ const SlideCreator = { trailerContainer, ), ); - const host = document.getElementById(`yt-player-${itemId}`); host?.replaceChildren(video); STATE.slideshow.players[itemId] = createLocalPlayer(video); STATE.slideshow.slideVideoIds[itemId] = `local:${itemId}`; } else { - STATE.slideshow.slideVideoIds[itemId] = null; } } else if (videoId) { const startTime = await ApiUtils.getSkipSegments(videoId); - loadYouTubeAPI().then((YT) => { if (!YT) return; if (!document.getElementById(`trailer-${itemId}`)) return; - STATE.slideshow.players[itemId] = new YT.Player( `yt-player-${itemId}`, { @@ -2832,7 +2428,6 @@ const SlideCreator = { trailerContainer, ), onReady: (e) => e.target.mute(), - onError: (e) => SlideshowManager.onPlayerError(e, itemId, trailerContainer), }, @@ -2840,7 +2435,6 @@ const SlideCreator = { ); }); } - return slide; } catch (error) { console.error("Error creating slide for item:", error, itemId); @@ -2848,12 +2442,9 @@ const SlideCreator = { } }, }; - const SlideshowManager = { - createPaginationDots() { const container = SlideUtils.getOrCreateSlidesContainer(); - let dotsContainer = container.querySelector(".dots-container"); if (!dotsContainer) { dotsContainer = SlideUtils.createElement("div", { @@ -2861,31 +2452,23 @@ const SlideshowManager = { }); container.appendChild(dotsContainer); } - dotsContainer.replaceChildren(); - const count = Math.min(STATE.slideshow.totalItems, CONFIG.maxDots); - dotsContainer.style.display = count > 1 ? "" : "none"; - for (let i = 0; i < count; i++) { dotsContainer.appendChild( SlideUtils.createElement("span", { className: "dot", "data-index": i }), ); } - this.updateDots(); }, - updateDots() { const container = SlideUtils.getOrCreateSlidesContainer(); const dots = container.querySelectorAll(".dot"); const numDots = dots.length; if (!numDots) return; - const currentIndex = STATE.slideshow.currentSlideIndex; const totalItems = STATE.slideshow.totalItems; - const activeDotIndex = totalItems <= numDots ? currentIndex @@ -2893,102 +2476,77 @@ const SlideshowManager = { numDots - 1, Math.floor((currentIndex / totalItems) * numDots), ); - dots.forEach((dot, index) => { dot.classList.toggle("active", index === activeDotIndex); }); }, - restartProgress(slide) { if (!isMarqueeLayout()) return; - const bar = slide?.querySelector(".spec-progress"); if (!bar) return; - bar.style.transition = "none"; bar.style.width = "0%"; void bar.offsetWidth; - const running = !STATE.slideshow.isPaused && !STATE.slideshow.hoverHeld && !STATE.slideshow.isVideoPlaying; - if (!running) return; - bar.style.transition = `width ${CONFIG.shuffleInterval}ms linear`; bar.style.width = "100%"; }, - setProgressRunning(running) { if (!isMarqueeLayout()) return; - const bar = document.querySelector(".slide.active .spec-progress"); if (!bar) return; - if (!running) { - const current = getComputedStyle(bar).width; bar.style.transition = "none"; bar.style.width = current; return; } - const container = bar.parentElement; if (!container) return; const done = bar.getBoundingClientRect().width; const total = container.getBoundingClientRect().width || 1; - const remaining = Math.max( - 0, - CONFIG.shuffleInterval * (1 - done / total), - ); + const remaining = Math.max(0, CONFIG.shuffleInterval * (1 - done / total)); bar.style.transition = `width ${remaining}ms linear`; bar.style.width = "100%"; }, - repositionChrome() { const container = document.getElementById("slides-container"); if (!container) return; - const slide = container.querySelector(".slide.active") || container.querySelector(".slide"); if (slide) this.positionDots(slide, container); }, - watchContentHeight(container) { if (typeof ResizeObserver === "undefined") return; - const content = container.querySelector(".slide.active .slide-content"); if (!content) return; - if (!this.contentObserver) { this.contentObserver = new ResizeObserver(() => this.repositionChrome()); } this.contentObserver.disconnect(); this.contentObserver.observe(content); }, - positionDots(slide, container) { if (!isPlateLayout() && !isMarqueeLayout()) return; - const dots = container.querySelector(".dots-container"); const buttons = slide?.querySelector(".button-container"); if (!dots || !buttons) return; - const content = slide?.querySelector(".slide-content"); const containerTop = container.getBoundingClientRect().top; const anchor = Math.max( buttons.getBoundingClientRect().bottom, content ? content.getBoundingClientRect().bottom : 0, ); - const dotsHeight = dots.getBoundingClientRect().height || 6; const rows = document.querySelector(".homeSectionsContainer"); const rowsTop = rows ? rows.getBoundingClientRect().top - containerTop : Infinity; - const anchorTop = anchor - containerTop; const slack = rowsTop - anchorTop - dotsHeight; const gap = Math.max( @@ -2998,31 +2556,25 @@ const SlideshowManager = { Number.isFinite(slack) ? Math.floor(slack / 2) : Infinity, ), ); - const top = Math.round(anchorTop + gap); dots.style.top = `${top}px`; dots.style.bottom = "auto"; - container.querySelectorAll(".arrow").forEach((arrow) => { arrow.style.top = `${top - Math.round(arrow.offsetHeight / 2) + 3}px`; arrow.style.bottom = "auto"; arrow.style.transform = "none"; }); }, - toggleMute() { STATE.slideshow.isMuted = !STATE.slideshow.isMuted; - const btnIcon = document.querySelector(".volume-toggle i"); if (btnIcon) btnIcon.textContent = STATE.slideshow.isMuted ? "volume_off" : "volume_up"; - const currentId = STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex]; const player = STATE.slideshow.players[currentId]; - if (player && typeof player.setVolume === "function") { if (STATE.slideshow.isMuted) { player.mute(); @@ -3032,44 +2584,35 @@ const SlideshowManager = { } } }, - async updateCurrentSlide(index) { if (STATE.slideshow.isTransitioning) return; - if (STATE.slideshow.slideInterval) STATE.slideshow.slideInterval.stop(); this.clearSlideTimers(); - - STATE.slideshow.isTransitioning = true; + STATE.slideshow.isTransitioning = !0; const container = SlideUtils.getOrCreateSlidesContainer(); index = Math.max(0, Math.min(index, STATE.slideshow.totalItems - 1)); const currentItemId = STATE.slideshow.itemIds[index]; - this.resetTrailerState(currentItemId); - let currentSlide = document.querySelector( `.slide[data-item-id="${currentItemId}"]`, ); if (!currentSlide) currentSlide = await SlideCreator.createSlideForItemId(currentItemId); - if (!currentSlide) { console.warn(`Slideshow: could not build slide for ${currentItemId}.`); - STATE.slideshow.isTransitioning = false; + STATE.slideshow.isTransitioning = !1; STATE.slideshow.currentSlideIndex = index; if (!STATE.slideshow.isPaused && STATE.slideshow.slideInterval) { STATE.slideshow.slideInterval.restart(); } return; } - const prevVisible = container.querySelector(".slide.active"); if (prevVisible) prevVisible.classList.remove("active"); - currentSlide.classList.add("active"); if (!STATE.slideshow.firstSlideShown) { - STATE.slideshow.firstSlideShown = true; + STATE.slideshow.firstSlideShown = !0; mark("first-slide-visible"); - const firstBackdrop = currentSlide.querySelector(".backdrop"); if (firstBackdrop) { if (firstBackdrop.complete) mark("first-backdrop-painted"); @@ -3077,7 +2620,7 @@ const SlideshowManager = { firstBackdrop.addEventListener( "load", () => mark("first-backdrop-painted"), - { once: true }, + { once: !0 }, ); } } @@ -3085,10 +2628,8 @@ const SlideshowManager = { currentSlide.querySelector(".backdrop")?.classList.add("animate"); currentSlide.querySelector(".logo")?.classList.add("animate"); } - STATE.slideshow.currentSlideIndex = index; this.persistSessionIndex(index); - if (isPlateLayout()) { container.style.setProperty( "--plate", @@ -3100,17 +2641,14 @@ const SlideshowManager = { CONFIG.plateAccentFallback, ); } - this.updateDots(); this.positionDots(currentSlide, container); this.restartProgress(currentSlide); PageBackdrop.update(currentItemId); this.preloadAdjacentSlides(index); this.pruneSlideCache(); - const videoId = STATE.slideshow.slideVideoIds[currentItemId]; const canPlayTrailer = CONFIG.enableTrailers && Boolean(videoId); - if (canPlayTrailer) { STATE.slideshow.trailerStartTimer = setTimeout(() => { this.startTrailer(index, currentItemId, fallbackToTimer); @@ -3118,24 +2656,21 @@ const SlideshowManager = { } else { fallbackToTimer(); } - function fallbackToTimer() { if (!STATE.slideshow.isPaused && STATE.slideshow.slideInterval) { STATE.slideshow.slideInterval.restart(); } } - const logoImage = currentSlide.querySelector(".logo"); if (logoImage && !logoImage.complete) { logoImage.addEventListener( "load", () => this.positionDots(currentSlide, container), - { once: true }, + { once: !0 }, ); } - setTimeout(() => { - STATE.slideshow.isTransitioning = false; + STATE.slideshow.isTransitioning = !1; this.positionDots(currentSlide, container); this.watchContentHeight(container); if (prevVisible && CONFIG.slideAnimationEnabled) { @@ -3144,22 +2679,16 @@ const SlideshowManager = { } }, CONFIG.fadeTransitionDuration); }, - startTrailer(index, itemId, resumeTimer) { - if (STATE.slideshow.currentSlideIndex !== index) return; - if (STATE.slideshow.isPaused || !VisibilityObserver.wasVisible) { - return; } - const player = STATE.slideshow.players[itemId]; if (!player || typeof player.playVideo !== "function") { resumeTimer(); return; } - try { if (STATE.slideshow.isMuted) { player.mute(); @@ -3174,7 +2703,6 @@ const SlideshowManager = { resumeTimer(); return; } - clearTimeout(STATE.slideshow.trailerWatchdog); STATE.slideshow.trailerWatchdog = setTimeout(() => { if ( @@ -3188,17 +2716,14 @@ const SlideshowManager = { } }, CONFIG.trailerPlaybackWatchdogMs); }, - clearSlideTimers() { clearTimeout(STATE.slideshow.trailerStartTimer); clearTimeout(STATE.slideshow.trailerWatchdog); STATE.slideshow.trailerStartTimer = null; STATE.slideshow.trailerWatchdog = null; }, - resetTrailerState(keepItemId) { - STATE.slideshow.isVideoPlaying = false; - + STATE.slideshow.isVideoPlaying = !1; if (keepItemId) { const incoming = document.querySelector( `.slide[data-item-id="${keepItemId}"]`, @@ -3209,7 +2734,6 @@ const SlideshowManager = { ?.querySelector(".plot-container") ?.classList.remove("with-video"); } - Object.keys(STATE.slideshow.players).forEach((itemId) => { if (itemId === keepItemId) return; const player = STATE.slideshow.players[itemId]; @@ -3218,11 +2742,8 @@ const SlideshowManager = { player.pauseVideo(); if (typeof player.seekTo === "function") player.seekTo(0); } - } catch (error) { - - } + } catch (error) {} }); - document.querySelectorAll("#slides-container .slide").forEach((slide) => { if (slide.getAttribute("data-item-id") === keepItemId) return; slide.querySelector(".video-container")?.classList.remove("active"); @@ -3230,56 +2751,42 @@ const SlideshowManager = { slide.querySelector(".plot-container")?.classList.remove("with-video"); }); }, - async preloadAdjacentSlides(currentIndex) { const totalItems = STATE.slideshow.totalItems; if (!totalItems) return; - const preloadCount = CONFIG.preloadCount; - const nextIndex = (currentIndex + 1) % totalItems; const itemId = STATE.slideshow.itemIds[nextIndex]; - await SlideCreator.createSlideForItemId(itemId); - if (preloadCount > 1) { const prevIndex = (currentIndex - 1 + totalItems) % totalItems; const prevItemId = STATE.slideshow.itemIds[prevIndex]; - SlideCreator.createSlideForItemId(prevItemId); } }, - nextSlide() { const currentIndex = STATE.slideshow.currentSlideIndex; const totalItems = STATE.slideshow.totalItems; const nextIndex = (currentIndex + 1) % totalItems; this.updateCurrentSlide(nextIndex); }, - prevSlide() { const currentIndex = STATE.slideshow.currentSlideIndex; const totalItems = STATE.slideshow.totalItems; - const prevIndex = (currentIndex - 1 + totalItems) % totalItems; - this.updateCurrentSlide(prevIndex); }, - pruneSlideCache() { const currentIndex = STATE.slideshow.currentSlideIndex; const totalItems = STATE.slideshow.totalItems; const keepRange = 2; - Object.keys(STATE.slideshow.createdSlides).forEach((itemId) => { const index = STATE.slideshow.itemIds.indexOf(itemId); if (index === -1) return; - const linear = Math.abs(index - currentIndex); const distance = totalItems ? Math.min(linear, totalItems - linear) : linear; - if (distance > keepRange) { if (STATE.slideshow.players[itemId]) { try { @@ -3291,31 +2798,24 @@ const SlideshowManager = { } delete STATE.slideshow.players[itemId]; } - const slide = document.querySelector( `.slide[data-item-id="${itemId}"]`, ); if (slide) slide.remove(); - delete STATE.slideshow.createdSlides[itemId]; delete STATE.slideshow.slideVideoIds[itemId]; } }); }, - togglePause() { STATE.slideshow.isPaused = !STATE.slideshow.isPaused; - const paused = STATE.slideshow.isPaused; - document .getElementById("slides-container") ?.classList.toggle("is-paused", paused); - const currentId = STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex]; const player = STATE.slideshow.players[currentId]; - if (paused) { STATE.slideshow.slideInterval?.pause(); this.clearSlideTimers(); @@ -3323,20 +2823,17 @@ const SlideshowManager = { STATE.slideshow.slideInterval?.resume(); } this.setProgressRunning(!paused); - const pauseButton = document.querySelector(".pause-button"); if (pauseButton) { const label = paused ? LocalizationUtils.getLocalizedString("Play", "Play") : LocalizationUtils.getLocalizedString("ButtonPause", "Pause"); - pauseButton.innerHTML = ``; pauseButton.setAttribute("aria-label", label); pauseButton.setAttribute("title", label); } - if (paused) { if (player && typeof player.pauseVideo === "function") { player.pauseVideo(); @@ -3345,33 +2842,24 @@ const SlideshowManager = { player.playVideo(); } }, - SESSION_ORDER_KEY: "slideshowpure_order", - applySessionOrder(items) { - if (!CONFIG.rememberOrderForSession) { return SlideUtils.mixTypesInHead( SlideUtils.shuffleArray(items), CONFIG.mixTypesInFirst, ); } - try { const stored = JSON.parse( sessionStorage.getItem(this.SESSION_ORDER_KEY) || "null", ); - if (Array.isArray(stored?.ids) && stored.ids.length) { const byId = new Map(items.map((item) => [item.Id, item])); - const ordered = stored.ids - .map((id) => byId.get(id)) - .filter(Boolean); - + const ordered = stored.ids.map((id) => byId.get(id)).filter(Boolean); const known = new Set(ordered.map((item) => item.Id)); const added = items.filter((item) => !known.has(item.Id)); const result = ordered.concat(added); - if (result.length) { STATE.slideshow.resumeIndex = Math.min( Math.max(0, stored.index || 0), @@ -3381,10 +2869,7 @@ const SlideshowManager = { return result; } } - } catch (error) { - - } - + } catch (error) {} const shuffled = SlideUtils.mixTypesInHead( SlideUtils.shuffleArray(items), CONFIG.mixTypesInFirst, @@ -3392,7 +2877,6 @@ const SlideshowManager = { this.persistSessionOrder(shuffled, 0); return shuffled; }, - persistSessionOrder(items, index) { if (!CONFIG.rememberOrderForSession) return; try { @@ -3400,11 +2884,8 @@ const SlideshowManager = { this.SESSION_ORDER_KEY, JSON.stringify({ ids: items.map((item) => item.Id), index }), ); - } catch (error) { - - } + } catch (error) {} }, - persistSessionIndex(index) { if (!CONFIG.rememberOrderForSession) return; try { @@ -3414,100 +2895,80 @@ const SlideshowManager = { if (!stored?.ids) return; stored.index = index; sessionStorage.setItem(this.SESSION_ORDER_KEY, JSON.stringify(stored)); - } catch (error) { - - } + } catch (error) {} }, - initHoverPause() { if (!CONFIG.pauseOnHover) return; - const container = SlideUtils.getOrCreateSlidesContainer(); - container.addEventListener("pointerenter", (event) => { if (event.pointerType === "touch") return; - STATE.slideshow.hoverHeld = true; - + STATE.slideshow.hoverHeld = !0; STATE.slideshow.slideInterval?.pause(); this.clearSlideTimers(); - this.setProgressRunning(false); + this.setProgressRunning(!1); }); - container.addEventListener("pointerleave", (event) => { if (event.pointerType === "touch") return; if (!STATE.slideshow.hoverHeld) return; - STATE.slideshow.hoverHeld = false; - + STATE.slideshow.hoverHeld = !1; if ( !STATE.slideshow.isPaused && !STATE.slideshow.isVideoPlaying && VisibilityObserver.wasVisible ) { STATE.slideshow.slideInterval?.resume(); - - this.setProgressRunning(true); + this.setProgressRunning(!0); } }); }, - initTouchEvents() { const container = SlideUtils.getOrCreateSlidesContainer(); let touchStartX = 0; let touchEndX = 0; - container.addEventListener( "touchstart", (e) => { touchStartX = e.changedTouches[0].screenX; }, - { passive: true }, + { passive: !0 }, ); - container.addEventListener( "touchend", (e) => { touchEndX = e.changedTouches[0].screenX; this.handleSwipe(touchStartX, touchEndX); }, - { passive: true }, + { passive: !0 }, ); }, - handleSwipe(startX, endX) { const diff = endX - startX; - if (Math.abs(diff) < CONFIG.minSwipeDistance) { return; } - if (diff > 0) { this.prevSlide(); } else { this.nextSlide(); } }, - initKeyboardEvents() { const container = SlideUtils.getOrCreateSlidesContainer(); - container.setAttribute("tabindex", "-1"); container.addEventListener("mousedown", () => { - container.focus({ preventScroll: true }); + container.focus({ preventScroll: !0 }); }); - document.addEventListener("focusin", (event) => { STATE.slideshow.containerFocused = container.contains(event.target); }); document.addEventListener("focusout", (event) => { if (!container.contains(event.relatedTarget)) { - STATE.slideshow.containerFocused = false; + STATE.slideshow.containerFocused = !1; } }); - document.addEventListener("keydown", (event) => { if (!STATE.slideshow.containerFocused) return; if (!VisibilityObserver.wasVisible) return; - const active = document.activeElement; if ( active && @@ -3517,11 +2978,11 @@ const SlideshowManager = { ) { return; } - - const buttonRow = container.querySelector(".slide.active .button-container"); + const buttonRow = container.querySelector( + ".slide.active .button-container", + ); const focusedButton = buttonRow && buttonRow.contains(active) ? active : null; - switch (event.key) { case "ArrowRight": { const next = focusedButton?.nextElementSibling; @@ -3530,7 +2991,6 @@ const SlideshowManager = { event.preventDefault(); break; } - case "ArrowLeft": { const previous = focusedButton?.previousElementSibling; if (previous) previous.focus(); @@ -3538,69 +2998,54 @@ const SlideshowManager = { event.preventDefault(); break; } - case " ": this.togglePause(); event.preventDefault(); break; - case "Enter": - if (!focusedButton && active === container) { container.querySelector(".slide.active .play-button")?.click(); event.preventDefault(); } break; - case "Escape": container.blur(); - STATE.slideshow.containerFocused = false; + STATE.slideshow.containerFocused = !1; break; } }); }, - setTrailerVisible(itemId, trailerContainer, on) { const slide = document.querySelector(`.slide[data-item-id="${itemId}"]`); trailerContainer?.classList.toggle("active", on); slide?.querySelector(".backdrop")?.classList.toggle("with-video", on); slide?.querySelector(".plot-container")?.classList.toggle("with-video", on); }, - onTrailerPlaying(itemId, trailerContainer) { - const currentItemId = STATE.slideshow.itemIds[STATE.slideshow.currentSlideIndex]; if (itemId !== currentItemId) { try { STATE.slideshow.players[itemId]?.pauseVideo(); - } catch (error) { - - } - this.setTrailerVisible(itemId, trailerContainer, false); + } catch (error) {} + this.setTrailerVisible(itemId, trailerContainer, !1); return; } - clearTimeout(STATE.slideshow.trailerWatchdog); STATE.slideshow.trailerWatchdog = null; - - STATE.slideshow.isVideoPlaying = true; - this.setTrailerVisible(itemId, trailerContainer, true); + STATE.slideshow.isVideoPlaying = !0; + this.setTrailerVisible(itemId, trailerContainer, !0); STATE.slideshow.slideInterval?.stop(); }, - onTrailerEnded(itemId, trailerContainer) { - STATE.slideshow.isVideoPlaying = false; - this.setTrailerVisible(itemId, trailerContainer, false); + STATE.slideshow.isVideoPlaying = !1; + this.setTrailerVisible(itemId, trailerContainer, !1); this.nextSlide(); }, - onPlayerStateChange(event, itemId, trailerContainer) { if (!document.querySelector(`.slide[data-item-id="${itemId}"]`)) return; - const YTRef = window.YT; if (!YTRef || !YTRef.PlayerState) return; - if (event.data === YTRef.PlayerState.PLAYING) { this.onTrailerPlaying(itemId, trailerContainer); } else if (event.data === YTRef.PlayerState.ENDED) { @@ -3609,43 +3054,34 @@ const SlideshowManager = { event.data === YTRef.PlayerState.PAUSED || event.data === YTRef.PlayerState.CUED ) { - STATE.slideshow.isVideoPlaying = false; + STATE.slideshow.isVideoPlaying = !1; } }, - onPlayerError(event, itemId, trailerContainer) { console.warn( `Trailer for ${itemId} failed with YouTube error ${event?.data}; continuing without it.`, ); - clearTimeout(STATE.slideshow.trailerWatchdog); STATE.slideshow.trailerWatchdog = null; - STATE.slideshow.isVideoPlaying = false; - + STATE.slideshow.isVideoPlaying = !1; const slide = document.querySelector(`.slide[data-item-id="${itemId}"]`); trailerContainer?.classList.remove("active"); slide?.querySelector(".backdrop")?.classList.remove("with-video"); slide?.querySelector(".plot-container")?.classList.remove("with-video"); - STATE.slideshow.slideVideoIds[itemId] = null; - if (!STATE.slideshow.isPaused && STATE.slideshow.slideInterval) { STATE.slideshow.slideInterval.restart(); } }, - async loadSlideshowData() { try { - STATE.slideshow.isLoading = true; - + STATE.slideshow.isLoading = !0; mark("items-fetch-start"); - const listed = await ApiUtils.fetchListEntries(); let items = listed.ids.length || listed.filters.length ? await ApiUtils.fetchListedItems(listed) : await ApiUtils.fetchItemsFromServer(); - if (!items.length && (listed.ids.length || listed.filters.length)) { console.warn( "Slideshow: list.txt matched no items; using library items.", @@ -3653,18 +3089,14 @@ const SlideshowManager = { items = await ApiUtils.fetchItemsFromServer(); } mark("items-fetched"); - items = this.applySessionOrder(items); - STATE.slideshow.loadedItems = {}; items.forEach((item) => { STATE.slideshow.loadedItems[item.Id] = item; }); - const itemIds = items.map((item) => item.Id); STATE.slideshow.itemIds = itemIds; STATE.slideshow.totalItems = itemIds.length; - if (!itemIds.length) { console.warn( "Slideshow: no items with a Logo image were returned; nothing to show.", @@ -3672,9 +3104,7 @@ const SlideshowManager = { SlideUtils.getOrCreateSlidesContainer().style.display = "none"; return; } - this.createPaginationDots(); - STATE.slideshow.slideInterval = new SlideTimer(() => { if ( !STATE.slideshow.isPaused && @@ -3685,40 +3115,33 @@ const SlideshowManager = { } }, CONFIG.shuffleInterval); STATE.slideshow.slideInterval.stop(); - await this.updateCurrentSlide(STATE.slideshow.resumeIndex || 0); } catch (error) { console.error("Error loading slideshow data:", error); } finally { - STATE.slideshow.isLoading = false; + STATE.slideshow.isLoading = !1; } }, }; - const LayoutSync = { - attached: false, + attached: !1, published: {}, - publish(name, value) { - if (this.published[name] === value) return false; + if (this.published[name] === value) return !1; this.published[name] = value; document.documentElement.style.setProperty(name, value); - return true; + return !0; }, - update() { const page = document.querySelector(".page"); if (!page) return; - const offset = Math.max( 0, Math.round( page.getBoundingClientRect().top + document.scrollingElement.scrollTop, ), ); - let moved = this.publish("--slideshow-page-offset", `${offset}px`); - const rows = document.querySelector(".homeSectionsContainer"); const stage = document.getElementById("slides-container"); if (rows && stage) { @@ -3729,59 +3152,46 @@ const LayoutSync = { moved = this.publish("--slideshow-rows-top", `${rowsTop}px`) || moved; } } - if (moved) SlideshowManager.repositionChrome(); }, - init() { this.update(); if (this.attached) return; - this.attached = true; - - window.addEventListener("resize", () => this.update(), { passive: true }); + this.attached = !0; + window.addEventListener("resize", () => this.update(), { passive: !0 }); window.addEventListener("hashchange", () => this.update()); - document.fonts?.ready?.then(() => this.update()).catch(() => {}); - const ready = () => document.querySelector(".page") && document.querySelector(".homeSectionsContainer"); - if (ready()) { this.observeRows(); return; } - const observer = new MutationObserver(() => { if (!ready()) return; this.update(); this.observeRows(); observer.disconnect(); }); - observer.observe(document.body, { childList: true, subtree: true }); + observer.observe(document.body, { childList: !0, subtree: !0 }); setTimeout(() => observer.disconnect(), 30000); }, - observeRows() { if (this.rowsObserver || typeof ResizeObserver === "undefined") return; - const rows = document.querySelector(".homeSectionsContainer"); if (!rows) return; - this.rowsObserver = new ResizeObserver(() => this.update()); this.rowsObserver.observe(rows); - const page = document.querySelector(".page"); if (page) this.rowsObserver.observe(page); }, }; - const SettingsPanel = { element: null, controls: new Map(), - isOpen: false, - pausedByPanel: false, - + isOpen: !1, + pausedByPanel: !1, FIELDS: [ { key: "layout", @@ -3794,7 +3204,6 @@ const SettingsPanel = { ], }, { - key: "contentMode", label: "Display", type: "choice", @@ -3811,7 +3220,6 @@ const SettingsPanel = { if (!movies && !series) return "all"; if (movies && !series) return "movies"; if (!movies && series) return "series"; - return "even"; }, write: (value) => { @@ -3823,12 +3231,7 @@ const SettingsPanel = { return { maxMovies: 0, maxSeries: 0 }; }, }, - { - - key: "libraries", - label: "Libraries", - type: "multichoice", - }, + { key: "libraries", label: "Libraries", type: "multichoice" }, { key: "shuffleInterval", label: "Slide duration", @@ -3839,8 +3242,14 @@ const SettingsPanel = { format: (value) => `${Math.round(value / 1000)}s`, }, { key: "slideAnimationEnabled", label: "Motion effects", type: "switch" }, + { key: "syncPageBackdrop", label: "Sync page backdrop", type: "switch" }, { key: "pauseOnHover", label: "Pause on hover", type: "switch" }, { key: "enableTrailers", label: "Trailers", type: "switch" }, + { + key: "allowTrailersOnTouch", + label: "Trailers on phones", + type: "switch", + }, { key: "trailerVolume", label: "Trailer volume", @@ -3850,11 +3259,7 @@ const SettingsPanel = { step: 5, format: (value) => `${value}%`, }, - { - key: "trailerLibraries", - label: "Trailers from", - type: "multichoice", - }, + { key: "trailerLibraries", label: "Trailers from", type: "multichoice" }, { key: "rememberOrderForSession", label: "Resume where I left off", @@ -3862,17 +3267,14 @@ const SettingsPanel = { }, { key: "respectDataSaver", label: "Respect data saver", type: "switch" }, ], - build() { if (this.element) return this.element; - const panel = SlideUtils.createElement("div", { className: "ss-settings", role: "dialog", "aria-label": "Slideshow settings", "aria-hidden": "true", }); - const header = SlideUtils.createElement("div", { className: "ss-settings-head", innerHTML: "

Slideshow

", @@ -3887,12 +3289,14 @@ const SettingsPanel = { }), ); panel.append(header); - - const body = SlideUtils.createElement("div", { className: "ss-settings-body" }); + const body = SlideUtils.createElement("div", { + className: "ss-settings-body", + }); this.FIELDS.forEach((field) => body.append(this.buildRow(field))); panel.append(body); - - const footer = SlideUtils.createElement("div", { className: "ss-settings-foot" }); + const footer = SlideUtils.createElement("div", { + className: "ss-settings-foot", + }); footer.append( SlideUtils.createElement("button", { className: "ss-settings-reset", @@ -3902,17 +3306,14 @@ const SettingsPanel = { }), ); panel.append(footer); - panel.addEventListener("keydown", (event) => { event.stopPropagation(); if (event.key === "Escape") this.close(); }); - document.body.append(panel); this.element = panel; return panel; }, - buildRow(field) { const locked = (field.locks || [field.key]).some((key) => lockedConfigKeys.has(key), @@ -3925,7 +3326,6 @@ const SettingsPanel = { textContent: field.label, }); row.append(label); - if (field.type === "switch") { const input = SlideUtils.createElement("input", { type: "checkbox", @@ -3936,7 +3336,6 @@ const SettingsPanel = { row.append(input); this.controls.set(field.key, input); } - if (field.type === "range") { const readout = SlideUtils.createElement("span", { className: "ss-set-value", @@ -3948,7 +3347,6 @@ const SettingsPanel = { max: String(field.max), step: String(field.step), disabled: locked, - oninput: (event) => { readout.textContent = field.format(Number(event.target.value)); }, @@ -3960,7 +3358,6 @@ const SettingsPanel = { input._readout = readout; input._format = field.format; } - if (field.type === "choice") { const group = SlideUtils.createElement("div", { className: "ss-choice" }); const buttons = field.options.map(([value, text]) => { @@ -3979,17 +3376,14 @@ const SettingsPanel = { row.append(group); this.controls.set(field.key, { group, buttons }); } - if (field.type === "multichoice") { const group = SlideUtils.createElement("div", { className: "ss-multi" }); const boxes = new Map(); row.classList.add("is-stacked"); row.append(group); this.controls.set(field.key, { group, boxes }); - this.populateMultichoice(field, group, boxes, locked); } - if (locked) { row.append( SlideUtils.createElement("span", { @@ -4000,12 +3394,9 @@ const SettingsPanel = { } return row; }, - async populateMultichoice(field, group, boxes, locked) { if (boxes.size) return; - const views = await ApiUtils.fetchViews(); - if (!views.length) { group.append( SlideUtils.createElement("span", { @@ -4015,7 +3406,6 @@ const SettingsPanel = { ); return; } - views.forEach((view) => { const item = SlideUtils.createElement("label", { className: "ss-multi-item", @@ -4033,36 +3423,28 @@ const SettingsPanel = { group.append(item); boxes.set(view.Name, input); }); - this.sync(); }, - commitMultichoice(key) { const control = this.controls.get(key); const names = [...control.boxes] .filter(([, input]) => input.checked) .map(([name]) => name); - if (!names.length) { this.sync(); return; } - this.commit(key, names.length === control.boxes.size ? [] : names); }, - commit(key, value) { const field = this.FIELDS.find((entry) => entry.key === key); setConfig(field?.write ? field.write(value) : { [key]: value }); - this.sync(); }, - sync() { this.controls.forEach((control, key) => { const field = this.FIELDS.find((entry) => entry.key === key); const value = field?.read ? field.read() : CONFIG[key]; - if (control instanceof HTMLInputElement) { if (control.type === "checkbox") { control.checked = Boolean(value); @@ -4074,7 +3456,6 @@ const SettingsPanel = { } return; } - if (control.boxes) { const chosen = new Set((value || []).map(String)); control.boxes.forEach((input, name) => { @@ -4082,21 +3463,18 @@ const SettingsPanel = { }); return; } - control.buttons.forEach((button) => { const active = button.dataset.value === String(value); button.classList.toggle("is-active", active); button.setAttribute("aria-pressed", active ? "true" : "false"); }); }); - const volume = this.controls.get("trailerVolume"); if (volume instanceof HTMLInputElement) { const off = !CONFIG.enableTrailers; volume.disabled = off || lockedConfigKeys.has("trailerVolume"); volume.closest(".ss-set-row")?.classList.toggle("is-inactive", off); } - const trailerLibs = this.controls.get("trailerLibraries"); if (trailerLibs?.boxes) { const off = !CONFIG.enableTrailers; @@ -4109,56 +3487,45 @@ const SettingsPanel = { ?.classList.toggle("is-inactive", off); } }, - reset() { clearStoredConfig(); - applyConfig(CONFIG_SERVER_DEFAULTS, { trusted: true, label: "reset" }); + applyConfig(CONFIG_SERVER_DEFAULTS, { trusted: !0, label: "reset" }); resetSlideshowState(); bootstrap(); this.sync(); }, - open() { this.build(); this.sync(); - this.isOpen = true; + this.isOpen = !0; this.element.classList.add("is-open"); - - document - .getElementById("slides-container") - ?.classList.add("settings-open"); + document.getElementById("slides-container")?.classList.add("settings-open"); this.element.setAttribute("aria-hidden", "false"); - if (!STATE.slideshow.isPaused) { STATE.slideshow.slideInterval?.pause(); - this.pausedByPanel = true; + this.pausedByPanel = !0; } - this.element.querySelector(".ss-settings-close")?.focus(); - document.addEventListener("pointerdown", this.onOutsidePointer, true); + document.addEventListener("pointerdown", this.onOutsidePointer, !0); }, - close() { if (!this.element) return; - this.isOpen = false; + this.isOpen = !1; this.element.classList.remove("is-open"); document .getElementById("slides-container") ?.classList.remove("settings-open"); this.element.setAttribute("aria-hidden", "true"); - if (this.pausedByPanel) { if (!STATE.slideshow.isPaused) STATE.slideshow.slideInterval?.resume(); - this.pausedByPanel = false; + this.pausedByPanel = !1; } - document.removeEventListener("pointerdown", this.onOutsidePointer, true); + document.removeEventListener("pointerdown", this.onOutsidePointer, !0); document.querySelector(".ss-settings-toggle")?.focus(); }, - toggle() { this.isOpen ? this.close() : this.open(); }, - onOutsidePointer: (event) => { const panel = SettingsPanel.element; if (!panel || !SettingsPanel.isOpen) return; @@ -4167,10 +3534,8 @@ const SettingsPanel = { SettingsPanel.close(); }, }; - const initArrowNavigation = () => { const container = SlideUtils.getOrCreateSlidesContainer(); - const leftArrow = SlideUtils.createElement("div", { className: "arrow left-arrow", innerHTML: '', @@ -4183,7 +3548,6 @@ const initArrowNavigation = () => { SlideshowManager.prevSlide(); }, }); - const rightArrow = SlideUtils.createElement("div", { className: "arrow right-arrow", innerHTML: '', @@ -4196,7 +3560,6 @@ const initArrowNavigation = () => { SlideshowManager.nextSlide(); }, }); - const volumeBtn = SlideUtils.createElement("div", { className: "volume-toggle ss-chrome", innerHTML: 'volume_off', @@ -4206,7 +3569,6 @@ const initArrowNavigation = () => { SlideshowManager.toggleMute(); }, }); - const pauseButton = SlideUtils.createElement("div", { className: "pause-button ss-chrome", innerHTML: 'pause', @@ -4219,7 +3581,6 @@ const initArrowNavigation = () => { SlideshowManager.togglePause(); }, }); - const settingsBtn = SlideUtils.createElement("div", { className: "ss-settings-toggle ss-chrome", innerHTML: 'settings', @@ -4239,54 +3600,41 @@ const initArrowNavigation = () => { SettingsPanel.toggle(); }, }); - container.append(leftArrow, rightArrow, volumeBtn, pauseButton, settingsBtn); }; - const isTouchLayout = () => window.matchMedia(`(max-width: ${CONFIG.touchLayoutMaxWidth}px)`).matches; - const isPlateLayout = () => CONFIG.layout === "plate"; - const isMarqueeLayout = () => CONFIG.layout === "marquee"; - const slidesInit = async () => { if (STATE.slideshow.hasInitialized) { console.log("⚠️ Slideshow already initialized, skipping"); return; } - STATE.slideshow.hasInitialized = true; - + STATE.slideshow.hasInitialized = !0; try { console.log("🌟 Initializing Enhanced Jellyfin Slideshow"); - await SlideshowManager.loadSlideshowData(); - LayoutSync.init(); - SlideshowManager.initTouchEvents(); SlideshowManager.initHoverPause(); initArrowNavigation(); - if (!STATE.slideshow.listenersAttached) { - STATE.slideshow.listenersAttached = true; + STATE.slideshow.listenersAttached = !0; SlideshowManager.initKeyboardEvents(); initPageVisibilityHandler(); VisibilityObserver.init(); } else { VisibilityObserver.updateVisibility(); } - console.log("✅ Enhanced Jellyfin Slideshow initialized successfully"); } catch (error) { console.error("Error initializing slideshow:", error); - STATE.slideshow.hasInitialized = false; + STATE.slideshow.hasInitialized = !1; } }; - const initPageVisibilityHandler = () => { - let wasVideoPlayingBeforeHide = false; - + let wasVideoPlayingBeforeHide = !1; document.addEventListener("visibilitychange", () => { if (document.hidden) { console.log("Tab inactive - pausing slideshow and videos"); @@ -4301,7 +3649,7 @@ const initPageVisibilityHandler = () => { if (typeof player.pauseVideo === "function") { try { player.pauseVideo(); - STATE.slideshow.isVideoPlaying = false; + STATE.slideshow.isVideoPlaying = !1; } catch (e) { console.warn("Error pausing video on tab hide:", e); } @@ -4321,7 +3669,7 @@ const initPageVisibilityHandler = () => { if (typeof player.playVideo === "function") { try { player.playVideo(); - STATE.slideshow.isVideoPlaying = true; + STATE.slideshow.isVideoPlaying = !0; } catch (e) { console.warn("Error resuming video on tab show:", e); if (STATE.slideshow.slideInterval) { @@ -4334,12 +3682,11 @@ const initPageVisibilityHandler = () => { STATE.slideshow.slideInterval.start(); } } - wasVideoPlayingBeforeHide = false; + wasVideoPlayingBeforeHide = !1; } } }); }; - window.slideshowPure = { CONFIG, STATE, @@ -4359,10 +3706,8 @@ window.slideshowPure = { prevSlide: () => { SlideshowManager.prevSlide(); }, - reset: () => resetSlideshowState(), bootstrap: () => bootstrap(), - setConfig, clearConfig: clearStoredConfig, applyConfig,