cleaned, fix test.yaml, add exceptions and checks in .py

This commit is contained in:
MLH
2024-12-10 01:00:50 +01:00
parent c11fd92123
commit 987144fc0c
10 changed files with 66 additions and 1769 deletions

View File

@ -1,13 +1,23 @@
from calendar import c
import os
import shutil
from tabnanny import check
import yaml
import re
import sys
# Global variable to track errors
errorList = {"copies": 0, "modifications": 0}
# Initialize results
results = {"copies": 0, "modifications": 0}
def loadConfig(configPath):
"""Load YAML configuration."""
try:
print(f"Loading configuration from {configPath}")
print(f"Loading configuration from {configPath} ...")
with open(configPath, 'r', encoding='utf-8') as file:
config = yaml.safe_load(file)
@ -15,10 +25,10 @@ def loadConfig(configPath):
if not config:
raise ValueError("Empty configuration file")
print("Configuration loaded successfully.")
print("Configuration loaded successfully.\n")
return config
except FileNotFoundError:
print(f"Error: Configuration file not found at {configPath}")
print(f"Error: Configuration file not found at ./{configPath}")
sys.exit(1)
except yaml.YAMLError as e:
print(f"Error parsing YAML configuration: {e}")
@ -30,11 +40,11 @@ def loadConfig(configPath):
def ensureDirectory(path):
"""Ensure that a directory exists."""
print(f"Checking/creating directory: {path}")
print(f"\nChecking/creating directory: {path}")
os.makedirs(path, exist_ok=True)
def copySources(config, destinationDirectory, results):
def copySources(config, destinationDirectory):
"""Copy files and folders according to copy rules."""
print("Starting source file/folder copy process...")
for rule in config.get('copy_rules', []):
@ -43,17 +53,30 @@ def copySources(config, destinationDirectory, results):
# Distinguish between sources with explicit target and without
if isinstance(source, dict):
sourcePath = source['source']
targetPath = os.path.join(destinationDirectory, source['target'])
#targetPath = os.path.join(destinationDirectory, source['target'])
checkTargetPath = source['target']
# Check for absolute paths in target and correct them if necessary
if os.path.isabs(checkTargetPath):
targetPath = os.path.normpath(os.path.join(destinationDirectory, checkTargetPath.lstrip('./')))
raise ValueError(f"Absolute path incorrect: Corrected {targetPath} to {targetPath}.")
else:
sourcePath = source
targetPath = os.path.join(destinationDirectory, os.path.basename(sourcePath))
#targetPath = os.path.normpath(os.path.join(destinationDirectory, os.path.basename(sourcePath).lstrip('./')))
# Check if source exists
if not os.path.exists(sourcePath):
raise FileNotFoundError(f"Source path does not exist: {sourcePath}")
# Create target directory
ensureDirectory(os.path.dirname(targetPath))
# Copy or replace mode
if rule.get('mode') == 'replace' and os.path.exists(targetPath):
print(f"Replacing existing path: {targetPath}")
print(f"Replacing existing file/folder: {targetPath}")
# Explicitly handle directory or file deletion
if os.path.isdir(targetPath):
@ -69,6 +92,8 @@ def copySources(config, destinationDirectory, results):
if srcMtime <= destMtime:
print(f"Skipping {sourcePath}: Destination is up to date")
break # Skip this source file/folder
else:
print(f"Should update {sourcePath} to {targetPath}, but {sourcePath} is not (yet) existing.")
# Copy files or directories
if os.path.isdir(sourcePath):
@ -79,15 +104,30 @@ def copySources(config, destinationDirectory, results):
print(f"Copying file: {sourcePath} -> {targetPath}")
shutil.copy2(sourcePath, targetPath)
results['copies'] += 1
except FileNotFoundError as e:
print(f"Error: {e}")
errorList["copies"] += 1
continue
except ValueError as e:
print(f"Configuration error: {e}")
errorList["copies"] += 1
continue
except PermissionError:
print(f"Error: Permission denied when copying {sourcePath}")
errorList["copies"] += 1
continue
except OSError as e:
print(f"Error copying {sourcePath}: {e}")
errorList["copies"] += 1
continue
print("Source file/folder copy process completed.")
print("\nSource file & folder copy/replace process completed.\n")
def modifyFiles(config, destinationDirectory, results):
def modifyFiles(config, destinationDirectory):
"""Modify files according to modification rules."""
print("Starting file modification process...")
for rule in config.get('modification_rules', []):
@ -136,10 +176,15 @@ def modifyFiles(config, destinationDirectory, results):
results['modifications'] += 1
except PermissionError:
print(f"Error: Permission denied when modifying {filePath}")
errorList["modifications"] += 1
continue
except IOError as e:
print(f"Error reading/writing file {filePath}: {e}")
errorList["modifications"] += 1
continue
print("File modification process completed.")
print("\nFile modification process completed.\n")
def main():
@ -154,23 +199,26 @@ def main():
# Load configuration
config = loadConfig(configPath)
# Initialize results
results = {"copies": 0, "modifications": 0}
# Ensure destination directory
destinationDirectory = config.get('destination_directory', './web')
ensureDirectory(destinationDirectory)
# Copy files and folders
copySources(config, destinationDirectory, results)
copySources(config, destinationDirectory)
# Modify files
modifyFiles(config, destinationDirectory, results)
modifyFiles(config, destinationDirectory)
# Print results
print(f'\nTotal successful copies: {results["copies"]}')
print(f'Total file modifications: {results["modifications"]}')
print(f"All operations in {destinationDirectory} completed successfully.")
if errorList["copies"] > 0 or errorList["modifications"] > 0:
print("Errors occurred during the process. Check the output for details.")
print(f"{errorList['copies']} copy errors.")
print(f"{errorList['modifications']} modification errors.")
else:
print(f"All operations in {destinationDirectory} completed successfully.")
if __name__ == '__main__':

View File

