Letterboxd Poster Ambience

Ambient background color for film posters.

// ==UserScript==
// @name         Letterboxd Poster Ambience
// @version      1.4
// @description  Ambient background color for film posters.
// @author       seaque
// @license       MIT
// @match        *://*.letterboxd.com/film/*
// @match        *://*.letterboxd.com/*/*/film/*
// @icon         https://a.ltrbxd.com/logos/letterboxd-decal-dots-neg-rgb.svg
// @namespace    https://github.com/seaque/tampermonkey-scripts
// @grant        GM_addStyle
// @run-at       document-start
// ==/UserScript==
/* globals jQuery, $, waitForKeyElements */

(function() {
    'use strict';

    GM_addStyle(`
        .ambientDiv {
          position: absolute;
          -webkit-filter: blur(100px);
          filter: blur(100px);
        }
        .ambientDiv img {
          width: 100%;
          height: auto;
          max-width: 280px;
          max-height: 420px;

          opacity: 0;
          animation: show 500ms 50ms cubic-bezier(.45,.05,.55,.95) forwards;
        }
        @keyframes show {
          25% {
            opacity: 0.2;
            transform: none;
          }
          100% {
            opacity: 0.85;
            transform: none;
          }
        }
    `);
    
    let imageElement;
    const logPrefix = '%cLETTERBOXD POSTER AMBIENCE';
    const logStyle = [
        'color: white',
        'background: linear-gradient(90deg,rgba(255, 128, 0, 1) 0%, rgba(0, 224, 84, 1) 50%, rgba(64, 188, 244, 1) 100%);',
        'border-radius: 4px',
        'padding: 0 4px',
    ].join(';');

    const getPosterImage = () => {
        try {
            const posterImage = new Image();

            imageElement = $('.film-poster > div > img');
            if (!imageElement || imageElement.length === 0) {
                throw new Error('Poster image element not found.');
            }

            const url = imageElement[0].getAttribute('srcset');
            if (!url) {
                throw new Error('Poster image URL not found.');
            }

            console.info(logPrefix, logStyle, `Fetched poster: "${url}"`);
            posterImage.crossOrigin = "Anonymous";
            posterImage.src = url;

            return posterImage;
        } catch (error) {
            console.error(logPrefix, logStyle, `Error fetching poster: ${error.message}`);
            return null;
        }
    };

    const createAmbientDom = (img, w, h) => {
        const ambient_div = document.createElement('div');
        ambient_div.setAttribute('class', 'ambientDiv');
        ambient_div.append(img);
        return ambient_div;
    };

    const addAmbientDiv = () => {
        const img = getPosterImage();
        if (!img) return;

        img.addEventListener("load", () => {
            const width = img.naturalWidth;
            const height = img.naturalHeight;

            const parent = $('.-single')
            if (parent) {
                console.info(logPrefix, logStyle, `Parent element:`, parent);
                parent.prepend(createAmbientDom(img, width, height));
            } else {
                console.error("Parent element not found.");
            }
        });
    };

    let funcInterval = setInterval(() => {
        addAmbientDiv();
    }, 3000);

    setTimeout(() => {
        if (funcInterval) {
            if ( $('.ambientDiv img').length ) { console.info(logPrefix, logStyle, 'Poster Ambience Applied.'); }
            else { console.info(logPrefix, logStyle, 'Time Out! Exiting.'); }
            clearInterval(funcInterval);
            funcInterval = null;
        }
    }, 6000);
})();