2025-07-21 09:36:14 +00:00
// ==UserScript==
// @name improved poster
// @version 9.0
// @date 19.07.25
// @description Galerie d'affiches, badges TMDB/IMDB, options menu stables
// @author Aerya + adapation UNFR
// @match *://*/*
// @grant none
// ==/UserScript==
( function ( ) {
'use strict' ;
/ * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2025-07-21 09:37:13 +00:00
* 1. AUTO UPDATE DISCRET MULTI - URL
2025-07-21 09:36:14 +00:00
* === === === === === === === === === === === === === === === === === === === === === === * /
function getLocalVersion ( ) {
try {
let scriptText = document . currentScript ? . text || '' ;
if ( ! scriptText ) {
const scripts = document . querySelectorAll ( 'script' ) ;
for ( let s of scripts ) {
2025-07-21 09:39:49 +00:00
if ( s . textContent . includes ( '@name improved poster' ) ) {
2025-07-21 09:36:14 +00:00
scriptText = s . textContent ;
break ;
}
}
}
const match = scriptText . match ( /@version\s+([0-9]+(?:\.[0-9]+)*)/ ) ;
return match ? match [ 1 ] : null ;
} catch {
return null ;
}
}
const UPDATE _INTERVAL _MS = 12 * 60 * 60 * 1000 ;
const UPDATE _LS _KEY = 'afficheLastUpdateCheck' ;
const LOCAL _VERSION = getLocalVersion ( ) || '0.0.0' ;
const UPDATE _URLS = [
2025-07-21 09:39:49 +00:00
` ${ location . protocol } //tig. ${ location . hostname } /UNFR/improved-poster/raw/branch/main/improved_poster.js `
2025-07-21 09:36:14 +00:00
] ;
function parseVersion ( txt ) {
const m = txt . match ( /@version\s+([0-9]+(?:\.[0-9]+)*)/ ) ;
return m ? m [ 1 ] : null ;
}
function isNewer ( remote , local ) {
const R = remote . split ( '.' ) . map ( Number ) ;
const L = local . split ( '.' ) . map ( Number ) ;
for ( let i = 0 ; i < Math . max ( R . length , L . length ) ; i ++ ) {
const a = R [ i ] || 0 , b = L [ i ] || 0 ;
if ( a > b ) return true ;
if ( a < b ) return false ;
}
return false ;
}
( function checkUpdateMulti ( ) {
const last = parseInt ( localStorage . getItem ( UPDATE _LS _KEY ) || '0' , 10 ) ;
const now = Date . now ( ) ;
if ( now - last < UPDATE _INTERVAL _MS ) return ;
localStorage . setItem ( UPDATE _LS _KEY , now . toString ( ) ) ;
Promise . all (
UPDATE _URLS . map ( url =>
fetch ( ` ${ url } ?_= ${ now } ` )
. then ( r => r . text ( ) )
. then ( txt => ( { url , version : parseVersion ( txt ) } ) )
. catch ( ( ) => null )
)
) . then ( results => {
const valid = results . filter ( x => x && x . version && isNewer ( x . version , LOCAL _VERSION ) ) ;
if ( ! valid . length ) return ;
valid . sort ( ( a , b ) => {
const aV = a . version . split ( '.' ) . map ( Number ) ;
const bV = b . version . split ( '.' ) . map ( Number ) ;
for ( let i = 0 ; i < Math . max ( aV . length , bV . length ) ; i ++ ) {
if ( ( aV [ i ] || 0 ) > ( bV [ i ] || 0 ) ) return - 1 ;
if ( ( aV [ i ] || 0 ) < ( bV [ i ] || 0 ) ) return 1 ;
}
return 0 ;
} ) ;
// Juste prévenir discrètement sans ouvrir d’ onglet
//toast(`Nouvelle version dispo : ${valid[0].version} ! Actualise pour mettre à jour.`, 'success', 8000);
} ) ;
} ) ( ) ;
/ * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
* 0. CONDITIONS D ’ ACTIVATION
* === === === === === === === === === === === === === === === === === === === === === === * /
if ( ! document . getElementById ( 'm0de_afficheS_EnAbled' ) ) return ;
// === Toast notif
function toast ( msg , type = 'info' , ms = 3500 ) {
let color = type === 'error' ? '#c00' : type === 'success' ? '#15a92e' : '#368efc' ;
let bg = type === 'error' ? '#ffd5d5' : type === 'success' ? '#dbffe0' : '#e2eafc' ;
let box = document . createElement ( 'div' ) ;
box . textContent = msg ;
box . style = ` position:fixed;top:26px;right:24px;z-index:99999;padding:14px 22px;font-size:17px;font-weight:bold;border-radius:9px;background: ${ bg } ;color: ${ color } ;box-shadow:0 4px 24px #0002;opacity:0.98; ` ;
document . body . appendChild ( box ) ;
setTimeout ( ( ) => box . remove ( ) , ms ) ;
}
// === Persistance config
const INSTANCES _KEY = 'afficheUsenetInstances' ;
const STORAGE _SECTIONS _KEY = 'afficheModeSections' ;
const STORAGE _MINWIDTH _KEY = 'afficheMinWidth' ;
const STORAGE _FONT _SIZE _KEY = 'afficheRlzFontSize' ;
const STORAGE _SHOW _TMDB _KEY = 'afficheShowTmdb' ;
const STORAGE _RELEASE _NEWTAB _KEY = 'afficheReleaseNewTab' ;
const TMDB _CACHE _KEY = 'afficheTmdbCache' ;
function getInstances ( ) { try { return JSON . parse ( localStorage . getItem ( INSTANCES _KEY ) ) || { } ; } catch { return { } ; } }
function setInstances ( obj ) { localStorage . setItem ( INSTANCES _KEY , JSON . stringify ( obj ) ) ; }
function getSections ( ) {
let arr = [ ] ;
try { arr = JSON . parse ( localStorage . getItem ( STORAGE _SECTIONS _KEY ) ) || [ ] ; }
catch { arr = [ ] ; }
return Array . isArray ( arr ) ? arr : [ ] ;
}
function setSections ( arr ) { localStorage . setItem ( STORAGE _SECTIONS _KEY , JSON . stringify ( arr ) ) ; }
function minW ( ) { return parseInt ( localStorage . getItem ( STORAGE _MINWIDTH _KEY ) || '260' , 10 ) ; }
function fontSz ( ) { return parseInt ( localStorage . getItem ( STORAGE _FONT _SIZE _KEY ) || '22' , 10 ) ; }
function showTmdb ( ) { return localStorage . getItem ( STORAGE _SHOW _TMDB _KEY ) !== '0' ; }
function openReleasesInNewTab ( ) { return localStorage . getItem ( STORAGE _RELEASE _NEWTAB _KEY ) === '1' ; }
function imgIcon ( url , alt ) {
return ` <span style="background:rgba(255,255,255,0.78);border-radius:10px;padding:2px 2px 2px 2px;display:inline-block;">
< img src = "${url}" alt = "${alt}" style = "width:20px;height:20px;vertical-align:middle;filter: drop-shadow(0 0 5px #0002);border-radius:8px;background:transparent;border:none;display:block;" >
< / s p a n > ` ;
}
function getIcon ( type ) {
if ( type === 'tmdb' ) return imgIcon ( "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/tmdb.svg" , "TMDB" ) ;
if ( type === 'imdb' ) return imgIcon ( "https://www.svgrepo.com/show/349409/imdb.svg" , "IMDB" ) ;
return '' ;
}
// === TMDB fetch/cache (similaire)
function fetchTmdb ( type , id ) {
if ( ! id ) return Promise . resolve ( null ) ;
let cache = { } ;
try { cache = JSON . parse ( localStorage . getItem ( TMDB _CACHE _KEY ) ) || { } ; } catch { cache = { } ; }
const key = ` ${ type } _ ${ id } ` ;
const now = Date . now ( ) ;
if ( cache [ key ] && now - cache [ key ] . fetched < 24 * 60 * 60 * 1000 ) return Promise . resolve ( cache [ key ] . data ) ;
return fetch ( ` ${ location . origin } /proxy_tmdb?type= ${ type } &id= ${ id } ` )
. then ( r => r . json ( ) )
. then ( d => { cache [ key ] = { data : d , fetched : now } ; localStorage . setItem ( TMDB _CACHE _KEY , JSON . stringify ( cache ) ) ; return d ; } )
. catch ( ( ) => null ) ;
}
// === Détection section
const CATEGORIES = [
{ key : 'HOME' , label : 'Accueil' } ,
{ key : 'MOVIE' , label : 'Films' } ,
{ key : 'TV' , label : 'Séries/Mangas' }
] ;
function getSection ( ) {
const p = new URLSearchParams ( location . search ) ;
const s = p . get ( 'section' ) ;
const vm = p . get ( 'vm' ) ;
return ( ! s && ( vm === '1' || ! vm ) ) ? 'HOME' : ( s || '' ) . toUpperCase ( ) ;
}
// === Extract date/size
function extractDateAndSize ( card ) {
let date = '' , size = '' ;
card . querySelectorAll ( '.badge' ) . forEach ( b => {
const t = b . textContent . trim ( ) ;
if ( ! size && /([0-9]+(?:\.[0-9]+)?)( ?(GB|MB|G|M|Go|Mo))$/i . test ( t ) ) size = t ;
if ( ! date && /\d{2}\/\d{2}\/\d{2}/ . test ( t ) ) date = t . match ( /\d{2}\/\d{2}\/\d{2}/ ) [ 0 ] ;
} ) ;
return { date , size } ;
}
// === Overlay (profil qualité)
function showProfileSelector ( profiles , cb ) {
document . querySelectorAll ( '.usenet-qprof-popover' ) . forEach ( e => e . remove ( ) ) ;
let pop = document . createElement ( 'div' ) ;
pop . className = "usenet-qprof-popover" ;
pop . style = ` position:fixed;z-index:99999;top:70px;right:32px;background:#23233a;color:#ffe388;
border : 2 px solid # ffe388 ; border - radius : 12 px ; box - shadow : 0 3 px 16 px # 222 a ;
padding : 18 px 28 px ; min - width : 260 px ; ` ;
pop . innerHTML = ` <b>Profil Qualité :</b><br><br> ` ;
profiles . forEach ( prof => {
let btn = document . createElement ( 'button' ) ;
btn . textContent = prof . name ;
btn . style = ` display:block;width:100%;margin:6px 0;padding:10px 0;font-size:17px;border:none;background:#f9d72c;color:#222;font-weight:bold;border-radius:8px;cursor:pointer; ` ;
btn . onclick = ( ) => { pop . remove ( ) ; cb ( prof . id ) ; } ;
pop . appendChild ( btn ) ;
} ) ;
let cancel = document . createElement ( 'button' ) ;
cancel . textContent = "Annuler" ;
cancel . style = ` display:block;width:100%;margin:14px 0 0 0;padding:10px 0;font-size:15px;border:none;background:#ddd;color:#444;border-radius:8px;cursor:pointer; ` ;
cancel . onclick = ( ) => pop . remove ( ) ;
pop . appendChild ( cancel ) ;
document . body . appendChild ( pop ) ;
}
// === Affichage
function transformAffiches ( ) {
const section = getSection ( ) ;
const conf = getSections ( ) ;
const activate = conf . includes ( section ) ;
if ( ! CATEGORIES . some ( c => c . key === section ) || ! activate ) return ;
const cards = Array . from ( document . querySelectorAll ( '.containert.article .card.affichet' ) ) ;
const containers = document . querySelectorAll ( '.containert.article' ) ;
if ( ! cards . length || ! containers . length ) return ;
const groups = new Map ( ) ;
cards . forEach ( card => {
const href = card . querySelector ( 'a[href*="?d=fiche"]' ) ? . getAttribute ( 'href' ) || '' ;
let id = '' , type = 'movie' ;
let m = href . match ( /movieid=(\d+)/i ) ;
if ( m ) { id = m [ 1 ] ; }
else if ( ( m = href . match ( /tvid=(\d+)/i ) ) || ( m = href . match ( /mangaid=(\d+)/i ) ) ) { id = m [ 1 ] ; type = 'tv' ; }
else {
const inp = card . querySelector ( 'input#tmdb_id' ) ;
if ( inp ) id = inp . value ;
}
if ( ! id ) return ;
const key = ` ${ type } _ ${ id } ` ;
if ( ! groups . has ( key ) ) groups . set ( key , { type , id , cards : [ ] } ) ;
groups . get ( key ) . cards . push ( card ) ;
} ) ;
const gallery = Object . assign ( document . createElement ( 'div' ) , {
className : 'd-flex flex-wrap' ,
style : 'justify-content:center;margin-top:20px;gap:8px;padding:0 12px;width:100%;margin-left:auto;margin-right:auto;'
} ) ;
groups . forEach ( group => {
const card0 = group . cards [ 0 ] ;
const img0 = card0 . querySelector ( 'img.card-img-top' ) ;
if ( ! img0 ) return ;
const mH = img0 . height || 330 ;
const cardDiv = Object . assign ( document . createElement ( 'div' ) , { style : ` flex:0 0 ${ minW ( ) } px;max-width: ${ minW ( ) } px;position:relative;display:block; ` } ) ;
// === Badges TMDB/IMDB
if ( showTmdb ( ) ) {
fetchTmdb ( group . type , group . id ) . then ( data => {
if ( ! data ) return ;
const badgeWrap = Object . assign ( document . createElement ( 'div' ) , { style : 'position:absolute;top:7px;left:8px;display:flex;flex-direction:column;gap:3px;z-index:15;pointer-events:none;' } ) ;
function addBadge ( icon , score , votes , url , type = 'tmdb' ) {
if ( ! score || score === '?' ) return ;
const color = "#111" ;
const el = Object . assign ( document . createElement ( url ? 'a' : 'span' ) , {
innerHTML : `
< span style = "background:rgba(255,255,255,0.78);border-radius:9px;display:flex;align-items:center;padding:2px 10px 2px 6px;box-shadow:0 2px 8px #0002;" >
$ { getIcon ( type ) }
< span style = "font-size:19px;font-weight:bold;color:${color};text-shadow:0 1px 2px #fff9,0 1px 2px #2221;margin-left:2px;" > $ { score } < / s p a n >
< span style = "font-size:13px;font-weight:bold;color:${color};margin-left:5px;" > $ { votes } < / s p a n >
< / s p a n > ` ,
style : 'margin-bottom:5px;margin-right:3px;pointer-events:auto;text-decoration:none;'
} ) ;
if ( url ) { el . href = url ; el . target = '_blank' ; el . rel = 'noopener noreferrer' ; el . title = 'Voir la fiche' ; }
badgeWrap . appendChild ( el ) ;
}
addBadge ( 'tmdb' ,
data . vote _average ? Number ( data . vote _average ) . toFixed ( 1 ) : '?' ,
data . vote _count ? ` ( ${ data . vote _count } ) ` : '' ,
` https://www.themoviedb.org/ ${ group . type === 'tv' ? 'tv' : 'movie' } / ${ group . id } ` ,
'tmdb' ) ;
addBadge ( 'imdb' ,
data . note _imdb ? Number ( data . note _imdb ) . toFixed ( 1 ) : '?' ,
data . vote _imdb ? ` ( ${ data . vote _imdb } ) ` : '' ,
null ,
'imdb' ) ;
if ( badgeWrap . childElementCount ) cardDiv . appendChild ( badgeWrap ) ;
} ) ;
}
// === Image et overlay
const imgClone = img0 . cloneNode ( true ) ;
imgClone . style . width = ` ${ minW ( ) } px ` ;
imgClone . style . cursor = 'pointer' ;
cardDiv . appendChild ( imgClone ) ;
// === Overlay releases
const tooltip = Object . assign ( document . createElement ( 'div' ) , {
className : 'affiche-tooltip' ,
style : ` position:absolute;top:0;left:0;background:rgba(10,10,10,0.98);color:#fff;padding:22px 36px 26px 36px;border-radius:10px;font-size: ${ fontSz ( ) } px;font-weight:400;width: ${ Math . min ( innerWidth - 40 , 1150 ) } px;max-width:99vw;min-height: ${ mH } px;z-index:1010;box-shadow:0 0 14px 6px rgba(0,0,0,0.7);white-space:normal;display:none;pointer-events:auto;overflow:hidden; `
} ) ;
const adjustW = ( ) => {
tooltip . style . width = Math . min ( innerWidth - 40 , 1150 ) + 'px' ;
const cardRect = cardDiv . getBoundingClientRect ( ) ;
const overlayW = tooltip . offsetWidth || Math . min ( innerWidth - 40 , 1150 ) ;
let left = cardRect . left ;
if ( left + overlayW > window . innerWidth - 20 ) {
tooltip . style . left = '' ;
tooltip . style . right = '0' ;
} else {
tooltip . style . left = '0' ;
tooltip . style . right = '' ;
}
} ;
addEventListener ( 'resize' , adjustW ) ;
const typeLabel = group . type === 'tv' ? 'série' : 'film' ;
const typeGen = group . type === 'tv' ? 'la' : 'le' ;
tooltip . innerHTML = ` <div style="display:flex;align-items:center;justify-content:flex-start;margin-bottom:18px;width:100%;">
< span style = "font-size:${fontSz()}px;font-weight:600;color:#45e3ee;margin-right:24px;display:flex;align-items:center;" >
< span style = "font-size:${fontSz() + 2}px;vertical-align:middle;" > & # 8595 ; < / s p a n > & n b s p ; & n b s p ; L e s d e r n i è r e s r e l e a s e s & n b s p ; & n b s p ; < s p a n s t y l e = " f o n t - s i z e : $ { f o n t S z ( ) + 2 } p x ; v e r t i c a l - a l i g n : m i d d l e ; " > & # 8 5 9 5 ; < / s p a n >
< / s p a n >
< a href = "${card0.querySelector('a[href*=" ? d = fiche "]')?.href || '#'}" style = "color:#ffd04e;font-size:${fontSz() - 1}px;font-weight:600;text-decoration:none;" $ { openReleasesInNewTab ( ) ? ' target="_blank"' : '' } > Voir toutes les releases pour $ { typeGen } $ { typeLabel } < / a >
< / d i v > ` ;
// === Details releases
let html = '' ;
group . cards . forEach ( sub => {
const title = sub . querySelector ( '.card-header' ) ? . textContent . trim ( ) || '' ;
const { date , size } = extractDateAndSize ( sub ) ;
let bodyHTML = sub . querySelector ( '.card-body' ) ? . innerHTML || '' ;
let nfoHTML = '' ;
if ( bodyHTML ) {
const tmp = document . createElement ( 'div' ) ;
tmp . innerHTML = bodyHTML ;
const spans = tmp . querySelectorAll ( 'span.mx-1' ) ;
for ( const s of spans ) {
const a = s . querySelector ( 'a[data-target="#NFO"]' ) ;
if ( a ) { nfoHTML = s . outerHTML ; s . remove ( ) ; break ; }
}
Array . from ( tmp . querySelectorAll ( 'span.mx-1' ) ) . slice ( 4 ) . forEach ( s => s . remove ( ) ) ;
bodyHTML = Array . from ( tmp . childNodes ) . map ( x => x . outerHTML || '' ) . join ( '' ) ;
}
html += ` <div style="margin-bottom:12px;display:flex;align-items:center;gap:12px;">
< span style = "flex:3 1 70%;font-size:${fontSz()}px;font-weight:400;color:#cde5fc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title = "${title}" > $ { title } < / s p a n >
< span style = "margin-left:12px;font-size:${fontSz() - 2}px;font-weight:400;color:#b5dbff;white-space:nowrap;" > $ { size } $ { size && date ? ' • ' : '' } $ { date } < / s p a n >
< div style = "display:inline-flex;gap:10px;margin-left:12px;align-items:center;" > $ { bodyHTML } $ { nfoHTML } < / d i v >
< / d i v > ` ;
} ) ;
const wrap = document . createElement ( 'div' ) ;
wrap . className = 'affiche-releases' ;
wrap . style . maxHeight = group . cards . length > 10 ? '430px' : 'none' ;
wrap . style . overflowY = group . cards . length > 10 ? 'auto' : 'visible' ;
wrap . innerHTML = html ;
tooltip . appendChild ( wrap ) ;
setTimeout ( ( ) => { tooltip . style . minHeight = '' ; tooltip . style . height = '' ; tooltip . style . maxHeight = 'none' ; } , 10 ) ;
let open = false ;
imgClone . addEventListener ( 'click' , e => {
e . stopPropagation ( ) ;
adjustW ( ) ;
document . querySelectorAll ( '.affiche-tooltip' ) . forEach ( d => d . style . display = 'none' ) ;
tooltip . style . display = open ? 'none' : 'block' ;
open = ! open ;
} ) ;
document . addEventListener ( 'click' , e => {
if ( ! cardDiv . contains ( e . target ) ) {
tooltip . style . display = 'none' ;
open = false ;
}
} ) ;
cardDiv . appendChild ( tooltip ) ;
gallery . appendChild ( cardDiv ) ;
} ) ;
containers . forEach ( ( c , i ) => { c . innerHTML = '' ; if ( i === 0 ) c . appendChild ( gallery ) ; } ) ;
}
// === Menu config
function createMenu ( ) {
if ( document . getElementById ( 'affiche-mode-menu-container' ) ) return ;
const box = Object . assign ( document . createElement ( 'div' ) , {
id : 'affiche-mode-menu-container' ,
style : 'position:fixed;bottom:20px;right:190px;z-index:19999;padding:0;font-size:14px;color:#fff;'
} ) ;
const toggle = Object . assign ( document . createElement ( 'button' ) , {
textContent : '🎬 Mode Affiches Alternatif' ,
style : 'cursor:pointer;font-weight:bold;background:#309d98;color:#fff;border:none;border-radius:5px;padding:6px 12px;margin-bottom:8px;z-index:19999;display: block; margin-left: auto; margin-right: auto;'
} ) ;
toggle . addEventListener ( 'click' , ( ) => { menu . style . display = menu . style . display === 'none' ? 'block' : 'none' ; } ) ;
const menu = Object . assign ( document . createElement ( 'div' ) , {
style : 'display:none;margin-top:8px;z-index:20001;padding:12px 18px 16px 18px;background:#18181c;border:1px solid #444;border-radius:8px;box-shadow:0 2px 12px #000d;'
} ) ;
// === Sections à activer
const conf = getSections ( ) ;
CATEGORIES . forEach ( cat => {
const wrapper = document . createElement ( 'div' ) ;
const cb = Object . assign ( document . createElement ( 'input' ) , { type : 'checkbox' , id : ` chk_ ${ cat . key } ` , checked : conf . includes ( cat . key ) } ) ;
cb . addEventListener ( 'change' , ( ) => {
let newConf = getSections ( ) ;
if ( cb . checked && ! newConf . includes ( cat . key ) ) newConf . push ( cat . key ) ;
if ( ! cb . checked && newConf . includes ( cat . key ) ) newConf = newConf . filter ( x => x !== cat . key ) ;
setSections ( newConf ) ;
location . reload ( ) ;
} ) ;
const lab = Object . assign ( document . createElement ( 'label' ) , { textContent : cat . label , htmlFor : cb . id , style : 'margin-left:7px;color:#ffe388;' } ) ;
wrapper . appendChild ( cb ) ; wrapper . appendChild ( lab ) ; menu . appendChild ( wrapper ) ;
} ) ;
// === TMDB
const tmdbRow = document . createElement ( 'div' ) ; tmdbRow . style . marginTop = '14px' ;
const cbTmdb = Object . assign ( document . createElement ( 'input' ) , { type : 'checkbox' , id : 'chk_tmdb' , checked : showTmdb ( ) } ) ;
cbTmdb . addEventListener ( 'change' , ( ) => { localStorage . setItem ( STORAGE _SHOW _TMDB _KEY , cbTmdb . checked ? '1' : '0' ) ; location . reload ( ) ; } ) ;
const labTmdb = Object . assign ( document . createElement ( 'label' ) , { textContent : "Afficher la note TMDB sur l'affiche" , htmlFor : cbTmdb . id , style : 'margin-left:7px;color:#ffe388;' } ) ;
tmdbRow . appendChild ( cbTmdb ) ; tmdbRow . appendChild ( labTmdb ) ; menu . appendChild ( tmdbRow ) ;
// === Taille affiche
const sizeRow = Object . assign ( document . createElement ( 'div' ) , { textContent : 'Taille des affiches :' , style : 'margin-top:14px;margin-bottom:2px;color:#ffe388;' } ) ;
const sliderSize = Object . assign ( document . createElement ( 'input' ) , { type : 'range' , min : '200' , max : '360' , step : '10' , value : minW ( ) , style : 'width:180px;vertical-align:middle;' } ) ;
const spanSize = Object . assign ( document . createElement ( 'span' ) , { textContent : ` ${ minW ( ) } px ` , style : 'margin-left:8px;color:#ffe388;' } ) ;
sliderSize . addEventListener ( 'input' , ( ) => { spanSize . textContent = ` ${ sliderSize . value } px ` ; } ) ;
sliderSize . addEventListener ( 'change' , ( ) => { localStorage . setItem ( STORAGE _MINWIDTH _KEY , sliderSize . value ) ; location . reload ( ) ; } ) ;
menu . appendChild ( sizeRow ) ; menu . appendChild ( sliderSize ) ; menu . appendChild ( spanSize ) ;
// === Police overlay
const fontRow = Object . assign ( document . createElement ( 'div' ) , { textContent : 'Taille du texte (overlay) :' , style : 'margin-top:16px;margin-bottom:2px;color:#ffe388;' } ) ;
const sliderFont = Object . assign ( document . createElement ( 'input' ) , { type : 'range' , min : '14' , max : '28' , step : '2' , value : fontSz ( ) , style : 'width:140px;vertical-align:middle;' } ) ;
const spanFont = Object . assign ( document . createElement ( 'span' ) , { textContent : ` ${ fontSz ( ) } px ` , style : 'margin-left:8px;color:#ffe388;' } ) ;
sliderFont . addEventListener ( 'input' , ( ) => { spanFont . textContent = ` ${ sliderFont . value } px ` ; document . querySelectorAll ( '.affiche-tooltip' ) . forEach ( d => d . style . fontSize = sliderFont . value + 'px' ) ; } ) ;
sliderFont . addEventListener ( 'change' , ( ) => { localStorage . setItem ( STORAGE _FONT _SIZE _KEY , sliderFont . value ) ; location . reload ( ) ; } ) ;
menu . appendChild ( fontRow ) ; menu . appendChild ( sliderFont ) ; menu . appendChild ( spanFont ) ;
// === Option nouvel onglet releases
const newTabRow = document . createElement ( 'div' ) ; newTabRow . style . marginTop = '14px' ;
const cbNewTab = Object . assign ( document . createElement ( 'input' ) , { type : 'checkbox' , id : 'chk_newtab' , checked : openReleasesInNewTab ( ) } ) ;
cbNewTab . addEventListener ( 'change' , ( ) => { localStorage . setItem ( STORAGE _RELEASE _NEWTAB _KEY , cbNewTab . checked ? '1' : '0' ) ; location . reload ( ) ; } ) ;
2025-07-21 09:38:15 +00:00
const labNewTab = Object . assign ( document . createElement ( 'label' ) , { textContent : "Ouvrir « Voir toutes les releases » dans un nouvel onglet" , htmlFor : cbNewTab . id , style : 'margin-left:7px;color:#ffe388;' } ) ;
2025-07-21 09:36:14 +00:00
newTabRow . appendChild ( cbNewTab ) ; newTabRow . appendChild ( labNewTab ) ; menu . appendChild ( newTabRow ) ;
box . appendChild ( toggle ) ; box . appendChild ( menu ) ;
document . body . appendChild ( box ) ;
// Haut de page
if ( ! document . getElementById ( 'remonter-haut-btn' ) ) {
const up = Object . assign ( document . createElement ( 'button' ) , { id : 'remonter-haut-btn' , textContent : '↑ Haut de page' , style : 'position:fixed;bottom:22px;right:26px;background:#309d98;color:#fff;border:none;border-radius:6px;padding:8px 20px;font-weight:bold;font-size:16px;cursor:pointer;box-shadow:0 3px 12px rgba(0,0,0,0.14);z-index:99999;' } ) ;
up . addEventListener ( 'click' , ( ) => scrollTo ( { top : 0 , behavior : 'smooth' } ) ) ;
document . body . appendChild ( up ) ;
}
}
// === Init
function start ( ) { createMenu ( ) ; transformAffiches ( ) ; }
if ( document . readyState !== 'loading' ) start ( ) ;
else document . addEventListener ( 'DOMContentLoaded' , start ) ;
} ) ( ) ;