@ -5,7 +5,7 @@ destination_directory: './web'
copy_rules:
- sources:
- source: './img/icon-transparent.png'
target: './assets/img/icon-transparent.png'
target: 'assets/img/icon-transparent.png'
mode: 'replace' # Überschreibt vorhandene Dateien/Ordner
@ -13,7 +13,7 @@ copy_rules:
- './ui' # Gesamter Ordner/Datei wird in destination_directory kopiert
- source: './img/background.png'
target: './assets/img/background.png'
target: 'assets/img/background.png'
mode: 'copy' # Kopiert Dateien/Ordner
# Modifikationsregeln

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@ -1,191 +0,0 @@
import os
import re
import sys
import yaml
import shutil
import glob
def load_configuration(config_path):
"""
Load configuration from a YAML file.
"""
with open(config_path, 'r', encoding='utf-8') as config_file:
return yaml.safe_load(config_file)
def copy_with_mode(src, dest, mode='copy'):
"""
Copy files or directories with different modes and specific source/target paths.
"""
try:
# Ensure full paths are used
src = os.path.abspath(src)
dest = os.path.abspath(dest)
# Ensure destination directory exists
os.makedirs(os.path.dirname(dest), exist_ok=True)
# Determine copy mode
if mode == 'copy':
# Skip if destination already exists
if os.path.exists(dest):
print(f'Skipping {src}: Destination already exists')
return False
elif mode == 'replace':
# Remove existing destination before copying
if os.path.exists(dest):
if os.path.isdir(dest):
shutil.rmtree(dest)
else:
os.remove(dest)
elif mode == 'update':
# Only copy if source is newer
if os.path.exists(dest):
src_mtime = os.path.getmtime(src)
dest_mtime = os.path.getmtime(dest)
if src_mtime <= dest_mtime:
print(f'Skipping {src}: Destination is up to date')
return False
# Perform copy
if os.path.isdir(src):
shutil.copytree(src, dest)
else:
shutil.copy2(src, dest)
print(f'Copied: {src} -> {dest}')
return True
except Exception as e:
print(f'Error copying {src}: {e}')
return False
def matches_file_pattern(filename, pattern):
"""
Check if filename matches the given pattern.
"""
return re.search(pattern, filename) is not None
def apply_insert_rules(content, insert_rules):
"""
Apply insertion rules to the content.
"""
modified_content = content
modified = False
for rule in insert_rules:
insert_text = rule.get('insert_text')
# Insert after specific text
if 'after_text' in rule:
search_text = rule.get('after_text')
if insert_text not in modified_content:
modified_content = modified_content.replace(
search_text,
f'{search_text} {insert_text}'
)
modified = True
# Insert before specific text
elif 'before_text' in rule:
search_text = rule.get('before_text')
if insert_text not in modified_content:
modified_content = modified_content.replace(
search_text,
f'{insert_text} {search_text}'
)
modified = True
return modified_content, modified
def apply_replace_rules(content, replace_rules):
"""
Apply replacement rules to the content.
"""
modified_content = content
modified = False
for rule in replace_rules:
old_text = rule.get('old_text')
new_text = rule.get('new_text')
# Replace text if found
if old_text in modified_content:
modified_content = modified_content.replace(old_text, new_text)
modified = True
return modified_content, modified
def process_copy_and_modify_rules(config):
"""
Process copy, insertion, and replacement rules.
"""
successful_operations = {
'copies': 0,
'modifications': 0
}
# Process copy rules
for copy_rule in config.get('copy_rules', []):
sources = copy_rule.get('sources', [])
mode = copy_rule.get('mode', 'copy')
for source_info in sources:
# Support both simple string and dictionary input
if isinstance(source_info, str):
src = source_info
dest = os.path.join(
config.get('destination_directory', '.'),
os.path.basename(src)
)
elif isinstance(source_info, dict):
src = source_info.get('source')
dest = source_info.get('target')
# Fallback if target is not specified
if not dest:
dest = os.path.join(
config.get('destination_directory', '.'),
os.path.basename(src)
)
else:
print(f'Invalid source configuration: {source_info}')
continue
# Expand potential wildcards
matching_sources = glob.glob(src)
for matched_src in matching_sources:
# Determine full destination path
if os.path.isdir(matched_src):
full_dest = os.path.join(dest, os.path.basename(matched_src))
else:
full_dest = dest
# Perform copy
if copy_with_mode(matched_src, full_dest, mode):
successful_operations['copies'] += 1
# Rest of the function remains the same as in the original script
# ... (modification rules processing)
return successful_operations
def main():
# Check command-line argument
if len(sys.argv) < 2:
print("Please provide the path to the configuration file.")
sys.exit(1)
config_path = sys.argv[1]
# Load configuration and process rules
config = load_configuration(config_path)
results = process_copy_and_modify_rules(config)
print(f'\nTotal successful copies: {results["copies"]}')
print(f'Total file modifications: {results["modifications"]}')
if __name__ == '__main__':
main()

View File

@ -1,30 +0,0 @@
# Zielverzeichnis für Operationen
destination_directory: './web/'
# Kopierregeln
copy_rules:
- sources:
- source: './img/icon-transparent.png'
target: './assets/img/icon-transparent.png'
mode: 'replace' # Überschreibt vorhandene Dateien/Ordner
- sources:
- './ui'
- source: './img/background.png'
target: './assets/img/background.png'
mode: 'copy' # Kopiert Dateien/Ordner
# Modifikationsregeln
modification_rules:
- file_pattern: 'test..*.txt'
insert_rules:
- after_text: 'This is an after text'
insert_text: ' added after text '
- before_text: 'This is a before text'
insert_text: ' added before text '
- old_text: 'Replace this text'
new_text: 'Replaced text'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

