|
1 | 1 | (function () { |
2 | 2 | 'use strict'; |
3 | 3 | let apiKey = null; |
| 4 | + let currentImageUrl = null; |
4 | 5 |
|
5 | 6 | async function waitForElement(selector) { |
6 | 7 | return new Promise(resolve => { |
|
21 | 22 | } catch (e) { console.error("TMDB Plugin: Settings failed", e); } |
22 | 23 | } |
23 | 24 |
|
| 25 | + const injectBaseStyles = () => { |
| 26 | + if (document.getElementById('tmdb-base-style')) return; |
| 27 | + const style = document.createElement('style'); |
| 28 | + style.id = 'tmdb-base-style'; |
| 29 | + style.innerHTML = ` |
| 30 | + #group-page::before, #group-page::after { |
| 31 | + content: ""; position: fixed; top: 0; left: 0; width: 100%; height: 100%; |
| 32 | + z-index: -1; background-size: cover; background-attachment: fixed; |
| 33 | + background-position: center; transition: opacity 1.2s ease-in-out; |
| 34 | + opacity: 0; pointer-events: none; |
| 35 | + } |
| 36 | + /* .tmdb-active controls the initial fade-in from Stash background */ |
| 37 | + .tmdb-active #group-page { background: transparent !important; } |
| 38 | + .tmdb-active #group-page .background-image-container { display: none !important; } |
| 39 | + .tmdb-active #group-page .detail-header, |
| 40 | + .tmdb-active #group-page .filtered-list-toolbar, |
| 41 | + .tmdb-active #group-page .card { |
| 42 | + background-color: transparent !important; box-shadow: none !important; |
| 43 | + } |
| 44 | + .tmdb-active #group-page .detail-body nav { border-bottom: none !important; } |
| 45 | + `; |
| 46 | + document.head.appendChild(style); |
| 47 | + }; |
| 48 | + |
| 49 | + let activeLayer = 'before'; // Keep track of which layer is currently visible |
| 50 | + |
24 | 51 | const updateBackdrop = async (tmdbUrl) => { |
25 | 52 | if (!apiKey) await getSettings(); |
26 | | - if (!apiKey) return; |
| 53 | + if (!apiKey || !tmdbUrl) return; |
27 | 54 |
|
28 | 55 | const idMatch = tmdbUrl.match(/(movie|tv|collection)\/(\d+)/); |
29 | 56 | if (!idMatch) return; |
|
34 | 61 | const data = await response.json(); |
35 | 62 |
|
36 | 63 | if (data.backdrops?.length > 0) { |
37 | | - const imageUrl = `https://image.tmdb.org/t/p/original${data.backdrops[Math.floor(Math.random() * data.backdrops.length)].file_path}`; |
| 64 | + const randomPath = data.backdrops[Math.floor(Math.random() * data.backdrops.length)].file_path; |
| 65 | + const imageUrl = `https://image.tmdb.org/t/p/original${randomPath}`; |
38 | 66 |
|
39 | | - // 1. Get or Create the style block |
40 | | - let styleBlock = document.getElementById('tmdb-dynamic-style'); |
41 | | - if (!styleBlock) { |
42 | | - styleBlock = document.createElement('style'); |
43 | | - styleBlock.id = 'tmdb-dynamic-style'; |
44 | | - document.head.appendChild(styleBlock); |
45 | | - } |
| 67 | + if (imageUrl === currentImageUrl && document.body.classList.contains('tmdb-active')) return; |
46 | 68 |
|
47 | | - // 2. Start Fade Out: Only the background layer |
48 | | - const styleBase = ` |
49 | | - #group-page { position: relative; min-height: 100vh; background: transparent !important; } |
50 | | - #group-page::before { |
51 | | - content: ""; |
52 | | - position: fixed; |
53 | | - top: 0; left: 0; width: 100%; height: 100%; |
54 | | - z-index: -1; |
55 | | - background-size: cover; |
56 | | - background-attachment: fixed; |
57 | | - background-position: center; |
58 | | - transition: opacity 0.8s ease-in-out; |
59 | | - } |
60 | | - `; |
61 | | - |
62 | | - // Set opacity to 0 on the existing background layer |
63 | | - const currentStyle = styleBlock.innerHTML; |
64 | | - styleBlock.innerHTML = styleBase + currentStyle + `#group-page::before { opacity: 0 !important; }`; |
| 69 | + const img = new Image(); |
| 70 | + img.src = imageUrl; |
| 71 | + await img.decode(); |
65 | 72 |
|
66 | | - // 3. Preload and Wait for fade out |
67 | | - await Promise.all([ |
68 | | - new Promise(resolve => setTimeout(resolve, 800)), |
69 | | - new Promise(resolve => { const img = new Image(); img.src = imageUrl; img.onload = resolve; }) |
70 | | - ]); |
| 73 | + let dynamicStyle = document.getElementById('tmdb-dynamic-image'); |
| 74 | + if (!dynamicStyle) { |
| 75 | + dynamicStyle = document.createElement('style'); |
| 76 | + dynamicStyle.id = 'tmdb-dynamic-image'; |
| 77 | + document.head.appendChild(dynamicStyle); |
| 78 | + } |
71 | 79 |
|
72 | | - // 4. Update image and Fade In |
73 | | - styleBlock.innerHTML = ` |
74 | | - ${styleBase} |
75 | | - #group-page::before { |
76 | | - background-image: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url("${imageUrl}"); |
77 | | - opacity: 1 !important; |
78 | | - } |
79 | | - #group-page .background-image-container { display: none !important; } |
80 | | - #group-page .detail-header, #group-page .filtered-list-toolbar, #group-page .card { |
81 | | - background-color: transparent !important; |
82 | | - box-shadow: none !important; |
83 | | - } |
84 | | - #group-page .detail-body nav { border-bottom: none !important; } |
85 | | - `; |
| 80 | + injectBaseStyles(); |
| 81 | + const isAlreadyActive = document.body.classList.contains('tmdb-active'); |
| 82 | + |
| 83 | + if (!isAlreadyActive) { |
| 84 | + // INITIAL LOAD |
| 85 | + dynamicStyle.innerHTML = ` |
| 86 | + #group-page::before { background-image: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url("${imageUrl}"); opacity: 1 !important; } |
| 87 | + #group-page::after { opacity: 0 !important; } |
| 88 | + `; |
| 89 | + document.body.classList.add('tmdb-active'); |
| 90 | + activeLayer = 'before'; |
| 91 | + } else { |
| 92 | + // SUBPAGE NAVIGATION (CROSS-FADE) |
| 93 | + const nextLayer = activeLayer === 'before' ? 'after' : 'before'; |
| 94 | + |
| 95 | + // We update the styles so the 'next' layer gets the new image and fades in, |
| 96 | + // while the 'current' layer fades out but KEEPS its old image during the transition. |
| 97 | + dynamicStyle.innerHTML = ` |
| 98 | + #group-page::${activeLayer} { background-image: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url("${currentImageUrl}"); opacity: 0 !important; } |
| 99 | + #group-page::${nextLayer} { background-image: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url("${imageUrl}"); opacity: 1 !important; } |
| 100 | + `; |
| 101 | + activeLayer = nextLayer; |
| 102 | + } |
| 103 | + |
| 104 | + currentImageUrl = imageUrl; |
| 105 | + } else { |
| 106 | + clearUI(); |
86 | 107 | } |
87 | | - } catch (e) { console.error("TMDB Plugin Error:", e); } |
| 108 | + } catch (e) { |
| 109 | + console.error("TMDB Plugin Error:", e); |
| 110 | + clearUI(); |
| 111 | + } |
88 | 112 | }; |
89 | 113 |
|
| 114 | + function clearUI() { |
| 115 | + document.body.classList.remove('tmdb-active'); |
| 116 | + currentImageUrl = null; |
| 117 | + const dynamic = document.getElementById('tmdb-dynamic-image'); |
| 118 | + if (dynamic) dynamic.remove(); |
| 119 | + } |
90 | 120 |
|
91 | 121 | async function updateDOM() { |
92 | 122 | const match = window.location.pathname.match(/\/groups\/(\d+)/); |
93 | | - if (!match) return; |
94 | | - |
95 | | - const id = match[1]; |
96 | | - // Wait for the specific ID shown in your HTML snippet |
| 123 | + if (!match) { clearUI(); return; } |
| 124 | + const groupId = match[1]; |
97 | 125 | await waitForElement('#group-page'); |
98 | | - |
99 | 126 | const gRes = await fetch('/graphql', { |
100 | 127 | method: 'POST', |
101 | 128 | headers: { 'Content-Type': 'application/json' }, |
102 | | - body: JSON.stringify({ |
103 | | - query: `query FindGroup($id: ID!) { findGroup(id: $id) { urls } }`, |
104 | | - variables: { id: id } |
105 | | - }) |
| 129 | + body: JSON.stringify({ query: `query FindGroup($id: ID!) { findGroup(id: $id) { urls } }`, variables: { id: groupId } }) |
106 | 130 | }); |
107 | | - const gData = await gRes.json(); |
108 | | - const urls = gData.data?.findGroup?.urls || []; |
| 131 | + const gResult = await gRes.json(); |
| 132 | + const urls = gResult.data?.findGroup?.urls || []; |
109 | 133 | const tmdbUrl = urls.find(u => u.toLowerCase().includes('themoviedb.org')); |
110 | | - |
111 | | - if (tmdbUrl) { |
112 | | - updateBackdrop(tmdbUrl); |
113 | | - } else { |
114 | | - const styleBlock = document.getElementById('tmdb-dynamic-style'); |
115 | | - if (styleBlock) styleBlock.remove(); |
116 | | - } |
| 134 | + if (tmdbUrl) { updateBackdrop(tmdbUrl); } else { clearUI(); } |
117 | 135 | } |
118 | 136 |
|
119 | | - const handlePathChange = () => { |
120 | | - if (window.location.pathname.match(/\/groups\/\d+/)) { |
121 | | - updateDOM(); |
122 | | - } |
123 | | - }; |
124 | | - |
125 | | - // MutationObserver to catch Stash's internal navigation |
126 | 137 | const observeUrlChange = () => { |
127 | 138 | let oldHref = document.location.href; |
128 | | - const body = document.querySelector("body"); |
129 | 139 | const observer = new MutationObserver(() => { |
130 | | - if (oldHref !== document.location.href) { |
131 | | - oldHref = document.location.href; |
132 | | - handlePathChange(); |
133 | | - } |
| 140 | + if (oldHref !== document.location.href) { oldHref = document.location.href; updateDOM(); } |
134 | 141 | }); |
135 | | - observer.observe(body, { childList: true, subtree: true }); |
| 142 | + observer.observe(document.body, { childList: true, subtree: true }); |
136 | 143 | }; |
137 | 144 |
|
138 | | - handlePathChange(); |
| 145 | + updateDOM(); |
139 | 146 | observeUrlChange(); |
140 | 147 | })(); |
0 commit comments