@ -1,517 +0,0 @@
let title = 'Spotlight'; // Title of the slideshow
let listFileName = 'list.txt'; // Name of the file containing the list of movie IDs
let token = 'YOURAPIKEYHERE'; // Your Jellyfin API key
let moviesSeriesBoth = 3; // 1 for movies, 2 for series, 3 for both
let shuffleInterval = 15000; // Time in milliseconds before the next slide is shown, unless trailer is playing
let useTrailers = true; // Set to false to disable trailers
let setRandomMovie = true; // Set to false to disable random movie selection from the list
let showOnOtherPages = false; // Set to true to show the slideshow on all pages eg. favorites tab, requests tab, etc.
let showTitle = true; // Set to false to hide the title
let plotMaxLength = 550; // Maximum number of characters in the plot
let isChangingSlide = false, player = null, slideChangeTimeout = null, isHomePageActive = false;
let currentLocation = window.top.location.href;
let movieList = [], currentMovieIndex = 0;
const createElem = (tag, className, textContent, src, alt) => {
const elem = document.createElement(tag);
if (className) elem.className = className;
if (textContent) elem.textContent = textContent;
if (src) elem.src = src;
if (alt) elem.alt = alt;
return elem;
};
// Check for screen size below 1000px
function isMobile() {
return window.innerWidth <= 1000;
}
const truncateText = (text, maxLength) => text.length > maxLength ? text.substr(0, maxLength) + '...' : text;
const cleanup = () => {
if (player) { player.destroy(); player = null; }
clearTimeout(slideChangeTimeout);
const container = document.getElementById('slides-container');
if (container) container.innerHTML = '';
};
const createSlideElement = (movie, hasVideo = false) => {
cleanup();
const container = document.getElementById('slides-container');
// Optional titel
/*if (showTitle) {
//slide.appendChild(createElem('div', 'heading', title));
const titleElem = createElem('div', 'spotlight-title', title);
container.appendChild(titleElem);
}*/
const titleElem = createElem('div', 'spotlight-title', "Spotlight");
container.appendChild(titleElem);
console.log(testElem); // Soll ein <div class="test-class">Test-Content</div> ausgeben
//titleElem.textContent = 'Test Titel';
const slide = createElem('div', 'slide');
['backdrop', 'logo'].forEach(type => slide.appendChild(createElem('img', type, null, `/Items/${movie.Id}/Images/${type.charAt(0).toUpperCase() + type.slice(1)}${type === 'backdrop' ? '/0' : ''}`, type)));
const textContainer = createElem('div', 'text-container');
const premiereYear = movie.PremiereDate ? new Date(movie.PremiereDate).getFullYear() : 'Unknown';
const additionalInfo = movie.Type === 'Series' ?
(movie.ChildCount ? `${movie.ChildCount} Season${movie.ChildCount > 1 ? 's' : ''}` : 'Unknown Seasons') :
(movie.RunTimeTicks ? `${Math.round(movie.RunTimeTicks / 600000000)} min` : 'Unknown Runtime');
let loremText = `
<span style="background: transparent; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.05em; padding-bottom: 0.05em; margin-left: 1em;">${additionalInfo}</span>
<span style="background: transparent; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.05em; padding-bottom: 0.05em; margin-left: 1em;">${premiereYear}</span> `;
if (movie.CommunityRating) {
loremText += `<span style="background: transparent; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.05em; padding-bottom: 0.05em; margin-left: 1em;">
<i class="star-icon fas fa-star"></i> ${movie.CommunityRating.toFixed(1)}
</span> `;
}
if (movie.CriticRating) {
loremText += `<span style="background: transparent; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.05em; padding-bottom: 0.05em; margin-left: 1em;">
<img src="https://i.imgur.com/rMvyQMt.png" alt="Rotten Tomatoes" style="width: 1.05em; height: 1.25em; font-size: 0.9em; padding-right: 0.1em; margin-left: -0.1em; vertical-align: bottom;">
${movie.CriticRating}%</span>`;
}
// Age Rating
const ageRating = movie.OfficialRating ? movie.OfficialRating : 'NR';
const ratingClass = ageRating.toLowerCase().replace(/ /g, '-');
const ageRatingDiv = createElem('div', 'age-rating');
const ageRatingSpan = createElem('span', ratingClass, ageRating);
ageRatingSpan.style.cssText = 'border: 0.09em solid currentColor; border-radius: .1em; padding: 0.2em; padding-top: 0.125em; padding-bottom: 0.26em; display: inline-block; text-align: center; line-height: 0.8em;';
ageRatingDiv.appendChild(ageRatingSpan);
slide.appendChild(ageRatingDiv);
// Genres Section
const genresDiv = createElem('div', 'genres');
if (movie.Genres && movie.Genres.length > 0) {
movie.Genres.forEach((genre, index) => {
const genreElem = createElem('span', 'genre-item');
genreElem.textContent = genre;
genreElem.style.backgroundColor = 'transparent';
genreElem.style.paddingLeft = '0.5em';
genreElem.style.paddingRight = '0.5em';
genreElem.style.paddingTop = '0.1em';
genreElem.style.paddingBottom = '0.1em';
genreElem.style.color = 'white';
genreElem.style.borderRadius = '0em';
genreElem.style.marginRight = '0em';
genresDiv.appendChild(genreElem);
if (index < movie.Genres.length - 1) {
const separator = createElem('span', 'material-symbols-outlined');
separator.textContent = 'stat_0'; // The separator symbol
genresDiv.appendChild(separator);
}
});
} else {
genresDiv.textContent = 'Genres: N/A';
}
slide.appendChild(genresDiv);
const loremDiv = createElem('div', 'lorem-ipsum');
loremDiv.innerHTML = loremText;
textContainer.appendChild(loremDiv);
textContainer.appendChild(createElem('div', 'plot', truncateText(movie.Overview, plotMaxLength)));
slide.appendChild(textContainer);
const backButton = createElem('div', 'back-button');
const backIcon = createElem('i', 'material-icons');
backIcon.textContent = 'chevron_left';
const skipButton = createElem('div', 'skip-button');
const skipIcon = createElem('i', 'material-icons');
skipIcon.textContent = 'chevron_right';
backButton.appendChild(backIcon);
skipButton.appendChild(skipIcon);
skipIcon.onclick = (e) => { e.stopPropagation(); fetchRandomMovie(); };
backIcon.onclick = (e) => { e.stopPropagation(); previousMovie(); };
slide.appendChild(backButton);
slide.appendChild(skipButton);
// Create Details buttons
const buttonsContainer = createElem('div', 'buttons-container');
// Details Button
const detailsButton = createElem('button', 'details-button');
const playIcon = createElem('i', 'fas fa-play');
detailsButton.appendChild(playIcon);
detailsButton.appendChild(document.createTextNode(' Play'));
detailsButton.onclick = (e) => {
e.stopPropagation();
window.top.location.href = `/#!/details?id=${movie.Id}`; // Navigate to details page
};
// Append buttons to the container
buttonsContainer.appendChild(detailsButton);
slide.appendChild(buttonsContainer);
const overlay = createElem('div', 'clickable-overlay');
overlay.onclick = () => window.top.location.href = `/#!/details?id=${movie.Id}`;
slide.appendChild(overlay);
if (hasVideo && movie.RemoteTrailers && movie.RemoteTrailers.length > 0) {
const trailerUrl = movie.RemoteTrailers[0].Url;
const watchTrailerButton = createElem('button', 'watch-trailer-button');
const trailerIcon = document.createElement('i');
trailerIcon.className = 'fas fa-film';
watchTrailerButton.appendChild(trailerIcon);
watchTrailerButton.appendChild(document.createTextNode('Trailer'));
watchTrailerButton.onclick = (e) => {
e.stopPropagation();
if (isMobile()) {
// Show the video in an overlay for mobile
showVideoOverlay(trailerUrl);
} else {
// Open the trailer in a new tab for desktop
window.open(trailerUrl, '_blank');
}
};
buttonsContainer.appendChild(watchTrailerButton);
}
if (useTrailers && hasVideo && movie.RemoteTrailers?.length > 0) {
const videoId = new URL(movie.RemoteTrailers[0].Url).searchParams.get('v');
const videoContainer = createElem('div', 'video-container');
const videoElement = createElem('div', 'video-player');
videoContainer.appendChild(videoElement);
slide.appendChild(videoContainer);
player = new YT.Player(videoElement, {
height: '100%',
width: '100%',
videoId,
playerVars: {
mute: 1 // Start the video muted
},
events: {
'onReady': event => {
event.target.playVideo();
},
'onStateChange': event => {
if (event.data === YT.PlayerState.PLAYING) {
// Only show when YT video is successfully playing
const backdrop = document.querySelector('.backdrop');
if (backdrop) {
backdrop.style.width = 'calc(100% - 23vw)';
backdrop.style.left = '0vw';
}
const plot = document.querySelector('.plot');
if (plot) plot.style.width = 'calc(100% - 36.4vw)';
const loremIpsum = document.querySelector('.lorem-ipsum');
if (loremIpsum) loremIpsum.style.paddingRight = '32.4vw';
const logo = document.querySelector('.logo');
if (logo) logo.style.left = 'calc(50% - 14.2vw)';
videoContainer.style.width = '34.4vw';
} else if (event.data === YT.PlayerState.ENDED) {
setTimeout(fetchRandomMovie, 100);
}
},
'onError': () => {
console.error(`YouTube prevented playback of '${movie.Name}'`);
if (player) {
player.destroy();
player = null;
}
// Reset style when a YT error occurs
const backdrop = document.querySelector('.backdrop');
if (backdrop) backdrop.style.width = '100%';
const plot = document.querySelector('.plot');
if (plot) plot.style.width = '98%';
const loremIpsum = document.querySelector('.lorem-ipsum');
if (loremIpsum) loremIpsum.style.paddingRight = '0';
const logo = document.querySelector('.logo');
if (logo) logo.style.left = '50%';
videoContainer.style.width = '0';
startSlideChangeTimer();
}
}
});
} else {
startSlideChangeTimer();
}
container.innerHTML = '';
container.appendChild(slide);
};
// Fetch the previous movie in the list
function previousMovie() {
if (isChangingSlide) return;
isChangingSlide = true;
// Reset index or set to the end of the list if we are at the first element
currentMovieIndex = (currentMovieIndex - 2 + movieList.length) % movieList.length;
fetchNextMovie();
}
function addSwipeListeners(slide) {
let startX, startY, distX, distY;
const threshold = 50;
const restraint = 100;
slide.addEventListener('touchstart', e => {
const touch = e.touches[0];
startX = touch.clientX;
startY = touch.clientY;
});
slide.addEventListener('touchmove', e => {
const touch = e.touches[0];
distX = touch.clientX - startX;
distY = touch.clientY - startY;
});
slide.addEventListener('touchend', () => {
if (Math.abs(distX) > threshold && Math.abs(distY) < restraint) {
if (distX > 0) {
console.log('Swipe Right');
} else {
console.log('Swipe Left');
fetchRandomMovie();
}
}
distX = distY = 0;
});
}
// Show the video overlay
function showVideoOverlay(trailerUrl) {
const videoOverlay = document.getElementById('video-overlay');
const videoFrame = document.getElementById('trailer-video');
const closeOverlay = document.getElementById('close-overlay');
// Extract video ID from trailer URL
const videoId = new URL(trailerUrl).searchParams.get('v');
const embedUrl = `https://www.youtube.com/embed/${videoId}?autoplay=1`;
// Set iframe's source to trailer URL
videoFrame.src = embedUrl;
// Show the overlay
videoOverlay.style.display = 'block';
// Pause the slide timer when the video overlay is open
clearSlideChangeTimeout();
// Close the overlay when the X is clicked
closeOverlay.onclick = () => {
videoOverlay.style.display = 'none';
videoFrame.src = ''; // Stop the video
};
// Close the overlay when clicking outside content
window.onclick = (event) => {
if (event.target === videoOverlay) {
videoOverlay.style.display = 'none';
videoFrame.src = '';
}
};
}
// Close video overlay and restart slide timer
function closeVideoOverlay() {
const videoOverlay = document.getElementById('video-overlay');
const videoFrame = document.getElementById('trailer-video');
// Hide overlay
videoOverlay.style.display = 'none';
// Reset the iframe source
videoFrame.src = '';
// Restart slide change timer when video overlay is closed
startSlideChangeTimer();
}
function clearSlideChangeTimeout() {
if (slideChangeTimeout) {
clearTimeout(slideChangeTimeout);
slideChangeTimeout = null;
}
}
const startSlideChangeTimer = () => { clearTimeout(slideChangeTimeout); slideChangeTimeout = setTimeout(fetchRandomMovie, shuffleInterval); };
const checkBackdropAndLogo = movie => {
Promise.all(['/Images/Backdrop/0', '/Images/Logo'].map(url =>
fetch(`/Items/${movie.Id}${url}`, { method: 'HEAD' }).then(response => response.ok)
)).then(([backdropExists, logoExists]) =>
backdropExists && logoExists ? createSlideElement(movie, true) : fetchRandomMovie()
).catch(() => fetchRandomMovie());
};
const readCustomList = () =>
fetch(listFileName + '?' + new Date().getTime())
.then(response => response.ok ? response.text() : null)
.then(text => {
if (!text) return null;
const lines = text.split('\n').filter(Boolean);
title = lines.shift() || title;
return lines.map(line => line.trim().substring(0, 32));
})
.catch(() => null);
// using Fisher-Yates shuffle algorithm if list is available and setRandomMovie is set to true
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
// Generate a random index between 0 and i
const j = Math.floor(Math.random() * (i + 1));
// Swap elements at indices i and j
[array[i], array[j]] = [array[j], array[i]];
//var temp = array[i];
//array[i] = array[j];
//array[j] = temp;
}
return array;
};
//const shuffleArray = (array) => array.sort(() => Math.random() - 0.5); //better use Fisher-Yates shuffle algorithm
const fetchRandomMovie = () => {
if (isChangingSlide) return;
isChangingSlide = true;
if (movieList.length === 0) {
readCustomList().then(list => {
if (list) {
movieList = list;
//// Shuffle the list if it was set by the user
//if (setRandomMovie) {
// shuffleArray(movieList);
//}
currentMovieIndex = 0;
}
fetchNextMovie();
});
} else fetchNextMovie();
};
const fetchNextMovie = () => {
const fetchCurrentUserId = () =>
fetch('/Sessions', {
headers: { 'Authorization': `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"` }
})
.then(response => response.json())
.then(sessions => {
const currentSession = sessions.find(session => session.UserId);
return currentSession ? currentSession.UserId : null;
})
.catch(() => null);
fetchCurrentUserId().then(currentUserId => {
if (!currentUserId) {
console.error('Could not retrieve the current user ID.');
return;
}
const headers = { 'Authorization': `MediaBrowser Client="Jellyfin Web", Device="YourDeviceName", DeviceId="YourDeviceId", Version="YourClientVersion", Token="${token}"` };
if (movieList.length > 0) {
if (currentMovieIndex >= movieList.length) currentMovieIndex = 0;
const movieId = movieList[currentMovieIndex];
currentMovieIndex++;
fetch(`/Users/${currentUserId}/Items/${movieId}?Fields=Overview,RemoteTrailers,PremiereDate,RunTimeTicks,ChildCount,Genres`, { headers })
.then(response => response.json())
.then(checkBackdropAndLogo)
.catch(() => startSlideChangeTimer())
.finally(() => { isChangingSlide = false; });
} else {
const itemTypes = moviesSeriesBoth === 1 ? 'Movie' : (moviesSeriesBoth === 2 ? 'Series' : 'Movie,Series');
fetch(`/Users/${currentUserId}/Items?IncludeItemTypes=${itemTypes}&Recursive=true&Limit=1&SortBy=random&Fields=Id,Overview,RemoteTrailers,PremiereDate,RunTimeTicks,ChildCount,Genres`, { headers })
.then(response => response.json())
.then(data => { if (data.Items[0]) checkBackdropAndLogo(data.Items[0]); })
.catch(() => startSlideChangeTimer())
.finally(() => { isChangingSlide = false; });
}
});
};
const checkNavigation = () => {
const newLocation = window.top.location.href;
// Check if the user is navigating to or from the homepage
if (newLocation !== currentLocation) {
currentLocation = newLocation;
const isHomePage = url => url.includes('/home') || url.endsWith('/web/') || url.endsWith('/web/index.html');
if (isHomePage(newLocation)) {
if (!isHomePageActive) {
console.log("Returning to homepage, reactivating slideshow");
isHomePageActive = true;
cleanup();
fetchRandomMovie();
}
} else if (isHomePageActive) {
console.log("Leaving homepage, cleaning up slideshow");
isHomePageActive = false;
cleanup();
}
}
// Check if parent is available and if the iframe is in an embedded environment
if (!showOnOtherPages && window.parent && window.parent !== window) {
const homeTab = window.parent.document.getElementById('homeTab');
const isHomeTabActive = homeTab && homeTab.classList.contains('is-active');
// Check if the user is switching tabs while on the homepage to eg. favorites or requests, if so, stop the slideshow
if (isHomeTabActive && !isHomePageActive) {
console.log("HomeTab is active, reactivating slideshow");
isHomePageActive = true;
window.parent.document.querySelector('.featurediframe').style.display = 'block';
cleanup();
fetchRandomMovie();
} else if (!isHomeTabActive && isHomePageActive) {
console.log("Leaving HomeTab, cleaning up slideshow");
isHomePageActive = false;
window.parent.document.querySelector('.featurediframe').style.display = 'none';
cleanup();
}
} else {
console.error("Spotlight iframe is not in an embedded environment, has a different domain or showOnOtherPages is set to true");
}
};
setInterval(checkNavigation, 60);
document.addEventListener('DOMContentLoaded', () => {
if (window.innerWidth < 1001) useTrailers = false;
const isHomePage = url => url.includes('/home') || url.endsWith('/web/') || url.endsWith('/web/index.html');
if (isHomePage(window.top.location.href)) {
isHomePageActive = true;
readCustomList().then(list => {
if (list) {
movieList = list;
// Shuffle the list if it was set by the user
if (setRandomMovie) {
shuffleArray(movieList);
}
currentMovieIndex = 0;
}
fetchRandomMovie();
});
}
});
window.addEventListener('unload', cleanup);
window.addEventListener('popstate', checkNavigation);

View File

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

View File

@ -1,992 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap');
body {
margin: 0;
padding: 0;
overflow: hidden;
}
.slide {
position: relative;
width: 100vw;
height: 22em;
cursor: pointer;
border-radius: 2em;
}
.slide:focus {
outline: 2px solid #fff;
}
.backdrop {
position: absolute;
top: 0em;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center 30%;
z-index: 1;
transition: width 0.5s ease, filter 0.8s ease, scale 2s ease;
transform-origin: top;
animation: objectPositionAnimation 45s ease-in-out infinite;
border-radius: 1em;
}
.logo {
position: relative;
transform: translateX(-50%) translateY(-50%);
top: 56%;
max-height: 9em;
max-width: 29em;
width: auto;
z-index: 3;
text-shadow: -2px 2px 4px rgba(0, 0, 0, 0.5);
filter: drop-shadow(1px 1px 1px);
pointer-events: none;
transition: transform 0.3s ease, max-height 0.3s ease, max-width 0.3s ease, left 0.5s ease;
}
.heading {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 2.3em;
background-color: transparent;
font-family: "Titillium Web", sans-serif;
color: #D3D3D3;
text-shadow: 1px 1px 4px rgba(0, 0, 0, 1);
font-size: 22px;
display: flex;
align-items: center;
justify-content: flex-start;
/* z-index: 1; */
padding: 10px;
padding-left: 0px;
box-sizing: border-box;
}
.spotlight-title {
display: block;
font-size: 24px;
color: white;
text-align: left;
padding: 10px;
background-color: rgba(255, 0, 0, 0.5);
/*z-index: 9999; /* Bringt das Element in den Vordergrund */
/*position: relative; /* Muss gesetzt sein, wenn z-index verwendet wird */
}
.details-button {
display: none;
}
/* MARK: modified
css for back and skip button
*/
.back-button,
.skip-button {
position: absolute;
color: #D3D3D3;
cursor: pointer;
z-index: 10;
font-family: "Titillium Web", sans-serif;
text-shadow: 1px 1px 4px rgba(0, 0, 0, 1);
font-style: normal;
font-weight: 400;
letter-spacing: normal;
line-height: 1;
text-transform: none;
white-space: nowrap;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
-moz-osx-font-smoothing: grayscale;
-webkit-font-feature-settings: "liga";
font-feature-settings: "liga";
border-radius: 50%;
box-sizing: border-box;
opacity: 0.5;
transition: opacity 0.3s ease, background 0.3s ease;
font-size: 1.62em;
width: 1.7em;
height: 1.7em;
display: flex;
align-items: center;
justify-content: center;
}
.back-button:hover,
.skip-button:hover {
background: #ffffff70;
opacity: 1;
}
.skip-button {
right: 0.5em;
top: 50%;
transform: translateY(-50%);
}
.back-button {
left: 0.5em;
top: 50%;
transform: translateY(-50%);
/* display: none; */ /* Optional, if activated later */
}
.material-icons {
font-size: 1em;
}
.video-container {
position: absolute;
right: 0;
top: 0px;
width: 0;
height: calc(100%);
overflow: hidden;
transition: width 0.5s ease;
z-index: 5;
border-top-right-radius: 0.5em;
border-bottom-right-radius: 0.5em;
}
.video-player {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 7;
}
.clickable-overlay {
position: absolute;
top: 50px;
left: 0;
width: 100%;
height: calc(100% - 50px);
z-index: 2;
cursor: pointer;
background: transparent;
transition: background 0.8s ease, backdrop-filter 0.8s ease;
}
.lorem-ipsum {
position: absolute;
bottom: 20.1em;
right: 2.5em;
font-family: "Titillium Web", sans-serif;
font-size: 1em;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
opacity: 1;
color: #fff;
z-index: 9;
box-sizing: border-box;
background: transparent;
max-width: fit-content;
padding-left: 90vw;
padding-right: 0.3em;
display: flex;
flex-direction: row;
max-height: 1.9em;
padding-top: 0.15em;
white-space: nowrap;
transition: padding-right 0.3s ease;
}
.lorem-ipsum span {
margin-right: 0.2em;
}
.age-rating {
position: absolute;
top: 0.1em;
left: 2em;
text-align: left;
font-family: "Titillium Web", sans-serif;
font-size: 1.2em;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
opacity: 1;
color: #fff;
z-index: 7;
box-sizing: border-box;
background: transparent;
max-width: fit-content;
padding-left: 0.3em;
padding-right: 0.3em;
display: flex;
flex-direction: row;
max-height: 1.9em;
padding-top: 0.15em;
white-space: nowrap;
}
.plot {
position: absolute;
bottom: 0.1em;
left: 2.2em;
right: 0;
color: #fff;
font-family: "Titillium Web", sans-serif;
font-size: 0.95em;
font-weight: 500;
background: transparent;
padding: 10px;
z-index: 7;
box-sizing: border-box;
padding-bottom: 3px;
padding-top: 0.5em;
line-height: 1.2em;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
border-bottom-left-radius: 0.45em;
transition: opacity 0.1s ease, max-height 0.8s ease, width 0.3s ease;
opacity: 0;
max-width: 96vw;
opacity: 1;
-webkit-line-clamp: 2;
line-clamp: 2;
max-height: 7.8em;
}
.genres {
position: absolute;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
top: -0.25em;
color: #fff;
font-family: "Titillium Web", sans-serif;
font-size: 1em;
opacity: 1;
transition: opacity 0.3s ease;
max-width: 39em;
overflow: hidden;
height: 1.95em;
padding-top: 0.25em;
line-height: 2em;
white-space: nowrap;
}
.premiere-year {
position: absolute;
top: 5.75em;
left: 0.5em;
font-size: 0.9em;
font-family: "Titillium Web", sans-serif;
color: #fff;
z-index: 3;
opacity: 0;
transition: opacity 0.3s;
text-align: center;
width: 3.5em;
}
.additional-info {
position: absolute;
top: 7.3em;
left: 31em;
font-size: 0.9em;
font-family: "Titillium Web", sans-serif;
color: #fff;
z-index: 3;
opacity: 1;
transition: opacity 0.3s;
text-align: right;
width: 5em;
}
.community-rating {
position: absolute;
font-family: "Titillium Web", sans-serif;
color: #fff;
z-index: 3;
opacity: 1;
transition: opacity 0.3s;
text-align: center;
width: 3.6em;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.critic-rating {
position: absolute;
top: 5.75em;
right: 73%;
font-size: 0.9em;
font-family: "Titillium Web", sans-serif;
color: #fff;
z-index: 3;
opacity: 1;
transition: opacity 0.3s;
text-align: center;
width: 3.9em;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
@keyframes objectPositionAnimation {
0% {
object-position: center 30%;
}
40% {
object-position: center 80%;
}
80% {
object-position: center 20%;
}
100% {
object-position: center 30%;
}
}
.slide:hover .backdrop {
filter: brightness(95%) contrast(105%) saturate(105%);
transform: scale(1);
}
/* .slide:hover .clickable-overlay {} */
.slide:hover .clickable-overlay::before {
opacity: 0;
backdrop-filter: blur(0px);
}
/*
.slide:hover .logo {}
.slide:hover .plot {}
*/
.slide:hover .lorem-ipsum::before {
opacity: 1;
backdrop-filter: blur(2px);
transform: translateX(0);
}
.star-icon {
color: gold;
font-size: 0.9em;
padding-right: 0.2em;
margin-left: -0.1em;
}
@media (max-width: 1000px) {
.age-rating {
position: absolute;
top: 1em;
left: 1em;
font-size: 0.85em;
}
.lorem-ipsum {
font-size: 0.7em;
transform: translateX(50%);
right: 50vw !important;
padding-left: 0;
padding-right: 1em;
}
.logo {
transform: translateX(-50%) translateY(-50%);
top: 10.5em;
left: 50% !important;
max-width: 70%;
max-height: 45%;
transform-origin: 50% 50%;
}
.plot {
display: none;
opacity: 1 !important;
font-size: 90%;
bottom: 0.5em;
-webkit-line-clamp: 2;
line-clamp: 2;
max-height: 3.3em;
overflow: hidden;
width: 98vw !important;
max-width: 98vw;
line-height: 1.45em;
}
.lorem-ipsum::before {
opacity: 0;
padding-bottom: 0.25em;
}
.clickable-overlay {
background: linear-gradient(0deg, rgb(0% 0% 0%) 0%, rgb(0% 0% 0% / 0.9990234375) 6.25%, rgba(0, 0, 0, 0.99) 12.5%, rgba(0, 0, 0, 0.97) 18.75%, rgba(0, 0, 0, 0.94) 25%, rgba(0, 0, 0, 0.88) 31.25%, rgba(0, 0, 0, 0.79) 37.5%, rgba(0, 0, 0, 0.67) 43.75%, rgba(0, 0, 0, 0.5) 50%, rgba(0, 0, 0, 0.33) 56.25%, rgba(0, 0, 0, 0.21) 62.5%, rgba(0, 0, 0, 0.12) 68.75%, rgba(0, 0, 0, 0.06) 75%, rgba(0, 0, 0, 0.03) 81.25%, rgba(0, 0, 0, 0.01) 87.5%, rgba(0, 0, 0, 0) 93.75%, rgba(0, 0, 0, 0) 100%);
opacity: 0.8;
}
.backdrop {
left: 0% !important;
width: 100% !important;
top: 0em;
}
.lorem-ipsum .full-content {
opacity: 1;
max-width: 100%;
}
.lorem-ipsum::before {
opacity: 1;
backdrop-filter: blur(2px);
transform: translateX(0);
}
.clickable-overlay::before {
opacity: 0.98;
backdrop-filter: blur(0px);
}
.slide:hover .clickable-overlay::before {
opacity: 0.98;
}
.genres {
bottom: 4em;
left: unset !important;
overflow: hidden;
max-width: 100vw !important;
white-space: nowrap;
left: 0;
z-index: 8;
color: #fff;
font-size: 0.7em;
}
.additional-info {
left: 18.5%;
top: 5em;
text-align: right;
}
.community-rating {
left: 86vw !important;
top: 19.45em !important;
text-align: left;
z-index: 8;
font-size: 0.85em !important;
}
.critic-rating {
font-size: 0.85em !important;
left: 70vw !important;
top: 19.45em !important;
z-index: 8;
}
.additional-info {
left: 18.5%;
top: 5em;
text-align: left;
}
.video-container {
max-width: 0 !important;
}
.watch-trailer-button {
display: flex !important;
top: unset !important;
bottom: 0.4em;
font-size: 0.7em !important;
background: #fff9 !important;
color: #000e !important;
font-weight: 600;
transform: translateX(-100%) !important;
left: 47% !important;
}
.slide {
box-shadow: none;
}
.text-container::before {
display: none !important;
}
.buttons-container {
display: flex;
position: absolute;
bottom: 0.6em;
left: 0em;
width: 100vw;
z-index: 10;
}
.details-button {
position: absolute;
border: none;
border-radius: 5px;
padding: 0.5em 1.5em;
font-family: "Titillium Web", sans-serif;
cursor: pointer;
z-index: 10;
transition: background-color 0.3s ease;
align-items: center;
justify-content: center;
bottom: 0.4em;
font-size: 0.7em !important;
background: #fff9;
color: #000e;
font-weight: 600;
transform: translateX(100%) !important;
right: 47%;
display: flex;
padding-right: 2em;
}
.details-button:hover {
background-color: #fffc;
}
.watch-trailer-button:hover {
background-color: #fffc !important;
}
.fas {
padding: 0.5em;
}
}
@media (max-width:1000px) and (orientation:portrait) {
.back-button {
left: 0em;
}
.skip-button {
right: 0em;
/*top: 50% !important;*/
}
.genres {
right: 50vw;
position: absolute;
max-width: 80vw !important;
overflow: hidden;
top: unset;
transform: translateX(50%);
bottom: 6.5em;
}
.lorem-ipsum {
position: absolute;
left: unset;
bottom: 4.75em;
}
}
@media (max-width:1000px) and (orientation:landscape) {
.community-rating {
left: 89vw !important;
}
.critic-rating {
left: 76vw !important;
}
.back-button {
left: 0em;
}
.skip-button {
right: 0em;
/*top: 50% !important;*/
}
.genres {
position: absolute;
bottom: 6.5em;
top: unset;
transform: translateX(50%);
right: 50vw;
}
.lorem-ipsum {
position: absolute;
left: unset;
bottom: 4.75em;
}
}
@media (min-width: 1001px) {
.logo {
max-height: 45%;
top: 50vh;
max-width: 47em;
left: 50%;
transition: transform 0.1s ease, max-height 0.3s ease;
}
}
.watch-trailer-button {
display: none;
}
.backdrop {
left: 0em;
width: 100%;
transition: width 0.5s ease, left 0.5s ease, filter 0.8s ease, transform 2s ease;
}
.genres {
left: 8em;
z-index: 9;
max-width: 39em;
}
.community-rating {
top: 16.45em;
left: 31em;
font-size: 1em;
}
.critic-rating {
position: absolute;
top: 16.45em;
left: 26.5em;
font-size: 1em;
}
.text-container::before {
content: '';
z-index: 5;
display: flex;
position: absolute;
width: 100vw;
top: -19.3em;
height: 3em;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.7) 6.25%, rgba(0, 0, 0, 0.68) 12.5%, rgba(0, 0, 0, 0.66) 18.75%, rgba(0, 0, 0, 0.64) 25%, rgba(0, 0, 0, 0.6) 31.25%, rgba(0, 0, 0, 0.56) 37.5%, rgba(0, 0, 0, 0.51) 43.75%, rgba(0, 0, 0, 0.45) 50%, rgba(0, 0, 0, 0.38) 56.25%, rgba(0, 0, 0, 0.31) 62.5%, rgba(0, 0, 0, 0.22) 68.75%, rgba(0, 0, 0, 0.14) 75%, rgba(0, 0, 0, 0.13) 81.25%, rgba(0, 0, 0, 0) 100%);
opacity: 0.8;
backdrop-filter: blur(1px);
border-top-right-radius: 1em;
border-top-left-radius: 1em;
-webkit-mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 3%, rgba(0, 0, 0, 0.5) 7%, rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0.9) 25%, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 100%);
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 3%, rgba(0, 0, 0, 0.5) 7%, rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0.9) 25%, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 100%);
}
.text-container::after {
content: '';
background: linear-gradient(0deg, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.7) 6.25%, rgba(0, 0, 0, 0.68) 12.5%, rgba(0, 0, 0, 0.66) 18.75%, rgba(0, 0, 0, 0.64) 25%, rgba(0, 0, 0, 0.6) 31.25%, rgba(0, 0, 0, 0.56) 37.5%, rgba(0, 0, 0, 0.51) 43.75%, rgba(0, 0, 0, 0.45) 50%, rgba(0, 0, 0, 0.38) 56.25%, rgba(0, 0, 0, 0.31) 62.5%, rgba(0, 0, 0, 0.22) 68.75%, rgba(0, 0, 0, 0.14) 75%, rgba(0, 0, 0, 0.13) 81.25%, rgba(0, 0, 0, 0) 100%);
z-index: 5;
display: flex;
position: absolute;
width: 100vw;
border-top-right-radius: 1em;
border-top-left-radius: 1em;
bottom: 0em;
height: 3em;
opacity: 0.8;
backdrop-filter: blur(1px);
-webkit-mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 3%, rgba(0, 0, 0, 0.5) 7%, rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0.9) 25%, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 100%);
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 3%, rgba(0, 0, 0, 0.5) 7%, rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0.9) 25%, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 100%);
}
.age-rating {
font-weight: 600;
font-size: 1.1em;
}
.additional-info {
display: none;
}
.age-rating span.gb-u,
.age-rating span.u {
background-color: #078c6d;
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.age-rating span.gb-pg,
.age-rating span.pg {
background-color: #d7a203;
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.age-rating span.gb-12a,
.age-rating span.\31 2A {
background-color: #ee7600;
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.age-rating span.gb-12,
.age-rating span.\31 2,
.age-rating span.gb-15,
.age-rating span.\31 2 {
background-color: #e19887;
color: #e2002d;
border: 0.09em solid white !important;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.age-rating span.pg-13 {
background-color: #157c0d;
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.age-rating span.tv-y7,
.age-rating span.tv-13,
.age-rating span.tv-14,
.age-rating span.\31 6,
.age-rating span.tv-ma,
.age-rating span.tv-y,
.age-rating span.tv-g,
.age-rating span.tv-pg {
background-color: #157c0d;
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.age-rating span.nc-17,
.age-rating span.gb-18,
.age-rating span.\31 8,
.age-rating span.r {
background-color: #c10f1f;
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.age-rating span.au-g,
.age-rating span.nz-g,
.age-rating span.eu-pg,
.age-rating span.ca-g,
.age-rating span.jp-g,
.age-rating span.de-0 {
background-color: #078c6d;
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.age-rating span.au-pg,
.age-rating span.nz-pg,
.age-rating span.eu-12,
.age-rating span.ca-pg,
.age-rating span.jp-pg12,
.age-rating span.de-6 {
background-color: #d7a203;
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.age-rating span.au-m,
.age-rating span.nz-m,
.age-rating span.eu-16,
.age-rating span.ca-14a,
.age-rating span.jp-r15,
.age-rating span.de-12 {
background-color: #07b1e0;
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.age-rating span.au-ma15,
.age-rating span.nz-r15,
.age-rating span.eu-18,
.age-rating span.ca-18a,
.age-rating span.jp-r18,
.age-rating span.de-16 {
background-color: #fc9712;
color: white;
border: 0.09em solid white !important;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.age-rating span.au-r18,
.age-rating span.nz-r18,
.age-rating span.ca-r,
.age-rating span.jp-r18+,
.age-rating span.de-18 {
background-color: #c10f1f;
color: white;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 1);
}
.video-container {
overflow: visible;
}
.watch-trailer-button {
position: absolute;
border: none;
border-radius: 5px;
padding: 0.5em 1em;
font-size: 0.9em;
font-family: "Titillium Web", sans-serif;
cursor: pointer;
z-index: 10;
transition: background-color 0.3s ease;
align-items: center;
justify-content: center;
padding-right: 1.5em;
}
.watch-trailer-button .fa-play {
margin-right: 0.5em;
}
.slide {
position: relative;
width: 100%;
overflow: hidden;
opacity: 1;
transition: opacity 0.31s ease-in-out, font-size 1s ease;
}
.fade-in {
opacity: 0;
visibility: hidden;
transition: opacity 0.31s ease-in-out;
}
.fade-in-active {
opacity: 1;
visibility: visible;
}
.fade-out {
opacity: 0;
}
.backdrop {
transition: opacity 0.3s ease-in-out, width 0.5s ease, transform 1.5s ease, filter 1s ease;
}
@media (min-width: 2000px) {
.slide {
font-size: 133%;
}
/* .backdrop {}
.age-rating {}
.back-button {
top: 38vh;
}
.skip-button {
top: 38vh;
}
.video-container {}
.text-container::before {}
.genres {}
.lorem-ipsum {}
*/
.logo {
top: 38vh;
}
}
@media (min-width: 3000px) {
.slide {
font-size: 200%;
}
/*
.age-rating {}
.skip-button {}
.backdrop {}
.text-container::before {}
.genres {}
.lorem-ipsum {}
*/
}
#video-overlay {
position: fixed;
top: 13vh;
left: 0vw;
width: 100%;
height: 140vh;
background-color: rgb(0, 0, 0);
z-index: 1000;
display: flex;
background-size: cover;
padding: 6vh;
padding-top: 10vh;
margin-top: -10vh;
}
#video-overlay-content {
position: relative;
width: 90%;
max-width: 100%;
height: 60%;
background-color: #000;
}
#close-overlay {
position: absolute;
top: -1em;
right: 0em;
font-size: 1.5em;
color: #7b7b7b;
cursor: pointer;
}
#countdown-bar-container {
display: none;
position: absolute;
left: 0%;
top: 3.1em;
width: 110%;
height: 0.15em !important;
background-color: rgba(0, 0, 0, 0);
z-index: 10;
}
#countdown-bar {
height: 100%;
width: 0%;
background-color: #ffffff;
opacity: 0.5;
transition: width 0.1s linear;
border-radius: 1em;
}
.video-player {
mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 3%, rgba(0, 0, 0, 0.5) 7%, rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0.9) 25%, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 100%);
-webkit-mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 3%, rgba(0, 0, 0, 0.5) 7%, rgba(0, 0, 0, 0.75) 15%, rgba(0, 0, 0, 0.9) 25%, rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 100%);
transition: mask-image 5s ease, -webkit-mask-image 5s ease;
}
.video-container:hover .video-player {
mask-image: none;
-webkit-mask-image: none;
}
.text-container {
z-index: 5;
display: flex;
position: absolute;
width: 100vw;
bottom: 0em;
height: 2.7em;
opacity: 1;
}
.material-symbols-outlined {
vertical-align: middle;
font-size: 0.85em;
opacity: 0.75;
}