{"id":31793,"date":"2026-08-01T12:46:52","date_gmt":"2026-08-01T16:46:52","guid":{"rendered":"https:\/\/allianceenjesus.com\/?page_id=31793"},"modified":"2026-08-01T18:26:18","modified_gmt":"2026-08-01T22:26:18","slug":"films-au-cinema","status":"publish","type":"page","link":"https:\/\/allianceenjesus.com\/?page_id=31793","title":{"rendered":"Films au cin\u00e9ma"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"31793\" class=\"elementor elementor-31793\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-d7523ed elementor-section-full_width ang-section-padding-initial elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no elementor-repeater-item-none elementor-repeater-item-none_hover wpr-column-slider-no wpr-equal-height-no\" data-id=\"d7523ed\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-70c7420 elementor-repeater-item-none elementor-repeater-item-none_hover\" data-id=\"70c7420\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap\">\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-j6jr01l elementor-section-content-middle elementor-section-height-full elementor-section-full_width ang-section-padding-initial elementor-section-height-default elementor-section-items-middle wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no elementor-repeater-item-none elementor-repeater-item-none_hover wpr-column-slider-no wpr-equal-height-no\" data-id=\"j6jr01l\" data-element_type=\"section\" data-e-type=\"section\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-b1eae7d elementor-repeater-item-none elementor-repeater-item-none_hover\" data-id=\"b1eae7d\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-e239bc3 elementor-repeater-item-none elementor-repeater-item-none_hover elementor-widget elementor-widget-spacer\" data-id=\"e239bc3\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-28f8f35 animated-slow e-transform e-transform elementor-repeater-item-none elementor-repeater-item-none_hover elementor-invisible elementor-widget elementor-widget-heading\" data-id=\"28f8f35\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;_animation&quot;:&quot;flash&quot;,&quot;_animation_delay&quot;:2000,&quot;_transform_translateX_effect&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:7,&quot;sizes&quot;:[]},&quot;_transform_translateY_effect&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:156,&quot;sizes&quot;:[]},&quot;_transform_translateX_effect_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_translateX_effect_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_translateY_effect_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_translateY_effect_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_skewX_effect&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_skewX_effect_tablet&quot;:{&quot;unit&quot;:&quot;deg&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_skewX_effect_mobile&quot;:{&quot;unit&quot;:&quot;deg&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_skewY_effect&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_skewY_effect_tablet&quot;:{&quot;unit&quot;:&quot;deg&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_skewY_effect_mobile&quot;:{&quot;unit&quot;:&quot;deg&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]}}\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\"><a href=\"http:\/\/www.alliance-en-j\u00e9sus.com\" target=\"_blank\" rel=\"noopener\">Bienvenue sur <br>Alliance en J\u00e9sus<\/a><\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2a00edd e-transform e-transform elementor-repeater-item-none elementor-repeater-item-none_hover elementor-invisible elementor-widget elementor-widget-heading\" data-id=\"2a00edd\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;_animation&quot;:&quot;fadeIn&quot;,&quot;_transform_translateX_effect&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:0,&quot;sizes&quot;:[]},&quot;_transform_translateY_effect&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:-153,&quot;sizes&quot;:[]},&quot;_transform_rotateZ_effect&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_rotateZ_effect_tablet&quot;:{&quot;unit&quot;:&quot;deg&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_rotateZ_effect_mobile&quot;:{&quot;unit&quot;:&quot;deg&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_translateX_effect_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_translateX_effect_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_translateY_effect_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;_transform_translateY_effect_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]}}\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\"><a href=\"http:\/\/www.alliance-en-j\u00e9sus.com\" target=\"_blank\" rel=\"noopener\">Bienvenue sur <br>Alliance en J\u00e9sus<\/a><\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-66f7555 elementor-section-boxed ang-section-padding-initial elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no elementor-repeater-item-none elementor-repeater-item-none_hover wpr-column-slider-no wpr-equal-height-no\" data-id=\"66f7555\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-1f90a72 elementor-repeater-item-none elementor-repeater-item-none_hover\" data-id=\"1f90a72\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-8eacd35 elementor-repeater-item-none elementor-repeater-item-none_hover elementor-widget elementor-widget-spacer\" data-id=\"8eacd35\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-d3f6e90 elementor-section-boxed ang-section-padding-initial elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no elementor-repeater-item-none elementor-repeater-item-none_hover wpr-column-slider-no wpr-equal-height-no\" data-id=\"d3f6e90\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-7d44ab9 elementor-repeater-item-none elementor-repeater-item-none_hover\" data-id=\"7d44ab9\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-692c640 elementor-repeater-item-none elementor-repeater-item-none_hover elementor-widget elementor-widget-spacer\" data-id=\"692c640\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t<div class=\"elementor-element elementor-element-b8c6a5a e-con-full e-flex elementor-repeater-item-default_padding elementor-repeater-item-none elementor-repeater-item-none_hover wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-parent\" data-id=\"b8c6a5a\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;ang_container_spacing_size&quot;:&quot;default_padding&quot;}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-d66ed5e elementor-repeater-item-none elementor-repeater-item-none_hover elementor-widget elementor-widget-html\" data-id=\"d66ed5e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html lang=\"fr\">\r\n<head>\r\n<meta charset=\"UTF-8\">\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n<title>Catalogue Films - Wikidata<\/title>\r\n\r\n<style>\r\n\r\n* { box-sizing: border-box; }\r\n\r\nbody {\r\n    margin: 0;\r\n    font-family: Arial, Helvetica, sans-serif;\r\n    background: #0b0b0d;\r\n    color: #fff;\r\n}\r\n\r\n.app {\r\n    max-width: 1400px;\r\n    margin: auto;\r\n    padding: 25px;\r\n}\r\n\r\nheader { text-align: center; margin-bottom: 25px; }\r\nheader h1 { margin: 0 0 10px; }\r\nheader p { opacity: .7; }\r\n\r\n.search-box {\r\n    background: #1d1d1d;\r\n    border-radius: 14px;\r\n    padding: 20px;\r\n    margin-bottom: 20px;\r\n}\r\n\r\n.field-row {\r\n    display: grid;\r\n    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\r\n    gap: 12px;\r\n    margin-bottom: 16px;\r\n}\r\n\r\n.field-row:last-child { margin-bottom: 0; }\r\n\r\n.field-row.search-row {\r\n    grid-template-columns: 3fr 1.4fr auto;\r\n}\r\n\r\n.field label {\r\n    display: block;\r\n    font-size: 11.5px;\r\n    text-transform: uppercase;\r\n    letter-spacing: .4px;\r\n    opacity: .55;\r\n    margin-bottom: 5px;\r\n}\r\n\r\ninput, select {\r\n    width: 100%;\r\n    padding: 12px;\r\n    border-radius: 8px;\r\n    border: 1px solid #444;\r\n    background: #111;\r\n    color: #fff;\r\n    font-size: 14px;\r\n}\r\n\r\nbutton {\r\n    padding: 12px 18px;\r\n    border: 0;\r\n    border-radius: 8px;\r\n    background: #f0c400;\r\n    color: #111;\r\n    font-weight: bold;\r\n    cursor: pointer;\r\n}\r\n\r\nbutton:hover { opacity: .85; }\r\nbutton:disabled { opacity: .4; cursor: not-allowed; }\r\n\r\n.toggle-filter {\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 10px;\r\n    padding: 12px;\r\n    border-radius: 8px;\r\n    border: 1px solid #444;\r\n    background: #111;\r\n    cursor: pointer;\r\n    user-select: none;\r\n}\r\n\r\n.toggle-filter input {\r\n    width: 18px;\r\n    height: 18px;\r\n    accent-color: #f0c400;\r\n    cursor: pointer;\r\n}\r\n\r\n.toggle-filter span { font-size: 13.5px; }\r\n\r\n.lexical-box {\r\n    border: 1px solid #444;\r\n    border-radius: 8px;\r\n    padding: 12px;\r\n    background: #111;\r\n}\r\n\r\n.lexical-box legend {\r\n    font-size: 11.5px;\r\n    text-transform: uppercase;\r\n    letter-spacing: .4px;\r\n    opacity: .65;\r\n    padding: 0 6px;\r\n}\r\n\r\n.lexical-options {\r\n    display: flex;\r\n    flex-wrap: wrap;\r\n    gap: 12px;\r\n    margin-top: 6px;\r\n}\r\n\r\n.lexical-options label {\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 6px;\r\n    font-size: 13px;\r\n    cursor: pointer;\r\n}\r\n\r\n.lexical-options input {\r\n    width: 16px;\r\n    height: 16px;\r\n    accent-color: #f0c400;\r\n    cursor: pointer;\r\n}\r\n\r\n.status { margin: 20px 0; text-align: center; min-height: 25px; }\r\n\r\n.progress {\r\n    width: 100%;\r\n    height: 8px;\r\n    background: #333;\r\n    border-radius: 10px;\r\n    overflow: hidden;\r\n    margin-top: 15px;\r\n}\r\n\r\n.progress-bar {\r\n    width: 0%;\r\n    height: 100%;\r\n    background: #f0c400;\r\n    transition: width .2s;\r\n}\r\n\r\n.results {\r\n    display: grid;\r\n    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\r\n    gap: 24px;\r\n}\r\n\r\n.card {\r\n    position: relative;\r\n    background: #1a1a1d;\r\n    border-radius: 14px;\r\n    overflow: hidden;\r\n    box-shadow: 0 4px 14px rgba(0,0,0,.4);\r\n    transition: transform .25s ease, box-shadow .25s ease;\r\n    display: flex;\r\n    flex-direction: column;\r\n}\r\n\r\n.card:hover {\r\n    transform: translateY(-6px);\r\n    box-shadow: 0 14px 30px rgba(0,0,0,.6);\r\n}\r\n\r\n.poster-wrap {\r\n    position: relative;\r\n    width: 100%;\r\n    aspect-ratio: 2\/3;\r\n    background: #222;\r\n    overflow: hidden;\r\n}\r\n\r\n.poster {\r\n    width: 100%;\r\n    height: 100%;\r\n    object-fit: cover;\r\n    display: block;\r\n    transition: transform .35s ease;\r\n}\r\n\r\n.card:hover .poster { transform: scale(1.06); }\r\n\r\n.year-badge {\r\n    position: absolute;\r\n    top: 10px;\r\n    left: 10px;\r\n    background: rgba(0,0,0,.65);\r\n    backdrop-filter: blur(3px);\r\n    padding: 4px 10px;\r\n    border-radius: 20px;\r\n    font-size: 12px;\r\n    font-weight: bold;\r\n}\r\n\r\n.religious-badge {\r\n    position: absolute;\r\n    top: 10px;\r\n    right: 10px;\r\n    background: rgba(240,196,0,.9);\r\n    color: #111;\r\n    padding: 4px 10px;\r\n    border-radius: 20px;\r\n    font-size: 11px;\r\n    font-weight: bold;\r\n}\r\n\r\n.poster-overlay {\r\n    position: absolute;\r\n    inset: 0;\r\n    background: linear-gradient(to top, rgba(0,0,0,.92) 0%, rgba(0,0,0,.15) 55%, rgba(0,0,0,0) 100%);\r\n    opacity: 0;\r\n    transition: opacity .25s ease;\r\n    display: flex;\r\n    align-items: flex-end;\r\n    padding: 14px;\r\n}\r\n\r\n.card:hover .poster-overlay { opacity: 1; }\r\n\r\n.overlay-btn {\r\n    display: inline-block;\r\n    width: 100%;\r\n    text-align: center;\r\n    padding: 10px;\r\n    background: #f0c400;\r\n    color: #111;\r\n    font-weight: bold;\r\n    text-decoration: none;\r\n    border-radius: 8px;\r\n    font-size: 13px;\r\n}\r\n\r\n.card-content {\r\n    padding: 14px 16px 16px;\r\n    flex: 1;\r\n    display: flex;\r\n    flex-direction: column;\r\n    gap: 6px;\r\n}\r\n\r\n.card h3 {\r\n    margin: 0;\r\n    font-size: 15px;\r\n    line-height: 1.3;\r\n    display: -webkit-box;\r\n    -webkit-line-clamp: 2;\r\n    -webkit-box-orient: vertical;\r\n    overflow: hidden;\r\n}\r\n\r\n.meta-line {\r\n    font-size: 12.5px;\r\n    color: rgba(255,255,255,.65);\r\n    line-height: 1.5;\r\n    display: -webkit-box;\r\n    -webkit-line-clamp: 2;\r\n    -webkit-box-orient: vertical;\r\n    overflow: hidden;\r\n}\r\n\r\n.meta-line b { color: rgba(255,255,255,.9); }\r\n\r\n.card-links {\r\n    margin-top: auto;\r\n    display: flex;\r\n    gap: 8px;\r\n    padding-top: 8px;\r\n}\r\n\r\n.link-primary, .link-secondary {\r\n    text-decoration: none;\r\n    text-align: center;\r\n    border-radius: 7px;\r\n    font-size: 12.5px;\r\n    font-weight: bold;\r\n    padding: 9px 6px;\r\n}\r\n\r\n.link-primary { flex: 2; background: #f0c400; color: #111; }\r\n.link-secondary { flex: 1; background: #2c2c30; color: #fff; }\r\n.link-primary:hover, .link-secondary:hover { opacity: .85; }\r\n\r\n.pagination {\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n    gap: 15px;\r\n    margin-top: 30px;\r\n    flex-wrap: wrap;\r\n}\r\n\r\n@media (max-width: 700px) {\r\n    .field-row.search-row { grid-template-columns: 1fr; }\r\n}\r\n\r\n<\/style>\r\n<\/head>\r\n\r\n<body>\r\n<div class=\"app\">\r\n\r\n<header>\r\n<h1>\ud83c\udfac Catalogue Films - Wikidata<\/h1>\r\n<p>Recherche directe dans Wikidata \u2014 aucune base locale<\/p>\r\n<\/header>\r\n\r\n<section class=\"search-box\">\r\n\r\n<div class=\"field-row search-row\">\r\n\r\n<div class=\"field\">\r\n<label for=\"search\">Recherche libre<\/label>\r\n<input id=\"search\" type=\"search\" placeholder=\"Mot ou partie de mot...\">\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label for=\"searchCategory\">Rechercher dans<\/label>\r\n<select id=\"searchCategory\">\r\n<option value=\"title\" selected>Titre<\/option>\r\n<option value=\"director\">R\u00e9alisateur<\/option>\r\n<option value=\"cast\">Acteur<\/option>\r\n<option value=\"all\">Titre + r\u00e9alisateur + acteur<\/option>\r\n<\/select>\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label>&nbsp;<\/label>\r\n<button id=\"searchButton\">\ud83d\udd0e Rechercher<\/button>\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field-row\">\r\n\r\n<div class=\"field\">\r\n<label for=\"dateFilter\">Date de sortie<\/label>\r\n<select id=\"dateFilter\">\r\n<option value=\"all\">Toutes les dates<\/option>\r\n<option value=\"with\">Avec date de sortie<\/option>\r\n<option value=\"without\">Sans date de sortie<\/option>\r\n<\/select>\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label for=\"day\">Derniers jours<\/label>\r\n<input id=\"day\" type=\"number\" placeholder=\"Ex : 7\" min=\"1\">\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label for=\"month\">Derniers mois<\/label>\r\n<input id=\"month\" type=\"number\" placeholder=\"Ex : 7\" min=\"1\">\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label for=\"year\">Ann\u00e9e (2026) ou N derni\u00e8res ann\u00e9es (3)<\/label>\r\n<input id=\"year\" type=\"number\" placeholder=\"Ex : 2026 ou 3\" min=\"1\">\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label for=\"yearFrom\">Ann\u00e9e de (fourchette)<\/label>\r\n<input id=\"yearFrom\" type=\"number\" placeholder=\"De\" min=\"1\">\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label for=\"yearTo\">Ann\u00e9e \u00e0 (fourchette)<\/label>\r\n<input id=\"yearTo\" type=\"number\" placeholder=\"\u00c0\" min=\"1\">\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field-row\">\r\n\r\n<div class=\"field\">\r\n<label for=\"dateFrom\">Date pr\u00e9cise de d\u00e9but<\/label>\r\n<input id=\"dateFrom\" type=\"date\">\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label for=\"dateTo\">Date pr\u00e9cise de fin<\/label>\r\n<input id=\"dateTo\" type=\"date\">\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field-row\">\r\n\r\n<div class=\"field\">\r\n<label for=\"titleFilter\">Titre<\/label>\r\n<select id=\"titleFilter\">\r\n<option value=\"all\">Tous les films<\/option>\r\n<option value=\"with\">Avec un titre<\/option>\r\n<option value=\"without\">Sans titre<\/option>\r\n<\/select>\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label for=\"typeFilter\">Type<\/label>\r\n<select id=\"typeFilter\">\r\n<option value=\"film\">Films uniquement<\/option>\r\n<option value=\"all\">Films et \u0153uvres audiovisuelles<\/option>\r\n<\/select>\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label for=\"language\">Langue du titre<\/label>\r\n<select id=\"language\">\r\n<option value=\"fr\" selected>\ud83c\uddeb\ud83c\uddf7 Fran\u00e7ais<\/option>\r\n<option value=\"en\">\ud83c\uddec\ud83c\udde7 Anglais<\/option>\r\n<option value=\"es\">\ud83c\uddea\ud83c\uddf8 Espagnol<\/option>\r\n<option value=\"de\">\ud83c\udde9\ud83c\uddea Allemand<\/option>\r\n<option value=\"it\">\ud83c\uddee\ud83c\uddf9 Italien<\/option>\r\n<option value=\"pt\">\ud83c\uddf5\ud83c\uddf9 Portugais<\/option>\r\n<option value=\"nl\">\ud83c\uddf3\ud83c\uddf1 N\u00e9erlandais<\/option>\r\n<option value=\"ar\">\ud83c\uddf8\ud83c\udde6 Arabe<\/option>\r\n<\/select>\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label for=\"sort\">Tri<\/label>\r\n<select id=\"sort\">\r\n<option value=\"newest\">Plus r\u00e9cents<\/option>\r\n<option value=\"oldest\">Plus anciens<\/option>\r\n<option value=\"title\">Titre A-Z<\/option>\r\n<\/select>\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label for=\"pageSize\">R\u00e9sultats par page<\/label>\r\n<select id=\"pageSize\">\r\n<option value=\"20\" selected>20<\/option>\r\n<option value=\"50\">50<\/option>\r\n<option value=\"100\">100<\/option>\r\n<\/select>\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label for=\"maxResults\">Nombre maxi de r\u00e9sultats<\/label>\r\n<input id=\"maxResults\" type=\"number\" placeholder=\"Illimit\u00e9\" min=\"1\">\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field-row\">\r\n\r\n<label class=\"toggle-filter\" for=\"religiousFilter\">\r\n<input type=\"checkbox\" id=\"religiousFilter\" checked>\r\n<span>\u271d\ufe0f Films chr\u00e9tiens \/ religieux (genre \/ sujet Wikidata)<\/span>\r\n<\/label>\r\n\r\n<fieldset class=\"lexical-box\">\r\n<legend>\u00c9largir la recherche (mots-cl\u00e9s dans le titre)<\/legend>\r\n<div class=\"lexical-options\">\r\n<label><input type=\"checkbox\" class=\"lexicalKeyword\" value=\"chr\u00e9tien,christian\"> Chr\u00e9tien<\/label>\r\n<label><input type=\"checkbox\" class=\"lexicalKeyword\" value=\"religieux,religious\"> Religieux<\/label>\r\n<label><input type=\"checkbox\" class=\"lexicalKeyword\" value=\"biblique,biblical,bible\"> Biblique<\/label>\r\n<label><input type=\"checkbox\" class=\"lexicalKeyword\" value=\"spirituel,spiritual\"> Spirituel<\/label>\r\n<\/div>\r\n<input\r\n    id=\"lexicalCustom\"\r\n    type=\"text\"\r\n    placeholder=\"Autres mots-cl\u00e9s, s\u00e9par\u00e9s par une virgule (ex : messe, pri\u00e8re)\"\r\n    style=\"margin-top:8px;\"\r\n>\r\n<\/fieldset>\r\n\r\n<div class=\"field\">\r\n<label>&nbsp;<\/label>\r\n<button id=\"cancelButton\" disabled>\u23f9 Annuler<\/button>\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label>&nbsp;<\/label>\r\n<button id=\"resetButton\">\ud83e\uddf9 R\u00e9initialiser<\/button>\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field-row\">\r\n\r\n<div class=\"field\">\r\n<label for=\"pageInput\">Aller \u00e0 la page<\/label>\r\n<input id=\"pageInput\" type=\"number\" min=\"1\" value=\"1\">\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label>&nbsp;<\/label>\r\n<button id=\"pageButton\">\ud83d\udcc4 Aller \u00e0 la page<\/button>\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label>&nbsp;<\/label>\r\n<button id=\"exportButton\">\u2b07\ufe0f Exporter CSV<\/button>\r\n<\/div>\r\n\r\n<div class=\"field\">\r\n<label>&nbsp;<\/label>\r\n<button id=\"reloadButton\">\ud83d\udd04 Rechercher<\/button>\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"progress\"><div id=\"progressBar\" class=\"progress-bar\"><\/div><\/div>\r\n\r\n<\/section>\r\n\r\n<div id=\"status\" class=\"status\">Pr\u00eat.<\/div>\r\n\r\n<div id=\"results\" class=\"results\"><\/div>\r\n\r\n<div class=\"pagination\">\r\n<button id=\"previousButton\">\u25c0 Pr\u00e9c\u00e9dent<\/button>\r\n<span id=\"pageInfo\">Page 1 \/ 1<\/span>\r\n<button id=\"nextButton\">Suivant \u25b6<\/button>\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n<script>\r\n\r\n\/* =========================================================\r\n   CONFIGURATION\r\n========================================================= *\/\r\n\r\nconst WIKIDATA_ENDPOINT = \"https:\/\/query.wikidata.org\/sparql\";\r\n\r\n\/\/ Genre \"Christian film\" \/ \"Bible film\"\r\nconst RELIGIOUS_GENRE_IDS = [\"wd:Q115122793\", \"wd:Q7717463\"];\r\n\r\n\/\/ Sujet principal : Christianisme \/ Bible \/ J\u00e9sus \/ religion\r\nconst RELIGIOUS_SUBJECT_IDS = [\"wd:Q5043\", \"wd:Q1845\", \"wd:Q302\", \"wd:Q9174\"];\r\n\r\n\/\/ Plafond de comptage : au-del\u00e0, on affiche \"N+ r\u00e9sultats\"\r\n\/\/ plut\u00f4t que de lancer un COUNT potentiellement tr\u00e8s co\u00fbteux\r\n\/\/ sur toute la base.\r\nconst COUNT_CAP = 3000;\r\n\r\n\/\/ Image utilis\u00e9e quand un film n'a pas d'affiche sur Wikidata.\r\n\/\/ G\u00e9n\u00e9r\u00e9e en SVG (pas de d\u00e9pendance r\u00e9seau, toujours disponible).\r\nconst DEFAULT_POSTER =\r\n    \"data:image\/svg+xml,\" +\r\n    encodeURIComponent(\r\n        '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 400 600\">' +\r\n        '<defs><linearGradient id=\"g\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">' +\r\n        '<stop offset=\"0\" stop-color=\"#34343e\"\/>' +\r\n        '<stop offset=\"1\" stop-color=\"#131316\"\/>' +\r\n        '<\/linearGradient><\/defs>' +\r\n        '<rect width=\"400\" height=\"600\" fill=\"url(#g)\"\/>' +\r\n        '<circle cx=\"200\" cy=\"248\" r=\"74\" fill=\"none\" stroke=\"#f0c400\" stroke-width=\"6\" opacity=\"0.9\"\/>' +\r\n        '<polygon points=\"184,210 184,286 250,248\" fill=\"#f0c400\" opacity=\"0.95\"\/>' +\r\n        '<text x=\"200\" y=\"412\" text-anchor=\"middle\" font-family=\"Arial, Helvetica, sans-serif\" ' +\r\n        'font-size=\"42\" font-weight=\"bold\" letter-spacing=\"10\" fill=\"#ffffff\" opacity=\"0.9\">FILM<\/text>' +\r\n        '<\/svg>'\r\n    );\r\n\r\n\/* =========================================================\r\n   VARIABLES\r\n========================================================= *\/\r\n\r\nlet currentPage = 1;\r\nlet totalPages = 1;\r\nlet totalKnown = true;\r\nlet hasNextPageUnknown = false;\r\nlet currentResults = [];\r\nlet abortController = null;\r\nlet isLoading = false;\r\n\r\n\/* =========================================================\r\n   ELEMENTS\r\n========================================================= *\/\r\n\r\nconst searchInput = document.getElementById(\"search\");\r\nconst searchCategory = document.getElementById(\"searchCategory\");\r\nconst dayInput = document.getElementById(\"day\");\r\nconst monthInput = document.getElementById(\"month\");\r\nconst yearInput = document.getElementById(\"year\");\r\nconst yearFromInput = document.getElementById(\"yearFrom\");\r\nconst yearToInput = document.getElementById(\"yearTo\");\r\nconst dateFromInput = document.getElementById(\"dateFrom\");\r\nconst dateToInput = document.getElementById(\"dateTo\");\r\nconst titleFilter = document.getElementById(\"titleFilter\");\r\nconst dateFilter = document.getElementById(\"dateFilter\");\r\nconst sortSelect = document.getElementById(\"sort\");\r\nconst typeFilter = document.getElementById(\"typeFilter\");\r\nconst religiousFilter = document.getElementById(\"religiousFilter\");\r\nconst lexicalCustomInput = document.getElementById(\"lexicalCustom\");\r\nconst languageSelect = document.getElementById(\"language\");\r\nconst pageSizeSelect = document.getElementById(\"pageSize\");\r\nconst maxResultsInput = document.getElementById(\"maxResults\");\r\nconst statusElement = document.getElementById(\"status\");\r\nconst resultsElement = document.getElementById(\"results\");\r\nconst progressBar = document.getElementById(\"progressBar\");\r\nconst cancelButton = document.getElementById(\"cancelButton\");\r\nconst pageInput = document.getElementById(\"pageInput\");\r\nconst previousButton = document.getElementById(\"previousButton\");\r\nconst nextButton = document.getElementById(\"nextButton\");\r\nconst pageInfo = document.getElementById(\"pageInfo\");\r\n\r\n\/* =========================================================\r\n   BLOC DE CONDITIONS PARTAGE\r\n   (utilis\u00e9 \u00e0 la fois par la requ\u00eate de comptage et par la\r\n   requ\u00eate de liste, pour ne jamais les laisser diverger)\r\n========================================================= *\/\r\n\r\n\/* =========================================================\r\n   UTILITAIRES DATE (fen\u00eatres glissantes \"N derniers jours \/\r\n   mois \/ ann\u00e9es\")\r\n========================================================= *\/\r\n\r\nfunction daysAgo(n) {\r\n    const d = new Date();\r\n    d.setDate(d.getDate() - n);\r\n    return d;\r\n}\r\n\r\nfunction monthsAgo(n) {\r\n    const d = new Date();\r\n    d.setMonth(d.getMonth() - n);\r\n    return d;\r\n}\r\n\r\nfunction yearsAgo(n) {\r\n    const d = new Date();\r\n    d.setFullYear(d.getFullYear() - n);\r\n    return d;\r\n}\r\n\r\nfunction buildWhereClause() {\r\n\r\n    const search = searchInput.value.trim();\r\n    const day = dayInput.value.trim();\r\n    const month = monthInput.value.trim();\r\n    const year = yearInput.value.trim();\r\n    const yearFrom = yearFromInput.value.trim();\r\n    const yearTo = yearToInput.value.trim();\r\n    const language = languageSelect.value || \"fr\";\r\n\r\n    \/\/ --- LABEL (langue choisie, fran\u00e7ais par d\u00e9faut) ---\r\n    const labelCondition = `\r\n        ?item rdfs:label ?itemLabel .\r\n        FILTER(LANG(?itemLabel) = \"${language}\")\r\n    `;\r\n\r\n    \/\/ --- TYPE ---\r\n    let typeCondition = `?item wdt:P31 wd:Q11424 .`;\r\n\r\n    if (typeFilter.value === \"all\") {\r\n        typeCondition = `\r\n            { ?item wdt:P31 wd:Q11424 . }\r\n            UNION\r\n            { ?item wdt:P31 ?type . ?type wdt:P279* wd:Q11424 . }\r\n        `;\r\n    }\r\n\r\n    \/\/ --- THEME RELIGIEUX (filtre structur\u00e9 + mots-cl\u00e9s) ---\r\n    const lexicalKeywords =\r\n        Array.from(document.querySelectorAll(\".lexicalKeyword:checked\"))\r\n            .flatMap(cb => cb.value.split(\",\"));\r\n\r\n    const customKeywordsRaw = lexicalCustomInput.value.trim();\r\n\r\n    if (customKeywordsRaw) {\r\n        customKeywordsRaw\r\n            .split(\",\")\r\n            .map(word => word.trim())\r\n            .filter(Boolean)\r\n            .forEach(word => lexicalKeywords.push(word));\r\n    }\r\n\r\n    const hasLexicalFilter = lexicalKeywords.length > 0;\r\n\r\n    let religiousCondition = \"\";\r\n\r\n    if (religiousFilter.checked || hasLexicalFilter) {\r\n\r\n        const branches = [];\r\n\r\n        if (religiousFilter.checked) {\r\n            branches.push(`\r\n                {\r\n                    ?item wdt:P136 ?religiousGenre .\r\n                    VALUES ?religiousGenre { ${RELIGIOUS_GENRE_IDS.join(\" \")} }\r\n                }\r\n            `);\r\n            branches.push(`\r\n                {\r\n                    ?item wdt:P921 ?religiousSubject .\r\n                    VALUES ?religiousSubject { ${RELIGIOUS_SUBJECT_IDS.join(\" \")} }\r\n                }\r\n            `);\r\n        }\r\n\r\n        if (hasLexicalFilter) {\r\n            const lexicalOr = lexicalKeywords\r\n                .map(word => `CONTAINS(LCASE(STR(?itemLabel)), LCASE(\"${word.replace(\/\"\/g, '\\\\\"')}\"))`)\r\n                .join(\" || \");\r\n\r\n            branches.push(`{ FILTER(${lexicalOr}) }`);\r\n        }\r\n\r\n        religiousCondition = branches.join(\" UNION \");\r\n    }\r\n\r\n    \/\/ --- RECHERCHE PAR CATEGORIE ---\r\n    let searchCondition = \"\";\r\n\r\n    if (search) {\r\n\r\n        const safeSearch = search.replace(\/\"\/g, '\\\\\"');\r\n        const category = searchCategory.value || \"title\";\r\n\r\n        const titleMatch = `CONTAINS(LCASE(STR(?itemLabel)), LCASE(\"${safeSearch}\"))`;\r\n\r\n        const directorBlock = `\r\n            ?item wdt:P57 ?searchDirector .\r\n            ?searchDirector rdfs:label ?searchDirectorLabel .\r\n            FILTER(LANG(?searchDirectorLabel) IN (\"fr\",\"en\"))\r\n            FILTER(CONTAINS(LCASE(STR(?searchDirectorLabel)), LCASE(\"${safeSearch}\")))\r\n        `;\r\n\r\n        const castBlock = `\r\n            ?item wdt:P161 ?searchCast .\r\n            ?searchCast rdfs:label ?searchCastLabel .\r\n            FILTER(LANG(?searchCastLabel) IN (\"fr\",\"en\"))\r\n            FILTER(CONTAINS(LCASE(STR(?searchCastLabel)), LCASE(\"${safeSearch}\")))\r\n        `;\r\n\r\n        if (category === \"title\") {\r\n            searchCondition = `FILTER(${titleMatch})`;\r\n        }\r\n        else if (category === \"director\") {\r\n            searchCondition = directorBlock;\r\n        }\r\n        else if (category === \"cast\") {\r\n            searchCondition = castBlock;\r\n        }\r\n        else {\r\n            searchCondition = `\r\n                { FILTER(${titleMatch}) }\r\n                UNION\r\n                { ${directorBlock} }\r\n                UNION\r\n                { ${castBlock} }\r\n            `;\r\n        }\r\n    }\r\n\r\n    \/\/ --- DATE ---\r\n    \/\/ Priorit\u00e9 : fourchette pr\u00e9cise (jour+mois+ann\u00e9e exacts) >\r\n    \/\/            fourchette d'ann\u00e9es > champs jour\/mois\/ann\u00e9e.\r\n    const dateFrom = dateFromInput.value;\r\n    const dateTo = dateToInput.value;\r\n\r\n    const hasFullDateRange = Boolean(dateFrom || dateTo);\r\n    const hasYearRange = Boolean(yearFrom || yearTo);\r\n    const hasRollingFilter = Boolean(day || month || year);\r\n\r\n    const hasDateFilter = hasFullDateRange || hasYearRange || hasRollingFilter;\r\n\r\n    let dateVariable = `OPTIONAL{ ?item wdt:P577 ?date . }`;\r\n\r\n    if (dateFilter.value === \"with\" || hasDateFilter) {\r\n        dateVariable = `?item wdt:P577 ?date .`;\r\n    }\r\n\r\n    if (dateFilter.value === \"without\") {\r\n        dateVariable = `FILTER NOT EXISTS{ ?item wdt:P577 ?anyDate . }`;\r\n    }\r\n\r\n    let dateFilterCondition = \"\";\r\n\r\n    if (hasDateFilter && dateFilter.value !== \"without\") {\r\n\r\n        const conditions = [];\r\n\r\n        if (hasFullDateRange) {\r\n\r\n            \/\/ Fourchette de dates pr\u00e9cises (ex : 05\/05\/2024 -> 31\/07\/2026)\r\n            if (dateFrom) conditions.push(`?date >= \"${dateFrom}T00:00:00Z\"^^xsd:dateTime`);\r\n            if (dateTo) conditions.push(`?date <= \"${dateTo}T23:59:59Z\"^^xsd:dateTime`);\r\n        }\r\n        else if (hasYearRange) {\r\n\r\n            \/\/ Fourchette d'ann\u00e9es (ex : 2022 -> 2026)\r\n            if (yearFrom) conditions.push(`YEAR(?date) >= ${Number(yearFrom)}`);\r\n            if (yearTo) conditions.push(`YEAR(?date) <= ${Number(yearTo)}`);\r\n        }\r\n        else {\r\n\r\n            const now = new Date();\r\n            const nowIso = now.toISOString();\r\n\r\n            \/\/ \"Jour\" = N derniers jours (fen\u00eatre glissante)\r\n            if (day) {\r\n                conditions.push(`?date >= \"${daysAgo(Number(day)).toISOString()}\"^^xsd:dateTime`);\r\n                conditions.push(`?date <= \"${nowIso}\"^^xsd:dateTime`);\r\n            }\r\n\r\n            \/\/ \"Mois\" = N derniers mois (fen\u00eatre glissante)\r\n            if (month) {\r\n                conditions.push(`?date >= \"${monthsAgo(Number(month)).toISOString()}\"^^xsd:dateTime`);\r\n                conditions.push(`?date <= \"${nowIso}\"^^xsd:dateTime`);\r\n            }\r\n\r\n            \/\/ \"Ann\u00e9e\" : un nombre qui ressemble \u00e0 une vraie ann\u00e9e\r\n            \/\/ (>= 100) est trait\u00e9 comme une ann\u00e9e pr\u00e9cise (ex : 2026).\r\n            \/\/ Un petit nombre (< 100) est trait\u00e9 comme \"N derni\u00e8res\r\n            \/\/ ann\u00e9es\" (ex : 3 -> 3 derni\u00e8res ann\u00e9es).\r\n            if (year) {\r\n\r\n                const yearNumber = Number(year);\r\n\r\n                if (yearNumber >= 100) {\r\n                    conditions.push(`YEAR(?date) = ${yearNumber}`);\r\n                }\r\n                else {\r\n                    conditions.push(`?date >= \"${yearsAgo(yearNumber).toISOString()}\"^^xsd:dateTime`);\r\n                    conditions.push(`?date <= \"${nowIso}\"^^xsd:dateTime`);\r\n                }\r\n            }\r\n        }\r\n\r\n        if (conditions.length) {\r\n            dateFilterCondition = `FILTER(${conditions.join(\" && \")})`;\r\n        }\r\n    }\r\n\r\n    return `\r\n        ${typeCondition}\r\n        ${labelCondition}\r\n        ${religiousCondition}\r\n        ${searchCondition}\r\n        ${dateVariable}\r\n        ${dateFilterCondition}\r\n    `;\r\n}\r\n\r\n\/* =========================================================\r\n   REQUETE DE COMPTAGE\r\n\r\n   Compte les films correspondant aux filtres, plafonn\u00e9 \u00e0\r\n   COUNT_CAP pour rester rapide m\u00eame sur une recherche large.\r\n========================================================= *\/\r\n\r\nfunction buildCountQuery() {\r\n\r\n    const where = buildWhereClause();\r\n\r\n    return `\r\n        PREFIX xsd: <http:\/\/www.w3.org\/2001\/XMLSchema#>\r\n        SELECT (COUNT(*) AS ?count) WHERE {\r\n            SELECT DISTINCT ?item WHERE{ ${where} }\r\n            LIMIT ${COUNT_CAP}\r\n        }\r\n    `;\r\n}\r\n\r\n\/* =========================================================\r\n   REQUETE DE LISTE (l\u00e9g\u00e8re, pagination r\u00e9elle)\r\n========================================================= *\/\r\n\r\nfunction buildListQuery(limit, offset) {\r\n\r\n    const where = buildWhereClause();\r\n\r\n    let order = \"DESC(?date)\";\r\n    if (sortSelect.value === \"oldest\") order = \"ASC(?date)\";\r\n    if (sortSelect.value === \"title\") order = \"LCASE(STR(?itemLabel))\";\r\n\r\n    return `\r\n        PREFIX xsd: <http:\/\/www.w3.org\/2001\/XMLSchema#>\r\n        SELECT DISTINCT ?item ?itemLabel ?date\r\n        WHERE{ ${where} }\r\n        ORDER BY ${order}\r\n        LIMIT ${limit}\r\n        OFFSET ${offset}\r\n    `;\r\n}\r\n\r\n\/* =========================================================\r\n   REQUETE D'ENRICHISSEMENT\r\n   (affiche, r\u00e9alisateur, casting, liens \u2014 uniquement pour\r\n   les films de la page affich\u00e9e)\r\n========================================================= *\/\r\n\r\nfunction buildDetailsQuery(itemUris) {\r\n\r\n    const values = itemUris.map(uri => `<${uri}>`).join(\" \");\r\n\r\n    return `\r\n        SELECT ?item\r\n               (SAMPLE(?poster) AS ?poster)\r\n               (SAMPLE(?image) AS ?image)\r\n               (GROUP_CONCAT(DISTINCT ?directorLabel; separator=\", \") AS ?directors)\r\n               (GROUP_CONCAT(DISTINCT ?castLabel; separator=\", \") AS ?cast)\r\n               (SAMPLE(?website) AS ?website)\r\n               (SAMPLE(?imdb) AS ?imdb)\r\n        WHERE{\r\n            VALUES ?item { ${values} }\r\n            OPTIONAL{ ?item wdt:P3383 ?poster . }\r\n            OPTIONAL{ ?item wdt:P18 ?image . }\r\n            OPTIONAL{\r\n                ?item wdt:P57 ?director .\r\n                ?director rdfs:label ?directorLabel .\r\n                FILTER(LANG(?directorLabel) IN (\"fr\",\"en\"))\r\n            }\r\n            OPTIONAL{\r\n                ?item wdt:P161 ?cast .\r\n                ?cast rdfs:label ?castLabel .\r\n                FILTER(LANG(?castLabel) IN (\"fr\",\"en\"))\r\n            }\r\n            OPTIONAL{ ?item wdt:P856 ?website . }\r\n            OPTIONAL{\r\n                ?item wdt:P345 ?imdbId .\r\n                BIND(CONCAT(\"https:\/\/www.imdb.com\/title\/\", ?imdbId) AS ?imdb)\r\n            }\r\n        }\r\n        GROUP BY ?item\r\n    `;\r\n}\r\n\r\n\/* =========================================================\r\n   REQUETE AVEC RETRY (POST, pour \u00e9viter les URL trop longues)\r\n========================================================= *\/\r\n\r\nasync function fetchWikidata(query, attempt = 0) {\r\n\r\n    const response = await fetch(WIKIDATA_ENDPOINT, {\r\n        method: \"POST\",\r\n        headers: {\r\n            \"Content-Type\": \"application\/x-www-form-urlencoded\",\r\n            \"Accept\": \"application\/sparql-results+json\"\r\n        },\r\n        body: \"query=\" + encodeURIComponent(query),\r\n        signal: abortController.signal\r\n    });\r\n\r\n    if ([429, 502, 503, 504].includes(response.status)) {\r\n\r\n        if (attempt < 3) {\r\n\r\n            const delay = (attempt + 1) * 2500;\r\n\r\n            statusElement.textContent =\r\n                \"\u23f3 Wikidata est temporairement charg\u00e9. Nouvelle tentative dans \"\r\n                + (delay \/ 1000) + \" secondes...\";\r\n\r\n            await new Promise(resolve => setTimeout(resolve, delay));\r\n\r\n            return fetchWikidata(query, attempt + 1);\r\n        }\r\n    }\r\n\r\n    if (!response.ok) {\r\n        throw new Error(\"Erreur HTTP \" + response.status);\r\n    }\r\n\r\n    return response.json();\r\n}\r\n\r\n\/* =========================================================\r\n   RECHERCHE (comptage -> liste -> enrichissement)\r\n========================================================= *\/\r\n\r\nasync function searchWikidata() {\r\n\r\n    if (isLoading) return;\r\n\r\n    isLoading = true;\r\n    abortController = new AbortController();\r\n    cancelButton.disabled = false;\r\n    progressBar.style.width = \"10%\";\r\n    resultsElement.innerHTML = \"\";\r\n    statusElement.textContent = \"\u23f3 Pr\u00e9paration de la recherche...\";\r\n\r\n    try {\r\n\r\n        if (titleFilter.value === \"without\") {\r\n            currentResults = [];\r\n            totalPages = 1;\r\n            statusElement.textContent =\r\n                \"\u26a0\ufe0f Wikidata ne permet pas de r\u00e9cup\u00e9rer fiablement les films \"\r\n                + \"sans titre dans cette requ\u00eate l\u00e9g\u00e8re.\";\r\n            renderResults();\r\n            return;\r\n        }\r\n\r\n        const pageSize = Number(pageSizeSelect.value) || 20;\r\n\r\n        const maxResultsRaw = maxResultsInput.value.trim();\r\n        const maxResults = maxResultsRaw ? Math.max(1, Number(maxResultsRaw)) : null;\r\n\r\n        \/* --- ETAPE 1 : comptage total (si possible) --- *\/\r\n\r\n        statusElement.textContent = \"\u23f3 Comptage des r\u00e9sultats...\";\r\n        progressBar.style.width = \"20%\";\r\n\r\n        let rawCount = null;\r\n        let cappedAtLimit = false;\r\n        let effectiveTotal = null;\r\n\r\n        try {\r\n            const countData = await fetchWikidata(buildCountQuery());\r\n            rawCount = Number(countData.results.bindings[0]?.count?.value || 0);\r\n            cappedAtLimit = rawCount >= COUNT_CAP;\r\n        }\r\n        catch (countError) {\r\n\r\n            if (countError.name === \"AbortError\") {\r\n                throw countError;\r\n            }\r\n\r\n            \/*\r\n            -------------------------------------------------\r\n            Le comptage peut \u00e9chouer (timeout, 500...) sur une\r\n            recherche trop large et peu s\u00e9lective (ex. \"tous\r\n            les films en fran\u00e7ais\", sans aucun filtre). Dans\r\n            ce cas on NE FAIT PAS \u00e9chouer toute la recherche :\r\n            on bascule sur l'ancien mode \"page par page sans\r\n            total connu\".\r\n            -------------------------------------------------\r\n            *\/\r\n\r\n            console.warn(\"Comptage indisponible, repli sans total exact :\", countError);\r\n\r\n            rawCount = null;\r\n        }\r\n\r\n        totalKnown = rawCount !== null;\r\n\r\n        if (totalKnown) {\r\n\r\n            effectiveTotal = maxResults ? Math.min(rawCount, maxResults) : rawCount;\r\n            totalPages = Math.max(1, Math.ceil(effectiveTotal \/ pageSize));\r\n\r\n            if (currentPage > totalPages) {\r\n                currentPage = totalPages;\r\n            }\r\n\r\n            if (effectiveTotal === 0) {\r\n                currentResults = [];\r\n                progressBar.style.width = \"100%\";\r\n                statusElement.textContent = \"\u274c Aucun r\u00e9sultat.\";\r\n                renderResults();\r\n                return;\r\n            }\r\n        }\r\n        else {\r\n            totalPages = Math.max(totalPages, currentPage);\r\n        }\r\n\r\n        \/* --- ETAPE 2 : liste des films de la page courante --- *\/\r\n\r\n        statusElement.textContent = \"\u23f3 Recherche des films...\";\r\n        progressBar.style.width = \"40%\";\r\n\r\n        const offset = (currentPage - 1) * pageSize;\r\n\r\n        let limit = pageSize;\r\n\r\n        if (maxResults) {\r\n            limit = Math.min(pageSize, Math.max(0, maxResults - offset));\r\n        }\r\n\r\n        \/\/ Si le total est inconnu, on demande un r\u00e9sultat de plus\r\n        \/\/ pour savoir s'il existe une page suivante.\r\n        const fetchLimit = totalKnown ? limit : limit + 1;\r\n\r\n        const listData = await fetchWikidata(buildListQuery(fetchLimit, offset));\r\n        let listRows = listData.results.bindings;\r\n\r\n        if (!totalKnown) {\r\n            hasNextPageUnknown = listRows.length > limit;\r\n            listRows = listRows.slice(0, limit);\r\n            totalPages = hasNextPageUnknown ? currentPage + 1 : currentPage;\r\n        }\r\n\r\n        progressBar.style.width = \"60%\";\r\n\r\n        if (listRows.length === 0) {\r\n            currentResults = [];\r\n            progressBar.style.width = \"100%\";\r\n            statusElement.textContent = \"\u274c Aucun r\u00e9sultat sur cette page.\";\r\n            renderResults();\r\n            return;\r\n        }\r\n\r\n        \/* --- ETAPE 3 : enrichissement (affiche, casting, liens) --- *\/\r\n\r\n        statusElement.textContent = \"\u23f3 R\u00e9cup\u00e9ration des affiches et informations...\";\r\n\r\n        const itemUris = listRows.map(row => row.item.value);\r\n        const detailsData = await fetchWikidata(buildDetailsQuery(itemUris));\r\n\r\n        const detailsByUri = {};\r\n        detailsData.results.bindings.forEach(row => {\r\n            detailsByUri[row.item.value] = row;\r\n        });\r\n\r\n        progressBar.style.width = \"90%\";\r\n\r\n        currentResults = listRows.map(row => convertResult(row, detailsByUri[row.item.value]));\r\n\r\n        progressBar.style.width = \"100%\";\r\n\r\n        let statusMessage;\r\n\r\n        if (totalKnown) {\r\n\r\n            statusMessage = \"\u2705 \" + effectiveTotal + \" r\u00e9sultat(s) trouv\u00e9(s)\";\r\n\r\n            if (cappedAtLimit && !maxResults) {\r\n                statusMessage += \" (au moins \" + COUNT_CAP + \" \u2014 affinez la recherche pour un compte exact)\";\r\n            }\r\n        }\r\n        else {\r\n            statusMessage =\r\n                \"\u2705 \" + listRows.length + \" r\u00e9sultat(s) affich\u00e9(s) \"\r\n                + \"(recherche trop large pour calculer un total exact \u2014 \"\r\n                + \"affinez avec un filtre pour l'obtenir)\";\r\n        }\r\n\r\n        statusElement.textContent = statusMessage + \".\";\r\n\r\n        renderResults();\r\n\r\n    }\r\n    catch (error) {\r\n\r\n        if (error.name === \"AbortError\") {\r\n            statusElement.textContent = \"\u23f9 Recherche annul\u00e9e.\";\r\n        }\r\n        else {\r\n            console.error(\"Wikidata :\", error);\r\n            statusElement.textContent = \"\u274c Impossible d'interroger Wikidata : \" + error.message;\r\n        }\r\n    }\r\n    finally {\r\n        isLoading = false;\r\n        cancelButton.disabled = true;\r\n        abortController = null;\r\n    }\r\n}\r\n\r\n\/* =========================================================\r\n   CONVERSION (fusion ligne liste + ligne d\u00e9tails)\r\n========================================================= *\/\r\n\r\nfunction convertResult(listRow, detailsRow) {\r\n\r\n    const details = detailsRow || {};\r\n\r\n    const poster = details.poster\r\n        ? details.poster.value\r\n        : (details.image ? details.image.value : \"\");\r\n\r\n    const website = details.website ? details.website.value : \"\";\r\n    const imdb = details.imdb ? details.imdb.value : \"\";\r\n    const uri = listRow.item ? listRow.item.value : \"\";\r\n\r\n    \/\/ Priorit\u00e9 : site officiel > IMDb > fiche Wikidata\r\n    const primaryLink = website || imdb || uri;\r\n\r\n    return {\r\n        uri,\r\n        title: listRow.itemLabel ? listRow.itemLabel.value : \"\",\r\n        poster,\r\n        directors: details.directors ? details.directors.value : \"\",\r\n        cast: details.cast ? details.cast.value : \"\",\r\n        date: listRow.date ? listRow.date.value : \"\",\r\n        website,\r\n        imdb,\r\n        primaryLink\r\n    };\r\n}\r\n\r\n\/* =========================================================\r\n   AFFICHAGE\r\n========================================================= *\/\r\n\r\nfunction renderResults() {\r\n\r\n    resultsElement.innerHTML = \"\";\r\n\r\n    const isReligiousSearch =\r\n        religiousFilter.checked\r\n        || document.querySelectorAll(\".lexicalKeyword:checked\").length > 0;\r\n\r\n    currentResults.forEach(film => {\r\n\r\n        const card = document.createElement(\"article\");\r\n        card.className = \"card\";\r\n\r\n        const year = film.date ? new Date(film.date).getFullYear() : \"\";\r\n\r\n        const castList = film.cast\r\n            ? film.cast.split(\", \").slice(0, 4).join(\", \")\r\n            : \"\";\r\n\r\n        card.innerHTML = `\r\n\r\n            <div class=\"poster-wrap\">\r\n\r\n                <img decoding=\"async\" class=\"poster\" src=\"${film.poster || DEFAULT_POSTER}\"\r\n                     alt=\"${escapeHTML(film.title)}\" loading=\"lazy\">\r\n\r\n                ${year ? `<div class=\"year-badge\">${year}<\/div>` : \"\"}\r\n\r\n                ${isReligiousSearch ? `<div class=\"religious-badge\">\u271d\ufe0f Religieux<\/div>` : \"\"}\r\n\r\n                <a class=\"poster-overlay\" href=\"${film.primaryLink}\" target=\"_blank\" rel=\"noopener\">\r\n                    <span class=\"overlay-btn\">\u25b6 Voir le film<\/span>\r\n                <\/a>\r\n\r\n            <\/div>\r\n\r\n            <div class=\"card-content\">\r\n\r\n                <h3>${escapeHTML(film.title || \"Titre non renseign\u00e9\")}<\/h3>\r\n\r\n                ${film.directors ? `<div class=\"meta-line\"><b>R\u00e9alisation :<\/b> ${escapeHTML(film.directors)}<\/div>` : \"\"}\r\n\r\n                ${castList ? `<div class=\"meta-line\"><b>Avec :<\/b> ${escapeHTML(castList)}<\/div>` : \"\"}\r\n\r\n                <div class=\"meta-line\">\ud83d\udcc5 ${formatDate(film.date)}<\/div>\r\n\r\n                <div class=\"card-links\">\r\n                    <a class=\"link-primary\" href=\"${film.primaryLink}\" target=\"_blank\" rel=\"noopener\">\u25b6 Voir le film<\/a>\r\n                    <a class=\"link-secondary\" href=\"${film.uri}\" target=\"_blank\" rel=\"noopener\">Wikidata<\/a>\r\n                <\/div>\r\n\r\n            <\/div>\r\n        `;\r\n\r\n        resultsElement.appendChild(card);\r\n    });\r\n\r\n    updatePagination();\r\n}\r\n\r\n\/* =========================================================\r\n   PAGINATION\r\n========================================================= *\/\r\n\r\nfunction updatePagination() {\r\n\r\n    if (totalKnown) {\r\n        pageInfo.textContent = \"Page \" + currentPage + \" \/ \" + totalPages;\r\n        previousButton.disabled = currentPage <= 1;\r\n        nextButton.disabled = currentPage >= totalPages;\r\n        pageInput.max = totalPages;\r\n    }\r\n    else {\r\n        pageInfo.textContent = \"Page \" + currentPage + \" (total non calcul\u00e9)\";\r\n        previousButton.disabled = currentPage <= 1;\r\n        nextButton.disabled = !hasNextPageUnknown;\r\n        pageInput.removeAttribute(\"max\");\r\n    }\r\n}\r\n\r\n\/* =========================================================\r\n   DATE \/ SECURITE\r\n========================================================= *\/\r\n\r\nfunction formatDate(date) {\r\n\r\n    if (!date) return \"Date non renseign\u00e9e\";\r\n\r\n    const d = new Date(date);\r\n\r\n    if (isNaN(d)) return date;\r\n\r\n    return d.toLocaleDateString(\"fr-FR\");\r\n}\r\n\r\nfunction escapeHTML(value) {\r\n    const div = document.createElement(\"div\");\r\n    div.textContent = value ? String(value) : \"\";\r\n    return div.innerHTML;\r\n}\r\n\r\n\/* =========================================================\r\n   EVENEMENTS\r\n========================================================= *\/\r\n\r\ncancelButton.addEventListener(\"click\", function () {\r\n    if (abortController) abortController.abort();\r\n});\r\n\r\ndocument.getElementById(\"searchButton\").addEventListener(\"click\", function () {\r\n    currentPage = 1;\r\n    searchWikidata();\r\n});\r\n\r\nsearchInput.addEventListener(\"keydown\", function (event) {\r\n    if (event.key === \"Enter\") {\r\n        currentPage = 1;\r\n        searchWikidata();\r\n    }\r\n});\r\n\r\nnextButton.addEventListener(\"click\", function () {\r\n    if (currentPage < totalPages) {\r\n        currentPage++;\r\n        searchWikidata();\r\n    }\r\n});\r\n\r\npreviousButton.addEventListener(\"click\", function () {\r\n    if (currentPage > 1) {\r\n        currentPage--;\r\n        searchWikidata();\r\n    }\r\n});\r\n\r\ndocument.getElementById(\"pageButton\").addEventListener(\"click\", function () {\r\n    let page = Number(pageInput.value);\r\n    if (page < 1) page = 1;\r\n    if (page > totalPages) page = totalPages;\r\n    currentPage = page;\r\n    searchWikidata();\r\n});\r\n\r\ndocument.getElementById(\"reloadButton\").addEventListener(\"click\", searchWikidata);\r\n\r\ndocument.getElementById(\"resetButton\").addEventListener(\"click\", function () {\r\n\r\n    if (isLoading && abortController) abortController.abort();\r\n\r\n    searchInput.value = \"\";\r\n    searchCategory.value = \"title\";\r\n    dayInput.value = \"\";\r\n    monthInput.value = \"\";\r\n    yearInput.value = \"\";\r\n    yearFromInput.value = \"\";\r\n    yearToInput.value = \"\";\r\n    dateFromInput.value = \"\";\r\n    dateToInput.value = \"\";\r\n    titleFilter.value = \"all\";\r\n    dateFilter.value = \"all\";\r\n    typeFilter.value = \"film\";\r\n    languageSelect.value = \"fr\";\r\n    religiousFilter.checked = true;\r\n\r\n    document.querySelectorAll(\".lexicalKeyword\").forEach(cb => { cb.checked = false; });\r\n    lexicalCustomInput.value = \"\";\r\n\r\n    sortSelect.value = \"newest\";\r\n    pageSizeSelect.value = \"20\";\r\n    maxResultsInput.value = \"\";\r\n\r\n    currentPage = 1;\r\n    totalPages = 1;\r\n    totalKnown = true;\r\n    hasNextPageUnknown = false;\r\n    currentResults = [];\r\n\r\n    resultsElement.innerHTML = \"\";\r\n    progressBar.style.width = \"0%\";\r\n    pageInput.value = 1;\r\n    pageInfo.textContent = \"Page 1 \/ 1\";\r\n\r\n    statusElement.textContent = \"Filtres r\u00e9initialis\u00e9s.\";\r\n});\r\n\r\ndocument.getElementById(\"exportButton\").addEventListener(\"click\", function () {\r\n\r\n    if (currentResults.length === 0) {\r\n        alert(\"Aucun r\u00e9sultat \u00e0 exporter.\");\r\n        return;\r\n    }\r\n\r\n    const header = [\"Titre\", \"R\u00e9alisateur(s)\", \"Casting\", \"Date\", \"Lien\", \"Wikidata\"];\r\n\r\n    const rows = currentResults.map(film => [\r\n        film.title, film.directors, film.cast, film.date, film.primaryLink, film.uri\r\n    ]);\r\n\r\n    const csv = [header, ...rows]\r\n        .map(row => row.map(value =>\r\n            '\"' + String(value || \"\").replace(\/\"\/g, '\"\"') + '\"'\r\n        ).join(\";\"))\r\n        .join(\"\\n\");\r\n\r\n    const blob = new Blob([\"\\ufeff\" + csv], { type: \"text\/csv;charset=utf-8\" });\r\n    const url = URL.createObjectURL(blob);\r\n    const link = document.createElement(\"a\");\r\n    link.href = url;\r\n    link.download = \"films-wikidata.csv\";\r\n    link.click();\r\n    URL.revokeObjectURL(url);\r\n});\r\n\r\n\/* On ne lance pas de requ\u00eate automatique au chargement. *\/\r\n\r\nstatusElement.textContent = \"Pr\u00eat. Entrez un terme ou lancez une recherche.\";\r\n\r\n<\/script>\r\n\r\n<\/body>\r\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-f6717ef animated-slow elementor-section-boxed ang-section-padding-initial elementor-section-height-default elementor-section-height-default wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no elementor-repeater-item-none elementor-repeater-item-none_hover wpr-column-slider-no wpr-equal-height-no elementor-invisible\" data-id=\"f6717ef\" data-element_type=\"section\" data-e-type=\"section\" data-settings=\"{&quot;animation&quot;:&quot;swing&quot;,&quot;animation_delay&quot;:200}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-727cd58 elementor-repeater-item-none elementor-repeater-item-none_hover\" data-id=\"727cd58\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-43a556e animated-slow external_elementor-repeater-item-none_external elementor-repeater-item-none elementor-repeater-item-none_hover elementor-invisible elementor-widget elementor-widget-image\" data-id=\"43a556e\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;_animation&quot;:&quot;fadeIn&quot;,&quot;_animation_delay&quot;:1500}\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img fetchpriority=\"high\" decoding=\"async\" width=\"275\" height=\"183\" src=\"https:\/\/allianceenjesus.com\/wp-content\/uploads\/2022\/03\/alliance-en-jesus-1-1.jpg\" class=\"attachment-large size-large wp-image-3707\" alt=\"\" \/>\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t<div class=\"elementor-element elementor-element-9ba776c e-flex e-con-boxed elementor-repeater-item-default_padding elementor-repeater-item-none elementor-repeater-item-none_hover wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-parent\" data-id=\"9ba776c\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;ang_container_spacing_size&quot;:&quot;default_padding&quot;}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-edea319 elementor-repeater-item-none elementor-repeater-item-none_hover elementor-widget elementor-widget-html\" data-id=\"edea319\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t```html\r\n<!DOCTYPE html>\r\n<html lang=\"fr\">\r\n<head>\r\n<meta charset=\"UTF-8\">\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n\r\n<title>Catalogue Films - Wikidata<\/title>\r\n\r\n<style>\r\n\r\n* {\r\n    box-sizing: border-box;\r\n}\r\n\r\nbody {\r\n    margin: 0;\r\n    font-family: Arial, Helvetica, sans-serif;\r\n    background: #0b0b0d;\r\n    color: #fff;\r\n}\r\n\r\n.app {\r\n    max-width: 1400px;\r\n    margin: auto;\r\n    padding: 25px;\r\n}\r\n\r\nheader {\r\n    text-align: center;\r\n    margin-bottom: 25px;\r\n}\r\n\r\nheader h1 {\r\n    margin: 0 0 10px;\r\n}\r\n\r\nheader p {\r\n    opacity: .7;\r\n}\r\n\r\n.search-box {\r\n    background: #1d1d1d;\r\n    border-radius: 14px;\r\n    padding: 20px;\r\n    margin-bottom: 20px;\r\n}\r\n\r\n.field-row {\r\n    display: grid;\r\n    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\r\n    gap: 12px;\r\n    margin-bottom: 16px;\r\n}\r\n\r\n.field-row:last-child {\r\n    margin-bottom: 0;\r\n}\r\n\r\n.field-row.search-row {\r\n    grid-template-columns: 3fr 1.5fr auto;\r\n}\r\n\r\n.field label {\r\n    display: block;\r\n    font-size: 11.5px;\r\n    text-transform: uppercase;\r\n    letter-spacing: .4px;\r\n    opacity: .55;\r\n    margin-bottom: 5px;\r\n}\r\n\r\ninput,\r\nselect {\r\n    width: 100%;\r\n    padding: 12px;\r\n    border-radius: 8px;\r\n    border: 1px solid #444;\r\n    background: #111;\r\n    color: #fff;\r\n    font-size: 14px;\r\n}\r\n\r\nselect[multiple] {\r\n    min-height: 145px;\r\n}\r\n\r\nbutton {\r\n    padding: 12px 18px;\r\n    border: 0;\r\n    border-radius: 8px;\r\n    background: #f0c400;\r\n    color: #111;\r\n    font-weight: bold;\r\n    cursor: pointer;\r\n}\r\n\r\nbutton:hover {\r\n    opacity: .85;\r\n}\r\n\r\nbutton:disabled {\r\n    opacity: .4;\r\n    cursor: not-allowed;\r\n}\r\n\r\n.secondary-button {\r\n    background: #2c2c30;\r\n    color: #fff;\r\n}\r\n\r\n.youtube-button {\r\n    background: #ff0000;\r\n    color: #fff;\r\n}\r\n\r\n.toggle-filter {\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 10px;\r\n    padding: 12px;\r\n    border-radius: 8px;\r\n    border: 1px solid #444;\r\n    background: #111;\r\n    cursor: pointer;\r\n    user-select: none;\r\n}\r\n\r\n.toggle-filter input {\r\n    width: 18px;\r\n    height: 18px;\r\n    accent-color: #f0c400;\r\n}\r\n\r\n.lexical-box {\r\n    border: 1px solid #444;\r\n    border-radius: 8px;\r\n    padding: 12px;\r\n    background: #111;\r\n}\r\n\r\n.lexical-box legend {\r\n    font-size: 11.5px;\r\n    text-transform: uppercase;\r\n    letter-spacing: .4px;\r\n    opacity: .65;\r\n    padding: 0 6px;\r\n}\r\n\r\n.help-text {\r\n    display: block;\r\n    margin-top: 6px;\r\n    font-size: 11px;\r\n    opacity: .55;\r\n}\r\n\r\n.status {\r\n    margin: 20px 0;\r\n    text-align: center;\r\n    min-height: 25px;\r\n}\r\n\r\n.progress {\r\n    width: 100%;\r\n    height: 8px;\r\n    background: #333;\r\n    border-radius: 10px;\r\n    overflow: hidden;\r\n    margin-top: 15px;\r\n}\r\n\r\n.progress-bar {\r\n    width: 0%;\r\n    height: 100%;\r\n    background: #f0c400;\r\n    transition: width .2s;\r\n}\r\n\r\n.results {\r\n    display: grid;\r\n    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\r\n    gap: 24px;\r\n}\r\n\r\n.card {\r\n    position: relative;\r\n    background: #1a1a1d;\r\n    border-radius: 14px;\r\n    overflow: hidden;\r\n    box-shadow: 0 4px 14px rgba(0,0,0,.4);\r\n    transition: transform .25s ease, box-shadow .25s ease;\r\n    display: flex;\r\n    flex-direction: column;\r\n}\r\n\r\n.card:hover {\r\n    transform: translateY(-6px);\r\n    box-shadow: 0 14px 30px rgba(0,0,0,.6);\r\n}\r\n\r\n.poster-wrap {\r\n    position: relative;\r\n    width: 100%;\r\n    aspect-ratio: 2\/3;\r\n    background: #222;\r\n    overflow: hidden;\r\n}\r\n\r\n.poster {\r\n    width: 100%;\r\n    height: 100%;\r\n    object-fit: cover;\r\n    display: block;\r\n}\r\n\r\n.year-badge {\r\n    position: absolute;\r\n    top: 10px;\r\n    left: 10px;\r\n    background: rgba(0,0,0,.7);\r\n    padding: 4px 10px;\r\n    border-radius: 20px;\r\n    font-size: 12px;\r\n    font-weight: bold;\r\n}\r\n\r\n.religious-badge {\r\n    position: absolute;\r\n    top: 10px;\r\n    right: 10px;\r\n    background: rgba(240,196,0,.9);\r\n    color: #111;\r\n    padding: 4px 10px;\r\n    border-radius: 20px;\r\n    font-size: 11px;\r\n    font-weight: bold;\r\n}\r\n\r\n.poster-overlay {\r\n    position: absolute;\r\n    inset: 0;\r\n    background: linear-gradient(\r\n        to top,\r\n        rgba(0,0,0,.92) 0%,\r\n        rgba(0,0,0,.15) 55%,\r\n        rgba(0,0,0,0) 100%\r\n    );\r\n    opacity: 0;\r\n    transition: opacity .25s ease;\r\n    display: flex;\r\n    align-items: flex-end;\r\n    padding: 14px;\r\n}\r\n\r\n.card:hover .poster-overlay {\r\n    opacity: 1;\r\n}\r\n\r\n.overlay-btn {\r\n    display: inline-block;\r\n    width: 100%;\r\n    text-align: center;\r\n    padding: 10px;\r\n    background: #f0c400;\r\n    color: #111;\r\n    font-weight: bold;\r\n    text-decoration: none;\r\n    border-radius: 8px;\r\n    font-size: 13px;\r\n}\r\n\r\n.card-content {\r\n    padding: 14px 16px 16px;\r\n    flex: 1;\r\n    display: flex;\r\n    flex-direction: column;\r\n    gap: 7px;\r\n}\r\n\r\n.card h3 {\r\n    margin: 0;\r\n    font-size: 17px;\r\n    line-height: 1.3;\r\n    color: #f0c400;\r\n}\r\n\r\n.meta-line {\r\n    font-size: 12.5px;\r\n    color: rgba(255,255,255,.7);\r\n    line-height: 1.5;\r\n}\r\n\r\n.meta-line b {\r\n    color: rgba(255,255,255,.95);\r\n}\r\n\r\n.card-links {\r\n    margin-top: auto;\r\n    display: flex;\r\n    gap: 8px;\r\n    padding-top: 8px;\r\n}\r\n\r\n.link-primary,\r\n.link-secondary,\r\n.link-youtube {\r\n    text-decoration: none;\r\n    text-align: center;\r\n    border-radius: 7px;\r\n    font-size: 12px;\r\n    font-weight: bold;\r\n    padding: 9px 6px;\r\n}\r\n\r\n.link-primary {\r\n    flex: 2;\r\n    background: #f0c400;\r\n    color: #111;\r\n}\r\n\r\n.link-secondary {\r\n    flex: 1;\r\n    background: #2c2c30;\r\n    color: #fff;\r\n}\r\n\r\n.link-youtube {\r\n    flex: 1;\r\n    background: #ff0000;\r\n    color: #fff;\r\n}\r\n\r\n.link-primary:hover,\r\n.link-secondary:hover,\r\n.link-youtube:hover {\r\n    opacity: .85;\r\n}\r\n\r\n.pagination {\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n    gap: 15px;\r\n    margin-top: 30px;\r\n    flex-wrap: wrap;\r\n}\r\n\r\n@media (max-width: 700px) {\r\n\r\n    .field-row.search-row {\r\n        grid-template-columns: 1fr;\r\n    }\r\n\r\n    .app {\r\n        padding: 12px;\r\n    }\r\n\r\n}\r\n\r\n<\/style>\r\n<\/head>\r\n\r\n<body>\r\n\r\n<div class=\"app\">\r\n\r\n<header>\r\n\r\n<h1>\ud83c\udfac Catalogue Films - Wikidata<\/h1>\r\n\r\n<p>\r\nRecherche directe dans Wikidata \u2014 aucune base locale et aucun stockage local\r\n<\/p>\r\n\r\n<\/header>\r\n\r\n\r\n<section class=\"search-box\">\r\n\r\n\r\n<!-- RECHERCHE -->\r\n\r\n<div class=\"field-row search-row\">\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"search\">\r\nRecherche libre\r\n<\/label>\r\n\r\n<input\r\nid=\"search\"\r\ntype=\"search\"\r\nplaceholder=\"Mot ou partie de mot... (facultatif)\"\r\n>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"searchCategory\">\r\nRechercher dans\r\n<\/label>\r\n\r\n<select id=\"searchCategory\">\r\n\r\n<option value=\"title\" selected>\r\nTitre\r\n<\/option>\r\n\r\n<option value=\"director\">\r\nR\u00e9alisateur\r\n<\/option>\r\n\r\n<option value=\"cast\">\r\nActeur\r\n<\/option>\r\n\r\n<option value=\"all\">\r\nTitre + r\u00e9alisateur + acteur\r\n<\/option>\r\n\r\n<\/select>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label>&nbsp;<\/label>\r\n\r\n<button id=\"searchButton\">\r\n\ud83d\udd0e Rechercher\r\n<\/button>\r\n\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n\r\n<!-- DATES -->\r\n\r\n<div class=\"field-row\">\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"dateFilter\">\r\nDate de sortie\r\n<\/label>\r\n\r\n<select id=\"dateFilter\">\r\n\r\n<option value=\"all\">\r\nToutes les dates\r\n<\/option>\r\n\r\n<option value=\"with\">\r\nAvec date de sortie\r\n<\/option>\r\n\r\n<option value=\"without\">\r\nSans date de sortie\r\n<\/option>\r\n\r\n<\/select>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"day\">\r\nDerniers jours\r\n<\/label>\r\n\r\n<input\r\nid=\"day\"\r\ntype=\"number\"\r\nplaceholder=\"Ex : 7\"\r\nmin=\"1\"\r\n>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"month\">\r\nDerniers mois\r\n<\/label>\r\n\r\n<input\r\nid=\"month\"\r\ntype=\"number\"\r\nplaceholder=\"Ex : 7\"\r\nmin=\"1\"\r\n>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"year\">\r\nAnn\u00e9e ou N derni\u00e8res ann\u00e9es\r\n<\/label>\r\n\r\n<input\r\nid=\"year\"\r\ntype=\"number\"\r\nplaceholder=\"2026 ou 3\"\r\nmin=\"1\"\r\n>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"yearFrom\">\r\nAnn\u00e9e de\r\n<\/label>\r\n\r\n<input\r\nid=\"yearFrom\"\r\ntype=\"number\"\r\nplaceholder=\"De\"\r\nmin=\"1\"\r\n>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"yearTo\">\r\nAnn\u00e9e \u00e0\r\n<\/label>\r\n\r\n<input\r\nid=\"yearTo\"\r\ntype=\"number\"\r\nplaceholder=\"\u00c0\"\r\nmin=\"1\"\r\n>\r\n\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n\r\n<!-- DATES PRECISES -->\r\n\r\n<div class=\"field-row\">\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"dateFrom\">\r\nDate pr\u00e9cise de d\u00e9but\r\n<\/label>\r\n\r\n<input\r\nid=\"dateFrom\"\r\ntype=\"date\"\r\n>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"dateTo\">\r\nDate pr\u00e9cise de fin\r\n<\/label>\r\n\r\n<input\r\nid=\"dateTo\"\r\ntype=\"date\"\r\n>\r\n\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n\r\n<!-- TYPE + LANGUES -->\r\n\r\n<div class=\"field-row\">\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"typeFilter\">\r\nType de contenu\r\n<\/label>\r\n\r\n<select id=\"typeFilter\">\r\n\r\n<option value=\"film\">\r\n\ud83c\udfac Films uniquement\r\n<\/option>\r\n\r\n<option value=\"cartoon\">\r\n\ud83c\udfa8 Dessins anim\u00e9s uniquement\r\n<\/option>\r\n\r\n<option value=\"film_cartoon\">\r\n\ud83c\udfac\ud83c\udfa8 Films + dessins anim\u00e9s\r\n<\/option>\r\n\r\n<option value=\"all\">\r\n\ud83d\udcfa Toutes les \u0153uvres audiovisuelles\r\n<\/option>\r\n\r\n<\/select>\r\n\r\n<\/div>\r\n\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"titleFilter\">\r\nPr\u00e9sence du titre\r\n<\/label>\r\n\r\n<select id=\"titleFilter\">\r\n\r\n<option value=\"all\">\r\nTous\r\n<\/option>\r\n\r\n<option value=\"with\">\r\nAvec un titre\r\n<\/option>\r\n\r\n<option value=\"without\">\r\nSans titre\r\n<\/option>\r\n\r\n<\/select>\r\n\r\n<\/div>\r\n\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"datePresenceFilter\">\r\nPr\u00e9sence de la date\r\n<\/label>\r\n\r\n<select id=\"datePresenceFilter\">\r\n\r\n<option value=\"all\">\r\nToutes\r\n<\/option>\r\n\r\n<option value=\"with\">\r\nAvec une date\r\n<\/option>\r\n\r\n<option value=\"without\">\r\nSans date\r\n<\/option>\r\n\r\n<\/select>\r\n\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n\r\n<!-- LANGUES MULTIPLES -->\r\n\r\n<div class=\"field-row\">\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"language\">\r\nLangues du titre \u2014 s\u00e9lection multiple\r\n<\/label>\r\n\r\n<select\r\nid=\"language\"\r\nmultiple\r\n>\r\n\r\n<option value=\"ALL\" selected>\r\n\ud83c\udf0d Toutes les langues\r\n<\/option>\r\n\r\n<option value=\"fr\">\r\n\ud83c\uddeb\ud83c\uddf7 Fran\u00e7ais\r\n<\/option>\r\n\r\n<option value=\"en\">\r\n\ud83c\uddec\ud83c\udde7 Anglais\r\n<\/option>\r\n\r\n<option value=\"es\">\r\n\ud83c\uddea\ud83c\uddf8 Espagnol\r\n<\/option>\r\n\r\n<option value=\"de\">\r\n\ud83c\udde9\ud83c\uddea Allemand\r\n<\/option>\r\n\r\n<option value=\"it\">\r\n\ud83c\uddee\ud83c\uddf9 Italien\r\n<\/option>\r\n\r\n<option value=\"pt\">\r\n\ud83c\uddf5\ud83c\uddf9 Portugais\r\n<\/option>\r\n\r\n<option value=\"nl\">\r\n\ud83c\uddf3\ud83c\uddf1 N\u00e9erlandais\r\n<\/option>\r\n\r\n<option value=\"ru\">\r\n\ud83c\uddf7\ud83c\uddfa Russe\r\n<\/option>\r\n\r\n<option value=\"uk\">\r\n\ud83c\uddfa\ud83c\udde6 Ukrainien\r\n<\/option>\r\n\r\n<option value=\"pl\">\r\n\ud83c\uddf5\ud83c\uddf1 Polonais\r\n<\/option>\r\n\r\n<option value=\"cs\">\r\n\ud83c\udde8\ud83c\uddff Tch\u00e8que\r\n<\/option>\r\n\r\n<option value=\"sk\">\r\n\ud83c\uddf8\ud83c\uddf0 Slovaque\r\n<\/option>\r\n\r\n<option value=\"hu\">\r\n\ud83c\udded\ud83c\uddfa Hongrois\r\n<\/option>\r\n\r\n<option value=\"ro\">\r\n\ud83c\uddf7\ud83c\uddf4 Roumain\r\n<\/option>\r\n\r\n<option value=\"bg\">\r\n\ud83c\udde7\ud83c\uddec Bulgare\r\n<\/option>\r\n\r\n<option value=\"el\">\r\n\ud83c\uddec\ud83c\uddf7 Grec\r\n<\/option>\r\n\r\n<option value=\"tr\">\r\n\ud83c\uddf9\ud83c\uddf7 Turc\r\n<\/option>\r\n\r\n<option value=\"ar\">\r\n\ud83c\uddf8\ud83c\udde6 Arabe\r\n<\/option>\r\n\r\n<option value=\"he\">\r\n\ud83c\uddee\ud83c\uddf1 H\u00e9breu\r\n<\/option>\r\n\r\n<option value=\"fa\">\r\n\ud83c\uddee\ud83c\uddf7 Persan\r\n<\/option>\r\n\r\n<option value=\"hi\">\r\n\ud83c\uddee\ud83c\uddf3 Hindi\r\n<\/option>\r\n\r\n<option value=\"bn\">\r\n\ud83c\udde7\ud83c\udde9 Bengali\r\n<\/option>\r\n\r\n<option value=\"zh\">\r\n\ud83c\udde8\ud83c\uddf3 Chinois\r\n<\/option>\r\n\r\n<option value=\"ja\">\r\n\ud83c\uddef\ud83c\uddf5 Japonais\r\n<\/option>\r\n\r\n<option value=\"ko\">\r\n\ud83c\uddf0\ud83c\uddf7 Cor\u00e9en\r\n<\/option>\r\n\r\n<option value=\"th\">\r\n\ud83c\uddf9\ud83c\udded Tha\u00ef\r\n<\/option>\r\n\r\n<option value=\"vi\">\r\n\ud83c\uddfb\ud83c\uddf3 Vietnamien\r\n<\/option>\r\n\r\n<option value=\"id\">\r\n\ud83c\uddee\ud83c\udde9 Indon\u00e9sien\r\n<\/option>\r\n\r\n<option value=\"ms\">\r\n\ud83c\uddf2\ud83c\uddfe Malais\r\n<\/option>\r\n\r\n<option value=\"sw\">\r\n\ud83c\uddf0\ud83c\uddea Swahili\r\n<\/option>\r\n\r\n<option value=\"la\">\r\n\ud83c\udfdb\ufe0f Latin\r\n<\/option>\r\n\r\n<option value=\"ca\">\r\nCatalan\r\n<\/option>\r\n\r\n<option value=\"eu\">\r\nBasque\r\n<\/option>\r\n\r\n<option value=\"gl\">\r\nGalicien\r\n<\/option>\r\n\r\n<option value=\"da\">\r\nDanois\r\n<\/option>\r\n\r\n<option value=\"sv\">\r\nSu\u00e9dois\r\n<\/option>\r\n\r\n<option value=\"no\">\r\nNorv\u00e9gien\r\n<\/option>\r\n\r\n<option value=\"fi\">\r\nFinnois\r\n<\/option>\r\n\r\n<option value=\"is\">\r\nIslandais\r\n<\/option>\r\n\r\n<option value=\"et\">\r\nEstonien\r\n<\/option>\r\n\r\n<option value=\"lv\">\r\nLetton\r\n<\/option>\r\n\r\n<option value=\"lt\">\r\nLituanien\r\n<\/option>\r\n\r\n<option value=\"sl\">\r\nSlov\u00e8ne\r\n<\/option>\r\n\r\n<option value=\"hr\">\r\nCroate\r\n<\/option>\r\n\r\n<option value=\"sr\">\r\nSerbe\r\n<\/option>\r\n\r\n<\/select>\r\n\r\n<span class=\"help-text\">\r\nMaintenez Ctrl (Windows) ou Cmd (Mac) pour s\u00e9lectionner plusieurs langues.\r\n<\/span>\r\n\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n\r\n<!-- FILTRES RELIGIEUX -->\r\n\r\n<div class=\"field-row\">\r\n\r\n<label class=\"toggle-filter\">\r\n\r\n<input\r\ntype=\"checkbox\"\r\nid=\"religiousFilter\"\r\nchecked\r\n>\r\n\r\n<span>\r\n\u271d\ufe0f Films chr\u00e9tiens \/ religieux\r\n<\/span>\r\n\r\n<\/label>\r\n\r\n\r\n<fieldset class=\"lexical-box\">\r\n\r\n<legend>\r\nInclure des mots-cl\u00e9s dans le titre\r\n<\/legend>\r\n\r\n<input\r\nid=\"lexicalCustom\"\r\ntype=\"text\"\r\nplaceholder=\"Ex : chr\u00e9tien, christian, bible, biblique, J\u00e9sus...\"\r\n>\r\n\r\n<\/fieldset>\r\n\r\n\r\n<fieldset class=\"lexical-box\">\r\n\r\n<legend>\r\n\ud83d\udeab Exclure des mots-cl\u00e9s\r\n<\/legend>\r\n\r\n<input\r\nid=\"excludeKeywords\"\r\ntype=\"text\"\r\nplaceholder=\"Ex : horreur, pornographie, violence\"\r\n>\r\n\r\n<span class=\"help-text\">\r\nS\u00e9parez les mots par des virgules.\r\n<\/span>\r\n\r\n<\/fieldset>\r\n\r\n<\/div>\r\n\r\n\r\n\r\n<!-- TRI ET PAGINATION -->\r\n\r\n<div class=\"field-row\">\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"sort\">\r\nTri\r\n<\/label>\r\n\r\n<select id=\"sort\">\r\n\r\n<option value=\"newest\">\r\nPlus r\u00e9cents\r\n<\/option>\r\n\r\n<option value=\"oldest\">\r\nPlus anciens\r\n<\/option>\r\n\r\n<option value=\"title\">\r\nTitre A-Z\r\n<\/option>\r\n\r\n<\/select>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"pageSize\">\r\nNombre de r\u00e9sultats par page\r\n<\/label>\r\n\r\n<input\r\nid=\"pageSize\"\r\ntype=\"number\"\r\nvalue=\"20\"\r\nmin=\"1\"\r\nmax=\"500\"\r\n>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"maxResults\">\r\nNombre maximum de r\u00e9sultats\r\n<\/label>\r\n\r\n<input\r\nid=\"maxResults\"\r\ntype=\"number\"\r\nplaceholder=\"Illimit\u00e9\"\r\nmin=\"1\"\r\n>\r\n\r\n<\/div>\r\n\r\n\r\n<div class=\"field\">\r\n\r\n<label for=\"pageInput\">\r\nNum\u00e9ro de page\r\n<\/label>\r\n\r\n<input\r\nid=\"pageInput\"\r\ntype=\"number\"\r\nmin=\"1\"\r\nvalue=\"1\"\r\n>\r\n\r\n<\/div>\r\n\r\n<\/div>\r\n\r\n\r\n\r\n<!-- BOUTONS -->\r\n\r\n<div class=\"field-row\">\r\n\r\n<button id=\"cancelButton\" disabled>\r\n\u23f9 Annuler\r\n<\/button>\r\n\r\n\r\n<button id=\"resetButton\" class=\"secondary-button\">\r\n\ud83e\uddf9 R\u00e9initialiser\r\n<\/button>\r\n\r\n\r\n<button id=\"pageButton\">\r\n\ud83d\udcc4 Aller \u00e0 la page\r\n<\/button>\r\n\r\n\r\n<button id=\"exportButton\">\r\n\u2b07\ufe0f Exporter CSV\r\n<\/button>\r\n\r\n\r\n<button id=\"youtubeAllButton\" class=\"youtube-button\">\r\n\u25b6\ufe0f Rechercher la s\u00e9lection sur YouTube\r\n<\/button>\r\n\r\n<\/div>\r\n\r\n\r\n\r\n<div class=\"progress\">\r\n\r\n<div\r\nid=\"progressBar\"\r\nclass=\"progress-bar\"\r\n><\/div>\r\n\r\n<\/div>\r\n\r\n\r\n<\/section>\r\n\r\n\r\n<div\r\nid=\"status\"\r\nclass=\"status\"\r\n>\r\nPr\u00eat.\r\n<\/div>\r\n\r\n\r\n<div\r\nid=\"results\"\r\nclass=\"results\"\r\n><\/div>\r\n\r\n\r\n<div class=\"pagination\">\r\n\r\n<button id=\"previousButton\">\r\n\u25c0 Pr\u00e9c\u00e9dent\r\n<\/button>\r\n\r\n<span id=\"pageInfo\">\r\nPage 1 \/ 1\r\n<\/span>\r\n\r\n<button id=\"nextButton\">\r\nSuivant \u25b6\r\n<\/button>\r\n\r\n<\/div>\r\n\r\n\r\n<\/div>\r\n\r\n\r\n\r\n<script>\r\n\r\n\/* =========================================================\r\nCONFIGURATION\r\n========================================================= *\/\r\n\r\nconst WIKIDATA_ENDPOINT =\r\n\"https:\/\/query.wikidata.org\/sparql\";\r\n\r\n\r\nconst RELIGIOUS_GENRE_IDS = [\r\n\"wd:Q115122793\",\r\n\"wd:Q7717463\"\r\n];\r\n\r\n\r\nconst RELIGIOUS_SUBJECT_IDS = [\r\n\"wd:Q5043\",\r\n\"wd:Q1845\",\r\n\"wd:Q302\",\r\n\"wd:Q9174\"\r\n];\r\n\r\n\r\nconst FILM_CLASS = \"wd:Q11424\";\r\n\r\n\r\n\/* =========================================================\r\nVARIABLES\r\n========================================================= *\/\r\n\r\nlet currentPage = 1;\r\n\r\nlet totalPages = 1;\r\n\r\nlet totalKnown = true;\r\n\r\nlet hasNextPageUnknown = false;\r\n\r\nlet currentResults = [];\r\n\r\nlet abortController = null;\r\n\r\nlet isLoading = false;\r\n\r\n\r\n\/* =========================================================\r\nELEMENTS\r\n========================================================= *\/\r\n\r\nconst searchInput =\r\ndocument.getElementById(\"search\");\r\n\r\nconst searchCategory =\r\ndocument.getElementById(\"searchCategory\");\r\n\r\nconst dayInput =\r\ndocument.getElementById(\"day\");\r\n\r\nconst monthInput =\r\ndocument.getElementById(\"month\");\r\n\r\nconst yearInput =\r\ndocument.getElementById(\"year\");\r\n\r\nconst yearFromInput =\r\ndocument.getElementById(\"yearFrom\");\r\n\r\nconst yearToInput =\r\ndocument.getElementById(\"yearTo\");\r\n\r\nconst dateFromInput =\r\ndocument.getElementById(\"dateFrom\");\r\n\r\nconst dateToInput =\r\ndocument.getElementById(\"dateTo\");\r\n\r\nconst titleFilter =\r\ndocument.getElementById(\"titleFilter\");\r\n\r\nconst dateFilter =\r\ndocument.getElementById(\"dateFilter\");\r\n\r\nconst datePresenceFilter =\r\ndocument.getElementById(\"datePresenceFilter\");\r\n\r\nconst typeFilter =\r\ndocument.getElementById(\"typeFilter\");\r\n\r\nconst religiousFilter =\r\ndocument.getElementById(\"religiousFilter\");\r\n\r\nconst lexicalCustomInput =\r\ndocument.getElementById(\"lexicalCustom\");\r\n\r\nconst excludeKeywordsInput =\r\ndocument.getElementById(\"excludeKeywords\");\r\n\r\nconst languageSelect =\r\ndocument.getElementById(\"language\");\r\n\r\nconst pageSizeInput =\r\ndocument.getElementById(\"pageSize\");\r\n\r\nconst maxResultsInput =\r\ndocument.getElementById(\"maxResults\");\r\n\r\nconst statusElement =\r\ndocument.getElementById(\"status\");\r\n\r\nconst resultsElement =\r\ndocument.getElementById(\"results\");\r\n\r\nconst progressBar =\r\ndocument.getElementById(\"progressBar\");\r\n\r\nconst cancelButton =\r\ndocument.getElementById(\"cancelButton\");\r\n\r\nconst pageInput =\r\ndocument.getElementById(\"pageInput\");\r\n\r\nconst previousButton =\r\ndocument.getElementById(\"previousButton\");\r\n\r\nconst nextButton =\r\ndocument.getElementById(\"nextButton\");\r\n\r\nconst pageInfo =\r\ndocument.getElementById(\"pageInfo\");\r\n\r\n\r\n\/* =========================================================\r\nDATES\r\n========================================================= *\/\r\n\r\nfunction daysAgo(n) {\r\n\r\n    const d = new Date();\r\n\r\n    d.setDate(\r\n        d.getDate() - n\r\n    );\r\n\r\n    return d;\r\n}\r\n\r\n\r\nfunction monthsAgo(n) {\r\n\r\n    const d = new Date();\r\n\r\n    d.setMonth(\r\n        d.getMonth() - n\r\n    );\r\n\r\n    return d;\r\n}\r\n\r\n\r\nfunction yearsAgo(n) {\r\n\r\n    const d = new Date();\r\n\r\n    d.setFullYear(\r\n        d.getFullYear() - n\r\n    );\r\n\r\n    return d;\r\n}\r\n\r\n\r\n\/* =========================================================\r\nLANGUES\r\n========================================================= *\/\r\n\r\nfunction getSelectedLanguages() {\r\n\r\n    const values =\r\n        Array.from(\r\n            languageSelect.selectedOptions\r\n        ).map(\r\n            option => option.value\r\n        );\r\n\r\n    if (\r\n        values.length === 0 ||\r\n        values.includes(\"ALL\")\r\n    ) {\r\n\r\n        return [];\r\n\r\n    }\r\n\r\n    return values;\r\n\r\n}\r\n\r\n\r\n\/* =========================================================\r\nMOTS CLES\r\n========================================================= *\/\r\n\r\nfunction getKeywords(value) {\r\n\r\n    return value\r\n        .split(\",\")\r\n        .map(\r\n            word => word.trim().toLowerCase()\r\n        )\r\n        .filter(Boolean);\r\n\r\n}\r\n\r\n\r\n\/* =========================================================\r\nCONDITIONS WIKIDATA\r\n========================================================= *\/\r\n\r\nfunction buildWhereClause() {\r\n\r\n    const search =\r\n        searchInput.value.trim();\r\n\r\n    const searchSafe =\r\n        search\r\n        .replace(\/\\\\\/g, \"\\\\\\\\\")\r\n        .replace(\/\"\/g, '\\\\\"');\r\n\r\n\r\n    const selectedLanguages =\r\n        getSelectedLanguages();\r\n\r\n\r\n    let languageCondition = \"\";\r\n\r\n\r\n    if (selectedLanguages.length > 0) {\r\n\r\n        const values =\r\n            selectedLanguages\r\n            .map(\r\n                lang => `\"${lang}\"`\r\n            )\r\n            .join(\" \");\r\n\r\n        languageCondition = `\r\n\r\n        FILTER(\r\n            LANG(?itemLabel)\r\n            IN (${values})\r\n        )\r\n\r\n        `;\r\n\r\n    }\r\n    else {\r\n\r\n        languageCondition = `\r\n\r\n        FILTER(\r\n            LANG(?itemLabel) != \"\"\r\n        )\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    \/* TYPE *\/\r\n\r\n    let typeCondition = `\r\n\r\n        ?item wdt:P31\/wdt:P279* wd:Q11424 .\r\n\r\n    `;\r\n\r\n\r\n    if (\r\n        typeFilter.value === \"cartoon\"\r\n    ) {\r\n\r\n        typeCondition = `\r\n\r\n        ?item wdt:P31\/wdt:P279* wd:Q157443 .\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    if (\r\n        typeFilter.value === \"film_cartoon\"\r\n    ) {\r\n\r\n        typeCondition = `\r\n\r\n        {\r\n\r\n            ?item wdt:P31\/wdt:P279* wd:Q11424 .\r\n\r\n        }\r\n\r\n        UNION\r\n\r\n        {\r\n\r\n            ?item wdt:P31\/wdt:P279* wd:Q157443 .\r\n\r\n        }\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    if (\r\n        typeFilter.value === \"all\"\r\n    ) {\r\n\r\n        typeCondition = `\r\n\r\n        ?item wdt:P31\/wdt:P279* wd:Q11424 .\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    \/* LABEL *\/\r\n\r\n    const labelCondition = `\r\n\r\n        ?item rdfs:label ?itemLabel .\r\n\r\n        ${languageCondition}\r\n\r\n    `;\r\n\r\n\r\n    \/* RECHERCHE *\/\r\n\r\n    let searchCondition = \"\";\r\n\r\n\r\n    if (search) {\r\n\r\n        const titleMatch = `\r\n\r\n        CONTAINS(\r\n\r\n            LCASE(\r\n                STR(?itemLabel)\r\n            ),\r\n\r\n            LCASE(\r\n                \"${searchSafe}\"\r\n            )\r\n\r\n        )\r\n\r\n        `;\r\n\r\n\r\n        const directorBlock = `\r\n\r\n        ?item wdt:P57 ?director .\r\n\r\n        ?director rdfs:label ?directorLabel .\r\n\r\n        FILTER(\r\n            LANG(?directorLabel)\r\n            IN (\"fr\",\"en\")\r\n        )\r\n\r\n        FILTER(\r\n\r\n            CONTAINS(\r\n\r\n                LCASE(\r\n                    STR(?directorLabel)\r\n                ),\r\n\r\n                LCASE(\r\n                    \"${searchSafe}\"\r\n                )\r\n\r\n            )\r\n\r\n        )\r\n\r\n        `;\r\n\r\n\r\n        const castBlock = `\r\n\r\n        ?item wdt:P161 ?actor .\r\n\r\n        ?actor rdfs:label ?actorLabel .\r\n\r\n        FILTER(\r\n            LANG(?actorLabel)\r\n            IN (\"fr\",\"en\")\r\n        )\r\n\r\n        FILTER(\r\n\r\n            CONTAINS(\r\n\r\n                LCASE(\r\n                    STR(?actorLabel)\r\n                ),\r\n\r\n                LCASE(\r\n                    \"${searchSafe}\"\r\n                )\r\n\r\n            )\r\n\r\n        )\r\n\r\n        `;\r\n\r\n\r\n        if (\r\n            searchCategory.value === \"title\"\r\n        ) {\r\n\r\n            searchCondition =\r\n            `FILTER(${titleMatch})`;\r\n\r\n        }\r\n\r\n\r\n        else if (\r\n            searchCategory.value === \"director\"\r\n        ) {\r\n\r\n            searchCondition =\r\n            directorBlock;\r\n\r\n        }\r\n\r\n\r\n        else if (\r\n            searchCategory.value === \"cast\"\r\n        ) {\r\n\r\n            searchCondition =\r\n            castBlock;\r\n\r\n        }\r\n\r\n\r\n        else {\r\n\r\n            searchCondition = `\r\n\r\n            {\r\n\r\n                FILTER(\r\n                    ${titleMatch}\r\n                )\r\n\r\n            }\r\n\r\n            UNION\r\n\r\n            {\r\n\r\n                ${directorBlock}\r\n\r\n            }\r\n\r\n            UNION\r\n\r\n            {\r\n\r\n                ${castBlock}\r\n\r\n            }\r\n\r\n            `;\r\n\r\n        }\r\n\r\n    }\r\n\r\n\r\n    \/* RELIGIEUX *\/\r\n\r\n    let religiousCondition = \"\";\r\n\r\n\r\n    if (\r\n        religiousFilter.checked\r\n    ) {\r\n\r\n        religiousCondition = `\r\n\r\n        {\r\n\r\n            ?item wdt:P136 ?genre .\r\n\r\n            VALUES ?genre {\r\n                ${RELIGIOUS_GENRE_IDS.join(\" \")}\r\n            }\r\n\r\n        }\r\n\r\n        UNION\r\n\r\n        {\r\n\r\n            ?item wdt:P921 ?subject .\r\n\r\n            VALUES ?subject {\r\n                ${RELIGIOUS_SUBJECT_IDS.join(\" \")}\r\n            }\r\n\r\n        }\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    \/* MOTS A INCLURE *\/\r\n\r\n    const includeKeywords =\r\n        getKeywords(\r\n            lexicalCustomInput.value\r\n        );\r\n\r\n\r\n    if (\r\n        includeKeywords.length > 0\r\n    ) {\r\n\r\n        const includeExpression =\r\n            includeKeywords\r\n            .map(\r\n                word =>\r\n                `CONTAINS(\r\n                    LCASE(STR(?itemLabel)),\r\n                    LCASE(\"${word.replace(\/\"\/g,'\\\\\"')}\")\r\n                )`\r\n            )\r\n            .join(\" || \");\r\n\r\n\r\n        const includeCondition = `\r\n\r\n        FILTER(\r\n            ${includeExpression}\r\n        )\r\n\r\n        `;\r\n\r\n\r\n        if (\r\n            religiousCondition\r\n        ) {\r\n\r\n            religiousCondition = `\r\n\r\n            {\r\n\r\n                ${religiousCondition}\r\n\r\n            }\r\n\r\n            UNION\r\n\r\n            {\r\n\r\n                ${includeCondition}\r\n\r\n            }\r\n\r\n            `;\r\n\r\n        }\r\n        else {\r\n\r\n            religiousCondition =\r\n                includeCondition;\r\n\r\n        }\r\n\r\n    }\r\n\r\n\r\n    \/* EXCLUSION *\/\r\n\r\n    const excludeKeywords =\r\n        getKeywords(\r\n            excludeKeywordsInput.value\r\n        );\r\n\r\n\r\n    let excludeCondition = \"\";\r\n\r\n\r\n    if (\r\n        excludeKeywords.length > 0\r\n    ) {\r\n\r\n        const expressions =\r\n            excludeKeywords\r\n            .map(\r\n                word =>\r\n                `CONTAINS(\r\n                    LCASE(STR(?itemLabel)),\r\n                    LCASE(\"${word.replace(\/\"\/g,'\\\\\"')}\")\r\n                )`\r\n            )\r\n            .join(\" || \");\r\n\r\n\r\n        excludeCondition = `\r\n\r\n        FILTER(\r\n            !(${expressions})\r\n        )\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    \/* DATE *\/\r\n\r\n    let dateCondition = \"\";\r\n\r\n\r\n    if (\r\n        datePresenceFilter.value === \"with\"\r\n    ) {\r\n\r\n        dateCondition += `\r\n\r\n        ?item wdt:P577 ?date .\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    else if (\r\n        datePresenceFilter.value === \"without\"\r\n    ) {\r\n\r\n        dateCondition += `\r\n\r\n        FILTER(\r\n            NOT EXISTS {\r\n                ?item wdt:P577 ?anyDate .\r\n            }\r\n        )\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    else {\r\n\r\n        dateCondition += `\r\n\r\n        OPTIONAL {\r\n            ?item wdt:P577 ?date .\r\n        }\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    const dateFrom =\r\n        dateFromInput.value;\r\n\r\n    const dateTo =\r\n        dateToInput.value;\r\n\r\n\r\n    if (dateFrom) {\r\n\r\n        dateCondition += `\r\n\r\n        FILTER(\r\n            ?date >=\r\n            \"${dateFrom}T00:00:00Z\"^^xsd:dateTime\r\n        )\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    if (dateTo) {\r\n\r\n        dateCondition += `\r\n\r\n        FILTER(\r\n            ?date <=\r\n            \"${dateTo}T23:59:59Z\"^^xsd:dateTime\r\n        )\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    \/* DATE FILTER ANCIEN *\/\r\n\r\n    if (\r\n        dateFilter.value === \"with\"\r\n    ) {\r\n\r\n        dateCondition += `\r\n\r\n        FILTER(\r\n            EXISTS {\r\n                ?item wdt:P577 ?dateCheck .\r\n            }\r\n        )\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    if (\r\n        dateFilter.value === \"without\"\r\n    ) {\r\n\r\n        dateCondition += `\r\n\r\n        FILTER(\r\n            NOT EXISTS {\r\n                ?item wdt:P577 ?dateCheck2 .\r\n            }\r\n        )\r\n\r\n        `;\r\n\r\n    }\r\n\r\n\r\n    \/* TITRE SANS TITRE *\/\r\n\r\n    if (\r\n        titleFilter.value === \"without\"\r\n    ) {\r\n\r\n        \/*\r\n        Les films sans label dans une langue donn\u00e9e\r\n        ne peuvent pas \u00eatre r\u00e9cup\u00e9r\u00e9s avec\r\n        ?itemLabel obligatoire.\r\n        Cette option est trait\u00e9e dans une requ\u00eate\r\n        s\u00e9par\u00e9e dans une future \u00e9volution.\r\n        *\/\r\n\r\n    }\r\n\r\n\r\n    return `\r\n\r\n        ${typeCondition}\r\n\r\n        ${labelCondition}\r\n\r\n        ${religiousCondition}\r\n\r\n        ${searchCondition}\r\n\r\n        ${excludeCondition}\r\n\r\n        ${dateCondition}\r\n\r\n    `;\r\n\r\n}\r\n\r\n\r\n\/* =========================================================\r\nREQUETE LISTE\r\n========================================================= *\/\r\n\r\nfunction buildListQuery(\r\n    limit,\r\n    offset\r\n) {\r\n\r\n    const where =\r\n        buildWhereClause();\r\n\r\n\r\n    let order =\r\n        \"DESC(?date)\";\r\n\r\n\r\n    if (\r\n        document.getElementById(\"sort\").value === \"oldest\"\r\n    ) {\r\n\r\n        order =\r\n            \"ASC(?date)\";\r\n\r\n    }\r\n\r\n\r\n    if (\r\n        document.getElementById(\"sort\").value === \"title\"\r\n    ) {\r\n\r\n        order =\r\n            \"LCASE(STR(?itemLabel))\";\r\n\r\n    }\r\n\r\n\r\n    return `\r\n\r\n    PREFIX wd:\r\n    <http:\/\/www.wikidata.org\/entity\/>\r\n\r\n    PREFIX wdt:\r\n    <http:\/\/www.wikidata.org\/prop\/direct\/>\r\n\r\n    PREFIX rdfs:\r\n    <http:\/\/www.w3.org\/2000\/01\/rdf-schema#>\r\n\r\n    PREFIX xsd:\r\n    <http:\/\/www.w3.org\/2001\/XMLSchema#>\r\n\r\n\r\n    SELECT DISTINCT\r\n        ?item\r\n        ?itemLabel\r\n        ?date\r\n\r\n    WHERE {\r\n\r\n        ${where}\r\n\r\n    }\r\n\r\n    ORDER BY\r\n        ${order}\r\n\r\n    LIMIT ${limit}\r\n\r\n    OFFSET ${offset}\r\n\r\n    `;\r\n\r\n}\r\n\r\n\r\n\/* =========================================================\r\nREQUETE DETAILS\r\n========================================================= *\/\r\n\r\nfunction buildDetailsQuery(\r\n    itemUris\r\n) {\r\n\r\n    const values =\r\n        itemUris\r\n        .map(\r\n            uri => `<${uri}>`\r\n        )\r\n        .join(\" \");\r\n\r\n\r\n    return `\r\n\r\n    PREFIX wdt:\r\n    <http:\/\/www.wikidata.org\/prop\/direct\/>\r\n\r\n    PREFIX rdfs:\r\n    <http:\/\/www.w3.org\/2000\/01\/rdf-schema#>\r\n\r\n\r\n    SELECT\r\n\r\n        ?item\r\n\r\n        (SAMPLE(?image) AS ?image)\r\n\r\n        (SAMPLE(?directorLabel)\r\n        AS ?directorLabel)\r\n\r\n        (GROUP_CONCAT(\r\n            DISTINCT ?castLabel;\r\n            separator=\", \"\r\n        ) AS ?cast)\r\n\r\n        (SAMPLE(?website)\r\n        AS ?website)\r\n\r\n        (SAMPLE(?imdb)\r\n        AS ?imdb)\r\n\r\n\r\n    WHERE {\r\n\r\n        VALUES ?item {\r\n            ${values}\r\n        }\r\n\r\n\r\n        OPTIONAL {\r\n\r\n            ?item wdt:P18 ?image .\r\n\r\n        }\r\n\r\n\r\n        OPTIONAL {\r\n\r\n            ?item wdt:P57 ?director .\r\n\r\n            ?director rdfs:label\r\n            ?directorLabel .\r\n\r\n            FILTER(\r\n                LANG(?directorLabel)\r\n                IN (\"fr\",\"en\")\r\n            )\r\n\r\n        }\r\n\r\n\r\n        OPTIONAL {\r\n\r\n            ?item wdt:P161 ?castPerson .\r\n\r\n            ?castPerson rdfs:label\r\n            ?castLabel .\r\n\r\n            FILTER(\r\n                LANG(?castLabel)\r\n                IN (\"fr\",\"en\")\r\n            )\r\n\r\n        }\r\n\r\n\r\n        OPTIONAL {\r\n\r\n            ?item wdt:P856\r\n            ?website .\r\n\r\n        }\r\n\r\n\r\n        OPTIONAL {\r\n\r\n            ?item wdt:P345\r\n            ?imdbId .\r\n\r\n            BIND(\r\n\r\n                CONCAT(\r\n                    \"https:\/\/www.imdb.com\/title\/\",\r\n                    ?imdbId\r\n                )\r\n\r\n                AS ?imdb\r\n\r\n            )\r\n\r\n        }\r\n\r\n    }\r\n\r\n    GROUP BY\r\n        ?item\r\n\r\n    `;\r\n\r\n}\r\n\r\n\r\n\/* =========================================================\r\nFETCH\r\n========================================================= *\/\r\n\r\nasync function fetchWikidata(\r\n    query\r\n) {\r\n\r\n    const response =\r\n        await fetch(\r\n            WIKIDATA_ENDPOINT,\r\n            {\r\n\r\n                method:\r\n                    \"POST\",\r\n\r\n                headers: {\r\n\r\n                    \"Content-Type\":\r\n                        \"application\/x-www-form-urlencoded\",\r\n\r\n                    \"Accept\":\r\n                        \"application\/sparql-results+json\"\r\n\r\n                },\r\n\r\n                body:\r\n                    \"query=\" +\r\n                    encodeURIComponent(query),\r\n\r\n                signal:\r\n                    abortController.signal\r\n\r\n            }\r\n        );\r\n\r\n\r\n    if (!response.ok) {\r\n\r\n        throw new Error(\r\n            \"Erreur HTTP \" +\r\n            response.status\r\n        );\r\n\r\n    }\r\n\r\n\r\n    return response.json();\r\n\r\n}\r\n\r\n\r\n\/* =========================================================\r\nPOSTER DE SECOURS\r\n========================================================= *\/\r\n\r\nfunction createFallbackPoster(\r\n    title\r\n) {\r\n\r\n    const safeTitle =\r\n        String(\r\n            title ||\r\n            \"Titre non renseign\u00e9\"\r\n        )\r\n        .replace(\r\n            \/[<>&'\"]\/g,\r\n            \"\"\r\n        );\r\n\r\n\r\n    const svg = `\r\n\r\n    <svg\r\n        xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\r\n        viewBox=\"0 0 400 600\"\r\n    >\r\n\r\n    <rect\r\n        width=\"400\"\r\n        height=\"600\"\r\n        fill=\"#17171a\"\r\n    \/>\r\n\r\n    <circle\r\n        cx=\"200\"\r\n        cy=\"210\"\r\n        r=\"70\"\r\n        fill=\"none\"\r\n        stroke=\"#f0c400\"\r\n        stroke-width=\"6\"\r\n    \/>\r\n\r\n    <polygon\r\n        points=\"180,175 180,245 240,210\"\r\n        fill=\"#f0c400\"\r\n    \/>\r\n\r\n    <text\r\n        x=\"200\"\r\n        y=\"350\"\r\n        text-anchor=\"middle\"\r\n        font-family=\"Arial\"\r\n        font-size=\"25\"\r\n        font-weight=\"bold\"\r\n        fill=\"#fff\"\r\n    >\r\n    ${safeTitle}\r\n    <\/text>\r\n\r\n    <\/svg>\r\n\r\n    `;\r\n\r\n\r\n    return \"data:image\/svg+xml;charset=UTF-8,\" +\r\n        encodeURIComponent(svg);\r\n\r\n}\r\n\r\n\r\n\/* =========================================================\r\nRECHERCHE\r\n========================================================= *\/\r\n\r\nasync function searchWikidata() {\r\n\r\n    if (isLoading) {\r\n        return;\r\n    }\r\n\r\n\r\n    isLoading = true;\r\n\r\n\r\n    abortController =\r\n        new AbortController();\r\n\r\n\r\n    cancelButton.disabled =\r\n        false;\r\n\r\n\r\n    progressBar.style.width =\r\n        \"10%\";\r\n\r\n\r\n    resultsElement.innerHTML =\r\n        \"\";\r\n\r\n\r\n    statusElement.textContent =\r\n        \"\u23f3 Recherche dans Wikidata...\";\r\n\r\n\r\n    try {\r\n\r\n        const pageSize =\r\n            Math.max(\r\n                1,\r\n                Math.min(\r\n                    500,\r\n                    Number(\r\n                        pageSizeInput.value\r\n                    ) || 20\r\n                )\r\n            );\r\n\r\n\r\n        pageSizeInput.value =\r\n            pageSize;\r\n\r\n\r\n        const offset =\r\n            (\r\n                currentPage - 1\r\n            ) *\r\n            pageSize;\r\n\r\n\r\n        const listData =\r\n            await fetchWikidata(\r\n                buildListQuery(\r\n                    pageSize + 1,\r\n                    offset\r\n                )\r\n            );\r\n\r\n\r\n        let rows =\r\n            listData.results.bindings;\r\n\r\n\r\n        hasNextPageUnknown =\r\n            rows.length >\r\n            pageSize;\r\n\r\n\r\n        rows =\r\n            rows.slice(\r\n                0,\r\n                pageSize\r\n            );\r\n\r\n\r\n        progressBar.style.width =\r\n            \"45%\";\r\n\r\n\r\n        if (\r\n            rows.length === 0\r\n        ) {\r\n\r\n            currentResults =\r\n                [];\r\n\r\n\r\n            totalPages =\r\n                currentPage;\r\n\r\n\r\n            statusElement.textContent =\r\n                \"\u274c Aucun r\u00e9sultat trouv\u00e9.\";\r\n\r\n\r\n            renderResults();\r\n\r\n\r\n            return;\r\n\r\n        }\r\n\r\n\r\n        const itemUris =\r\n            rows.map(\r\n                row =>\r\n                    row.item.value\r\n            );\r\n\r\n\r\n        statusElement.textContent =\r\n            \"\u23f3 R\u00e9cup\u00e9ration des affiches et informations...\";\r\n\r\n\r\n        const detailsData =\r\n            await fetchWikidata(\r\n                buildDetailsQuery(\r\n                    itemUris\r\n                )\r\n            );\r\n\r\n\r\n        const detailsByUri =\r\n            {};\r\n\r\n\r\n        detailsData.results.bindings\r\n            .forEach(\r\n                row => {\r\n\r\n                    detailsByUri[\r\n                        row.item.value\r\n                    ] = row;\r\n\r\n                }\r\n            );\r\n\r\n\r\n        currentResults =\r\n            rows.map(\r\n                row => {\r\n\r\n                    const details =\r\n                        detailsByUri[\r\n                            row.item.value\r\n                        ] || {};\r\n\r\n\r\n                    return {\r\n\r\n                        uri:\r\n                            row.item.value,\r\n\r\n                        title:\r\n                            row.itemLabel\r\n                            ? row.itemLabel.value\r\n                            : \"Titre non renseign\u00e9\",\r\n\r\n                        poster:\r\n                            details.image\r\n                            ? details.image.value\r\n                            : \"\",\r\n\r\n                        director:\r\n                            details.directorLabel\r\n                            ? details.directorLabel.value\r\n                            : \"\",\r\n\r\n                        cast:\r\n                            details.cast\r\n                            ? details.cast.value\r\n                            : \"\",\r\n\r\n                        date:\r\n                            row.date\r\n                            ? row.date.value\r\n                            : \"\",\r\n\r\n                        website:\r\n                            details.website\r\n                            ? details.website.value\r\n                            : \"\",\r\n\r\n                        imdb:\r\n                            details.imdb\r\n                            ? details.imdb.value\r\n                            : \"\"\r\n\r\n                    };\r\n\r\n                }\r\n            );\r\n\r\n\r\n        totalPages =\r\n            hasNextPageUnknown\r\n            ? currentPage + 1\r\n            : currentPage;\r\n\r\n\r\n        progressBar.style.width =\r\n            \"100%\";\r\n\r\n\r\n        statusElement.textContent =\r\n            \"\u2705 \" +\r\n            currentResults.length +\r\n            \" r\u00e9sultat(s) affich\u00e9(s).\";\r\n\r\n\r\n        renderResults();\r\n\r\n    }\r\n\r\n\r\n    catch (error) {\r\n\r\n        if (\r\n            error.name ===\r\n            \"AbortError\"\r\n        ) {\r\n\r\n            statusElement.textContent =\r\n                \"\u23f9 Recherche annul\u00e9e.\";\r\n\r\n        }\r\n\r\n        else {\r\n\r\n            console.error(\r\n                \"Wikidata :\",\r\n                error\r\n            );\r\n\r\n\r\n            statusElement.textContent =\r\n                \"\u274c Impossible d'interroger Wikidata : \" +\r\n                error.message;\r\n\r\n        }\r\n\r\n    }\r\n\r\n\r\n    finally {\r\n\r\n        isLoading =\r\n            false;\r\n\r\n\r\n        cancelButton.disabled =\r\n            true;\r\n\r\n\r\n        abortController =\r\n            null;\r\n\r\n    }\r\n\r\n}\r\n\r\n\r\n\/* =========================================================\r\nAFFICHAGE\r\n========================================================= *\/\r\n\r\nfunction renderResults() {\r\n\r\n    resultsElement.innerHTML =\r\n        \"\";\r\n\r\n\r\n    currentResults\r\n        .forEach(\r\n            film => {\r\n\r\n\r\n                const card =\r\n                    document.createElement(\r\n                        \"article\"\r\n                    );\r\n\r\n\r\n                card.className =\r\n                    \"card\";\r\n\r\n\r\n                const title =\r\n                    film.title ||\r\n                    \"Titre non renseign\u00e9\";\r\n\r\n\r\n                const poster =\r\n                    film.poster ||\r\n                    createFallbackPoster(\r\n                        title\r\n                    );\r\n\r\n\r\n                const year =\r\n                    film.date\r\n                    ? new Date(\r\n                        film.date\r\n                    ).getFullYear()\r\n                    : \"\";\r\n\r\n\r\n                const primaryLink =\r\n                    film.website ||\r\n                    film.imdb ||\r\n                    film.uri;\r\n\r\n\r\n                const youtubeUrl =\r\n                    \"https:\/\/www.youtube.com\/results?search_query=\" +\r\n                    encodeURIComponent(\r\n                        title +\r\n                        \" film complet\"\r\n                    );\r\n\r\n\r\n                card.innerHTML = `\r\n\r\n                <div class=\"poster-wrap\">\r\n\r\n\r\n                <img decoding=\"async\"\r\n\r\n                class=\"poster\"\r\n\r\n                src=\"${poster}\"\r\n\r\n                alt=\"${escapeHTML(title)}\"\r\n\r\n                loading=\"lazy\"\r\n\r\n                onerror=\"this.src='${createFallbackPoster(title)}'\"\r\n\r\n                >\r\n\r\n\r\n                ${\r\n                    year\r\n                    ? `<div class=\"year-badge\">${year}<\/div>`\r\n                    : \"\"\r\n                }\r\n\r\n\r\n                ${\r\n                    religiousFilter.checked\r\n                    ? `<div class=\"religious-badge\">\u271d\ufe0f Religieux<\/div>`\r\n                    : \"\"\r\n                }\r\n\r\n\r\n                <a\r\n\r\n                class=\"poster-overlay\"\r\n\r\n                href=\"${primaryLink}\"\r\n\r\n                target=\"_blank\"\r\n\r\n                rel=\"noopener\"\r\n\r\n                >\r\n\r\n                <span class=\"overlay-btn\">\r\n\r\n                \u25b6 Voir la fiche\r\n\r\n                <\/span>\r\n\r\n                <\/a>\r\n\r\n\r\n                <\/div>\r\n\r\n\r\n                <div class=\"card-content\">\r\n\r\n\r\n                <h3>\r\n\r\n                ${escapeHTML(title)}\r\n\r\n                <\/h3>\r\n\r\n\r\n                ${\r\n                    film.director\r\n                    ? `\r\n                    <div class=\"meta-line\">\r\n\r\n                    <b>R\u00e9alisation :<\/b>\r\n\r\n                    ${escapeHTML(\r\n                        film.director\r\n                    )}\r\n\r\n                    <\/div>\r\n                    `\r\n                    : \"\"\r\n                }\r\n\r\n\r\n                ${\r\n                    film.cast\r\n                    ? `\r\n                    <div class=\"meta-line\">\r\n\r\n                    <b>Avec :<\/b>\r\n\r\n                    ${escapeHTML(\r\n                        film.cast\r\n                    )}\r\n\r\n                    <\/div>\r\n                    `\r\n                    : \"\"\r\n                }\r\n\r\n\r\n                <div class=\"meta-line\">\r\n\r\n                \ud83d\udcc5\r\n\r\n                ${\r\n                    film.date\r\n                    ? formatDate(\r\n                        film.date\r\n                    )\r\n                    : \"Date non renseign\u00e9e\"\r\n                }\r\n\r\n                <\/div>\r\n\r\n\r\n                <div class=\"card-links\">\r\n\r\n\r\n                <a\r\n\r\n                class=\"link-primary\"\r\n\r\n                href=\"${primaryLink}\"\r\n\r\n                target=\"_blank\"\r\n\r\n                rel=\"noopener\"\r\n\r\n                >\r\n\r\n                \u25b6 Voir la fiche\r\n\r\n                <\/a>\r\n\r\n\r\n                <a\r\n\r\n                class=\"link-youtube\"\r\n\r\n                href=\"${youtubeUrl}\"\r\n\r\n                target=\"_blank\"\r\n\r\n                rel=\"noopener\"\r\n\r\n                >\r\n\r\n                \u25b6 YouTube\r\n\r\n                <\/a>\r\n\r\n\r\n                <a\r\n\r\n                class=\"link-secondary\"\r\n\r\n                href=\"${film.uri}\"\r\n\r\n                target=\"_blank\"\r\n\r\n                rel=\"noopener\"\r\n\r\n                >\r\n\r\n                Wikidata\r\n\r\n                <\/a>\r\n\r\n\r\n                <\/div>\r\n\r\n\r\n                <\/div>\r\n\r\n                `;\r\n\r\n\r\n                resultsElement\r\n                    .appendChild(\r\n                        card\r\n                    );\r\n\r\n            }\r\n        );\r\n\r\n\r\n    updatePagination();\r\n\r\n}\r\n\r\n\r\n\/* =========================================================\r\nPAGINATION\r\n========================================================= *\/\r\n\r\nfunction updatePagination() {\r\n\r\n    pageInfo.textContent =\r\n        \"Page \" +\r\n        currentPage;\r\n\r\n\r\n    previousButton.disabled =\r\n        currentPage <= 1;\r\n\r\n\r\n    nextButton.disabled =\r\n        !hasNextPageUnknown;\r\n\r\n\r\n    pageInput.value =\r\n        currentPage;\r\n\r\n}\r\n\r\n\r\n\/* =========================================================\r\nDATE\r\n========================================================= *\/\r\n\r\nfunction formatDate(\r\n    value\r\n) {\r\n\r\n    const date =\r\n        new Date(value);\r\n\r\n\r\n    if (\r\n        isNaN(\r\n            date.getTime()\r\n        )\r\n    ) {\r\n\r\n        return value;\r\n\r\n    }\r\n\r\n\r\n    return date.toLocaleDateString(\r\n        \"fr-FR\"\r\n    );\r\n\r\n}\r\n\r\n\r\n\/* =========================================================\r\nSECURITE\r\n========================================================= *\/\r\n\r\nfunction escapeHTML(\r\n    value\r\n) {\r\n\r\n    const div =\r\n        document.createElement(\r\n            \"div\"\r\n        );\r\n\r\n\r\n    div.textContent =\r\n        value ||\r\n        \"\";\r\n\r\n\r\n    return div.innerHTML;\r\n\r\n}\r\n\r\n\r\n\/* =========================================================\r\nEVENEMENTS\r\n========================================================= *\/\r\n\r\ndocument\r\n.getElementById(\"searchButton\")\r\n.addEventListener(\r\n    \"click\",\r\n    function() {\r\n\r\n        currentPage =\r\n            1;\r\n\r\n        searchWikidata();\r\n\r\n    }\r\n);\r\n\r\n\r\nsearchInput\r\n.addEventListener(\r\n    \"keydown\",\r\n    function(event) {\r\n\r\n        if (\r\n            event.key ===\r\n            \"Enter\"\r\n        ) {\r\n\r\n            currentPage =\r\n                1;\r\n\r\n            searchWikidata();\r\n\r\n        }\r\n\r\n    }\r\n);\r\n\r\n\r\ncancelButton\r\n.addEventListener(\r\n    \"click\",\r\n    function() {\r\n\r\n        if (\r\n            abortController\r\n        ) {\r\n\r\n            abortController.abort();\r\n\r\n        }\r\n\r\n    }\r\n);\r\n\r\n\r\nnextButton\r\n.addEventListener(\r\n    \"click\",\r\n    function() {\r\n\r\n        if (\r\n            hasNextPageUnknown\r\n        ) {\r\n\r\n            currentPage++;\r\n\r\n            searchWikidata();\r\n\r\n        }\r\n\r\n    }\r\n);\r\n\r\n\r\npreviousButton\r\n.addEventListener(\r\n    \"click\",\r\n    function() {\r\n\r\n        if (\r\n            currentPage >\r\n            1\r\n        ) {\r\n\r\n            currentPage--;\r\n\r\n            searchWikidata();\r\n\r\n        }\r\n\r\n    }\r\n);\r\n\r\n\r\ndocument\r\n.getElementById(\"pageButton\")\r\n.addEventListener(\r\n    \"click\",\r\n    function() {\r\n\r\n        const page =\r\n            Math.max(\r\n                1,\r\n                Number(\r\n                    pageInput.value\r\n                ) || 1\r\n            );\r\n\r\n\r\n        currentPage =\r\n            page;\r\n\r\n\r\n        searchWikidata();\r\n\r\n    }\r\n);\r\n\r\n\r\n\/* =========================================================\r\nYOUTUBE POUR LA SELECTION ACTUELLE\r\n========================================================= *\/\r\n\r\ndocument\r\n.getElementById(\"youtubeAllButton\")\r\n.addEventListener(\r\n    \"click\",\r\n    function() {\r\n\r\n        if (\r\n            currentResults.length === 0\r\n        ) {\r\n\r\n            alert(\r\n                \"Aucun film n'est actuellement affich\u00e9.\"\r\n            );\r\n\r\n            return;\r\n\r\n        }\r\n\r\n\r\n        currentResults\r\n            .forEach(\r\n                (film, index) => {\r\n\r\n                    setTimeout(\r\n                        function() {\r\n\r\n                            const url =\r\n                                \"https:\/\/www.youtube.com\/results?search_query=\" +\r\n                                encodeURIComponent(\r\n                                    film.title +\r\n                                    \" film complet\"\r\n                                );\r\n\r\n\r\n                            window.open(\r\n                                url,\r\n                                \"_blank\",\r\n                                \"noopener\"\r\n                            );\r\n\r\n                        },\r\n                        index * 300\r\n                    );\r\n\r\n                }\r\n            );\r\n\r\n    }\r\n);\r\n\r\n\r\n\/* =========================================================\r\nEXPORT CSV\r\n========================================================= *\/\r\n\r\ndocument\r\n.getElementById(\"exportButton\")\r\n.addEventListener(\r\n    \"click\",\r\n    function() {\r\n\r\n        if (\r\n            currentResults.length === 0\r\n        ) {\r\n\r\n            alert(\r\n                \"Aucun r\u00e9sultat \u00e0 exporter.\"\r\n            );\r\n\r\n            return;\r\n\r\n        }\r\n\r\n\r\n        const header = [\r\n\r\n            \"Titre\",\r\n\r\n            \"R\u00e9alisateur\",\r\n\r\n            \"Casting\",\r\n\r\n            \"Date\",\r\n\r\n            \"Affiche\",\r\n\r\n            \"Lien\",\r\n\r\n            \"Wikidata\"\r\n\r\n        ];\r\n\r\n\r\n        const rows =\r\n            currentResults.map(\r\n                film => [\r\n\r\n                    film.title,\r\n\r\n                    film.director,\r\n\r\n                    film.cast,\r\n\r\n                    film.date,\r\n\r\n                    film.poster,\r\n\r\n                    film.website ||\r\n                    film.imdb ||\r\n                    film.uri,\r\n\r\n                    film.uri\r\n\r\n                ]\r\n            );\r\n\r\n\r\n        const csv = [\r\n\r\n            header,\r\n\r\n            ...rows\r\n\r\n        ]\r\n        .map(\r\n            row =>\r\n\r\n            row.map(\r\n                value =>\r\n\r\n                '\"' +\r\n\r\n                String(\r\n                    value ||\r\n                    \"\"\r\n                )\r\n                .replace(\r\n                    \/\"\/g,\r\n                    '\"\"'\r\n                ) +\r\n\r\n                '\"'\r\n\r\n            )\r\n            .join(\";\")\r\n\r\n        )\r\n        .join(\"\\n\");\r\n\r\n\r\n        const blob =\r\n            new Blob(\r\n                [\r\n                    \"\\ufeff\" +\r\n                    csv\r\n                ],\r\n                {\r\n                    type:\r\n                    \"text\/csv;charset=utf-8\"\r\n                }\r\n            );\r\n\r\n\r\n        const url =\r\n            URL.createObjectURL(\r\n                blob\r\n            );\r\n\r\n\r\n        const link =\r\n            document.createElement(\r\n                \"a\"\r\n            );\r\n\r\n\r\n        link.href =\r\n            url;\r\n\r\n\r\n        link.download =\r\n            \"films-wikidata.csv\";\r\n\r\n\r\n        link.click();\r\n\r\n\r\n        URL.revokeObjectURL(\r\n            url\r\n        );\r\n\r\n    }\r\n);\r\n\r\n\r\n\/* =========================================================\r\nRESET\r\n========================================================= *\/\r\n\r\ndocument\r\n.getElementById(\"resetButton\")\r\n.addEventListener(\r\n    \"click\",\r\n    function() {\r\n\r\n\r\n        if (\r\n            abortController\r\n        ) {\r\n\r\n            abortController.abort();\r\n\r\n        }\r\n\r\n\r\n        searchInput.value =\r\n            \"\";\r\n\r\n\r\n        searchCategory.value =\r\n            \"title\";\r\n\r\n\r\n        dayInput.value =\r\n            \"\";\r\n\r\n\r\n        monthInput.value =\r\n            \"\";\r\n\r\n\r\n        yearInput.value =\r\n            \"\";\r\n\r\n\r\n        yearFromInput.value =\r\n            \"\";\r\n\r\n\r\n        yearToInput.value =\r\n            \"\";\r\n\r\n\r\n        dateFromInput.value =\r\n            \"\";\r\n\r\n\r\n        dateToInput.value =\r\n            \"\";\r\n\r\n\r\n        titleFilter.value =\r\n            \"all\";\r\n\r\n\r\n        dateFilter.value =\r\n            \"all\";\r\n\r\n\r\n        datePresenceFilter.value =\r\n            \"all\";\r\n\r\n\r\n        typeFilter.value =\r\n            \"film\";\r\n\r\n\r\n        religiousFilter.checked =\r\n            true;\r\n\r\n\r\n        lexicalCustomInput.value =\r\n            \"\";\r\n\r\n\r\n        excludeKeywordsInput.value =\r\n            \"\";\r\n\r\n\r\n        Array.from(\r\n            languageSelect.options\r\n        )\r\n        .forEach(\r\n            option => {\r\n\r\n                option.selected =\r\n                    option.value ===\r\n                    \"ALL\";\r\n\r\n            }\r\n        );\r\n\r\n\r\n        pageSizeInput.value =\r\n            \"20\";\r\n\r\n\r\n        maxResultsInput.value =\r\n            \"\";\r\n\r\n\r\n        currentPage =\r\n            1;\r\n\r\n\r\n        totalPages =\r\n            1;\r\n\r\n\r\n        hasNextPageUnknown =\r\n            false;\r\n\r\n\r\n        currentResults =\r\n            [];\r\n\r\n\r\n        resultsElement.innerHTML =\r\n            \"\";\r\n\r\n\r\n        progressBar.style.width =\r\n            \"0%\";\r\n\r\n\r\n        pageInput.value =\r\n            \"1\";\r\n\r\n\r\n        pageInfo.textContent =\r\n            \"Page 1\";\r\n\r\n\r\n        statusElement.textContent =\r\n            \"Filtres r\u00e9initialis\u00e9s.\";\r\n\r\n    }\r\n);\r\n\r\n\r\n\/* =========================================================\r\nGESTION DE \"TOUTES LES LANGUES\"\r\n========================================================= *\/\r\n\r\nlanguageSelect\r\n.addEventListener(\r\n    \"change\",\r\n    function() {\r\n\r\n        const allOption =\r\n            Array.from(\r\n                languageSelect.options\r\n            )\r\n            .find(\r\n                option =>\r\n                option.value ===\r\n                \"ALL\"\r\n            );\r\n\r\n\r\n        const selected =\r\n            Array.from(\r\n                languageSelect.selectedOptions\r\n            );\r\n\r\n\r\n        if (\r\n            selected.some(\r\n                option =>\r\n                option.value ===\r\n                \"ALL\"\r\n            )\r\n        ) {\r\n\r\n            Array.from(\r\n                languageSelect.options\r\n            )\r\n            .forEach(\r\n                option => {\r\n\r\n                    option.selected =\r\n                        option.value ===\r\n                        \"ALL\";\r\n\r\n                }\r\n            );\r\n\r\n        }\r\n        else if (\r\n            selected.length === 0\r\n        ) {\r\n\r\n            allOption.selected =\r\n                true;\r\n\r\n        }\r\n\r\n    }\r\n);\r\n\r\n\r\n\/* =========================================================\r\nINITIALISATION\r\n========================================================= *\/\r\n\r\nstatusElement.textContent =\r\n\"Pr\u00eat. Entrez un terme ou lancez une recherche.\";\r\n\r\n<\/script>\r\n\r\n<\/body>\r\n<\/html>\r\n```\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-242d692 e-flex e-con-boxed elementor-repeater-item-default_padding elementor-repeater-item-none elementor-repeater-item-none_hover wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-parent\" data-id=\"242d692\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;ang_container_spacing_size&quot;:&quot;default_padding&quot;}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-c46111f elementor-repeater-item-none elementor-repeater-item-none_hover elementor-widget elementor-widget-html\" data-id=\"c46111f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Catalogue Films - Wikidata<\/title>\n\n<style>\n\n* { box-sizing: border-box; }\n\nbody {\n    margin: 0;\n    font-family: Arial, Helvetica, sans-serif;\n    background: #0b0b0d;\n    color: #fff;\n}\n\n.app {\n    max-width: 1400px;\n    margin: auto;\n    padding: 25px;\n}\n\nheader { text-align: center; margin-bottom: 25px; }\nheader h1 { margin: 0 0 10px; }\nheader p { opacity: .7; }\n\n.search-box {\n    background: #1d1d1d;\n    border-radius: 14px;\n    padding: 20px;\n    margin-bottom: 20px;\n}\n\n.field-row {\n    display: grid;\n    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n    gap: 12px;\n    margin-bottom: 16px;\n}\n\n.field-row:last-child { margin-bottom: 0; }\n\n.field-row.search-row {\n    grid-template-columns: 3fr 1.4fr auto;\n}\n\n.field label {\n    display: block;\n    font-size: 11.5px;\n    text-transform: uppercase;\n    letter-spacing: .4px;\n    opacity: .55;\n    margin-bottom: 5px;\n}\n\n.field.full-width { grid-column: 1 \/ -1; }\n\ninput, select {\n    width: 100%;\n    padding: 12px;\n    border-radius: 8px;\n    border: 1px solid #444;\n    background: #111;\n    color: #fff;\n    font-size: 14px;\n}\n\nselect[multiple] { padding: 8px; }\n\nselect:disabled, input:disabled {\n    opacity: .4;\n    cursor: not-allowed;\n}\n\n.field-hint {\n    font-size: 11.5px;\n    opacity: .55;\n    margin-top: 6px;\n}\n\nbutton {\n    padding: 12px 18px;\n    border: 0;\n    border-radius: 8px;\n    background: #f0c400;\n    color: #111;\n    font-weight: bold;\n    cursor: pointer;\n}\n\nbutton:hover { opacity: .85; }\nbutton:disabled { opacity: .4; cursor: not-allowed; }\n\n.youtube-button { background: #ff0000; color: #fff; }\n.cinema-button { background: #2c2c30; color: #fff; border: 1px solid #f0c400; }\n\n.toggle-filter {\n    display: flex;\n    align-items: center;\n    gap: 10px;\n    padding: 12px;\n    border-radius: 8px;\n    border: 1px solid #444;\n    background: #111;\n    cursor: pointer;\n    user-select: none;\n}\n\n.toggle-filter input {\n    width: 18px;\n    height: 18px;\n    accent-color: #f0c400;\n    cursor: pointer;\n}\n\n.toggle-filter span { font-size: 13.5px; }\n\n.lexical-box {\n    border: 1px solid #444;\n    border-radius: 8px;\n    padding: 12px;\n    background: #111;\n}\n\n.lexical-box legend {\n    font-size: 11.5px;\n    text-transform: uppercase;\n    letter-spacing: .4px;\n    opacity: .65;\n    padding: 0 6px;\n}\n\n.lexical-options {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 12px;\n    margin-top: 6px;\n}\n\n.lexical-options label {\n    display: flex;\n    align-items: center;\n    gap: 6px;\n    font-size: 13px;\n    cursor: pointer;\n}\n\n.lexical-options input {\n    width: 16px;\n    height: 16px;\n    accent-color: #f0c400;\n    cursor: pointer;\n}\n\n.status { margin: 20px 0; text-align: center; min-height: 25px; }\n\n.progress {\n    width: 100%;\n    height: 8px;\n    background: #333;\n    border-radius: 10px;\n    overflow: hidden;\n    margin-top: 15px;\n}\n\n.progress-bar {\n    width: 0%;\n    height: 100%;\n    background: #f0c400;\n    transition: width .2s;\n}\n\n.results {\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));\n    gap: 24px;\n}\n\n.card {\n    position: relative;\n    background: #1a1a1d;\n    border-radius: 14px;\n    overflow: hidden;\n    box-shadow: 0 4px 14px rgba(0,0,0,.4);\n    transition: transform .25s ease, box-shadow .25s ease;\n    display: flex;\n    flex-direction: column;\n}\n\n.card:hover {\n    transform: translateY(-6px);\n    box-shadow: 0 14px 30px rgba(0,0,0,.6);\n}\n\n.poster-wrap {\n    position: relative;\n    width: 100%;\n    aspect-ratio: 2\/3;\n    background: #222;\n    overflow: hidden;\n}\n\n.poster {\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n    display: block;\n    transition: transform .35s ease;\n}\n\n.card:hover .poster { transform: scale(1.06); }\n\n\/* Nom du film affich\u00e9 SOUS le mot \"FILM\" du visuel de\n   remplacement (le mot \"FILM\" du SVG est positionn\u00e9 vers\n   68-70% de la hauteur de l'affiche). *\/\n.poster-fallback-title {\n    position: absolute;\n    left: 0;\n    right: 0;\n    top: 71%;\n    bottom: 6%;\n    display: flex;\n    align-items: flex-start;\n    justify-content: center;\n    text-align: center;\n    padding: 8px 14px 0;\n    font-weight: bold;\n    font-size: 14.5px;\n    line-height: 1.25;\n    color: #fff;\n    text-shadow: 0 2px 8px rgba(0,0,0,.9);\n    pointer-events: none;\n    overflow: hidden;\n}\n\n.year-badge {\n    position: absolute;\n    top: 10px;\n    left: 10px;\n    background: rgba(0,0,0,.65);\n    backdrop-filter: blur(3px);\n    padding: 4px 10px;\n    border-radius: 20px;\n    font-size: 12px;\n    font-weight: bold;\n}\n\n.religious-badge {\n    position: absolute;\n    top: 10px;\n    right: 10px;\n    background: rgba(240,196,0,.9);\n    color: #111;\n    padding: 4px 10px;\n    border-radius: 20px;\n    font-size: 11px;\n    font-weight: bold;\n}\n\n.poster-overlay {\n    position: absolute;\n    inset: 0;\n    background: linear-gradient(to top, rgba(0,0,0,.92) 0%, rgba(0,0,0,.15) 55%, rgba(0,0,0,0) 100%);\n    opacity: 0;\n    transition: opacity .25s ease;\n    display: flex;\n    align-items: flex-end;\n    padding: 14px;\n}\n\n.card:hover .poster-overlay { opacity: 1; }\n\n.overlay-btn {\n    display: inline-block;\n    width: 100%;\n    text-align: center;\n    padding: 10px;\n    background: #f0c400;\n    color: #111;\n    font-weight: bold;\n    text-decoration: none;\n    border-radius: 8px;\n    font-size: 13px;\n}\n\n.card-content {\n    padding: 14px 16px 16px;\n    flex: 1;\n    display: flex;\n    flex-direction: column;\n    gap: 6px;\n}\n\n.card h3 {\n    margin: 0;\n    font-size: 15px;\n    line-height: 1.3;\n    display: -webkit-box;\n    -webkit-line-clamp: 2;\n    -webkit-box-orient: vertical;\n    overflow: hidden;\n}\n\n.meta-line {\n    font-size: 12.5px;\n    color: rgba(255,255,255,.65);\n    line-height: 1.5;\n    display: -webkit-box;\n    -webkit-line-clamp: 2;\n    -webkit-box-orient: vertical;\n    overflow: hidden;\n}\n\n.meta-line b { color: rgba(255,255,255,.9); }\n\n.card-links {\n    margin-top: auto;\n    display: flex;\n    gap: 8px;\n    padding-top: 8px;\n}\n\n.link-primary, .link-secondary {\n    text-decoration: none;\n    text-align: center;\n    border-radius: 7px;\n    font-size: 12.5px;\n    font-weight: bold;\n    padding: 9px 6px;\n}\n\n.link-primary { flex: 2; background: #f0c400; color: #111; }\n.link-secondary { flex: 1; background: #2c2c30; color: #fff; }\n.link-primary:hover, .link-secondary:hover { opacity: .85; }\n\n.pagination {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    gap: 15px;\n    margin-top: 30px;\n    flex-wrap: wrap;\n}\n\n@media (max-width: 700px) {\n    .field-row.search-row { grid-template-columns: 1fr; }\n}\n\n<\/style>\n<\/head>\n\n<body>\n<div class=\"app\">\n\n<header>\n<h1>\ud83c\udfac Catalogue Films - Wikidata<\/h1>\n<p>Recherche directe dans Wikidata \u2014 aucune base locale<\/p>\n<\/header>\n\n<section class=\"search-box\">\n\n<div class=\"field-row search-row\">\n\n<div class=\"field\">\n<label for=\"search\">Recherche libre<\/label>\n<input id=\"search\" type=\"search\" placeholder=\"Mot ou partie de mot...\">\n<\/div>\n\n<div class=\"field\">\n<label for=\"searchCategory\">Rechercher dans<\/label>\n<select id=\"searchCategory\">\n<option value=\"title\" selected>Titre<\/option>\n<option value=\"director\">R\u00e9alisateur<\/option>\n<option value=\"cast\">Acteur<\/option>\n<option value=\"all\">Titre + r\u00e9alisateur + acteur<\/option>\n<\/select>\n<\/div>\n\n<div class=\"field\">\n<label>&nbsp;<\/label>\n<button id=\"searchButton\">\ud83d\udd0e Rechercher<\/button>\n<\/div>\n\n<\/div>\n\n\n<div class=\"field-row\">\n\n<div class=\"field\">\n<label for=\"dateFilter\">Date de sortie<\/label>\n<select id=\"dateFilter\">\n<option value=\"all\">Toutes les dates<\/option>\n<option value=\"with\">Avec date de sortie<\/option>\n<option value=\"without\">Sans date de sortie<\/option>\n<\/select>\n<\/div>\n\n<div class=\"field\">\n<label for=\"day\">Derniers jours<\/label>\n<input id=\"day\" type=\"number\" placeholder=\"Ex : 7\" min=\"1\">\n<\/div>\n\n<div class=\"field\">\n<label for=\"month\">Derniers mois<\/label>\n<input id=\"month\" type=\"number\" placeholder=\"Ex : 7\" min=\"1\">\n<\/div>\n\n<div class=\"field\">\n<label for=\"year\">Ann\u00e9e (2026) ou N derni\u00e8res ann\u00e9es (3)<\/label>\n<input id=\"year\" type=\"number\" placeholder=\"Ex : 2026 ou 3\" min=\"1\">\n<\/div>\n\n<div class=\"field\">\n<label for=\"yearFrom\">Ann\u00e9e de (fourchette)<\/label>\n<input id=\"yearFrom\" type=\"number\" placeholder=\"De\" min=\"1\">\n<\/div>\n\n<div class=\"field\">\n<label for=\"yearTo\">Ann\u00e9e \u00e0 (fourchette)<\/label>\n<input id=\"yearTo\" type=\"number\" placeholder=\"\u00c0\" min=\"1\">\n<\/div>\n\n<\/div>\n\n\n<div class=\"field-row\">\n\n<div class=\"field\">\n<label for=\"dateFrom\">Date pr\u00e9cise de d\u00e9but<\/label>\n<input id=\"dateFrom\" type=\"date\">\n<\/div>\n\n<div class=\"field\">\n<label for=\"dateTo\">Date pr\u00e9cise de fin<\/label>\n<input id=\"dateTo\" type=\"date\">\n<\/div>\n\n<\/div>\n\n\n<div class=\"field-row\">\n\n<div class=\"field\">\n<label for=\"titleFilter\">Titre<\/label>\n<select id=\"titleFilter\">\n<option value=\"all\">Tous les films<\/option>\n<option value=\"with\">Avec un titre<\/option>\n<option value=\"without\">Sans titre<\/option>\n<\/select>\n<\/div>\n\n<div class=\"field\">\n<label for=\"typeFilter\">Type<\/label>\n<select id=\"typeFilter\">\n<option value=\"film\" selected>Films uniquement<\/option>\n<option value=\"animation\">Dessins anim\u00e9s uniquement<\/option>\n<option value=\"film_animation\">Films + dessins anim\u00e9s<\/option>\n<option value=\"all\">Films et \u0153uvres audiovisuelles (large)<\/option>\n<\/select>\n<\/div>\n\n<div class=\"field\">\n<label for=\"sort\">Tri<\/label>\n<select id=\"sort\">\n<option value=\"newest\">Plus r\u00e9cents<\/option>\n<option value=\"oldest\">Plus anciens<\/option>\n<option value=\"title\">Titre A-Z<\/option>\n<\/select>\n<\/div>\n\n<div class=\"field\">\n<label for=\"pageSize\">R\u00e9sultats par page<\/label>\n<input id=\"pageSize\" type=\"number\" min=\"1\" max=\"500\" value=\"20\" list=\"pageSizePresets\">\n<datalist id=\"pageSizePresets\">\n<option value=\"20\">\n<option value=\"50\">\n<option value=\"100\">\n<option value=\"200\">\n<\/datalist>\n<\/div>\n\n<div class=\"field\">\n<label for=\"maxResults\">Nombre maxi de r\u00e9sultats<\/label>\n<input id=\"maxResults\" type=\"number\" placeholder=\"Illimit\u00e9\" min=\"1\">\n<\/div>\n\n<\/div>\n\n\n<div class=\"field-row\">\n\n<div class=\"field full-width\">\n<label for=\"language\">Langue(s) du titre <span id=\"languageCount\" style=\"opacity:.5; text-transform:none;\"><\/span><\/label>\n<select id=\"language\" multiple size=\"6\">\n<option disabled>Chargement des langues disponibles dans la base...<\/option>\n<\/select>\n\n<label class=\"toggle-filter\" style=\"margin-top:8px;\" for=\"allLanguages\">\n<input type=\"checkbox\" id=\"allLanguages\">\n<span>\ud83c\udf10 Toutes les langues (ignore la liste ci-dessus)<\/span>\n<\/label>\n\n<div class=\"field-hint\">\nLa liste ci-dessus est construite \u00e0 partir des langues r\u00e9ellement pr\u00e9sentes dans les libell\u00e9s de films sur Wikidata (\u00e9chantillon). Maintenez Ctrl (Cmd sur Mac) pour en s\u00e9lectionner plusieurs. Cochez \u00ab Toutes les langues \u00bb pour ne faire aucun tri par langue.\n<\/div>\n<\/div>\n\n<\/div>\n\n\n<div class=\"field-row\">\n\n<label class=\"toggle-filter\" for=\"religiousFilter\">\n<input type=\"checkbox\" id=\"religiousFilter\" checked>\n<span>\u271d\ufe0f Films chr\u00e9tiens \/ religieux (genre \/ sujet Wikidata)<\/span>\n<\/label>\n\n<fieldset class=\"lexical-box\">\n<legend>\u00c9largir la recherche (mots-cl\u00e9s dans le titre)<\/legend>\n<div class=\"lexical-options\">\n<label><input type=\"checkbox\" class=\"lexicalKeyword\" value=\"chr\u00e9tien,christian\"> Chr\u00e9tien<\/label>\n<label><input type=\"checkbox\" class=\"lexicalKeyword\" value=\"religieux,religious\"> Religieux<\/label>\n<label><input type=\"checkbox\" class=\"lexicalKeyword\" value=\"biblique,biblical,bible\"> Biblique<\/label>\n<label><input type=\"checkbox\" class=\"lexicalKeyword\" value=\"spirituel,spiritual\"> Spirituel<\/label>\n<\/div>\n<input\n    id=\"lexicalCustom\"\n    type=\"text\"\n    placeholder=\"Autres mots-cl\u00e9s, s\u00e9par\u00e9s par une virgule (ex : messe, pri\u00e8re)\"\n    style=\"margin-top:8px;\"\n>\n<\/fieldset>\n\n<fieldset class=\"lexical-box\">\n<legend>Exclure les films contenant ces mots (titre)<\/legend>\n<input\n    id=\"excludeWords\"\n    type=\"text\"\n    placeholder=\"Mots \u00e0 exclure, s\u00e9par\u00e9s par une virgule (ex : horreur, zombie)\"\n>\n<\/fieldset>\n\n<div class=\"field\">\n<label>&nbsp;<\/label>\n<button id=\"cancelButton\" disabled>\u23f9 Annuler<\/button>\n<\/div>\n\n<div class=\"field\">\n<label>&nbsp;<\/label>\n<button id=\"resetButton\">\ud83e\uddf9 R\u00e9initialiser<\/button>\n<\/div>\n\n<\/div>\n\n\n<div class=\"field-row\">\n\n<div class=\"field\">\n<label for=\"pageInput\">Aller \u00e0 la page<\/label>\n<input id=\"pageInput\" type=\"number\" min=\"1\" value=\"1\">\n<\/div>\n\n<div class=\"field\">\n<label>&nbsp;<\/label>\n<button id=\"pageButton\">\ud83d\udcc4 Aller \u00e0 la page<\/button>\n<\/div>\n\n<div class=\"field\">\n<label>&nbsp;<\/label>\n<button id=\"exportButton\">\u2b07\ufe0f Exporter CSV<\/button>\n<\/div>\n\n<div class=\"field\">\n<label>&nbsp;<\/label>\n<button id=\"reloadButton\">\ud83d\udd04 Rechercher<\/button>\n<\/div>\n\n<div class=\"field\">\n<label>&nbsp;<\/label>\n<button id=\"youtubeButton\" class=\"youtube-button\" type=\"button\">\u25b6\ufe0f S\u00e9lection sur YouTube<\/button>\n<\/div>\n\n<div class=\"field\">\n<label>&nbsp;<\/label>\n<button id=\"cinemaButton\" class=\"cinema-button\" type=\"button\">\ud83c\udfa6 Vue Cin\u00e9ma<\/button>\n<\/div>\n\n<\/div>\n\n\n<div class=\"progress\"><div id=\"progressBar\" class=\"progress-bar\"><\/div><\/div>\n\n<\/section>\n\n<div id=\"status\" class=\"status\">Pr\u00eat.<\/div>\n\n<div id=\"results\" class=\"results\"><\/div>\n\n<div class=\"pagination\">\n<button id=\"previousButton\">\u25c0 Pr\u00e9c\u00e9dent<\/button>\n<span id=\"pageInfo\">Page 1 \/ 1<\/span>\n<button id=\"nextButton\">Suivant \u25b6<\/button>\n<\/div>\n\n<\/div>\n\n\n<script>\n\n\/* =========================================================\n   CONFIGURATION\n========================================================= *\/\n\nconst WIKIDATA_ENDPOINT = \"https:\/\/query.wikidata.org\/sparql\";\n\n\/\/ Genre \"Christian film\" \/ \"Bible film\"\nconst RELIGIOUS_GENRE_IDS = [\"wd:Q115122793\", \"wd:Q7717463\"];\n\n\/\/ Sujet principal : Christianisme \/ Bible \/ J\u00e9sus \/ religion\nconst RELIGIOUS_SUBJECT_IDS = [\"wd:Q5043\", \"wd:Q1845\", \"wd:Q302\", \"wd:Q9174\"];\n\n\/\/ \"Film\" et \"film d'animation \/ dessin anim\u00e9\" sur Wikidata\nconst FILM_CLASS_ID = \"wd:Q11424\";\nconst ANIMATED_FILM_CLASS_ID = \"wd:Q202866\";\n\n\/\/ Plafond de comptage : au-del\u00e0, on affiche \"N+ r\u00e9sultats\"\n\/\/ plut\u00f4t que de lancer un COUNT potentiellement tr\u00e8s co\u00fbteux\n\/\/ sur toute la base.\nconst COUNT_CAP = 3000;\n\n\/\/ Nombre de films d'\u00e9chantillon utilis\u00e9 pour d\u00e9terminer quelles\n\/\/ langues sont r\u00e9ellement disponibles dans la base (\u00e9vite un\n\/\/ balayage complet de tout Wikidata, trop co\u00fbteux).\nconst LANGUAGE_SAMPLE_SIZE = 20000;\n\n\/\/ Nombre de films \"\u00e0 venir\" r\u00e9cup\u00e9r\u00e9s pour la vue Cin\u00e9ma.\nconst UPCOMING_LIMIT = 24;\n\n\/\/ Repli utilis\u00e9 si la d\u00e9tection dynamique des langues \u00e9choue\n\/\/ (Wikidata indisponible, requ\u00eate trop longue, etc.)\nconst FALLBACK_LANGUAGE_CODES = [\n    \"fr\",\"en\",\"es\",\"de\",\"it\",\"pt\",\"nl\",\"ar\",\"ru\",\"zh\",\"ja\",\"ko\",\n    \"hi\",\"tr\",\"pl\",\"sv\",\"no\",\"da\",\"fi\",\"el\",\"he\",\"cs\",\"hu\",\"ro\",\"uk\"\n];\n\n\/\/ Image utilis\u00e9e quand un film n'a pas d'affiche sur Wikidata.\n\/\/ G\u00e9n\u00e9r\u00e9e en SVG (pas de d\u00e9pendance r\u00e9seau, toujours disponible).\nconst DEFAULT_POSTER =\n    \"data:image\/svg+xml,\" +\n    encodeURIComponent(\n        '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 400 600\">' +\n        '<defs><linearGradient id=\"g\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">' +\n        '<stop offset=\"0\" stop-color=\"#34343e\"\/>' +\n        '<stop offset=\"1\" stop-color=\"#131316\"\/>' +\n        '<\/linearGradient><\/defs>' +\n        '<rect width=\"400\" height=\"600\" fill=\"url(#g)\"\/>' +\n        '<circle cx=\"200\" cy=\"248\" r=\"74\" fill=\"none\" stroke=\"#f0c400\" stroke-width=\"6\" opacity=\"0.9\"\/>' +\n        '<polygon points=\"184,210 184,286 250,248\" fill=\"#f0c400\" opacity=\"0.95\"\/>' +\n        '<text x=\"200\" y=\"412\" text-anchor=\"middle\" font-family=\"Arial, Helvetica, sans-serif\" ' +\n        'font-size=\"42\" font-weight=\"bold\" letter-spacing=\"10\" fill=\"#ffffff\" opacity=\"0.9\">FILM<\/text>' +\n        '<\/svg>'\n    );\n\n\/* =========================================================\n   VARIABLES\n========================================================= *\/\n\nlet currentPage = 1;\nlet totalPages = 1;\nlet totalKnown = true;\nlet hasNextPageUnknown = false;\nlet currentResults = [];\nlet abortController = null;\nlet isLoading = false;\n\n\/* =========================================================\n   ELEMENTS\n========================================================= *\/\n\nconst searchInput = document.getElementById(\"search\");\nconst searchCategory = document.getElementById(\"searchCategory\");\nconst dayInput = document.getElementById(\"day\");\nconst monthInput = document.getElementById(\"month\");\nconst yearInput = document.getElementById(\"year\");\nconst yearFromInput = document.getElementById(\"yearFrom\");\nconst yearToInput = document.getElementById(\"yearTo\");\nconst dateFromInput = document.getElementById(\"dateFrom\");\nconst dateToInput = document.getElementById(\"dateTo\");\nconst titleFilter = document.getElementById(\"titleFilter\");\nconst dateFilter = document.getElementById(\"dateFilter\");\nconst sortSelect = document.getElementById(\"sort\");\nconst typeFilter = document.getElementById(\"typeFilter\");\nconst religiousFilter = document.getElementById(\"religiousFilter\");\nconst lexicalCustomInput = document.getElementById(\"lexicalCustom\");\nconst excludeWordsInput = document.getElementById(\"excludeWords\");\nconst languageSelect = document.getElementById(\"language\");\nconst languageCountLabel = document.getElementById(\"languageCount\");\nconst allLanguagesCheckbox = document.getElementById(\"allLanguages\");\nconst pageSizeSelect = document.getElementById(\"pageSize\");\nconst maxResultsInput = document.getElementById(\"maxResults\");\nconst statusElement = document.getElementById(\"status\");\nconst resultsElement = document.getElementById(\"results\");\nconst progressBar = document.getElementById(\"progressBar\");\nconst cancelButton = document.getElementById(\"cancelButton\");\nconst pageInput = document.getElementById(\"pageInput\");\nconst previousButton = document.getElementById(\"previousButton\");\nconst nextButton = document.getElementById(\"nextButton\");\nconst pageInfo = document.getElementById(\"pageInfo\");\nconst youtubeButton = document.getElementById(\"youtubeButton\");\nconst cinemaButton = document.getElementById(\"cinemaButton\");\n\n\/* =========================================================\n   UTILITAIRES DATE (fen\u00eatres glissantes \"N derniers jours \/\n   mois \/ ann\u00e9es\")\n========================================================= *\/\n\nfunction daysAgo(n) {\n    const d = new Date();\n    d.setDate(d.getDate() - n);\n    return d;\n}\n\nfunction monthsAgo(n) {\n    const d = new Date();\n    d.setMonth(d.getMonth() - n);\n    return d;\n}\n\nfunction yearsAgo(n) {\n    const d = new Date();\n    d.setFullYear(d.getFullYear() - n);\n    return d;\n}\n\n\/* =========================================================\n   REQUETE SIMPLE (sans annulation, pour les appels ponctuels :\n   chargement des langues disponibles, vue Cin\u00e9ma)\n========================================================= *\/\n\nasync function fetchWikidataOnce(query) {\n\n    const response = await fetch(WIKIDATA_ENDPOINT, {\n        method: \"POST\",\n        headers: {\n            \"Content-Type\": \"application\/x-www-form-urlencoded\",\n            \"Accept\": \"application\/sparql-results+json\"\n        },\n        body: \"query=\" + encodeURIComponent(query)\n    });\n\n    if (!response.ok) {\n        throw new Error(\"Erreur HTTP \" + response.status);\n    }\n\n    return response.json();\n}\n\n\/* =========================================================\n   LANGUES DISPONIBLES DANS LA BASE\n\n   Interroge Wikidata pour ne proposer que les langues\n   r\u00e9ellement utilis\u00e9es dans les libell\u00e9s d'un \u00e9chantillon de\n   films (LANGUAGE_SAMPLE_SIZE), plut\u00f4t qu'une liste fig\u00e9e.\n   Les noms de langues affich\u00e9s sont g\u00e9n\u00e9r\u00e9s dynamiquement en\n   fran\u00e7ais via Intl.DisplayNames (aucune table \u00e0 maintenir).\n========================================================= *\/\n\nfunction languageDisplayName(code) {\n    try {\n        const displayNames = new Intl.DisplayNames([\"fr\"], { type: \"language\" });\n        const name = displayNames.of(code);\n        return name && name.toLowerCase() !== code.toLowerCase() ? name : code;\n    }\n    catch (error) {\n        return code;\n    }\n}\n\nfunction populateLanguageSelect(codes) {\n\n    const uniqueCodes = Array.from(new Set(codes)).filter(Boolean);\n\n    uniqueCodes.sort((a, b) =>\n        languageDisplayName(a).localeCompare(languageDisplayName(b), \"fr\")\n    );\n\n    languageSelect.innerHTML = \"\";\n\n    uniqueCodes.forEach(code => {\n        const option = document.createElement(\"option\");\n        option.value = code;\n        option.textContent = languageDisplayName(code) + \" (\" + code + \")\";\n        if (code === \"fr\") option.selected = true;\n        languageSelect.appendChild(option);\n    });\n\n    if (!languageSelect.querySelector(\"option[selected]\") && languageSelect.options.length) {\n        languageSelect.options[0].selected = true;\n    }\n\n    languageCountLabel.textContent = \"(\" + uniqueCodes.length + \" langues trouv\u00e9es)\";\n}\n\nasync function loadAvailableLanguages() {\n\n    try {\n\n        const query = `\n            SELECT DISTINCT ?lang WHERE {\n                {\n                    SELECT DISTINCT ?item WHERE {\n                        ?item wdt:P31 ${FILM_CLASS_ID} .\n                    }\n                    LIMIT ${LANGUAGE_SAMPLE_SIZE}\n                }\n                ?item rdfs:label ?itemLabel .\n                BIND(LANG(?itemLabel) AS ?lang)\n            }\n            ORDER BY ?lang\n        `;\n\n        const data = await fetchWikidataOnce(query);\n\n        const codes = data.results.bindings\n            .map(row => row.lang.value)\n            .filter(code => code && code.length <= 8);\n\n        if (codes.length === 0) {\n            throw new Error(\"Aucune langue retourn\u00e9e\");\n        }\n\n        populateLanguageSelect(codes);\n    }\n    catch (error) {\n        console.warn(\"Langues Wikidata indisponibles, repli sur une liste par d\u00e9faut :\", error);\n        populateLanguageSelect(FALLBACK_LANGUAGE_CODES);\n    }\n}\n\n\/* =========================================================\n   LANGUES SELECTIONNEES\n   Renvoie null si \"Toutes les langues\" est coch\u00e9 (= aucun\n   filtre de langue), sinon la liste des codes coch\u00e9s dans le\n   multi-select (fr par d\u00e9faut si rien n'est s\u00e9lectionn\u00e9).\n========================================================= *\/\n\nfunction getSelectedLanguages() {\n\n    if (allLanguagesCheckbox.checked) {\n        return null;\n    }\n\n    const selected = Array.from(languageSelect.selectedOptions)\n        .map(o => o.value)\n        .filter(v => v);\n\n    return selected.length ? selected : [\"fr\"];\n}\n\n\/* =========================================================\n   CONDITIONS DE BASE (partag\u00e9es par la recherche courante et\n   par la requ\u00eate \"films \u00e0 venir\" de la vue Cin\u00e9ma) : type,\n   langue, th\u00e8me religieux, exclusion, recherche libre.\n========================================================= *\/\n\nfunction buildBaseConditions() {\n\n    const search = searchInput.value.trim();\n\n    \/\/ --- LABEL (une, plusieurs, ou toutes les langues) ---\n\n    const selectedLanguages = getSelectedLanguages();\n\n    let labelCondition;\n\n    if (selectedLanguages === null) {\n        \/\/ Toutes les langues : aucun filtre sur LANG(). Le\n        \/\/ regroupement (GROUP BY) dans la requ\u00eate de liste se\n        \/\/ charge ensuite de ne garder qu'un seul libell\u00e9 par film.\n        labelCondition = `?item rdfs:label ?itemLabel .`;\n    }\n    else {\n        const langList = selectedLanguages.map(l => `\"${l}\"`).join(\",\");\n        labelCondition = `\n            ?item rdfs:label ?itemLabel .\n            FILTER(LANG(?itemLabel) IN (${langList}))\n        `;\n    }\n\n    \/\/ --- TYPE (films \/ dessins anim\u00e9s \/ \u0153uvres audiovisuelles) ---\n\n    let typeCondition = `?item wdt:P31 ${FILM_CLASS_ID} .`;\n\n    if (typeFilter.value === \"animation\") {\n        typeCondition = `\n            { ?item wdt:P31 ${ANIMATED_FILM_CLASS_ID} . }\n            UNION\n            { ?item wdt:P31 ?animType . ?animType wdt:P279* ${ANIMATED_FILM_CLASS_ID} . }\n        `;\n    }\n    else if (typeFilter.value === \"film_animation\") {\n        typeCondition = `\n            { ?item wdt:P31 ${FILM_CLASS_ID} . }\n            UNION\n            { ?item wdt:P31 ?filmType . ?filmType wdt:P279* ${FILM_CLASS_ID} . }\n            UNION\n            { ?item wdt:P31 ${ANIMATED_FILM_CLASS_ID} . }\n            UNION\n            { ?item wdt:P31 ?animType2 . ?animType2 wdt:P279* ${ANIMATED_FILM_CLASS_ID} . }\n        `;\n    }\n    else if (typeFilter.value === \"all\") {\n        typeCondition = `\n            { ?item wdt:P31 ${FILM_CLASS_ID} . }\n            UNION\n            { ?item wdt:P31 ?type . ?type wdt:P279* ${FILM_CLASS_ID} . }\n        `;\n    }\n\n    \/\/ --- THEME RELIGIEUX (filtre structur\u00e9 + mots-cl\u00e9s) ---\n    const lexicalKeywords =\n        Array.from(document.querySelectorAll(\".lexicalKeyword:checked\"))\n            .flatMap(cb => cb.value.split(\",\"));\n\n    const customKeywordsRaw = lexicalCustomInput.value.trim();\n\n    if (customKeywordsRaw) {\n        customKeywordsRaw\n            .split(\",\")\n            .map(word => word.trim())\n            .filter(Boolean)\n            .forEach(word => lexicalKeywords.push(word));\n    }\n\n    const hasLexicalFilter = lexicalKeywords.length > 0;\n\n    let religiousCondition = \"\";\n\n    if (religiousFilter.checked || hasLexicalFilter) {\n\n        const branches = [];\n\n        if (religiousFilter.checked) {\n            branches.push(`\n                {\n                    ?item wdt:P136 ?religiousGenre .\n                    VALUES ?religiousGenre { ${RELIGIOUS_GENRE_IDS.join(\" \")} }\n                }\n            `);\n            branches.push(`\n                {\n                    ?item wdt:P921 ?religiousSubject .\n                    VALUES ?religiousSubject { ${RELIGIOUS_SUBJECT_IDS.join(\" \")} }\n                }\n            `);\n        }\n\n        if (hasLexicalFilter) {\n            const lexicalOr = lexicalKeywords\n                .map(word => `CONTAINS(LCASE(STR(?itemLabel)), LCASE(\"${word.replace(\/\"\/g, '\\\\\"')}\"))`)\n                .join(\" || \");\n\n            branches.push(`{ FILTER(${lexicalOr}) }`);\n        }\n\n        religiousCondition = branches.join(\" UNION \");\n    }\n\n    \/\/ --- EXCLUSION PAR MOTS-CLES (ne pas afficher ces films) ---\n\n    const excludeWordsRaw = excludeWordsInput.value.trim();\n    let excludeCondition = \"\";\n\n    if (excludeWordsRaw) {\n\n        const excludeWords = excludeWordsRaw\n            .split(\",\")\n            .map(word => word.trim())\n            .filter(Boolean);\n\n        if (excludeWords.length) {\n            const excludeOr = excludeWords\n                .map(word => `CONTAINS(LCASE(STR(?itemLabel)), LCASE(\"${word.replace(\/\"\/g, '\\\\\"')}\"))`)\n                .join(\" || \");\n\n            excludeCondition = `FILTER(!(${excludeOr}))`;\n        }\n    }\n\n    \/\/ --- RECHERCHE PAR CATEGORIE ---\n    let searchCondition = \"\";\n\n    if (search) {\n\n        const safeSearch = search.replace(\/\"\/g, '\\\\\"');\n        const category = searchCategory.value || \"title\";\n\n        const titleMatch = `CONTAINS(LCASE(STR(?itemLabel)), LCASE(\"${safeSearch}\"))`;\n\n        const directorBlock = `\n            ?item wdt:P57 ?searchDirector .\n            ?searchDirector rdfs:label ?searchDirectorLabel .\n            FILTER(LANG(?searchDirectorLabel) IN (\"fr\",\"en\"))\n            FILTER(CONTAINS(LCASE(STR(?searchDirectorLabel)), LCASE(\"${safeSearch}\")))\n        `;\n\n        const castBlock = `\n            ?item wdt:P161 ?searchCast .\n            ?searchCast rdfs:label ?searchCastLabel .\n            FILTER(LANG(?searchCastLabel) IN (\"fr\",\"en\"))\n            FILTER(CONTAINS(LCASE(STR(?searchCastLabel)), LCASE(\"${safeSearch}\")))\n        `;\n\n        if (category === \"title\") {\n            searchCondition = `FILTER(${titleMatch})`;\n        }\n        else if (category === \"director\") {\n            searchCondition = directorBlock;\n        }\n        else if (category === \"cast\") {\n            searchCondition = castBlock;\n        }\n        else {\n            searchCondition = `\n                { FILTER(${titleMatch}) }\n                UNION\n                { ${directorBlock} }\n                UNION\n                { ${castBlock} }\n            `;\n        }\n    }\n\n    return `\n        ${typeCondition}\n        ${labelCondition}\n        ${religiousCondition}\n        ${excludeCondition}\n        ${searchCondition}\n    `;\n}\n\n\/* =========================================================\n   CONDITIONS DE DATE (recherche courante)\n========================================================= *\/\n\nfunction buildWhereClause() {\n\n    const day = dayInput.value.trim();\n    const month = monthInput.value.trim();\n    const year = yearInput.value.trim();\n    const yearFrom = yearFromInput.value.trim();\n    const yearTo = yearToInput.value.trim();\n\n    const base = buildBaseConditions();\n\n    \/\/ --- DATE ---\n    \/\/ Priorit\u00e9 : fourchette pr\u00e9cise (jour+mois+ann\u00e9e exacts) >\n    \/\/            fourchette d'ann\u00e9es > champs jour\/mois\/ann\u00e9e.\n    const dateFrom = dateFromInput.value;\n    const dateTo = dateToInput.value;\n\n    const hasFullDateRange = Boolean(dateFrom || dateTo);\n    const hasYearRange = Boolean(yearFrom || yearTo);\n    const hasRollingFilter = Boolean(day || month || year);\n\n    const hasDateFilter = hasFullDateRange || hasYearRange || hasRollingFilter;\n\n    let dateVariable = `OPTIONAL{ ?item wdt:P577 ?date . }`;\n\n    if (dateFilter.value === \"with\" || hasDateFilter) {\n        dateVariable = `?item wdt:P577 ?date .`;\n    }\n\n    if (dateFilter.value === \"without\") {\n        dateVariable = `FILTER NOT EXISTS{ ?item wdt:P577 ?anyDate . }`;\n    }\n\n    let dateFilterCondition = \"\";\n\n    if (hasDateFilter && dateFilter.value !== \"without\") {\n\n        const conditions = [];\n\n        if (hasFullDateRange) {\n\n            \/\/ Fourchette de dates pr\u00e9cises (ex : 05\/05\/2024 -> 31\/07\/2026)\n            if (dateFrom) conditions.push(`?date >= \"${dateFrom}T00:00:00Z\"^^xsd:dateTime`);\n            if (dateTo) conditions.push(`?date <= \"${dateTo}T23:59:59Z\"^^xsd:dateTime`);\n        }\n        else if (hasYearRange) {\n\n            \/\/ Fourchette d'ann\u00e9es (ex : 2022 -> 2026)\n            if (yearFrom) conditions.push(`YEAR(?date) >= ${Number(yearFrom)}`);\n            if (yearTo) conditions.push(`YEAR(?date) <= ${Number(yearTo)}`);\n        }\n        else {\n\n            const now = new Date();\n            const nowIso = now.toISOString();\n\n            \/\/ \"Jour\" = N derniers jours (fen\u00eatre glissante)\n            if (day) {\n                conditions.push(`?date >= \"${daysAgo(Number(day)).toISOString()}\"^^xsd:dateTime`);\n                conditions.push(`?date <= \"${nowIso}\"^^xsd:dateTime`);\n            }\n\n            \/\/ \"Mois\" = N derniers mois (fen\u00eatre glissante)\n            if (month) {\n                conditions.push(`?date >= \"${monthsAgo(Number(month)).toISOString()}\"^^xsd:dateTime`);\n                conditions.push(`?date <= \"${nowIso}\"^^xsd:dateTime`);\n            }\n\n            \/\/ \"Ann\u00e9e\" : un nombre qui ressemble \u00e0 une vraie ann\u00e9e\n            \/\/ (>= 100) est trait\u00e9 comme une ann\u00e9e pr\u00e9cise (ex : 2026).\n            \/\/ Un petit nombre (< 100) est trait\u00e9 comme \"N derni\u00e8res\n            \/\/ ann\u00e9es\" (ex : 3 -> 3 derni\u00e8res ann\u00e9es).\n            if (year) {\n\n                const yearNumber = Number(year);\n\n                if (yearNumber >= 100) {\n                    conditions.push(`YEAR(?date) = ${yearNumber}`);\n                }\n                else {\n                    conditions.push(`?date >= \"${yearsAgo(yearNumber).toISOString()}\"^^xsd:dateTime`);\n                    conditions.push(`?date <= \"${nowIso}\"^^xsd:dateTime`);\n                }\n            }\n        }\n\n        if (conditions.length) {\n            dateFilterCondition = `FILTER(${conditions.join(\" && \")})`;\n        }\n    }\n\n    return `\n        ${base}\n        ${dateVariable}\n        ${dateFilterCondition}\n    `;\n}\n\n\/* =========================================================\n   CONDITIONS \"A VENIR\" (vue Cin\u00e9ma) : m\u00eames filtres de type \/\n   langue \/ th\u00e8me \/ exclusion \/ recherche, mais date future.\n========================================================= *\/\n\nfunction buildUpcomingWhereClause() {\n\n    const base = buildBaseConditions();\n\n    return `\n        ${base}\n        ?item wdt:P577 ?date .\n        FILTER(?date > NOW())\n    `;\n}\n\n\/* =========================================================\n   REQUETE DE COMPTAGE\n\n   Compte les films correspondant aux filtres, plafonn\u00e9 \u00e0\n   COUNT_CAP pour rester rapide m\u00eame sur une recherche large.\n   (SELECT DISTINCT ?item : peu importe qu'un film ait\n   plusieurs libell\u00e9s dans plusieurs langues, il n'est compt\u00e9\n   qu'une seule fois.)\n========================================================= *\/\n\nfunction buildCountQuery() {\n\n    const where = buildWhereClause();\n\n    return `\n        PREFIX xsd: <http:\/\/www.w3.org\/2001\/XMLSchema#>\n        SELECT (COUNT(*) AS ?count) WHERE {\n            SELECT DISTINCT ?item WHERE{ ${where} }\n            LIMIT ${COUNT_CAP}\n        }\n    `;\n}\n\n\/* =========================================================\n   REQUETE DE LISTE (l\u00e9g\u00e8re, pagination r\u00e9elle)\n\n   Regroup\u00e9e par ?item (GROUP BY) : quand plusieurs langues\n   (ou \"toutes les langues\") sont s\u00e9lectionn\u00e9es, un m\u00eame film\n   peut avoir plusieurs libell\u00e9s correspondants. On n'en garde\n   qu'un seul par film (SAMPLE) pour \u00e9viter les doublons de\n   cartes \u00e0 l'affichage.\n========================================================= *\/\n\nfunction buildListQuery(limit, offset) {\n\n    const where = buildWhereClause();\n\n    let order = \"DESC(?dateAgg)\";\n    if (sortSelect.value === \"oldest\") order = \"ASC(?dateAgg)\";\n    if (sortSelect.value === \"title\") order = \"LCASE(STR(?labelAgg))\";\n\n    return `\n        PREFIX xsd: <http:\/\/www.w3.org\/2001\/XMLSchema#>\n        SELECT ?item (SAMPLE(?itemLabel) AS ?labelAgg) (SAMPLE(?date) AS ?dateAgg)\n        WHERE{ ${where} }\n        GROUP BY ?item\n        ORDER BY ${order}\n        LIMIT ${limit}\n        OFFSET ${offset}\n    `;\n}\n\n\/* =========================================================\n   REQUETE DE LISTE \"A VENIR\" (vue Cin\u00e9ma), tri\u00e9e par date de\n   sortie la plus proche.\n========================================================= *\/\n\nfunction buildUpcomingListQuery(limit) {\n\n    const where = buildUpcomingWhereClause();\n\n    return `\n        PREFIX xsd: <http:\/\/www.w3.org\/2001\/XMLSchema#>\n        SELECT ?item (SAMPLE(?itemLabel) AS ?labelAgg) (SAMPLE(?date) AS ?dateAgg)\n        WHERE{ ${where} }\n        GROUP BY ?item\n        ORDER BY ASC(?dateAgg)\n        LIMIT ${limit}\n    `;\n}\n\n\/* =========================================================\n   REQUETE D'ENRICHISSEMENT\n   (affiche, r\u00e9alisateur, casting, liens \u2014 uniquement pour\n   les films de la page affich\u00e9e)\n\n   Cha\u00eene de repli pour l'affiche : P3383 (affiche officielle)\n   en priorit\u00e9, puis P18 (image g\u00e9n\u00e9rique) si aucune affiche\n   n'est renseign\u00e9e sur Wikidata.\n========================================================= *\/\n\nfunction buildDetailsQuery(itemUris) {\n\n    const values = itemUris.map(uri => `<${uri}>`).join(\" \");\n\n    return `\n        SELECT ?item\n               (SAMPLE(?poster) AS ?poster)\n               (SAMPLE(?image) AS ?image)\n               (GROUP_CONCAT(DISTINCT ?directorLabel; separator=\", \") AS ?directors)\n               (GROUP_CONCAT(DISTINCT ?castLabel; separator=\", \") AS ?cast)\n               (SAMPLE(?website) AS ?website)\n               (SAMPLE(?imdb) AS ?imdb)\n        WHERE{\n            VALUES ?item { ${values} }\n            OPTIONAL{ ?item wdt:P3383 ?poster . }\n            OPTIONAL{ ?item wdt:P18 ?image . }\n            OPTIONAL{\n                ?item wdt:P57 ?director .\n                ?director rdfs:label ?directorLabel .\n                FILTER(LANG(?directorLabel) IN (\"fr\",\"en\"))\n            }\n            OPTIONAL{\n                ?item wdt:P161 ?cast .\n                ?cast rdfs:label ?castLabel .\n                FILTER(LANG(?castLabel) IN (\"fr\",\"en\"))\n            }\n            OPTIONAL{ ?item wdt:P856 ?website . }\n            OPTIONAL{\n                ?item wdt:P345 ?imdbId .\n                BIND(CONCAT(\"https:\/\/www.imdb.com\/title\/\", ?imdbId) AS ?imdb)\n            }\n        }\n        GROUP BY ?item\n    `;\n}\n\n\/* =========================================================\n   REQUETE AVEC RETRY (POST, pour \u00e9viter les URL trop longues)\n========================================================= *\/\n\nasync function fetchWikidata(query, attempt = 0) {\n\n    const response = await fetch(WIKIDATA_ENDPOINT, {\n        method: \"POST\",\n        headers: {\n            \"Content-Type\": \"application\/x-www-form-urlencoded\",\n            \"Accept\": \"application\/sparql-results+json\"\n        },\n        body: \"query=\" + encodeURIComponent(query),\n        signal: abortController.signal\n    });\n\n    if ([429, 502, 503, 504].includes(response.status)) {\n\n        if (attempt < 3) {\n\n            const delay = (attempt + 1) * 2500;\n\n            statusElement.textContent =\n                \"\u23f3 Wikidata est temporairement charg\u00e9. Nouvelle tentative dans \"\n                + (delay \/ 1000) + \" secondes...\";\n\n            await new Promise(resolve => setTimeout(resolve, delay));\n\n            return fetchWikidata(query, attempt + 1);\n        }\n    }\n\n    if (!response.ok) {\n        throw new Error(\"Erreur HTTP \" + response.status);\n    }\n\n    return response.json();\n}\n\n\/* =========================================================\n   RECHERCHE (comptage -> liste -> enrichissement)\n========================================================= *\/\n\nasync function searchWikidata() {\n\n    if (isLoading) return;\n\n    isLoading = true;\n    abortController = new AbortController();\n    cancelButton.disabled = false;\n    progressBar.style.width = \"10%\";\n    resultsElement.innerHTML = \"\";\n    statusElement.textContent = \"\u23f3 Pr\u00e9paration de la recherche...\";\n\n    try {\n\n        if (titleFilter.value === \"without\") {\n            currentResults = [];\n            totalPages = 1;\n            statusElement.textContent =\n                \"\u26a0\ufe0f Wikidata ne permet pas de r\u00e9cup\u00e9rer fiablement les films \"\n                + \"sans titre dans cette requ\u00eate l\u00e9g\u00e8re.\";\n            renderResults();\n            return;\n        }\n\n        const pageSize = Math.max(1, Number(pageSizeSelect.value) || 20);\n\n        const maxResultsRaw = maxResultsInput.value.trim();\n        const maxResults = maxResultsRaw ? Math.max(1, Number(maxResultsRaw)) : null;\n\n        \/* --- ETAPE 1 : comptage total (si possible) --- *\/\n\n        statusElement.textContent = \"\u23f3 Comptage des r\u00e9sultats...\";\n        progressBar.style.width = \"20%\";\n\n        let rawCount = null;\n        let cappedAtLimit = false;\n        let effectiveTotal = null;\n\n        try {\n            const countData = await fetchWikidata(buildCountQuery());\n            rawCount = Number(countData.results.bindings[0]?.count?.value || 0);\n            cappedAtLimit = rawCount >= COUNT_CAP;\n        }\n        catch (countError) {\n\n            if (countError.name === \"AbortError\") {\n                throw countError;\n            }\n\n            \/*\n            -------------------------------------------------\n            Le comptage peut \u00e9chouer (timeout, 500...) sur une\n            recherche trop large et peu s\u00e9lective (ex. \"tous\n            les films en fran\u00e7ais\", sans aucun filtre). Dans\n            ce cas on NE FAIT PAS \u00e9chouer toute la recherche :\n            on bascule sur l'ancien mode \"page par page sans\n            total connu\".\n            -------------------------------------------------\n            *\/\n\n            console.warn(\"Comptage indisponible, repli sans total exact :\", countError);\n\n            rawCount = null;\n        }\n\n        totalKnown = rawCount !== null;\n\n        if (totalKnown) {\n\n            effectiveTotal = maxResults ? Math.min(rawCount, maxResults) : rawCount;\n            totalPages = Math.max(1, Math.ceil(effectiveTotal \/ pageSize));\n\n            if (currentPage > totalPages) {\n                currentPage = totalPages;\n            }\n\n            if (effectiveTotal === 0) {\n                currentResults = [];\n                progressBar.style.width = \"100%\";\n                statusElement.textContent = \"\u274c Aucun r\u00e9sultat.\";\n                renderResults();\n                return;\n            }\n        }\n        else {\n            totalPages = Math.max(totalPages, currentPage);\n        }\n\n        \/* --- ETAPE 2 : liste des films de la page courante --- *\/\n\n        statusElement.textContent = \"\u23f3 Recherche des films...\";\n        progressBar.style.width = \"40%\";\n\n        const offset = (currentPage - 1) * pageSize;\n\n        let limit = pageSize;\n\n        if (maxResults) {\n            limit = Math.min(pageSize, Math.max(0, maxResults - offset));\n        }\n\n        \/\/ Si le total est inconnu, on demande un r\u00e9sultat de plus\n        \/\/ pour savoir s'il existe une page suivante.\n        const fetchLimit = totalKnown ? limit : limit + 1;\n\n        const listData = await fetchWikidata(buildListQuery(fetchLimit, offset));\n        let listRows = listData.results.bindings;\n\n        if (!totalKnown) {\n            hasNextPageUnknown = listRows.length > limit;\n            listRows = listRows.slice(0, limit);\n            totalPages = hasNextPageUnknown ? currentPage + 1 : currentPage;\n        }\n\n        progressBar.style.width = \"60%\";\n\n        if (listRows.length === 0) {\n            currentResults = [];\n            progressBar.style.width = \"100%\";\n            statusElement.textContent = \"\u274c Aucun r\u00e9sultat sur cette page.\";\n            renderResults();\n            return;\n        }\n\n        \/* --- ETAPE 3 : enrichissement (affiche, casting, liens) --- *\/\n\n        statusElement.textContent = \"\u23f3 R\u00e9cup\u00e9ration des affiches et informations...\";\n\n        const itemUris = listRows.map(row => row.item.value);\n        const detailsData = await fetchWikidata(buildDetailsQuery(itemUris));\n\n        const detailsByUri = {};\n        detailsData.results.bindings.forEach(row => {\n            detailsByUri[row.item.value] = row;\n        });\n\n        progressBar.style.width = \"90%\";\n\n        currentResults = listRows.map(row => convertResult(row, detailsByUri[row.item.value]));\n\n        progressBar.style.width = \"100%\";\n\n        let statusMessage;\n\n        if (totalKnown) {\n\n            statusMessage = \"\u2705 \" + effectiveTotal + \" r\u00e9sultat(s) trouv\u00e9(s)\";\n\n            if (cappedAtLimit && !maxResults) {\n                statusMessage += \" (au moins \" + COUNT_CAP + \" \u2014 affinez la recherche pour un compte exact)\";\n            }\n        }\n        else {\n            statusMessage =\n                \"\u2705 \" + listRows.length + \" r\u00e9sultat(s) affich\u00e9(s) \"\n                + \"(recherche trop large pour calculer un total exact \u2014 \"\n                + \"affinez avec un filtre pour l'obtenir)\";\n        }\n\n        statusElement.textContent = statusMessage + \".\";\n\n        renderResults();\n\n    }\n    catch (error) {\n\n        if (error.name === \"AbortError\") {\n            statusElement.textContent = \"\u23f9 Recherche annul\u00e9e.\";\n        }\n        else {\n            console.error(\"Wikidata :\", error);\n            statusElement.textContent = \"\u274c Impossible d'interroger Wikidata : \" + error.message;\n        }\n    }\n    finally {\n        isLoading = false;\n        cancelButton.disabled = true;\n        abortController = null;\n    }\n}\n\n\/* =========================================================\n   CONVERSION (fusion ligne liste + ligne d\u00e9tails)\n========================================================= *\/\n\nfunction convertResult(listRow, detailsRow) {\n\n    const details = detailsRow || {};\n\n    const poster = details.poster\n        ? details.poster.value\n        : (details.image ? details.image.value : \"\");\n\n    const website = details.website ? details.website.value : \"\";\n    const imdb = details.imdb ? details.imdb.value : \"\";\n    const uri = listRow.item ? listRow.item.value : \"\";\n\n    \/\/ Priorit\u00e9 : site officiel > IMDb > fiche Wikidata\n    const primaryLink = website || imdb || uri;\n\n    return {\n        uri,\n        title: listRow.labelAgg ? listRow.labelAgg.value : \"\",\n        poster,\n        directors: details.directors ? details.directors.value : \"\",\n        cast: details.cast ? details.cast.value : \"\",\n        date: listRow.dateAgg ? listRow.dateAgg.value : \"\",\n        website,\n        imdb,\n        primaryLink\n    };\n}\n\n\/* =========================================================\n   AFFICHAGE\n========================================================= *\/\n\nfunction renderResults() {\n\n    resultsElement.innerHTML = \"\";\n\n    const isReligiousSearch =\n        religiousFilter.checked\n        || document.querySelectorAll(\".lexicalKeyword:checked\").length > 0;\n\n    currentResults.forEach(film => {\n\n        const card = document.createElement(\"article\");\n        card.className = \"card\";\n\n        const year = film.date ? new Date(film.date).getFullYear() : \"\";\n\n        const castList = film.cast\n            ? film.cast.split(\", \").slice(0, 4).join(\", \")\n            : \"\";\n\n        const hasRealPoster = Boolean(film.poster);\n        const filmTitle = film.title || \"Titre non renseign\u00e9\";\n\n        card.innerHTML = `\n\n            <div class=\"poster-wrap\">\n\n                <img decoding=\"async\" class=\"poster\" src=\"${film.poster || DEFAULT_POSTER}\"\n                     alt=\"${escapeHTML(filmTitle)}\" loading=\"lazy\">\n\n                ${!hasRealPoster ? `<div class=\"poster-fallback-title\">${escapeHTML(filmTitle)}<\/div>` : \"\"}\n\n                ${year ? `<div class=\"year-badge\">${year}<\/div>` : \"\"}\n\n                ${isReligiousSearch ? `<div class=\"religious-badge\">\u271d\ufe0f Religieux<\/div>` : \"\"}\n\n                <a class=\"poster-overlay\" href=\"${film.primaryLink}\" target=\"_blank\" rel=\"noopener\">\n                    <span class=\"overlay-btn\">\u25b6 Voir le film<\/span>\n                <\/a>\n\n            <\/div>\n\n            <div class=\"card-content\">\n\n                <h3>${escapeHTML(filmTitle)}<\/h3>\n\n                ${film.directors ? `<div class=\"meta-line\"><b>R\u00e9alisation :<\/b> ${escapeHTML(film.directors)}<\/div>` : \"\"}\n\n                ${castList ? `<div class=\"meta-line\"><b>Avec :<\/b> ${escapeHTML(castList)}<\/div>` : \"\"}\n\n                <div class=\"meta-line\">\ud83d\udcc5 ${formatDate(film.date)}<\/div>\n\n                <div class=\"card-links\">\n                    <a class=\"link-primary\" href=\"${film.primaryLink}\" target=\"_blank\" rel=\"noopener\">\u25b6 Voir le film<\/a>\n                    <a class=\"link-secondary\" href=\"${film.uri}\" target=\"_blank\" rel=\"noopener\">Wikidata<\/a>\n                <\/div>\n\n            <\/div>\n        `;\n\n        resultsElement.appendChild(card);\n    });\n\n    updatePagination();\n}\n\n\/* =========================================================\n   PAGINATION\n========================================================= *\/\n\nfunction updatePagination() {\n\n    if (totalKnown) {\n        pageInfo.textContent = \"Page \" + currentPage + \" \/ \" + totalPages;\n        previousButton.disabled = currentPage <= 1;\n        nextButton.disabled = currentPage >= totalPages;\n        pageInput.max = totalPages;\n    }\n    else {\n        pageInfo.textContent = \"Page \" + currentPage + \" (total non calcul\u00e9)\";\n        previousButton.disabled = currentPage <= 1;\n        nextButton.disabled = !hasNextPageUnknown;\n        pageInput.removeAttribute(\"max\");\n    }\n}\n\n\/* =========================================================\n   DATE \/ SECURITE\n========================================================= *\/\n\nfunction formatDate(date) {\n\n    if (!date) return \"Date non renseign\u00e9e\";\n\n    const d = new Date(date);\n\n    if (isNaN(d)) return date;\n\n    return d.toLocaleDateString(\"fr-FR\");\n}\n\nfunction escapeHTML(value) {\n    const div = document.createElement(\"div\");\n    div.textContent = value ? String(value) : \"\";\n    return div.innerHTML;\n}\n\n\/* =========================================================\n   VUE CINEMA\n   Pr\u00e9sente la s\u00e9lection courante sous forme d'affiches\n   cliquables (comme au cin\u00e9ma), et ajoute une section\n   \"Prochainement\" avec les films \u00e0 venir de la base (si\n   disponibles), tri\u00e9s par date de sortie la plus proche.\n   Cliquer sur une affiche envoie directement vers la page\n   d'acc\u00e8s au visionnage du film (site officiel > IMDb >\n   fiche Wikidata en dernier recours).\n========================================================= *\/\n\nfunction renderCinemaCard(film) {\n\n    const title = film.title || \"Titre non renseign\u00e9\";\n    const year = film.date ? new Date(film.date).getFullYear() : \"\";\n    const poster = film.poster || DEFAULT_POSTER;\n    const hasPoster = Boolean(film.poster);\n\n    return `\n        <a class=\"cinema-card\" href=\"${film.primaryLink}\" target=\"_blank\" rel=\"noopener\">\n            <div class=\"cinema-poster-wrap\">\n                <img decoding=\"async\" src=\"${poster}\" alt=\"${escapeHTML(title)}\" loading=\"lazy\">\n                ${!hasPoster ? `<div class=\"cinema-fallback-title\">${escapeHTML(title)}<\/div>` : \"\"}\n                ${year ? `<div class=\"cinema-year\">${year}<\/div>` : \"\"}\n            <\/div>\n            <div class=\"cinema-title\">${escapeHTML(title)}<\/div>\n        <\/a>\n    `;\n}\n\nfunction renderCinemaHtml(nowShowing, upcoming, loading) {\n\n    const nowCards = nowShowing.map(renderCinemaCard).join(\"\");\n\n    let upcomingSection = \"\";\n\n    if (loading) {\n        upcomingSection = `<p class=\"cinema-loading\">\u23f3 Recherche des films \u00e0 venir dans la base...<\/p>`;\n    }\n    else if (upcoming.length) {\n        upcomingSection = `\n            <h2 class=\"cinema-section-title\">\ud83d\udd1c Prochainement<\/h2>\n            <div class=\"cinema-grid\">${upcoming.map(renderCinemaCard).join(\"\")}<\/div>\n        `;\n    }\n\n    return `\n        <!DOCTYPE html>\n        <html lang=\"fr\">\n        <head>\n        <meta charset=\"UTF-8\">\n        <title>Au cin\u00e9ma \u2014 S\u00e9lection<\/title>\n        <style>\n            * { box-sizing: border-box; }\n            body { margin:0; font-family: Arial, Helvetica, sans-serif; background:#0b0b0d; color:#fff; padding:28px; }\n            h1 { text-align:center; margin-bottom: 6px; }\n            .cinema-subtitle { text-align:center; opacity:.6; margin-bottom:28px; font-size:13px; }\n            .cinema-section-title { margin: 34px 0 16px; font-size:18px; border-left:4px solid #f0c400; padding-left:10px; }\n            .cinema-loading { opacity:.6; margin-top:20px; }\n            .cinema-grid { display:grid; grid-template-columns: repeat(auto-fill, minmax(170px,1fr)); gap:20px; }\n            .cinema-card { color:#fff; text-decoration:none; display:block; background:#1a1a1d; border-radius:12px; overflow:hidden; transition: transform .2s ease; }\n            .cinema-card:hover { transform: translateY(-4px); }\n            .cinema-poster-wrap { position:relative; width:100%; aspect-ratio:2\/3; background:#222; overflow:hidden; }\n            .cinema-poster-wrap img { width:100%; height:100%; object-fit:cover; display:block; }\n            .cinema-fallback-title {\n                position:absolute; left:0; right:0; top:71%; bottom:6%;\n                display:flex; align-items:flex-start; justify-content:center;\n                text-align:center; padding:8px 12px 0; font-weight:bold;\n                font-size:13px; line-height:1.25; text-shadow:0 2px 6px rgba(0,0,0,.9);\n            }\n            .cinema-year { position:absolute; top:8px; left:8px; background:rgba(0,0,0,.65); padding:3px 8px; border-radius:14px; font-size:11px; font-weight:bold; }\n            .cinema-title { padding:10px 12px 14px; font-size:13.5px; font-weight:bold; line-height:1.3; }\n        <\/style>\n        <\/head>\n        <body>\n            <h1>\ud83c\udfac Au cin\u00e9ma<\/h1>\n            <div class=\"cinema-subtitle\">Cliquez sur une affiche pour acc\u00e9der directement \u00e0 la page de visionnage du film<\/div>\n            <div class=\"cinema-grid\">${nowCards || \"<p>Aucun film \u00e0 afficher.<\/p>\"}<\/div>\n            ${upcomingSection}\n        <\/body>\n        <\/html>\n    `;\n}\n\nasync function openCinemaView() {\n\n    if (currentResults.length === 0) {\n        alert(\"Aucun film \u00e0 afficher. Lancez d'abord une recherche.\");\n        return;\n    }\n\n    const win = window.open(\"\", \"_blank\");\n\n    if (!win) {\n        alert(\"Merci d'autoriser les fen\u00eatres pop-up pour ce site afin d'ouvrir la vue cin\u00e9ma.\");\n        return;\n    }\n\n    win.document.open();\n    win.document.write(renderCinemaHtml(currentResults, [], true));\n    win.document.close();\n\n    let upcomingFilms = [];\n\n    try {\n\n        const listData = await fetchWikidataOnce(buildUpcomingListQuery(UPCOMING_LIMIT));\n        const rows = listData.results.bindings;\n\n        if (rows.length) {\n\n            const uris = rows.map(row => row.item.value);\n            const detailsData = await fetchWikidataOnce(buildDetailsQuery(uris));\n\n            const detailsByUri = {};\n            detailsData.results.bindings.forEach(row => {\n                detailsByUri[row.item.value] = row;\n            });\n\n            upcomingFilms = rows.map(row => convertResult(row, detailsByUri[row.item.value]));\n        }\n    }\n    catch (error) {\n        console.warn(\"Films \u00e0 venir indisponibles :\", error);\n    }\n\n    if (!win.closed) {\n        win.document.open();\n        win.document.write(renderCinemaHtml(currentResults, upcomingFilms, false));\n        win.document.close();\n    }\n}\n\n\/* =========================================================\n   EVENEMENTS\n========================================================= *\/\n\ncancelButton.addEventListener(\"click\", function () {\n    if (abortController) abortController.abort();\n});\n\ndocument.getElementById(\"searchButton\").addEventListener(\"click\", function () {\n    currentPage = 1;\n    searchWikidata();\n});\n\nsearchInput.addEventListener(\"keydown\", function (event) {\n    if (event.key === \"Enter\") {\n        currentPage = 1;\n        searchWikidata();\n    }\n});\n\nnextButton.addEventListener(\"click\", function () {\n    if (currentPage < totalPages) {\n        currentPage++;\n        searchWikidata();\n    }\n});\n\npreviousButton.addEventListener(\"click\", function () {\n    if (currentPage > 1) {\n        currentPage--;\n        searchWikidata();\n    }\n});\n\ndocument.getElementById(\"pageButton\").addEventListener(\"click\", function () {\n    let page = Number(pageInput.value);\n    if (page < 1) page = 1;\n    if (totalKnown && page > totalPages) page = totalPages;\n    currentPage = page;\n    searchWikidata();\n});\n\ndocument.getElementById(\"reloadButton\").addEventListener(\"click\", searchWikidata);\n\nallLanguagesCheckbox.addEventListener(\"change\", function () {\n    languageSelect.disabled = allLanguagesCheckbox.checked;\n});\n\nyoutubeButton.addEventListener(\"click\", function () {\n\n    if (currentResults.length === 0) {\n        alert(\"Aucun film \u00e0 afficher pour le moment. Lancez d'abord une recherche.\");\n        return;\n    }\n\n    const win = window.open(\"\", \"_blank\");\n\n    if (!win) {\n        alert(\"Merci d'autoriser les fen\u00eatres pop-up pour ce site afin d'ouvrir la s\u00e9lection YouTube.\");\n        return;\n    }\n\n    const linksHtml = currentResults.map(film => {\n        const title = film.title || \"Titre non renseign\u00e9\";\n        const query = encodeURIComponent(title + \" film complet\");\n        const url = \"https:\/\/www.youtube.com\/results?search_query=\" + query;\n        return \"<li><a href=\\\"\" + url + \"\\\" target=\\\"_blank\\\" rel=\\\"noopener\\\">\"\n            + escapeHTML(title) + \"<\/a><\/li>\";\n    }).join(\"\");\n\n    win.document.write(\n        \"<!DOCTYPE html><html lang=\\\"fr\\\"><head><meta charset=\\\"UTF-8\\\">\"\n        + \"<title>S\u00e9lection sur YouTube<\/title>\"\n        + \"<style>body{font-family:Arial,Helvetica,sans-serif;background:#0b0b0d;color:#fff;padding:24px;}\"\n        + \"h1{font-size:20px;} a{color:#f0c400;text-decoration:none;} a:hover{text-decoration:underline;}\"\n        + \"li{margin-bottom:10px;font-size:15px;}<\/style><\/head>\"\n        + \"<body><h1>\ud83c\udfac Recherche YouTube \u2014 s\u00e9lection actuelle (\" + currentResults.length + \" film(s))<\/h1>\"\n        + \"<ul>\" + linksHtml + \"<\/ul><\/body><\/html>\"\n    );\n\n    win.document.close();\n});\n\ncinemaButton.addEventListener(\"click\", openCinemaView);\n\ndocument.getElementById(\"resetButton\").addEventListener(\"click\", function () {\n\n    if (isLoading && abortController) abortController.abort();\n\n    searchInput.value = \"\";\n    searchCategory.value = \"title\";\n    dayInput.value = \"\";\n    monthInput.value = \"\";\n    yearInput.value = \"\";\n    yearFromInput.value = \"\";\n    yearToInput.value = \"\";\n    dateFromInput.value = \"\";\n    dateToInput.value = \"\";\n    titleFilter.value = \"all\";\n    dateFilter.value = \"all\";\n    typeFilter.value = \"film\";\n\n    Array.from(languageSelect.options).forEach(option => {\n        option.selected = (option.value === \"fr\");\n    });\n    allLanguagesCheckbox.checked = false;\n    languageSelect.disabled = false;\n\n    religiousFilter.checked = true;\n\n    document.querySelectorAll(\".lexicalKeyword\").forEach(cb => { cb.checked = false; });\n    lexicalCustomInput.value = \"\";\n    excludeWordsInput.value = \"\";\n\n    sortSelect.value = \"newest\";\n    pageSizeSelect.value = \"20\";\n    maxResultsInput.value = \"\";\n\n    currentPage = 1;\n    totalPages = 1;\n    totalKnown = true;\n    hasNextPageUnknown = false;\n    currentResults = [];\n\n    resultsElement.innerHTML = \"\";\n    progressBar.style.width = \"0%\";\n    pageInput.value = 1;\n    pageInfo.textContent = \"Page 1 \/ 1\";\n\n    statusElement.textContent = \"Filtres r\u00e9initialis\u00e9s.\";\n});\n\ndocument.getElementById(\"exportButton\").addEventListener(\"click\", function () {\n\n    if (currentResults.length === 0) {\n        alert(\"Aucun r\u00e9sultat \u00e0 exporter.\");\n        return;\n    }\n\n    const header = [\"Titre\", \"R\u00e9alisateur(s)\", \"Casting\", \"Date\", \"Lien\", \"Wikidata\"];\n\n    const rows = currentResults.map(film => [\n        film.title, film.directors, film.cast, film.date, film.primaryLink, film.uri\n    ]);\n\n    const csv = [header, ...rows]\n        .map(row => row.map(value =>\n            '\"' + String(value || \"\").replace(\/\"\/g, '\"\"') + '\"'\n        ).join(\";\"))\n        .join(\"\\n\");\n\n    const blob = new Blob([\"\\ufeff\" + csv], { type: \"text\/csv;charset=utf-8\" });\n    const url = URL.createObjectURL(blob);\n    const link = document.createElement(\"a\");\n    link.href = url;\n    link.download = \"films-wikidata.csv\";\n    link.click();\n    URL.revokeObjectURL(url);\n});\n\n\/* =========================================================\n   INITIALISATION\n   On ne lance pas de recherche automatique au chargement,\n   mais on r\u00e9cup\u00e8re tout de suite les langues disponibles\n   dans la base pour peupler le s\u00e9lecteur.\n========================================================= *\/\n\nstatusElement.textContent = \"Pr\u00eat. Entrez un terme ou lancez une recherche.\";\nloadAvailableLanguages();\n\n<\/script>\n\n<\/body>\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Bienvenue sur Alliance en J\u00e9sus Bienvenue sur Alliance en J\u00e9sus Catalogue Films &#8211; Wikidata \ud83c\udfac Catalogue Films &#8211; Wikidata Recherche directe dans Wikidata \u2014 aucune base locale Recherche libre Rechercher dans TitreR\u00e9alisateurActeurTitre + r\u00e9alisateur + acteur &nbsp; \ud83d\udd0e Rechercher Date de sortie Toutes les datesAvec date de sortieSans date de sortie Derniers jours Derniers mois Ann\u00e9e (2026) ou N derni\u00e8res ann\u00e9es (3) Ann\u00e9e de (fourchette) Ann\u00e9e \u00e0 (fourchette) Date pr\u00e9cise de d\u00e9but Date pr\u00e9cise de fin Titre Tous les filmsAvec un titreSans titre Type Films uniquementFilms et \u0153uvres audiovisuelles Langue du titre \ud83c\uddeb\ud83c\uddf7 Fran\u00e7ais\ud83c\uddec\ud83c\udde7 Anglais\ud83c\uddea\ud83c\uddf8 Espagnol\ud83c\udde9\ud83c\uddea Allemand\ud83c\uddee\ud83c\uddf9 Italien\ud83c\uddf5\ud83c\uddf9 Portugais\ud83c\uddf3\ud83c\uddf1 N\u00e9erlandais\ud83c\uddf8\ud83c\udde6 Arabe Tri Plus r\u00e9centsPlus anciensTitre A-Z R\u00e9sultats par page 2050100 Nombre maxi de r\u00e9sultats \u271d\ufe0f Films chr\u00e9tiens \/ religieux (genre \/ sujet Wikidata) \u00c9largir la recherche (mots-cl\u00e9s dans le titre) Chr\u00e9tien Religieux Biblique Spirituel &nbsp; \u23f9 Annuler &nbsp; \ud83e\uddf9 R\u00e9initialiser Aller \u00e0 la page &nbsp; \ud83d\udcc4 Aller \u00e0 la page &nbsp; \u2b07\ufe0f Exporter CSV &nbsp; \ud83d\udd04 Rechercher Pr\u00eat. \u25c0 Pr\u00e9c\u00e9dent Page 1 \/ 1 Suivant \u25b6 \u00ab\u00a0`html Catalogue Films &#8211; Wikidata \ud83c\udfac Catalogue Films &#8211; Wikidata Recherche directe dans Wikidata \u2014 aucune base locale et aucun stockage local Recherche libre Rechercher dans Titre R\u00e9alisateur Acteur Titre + r\u00e9alisateur + acteur &nbsp; \ud83d\udd0e Rechercher Date de sortie Toutes les dates Avec date de sortie Sans date de sortie Derniers jours Derniers mois Ann\u00e9e ou N derni\u00e8res ann\u00e9es Ann\u00e9e de Ann\u00e9e \u00e0 Date pr\u00e9cise de d\u00e9but Date pr\u00e9cise de fin Type de contenu \ud83c\udfac Films uniquement \ud83c\udfa8 Dessins anim\u00e9s uniquement \ud83c\udfac\ud83c\udfa8 Films + dessins anim\u00e9s \ud83d\udcfa Toutes les \u0153uvres audiovisuelles Pr\u00e9sence du titre Tous Avec un titre Sans titre Pr\u00e9sence de la date Toutes Avec une date Sans date Langues du titre \u2014 s\u00e9lection multiple \ud83c\udf0d Toutes les langues \ud83c\uddeb\ud83c\uddf7 Fran\u00e7ais \ud83c\uddec\ud83c\udde7 Anglais \ud83c\uddea\ud83c\uddf8 Espagnol \ud83c\udde9\ud83c\uddea Allemand \ud83c\uddee\ud83c\uddf9 Italien \ud83c\uddf5\ud83c\uddf9 Portugais \ud83c\uddf3\ud83c\uddf1 N\u00e9erlandais \ud83c\uddf7\ud83c\uddfa Russe \ud83c\uddfa\ud83c\udde6 Ukrainien \ud83c\uddf5\ud83c\uddf1 Polonais \ud83c\udde8\ud83c\uddff Tch\u00e8que \ud83c\uddf8\ud83c\uddf0 Slovaque \ud83c\udded\ud83c\uddfa Hongrois \ud83c\uddf7\ud83c\uddf4 Roumain \ud83c\udde7\ud83c\uddec Bulgare \ud83c\uddec\ud83c\uddf7 Grec \ud83c\uddf9\ud83c\uddf7 Turc \ud83c\uddf8\ud83c\udde6 Arabe \ud83c\uddee\ud83c\uddf1 H\u00e9breu \ud83c\uddee\ud83c\uddf7 Persan \ud83c\uddee\ud83c\uddf3 Hindi \ud83c\udde7\ud83c\udde9 Bengali \ud83c\udde8\ud83c\uddf3 Chinois \ud83c\uddef\ud83c\uddf5 Japonais \ud83c\uddf0\ud83c\uddf7 Cor\u00e9en \ud83c\uddf9\ud83c\udded Tha\u00ef \ud83c\uddfb\ud83c\uddf3 Vietnamien \ud83c\uddee\ud83c\udde9 Indon\u00e9sien \ud83c\uddf2\ud83c\uddfe Malais \ud83c\uddf0\ud83c\uddea Swahili \ud83c\udfdb\ufe0f Latin Catalan Basque Galicien Danois Su\u00e9dois Norv\u00e9gien Finnois Islandais Estonien Letton Lituanien Slov\u00e8ne Croate Serbe Maintenez Ctrl (Windows) ou Cmd (Mac) pour s\u00e9lectionner plusieurs langues. \u271d\ufe0f Films chr\u00e9tiens \/ religieux Inclure des mots-cl\u00e9s dans le titre \ud83d\udeab Exclure des mots-cl\u00e9s S\u00e9parez les mots par des virgules. Tri Plus r\u00e9cents Plus anciens Titre A-Z Nombre de r\u00e9sultats par page Nombre maximum de r\u00e9sultats Num\u00e9ro de page \u23f9 Annuler \ud83e\uddf9 R\u00e9initialiser \ud83d\udcc4 Aller \u00e0 la page \u2b07\ufe0f Exporter CSV \u25b6\ufe0f Rechercher la s\u00e9lection sur YouTube Pr\u00eat. \u25c0 Pr\u00e9c\u00e9dent Page 1 \/ 1 Suivant \u25b6 \u00ab\u00a0` Catalogue Films &#8211; Wikidata \ud83c\udfac Catalogue Films &#8211; Wikidata Recherche directe dans Wikidata \u2014 aucune base locale Recherche libre Rechercher dans TitreR\u00e9alisateurActeurTitre + r\u00e9alisateur + acteur &nbsp; \ud83d\udd0e Rechercher Date de sortie Toutes les datesAvec date de sortieSans date de sortie Derniers jours Derniers mois Ann\u00e9e (2026) ou N derni\u00e8res ann\u00e9es (3) Ann\u00e9e de (fourchette) Ann\u00e9e \u00e0 (fourchette) Date pr\u00e9cise de d\u00e9but Date pr\u00e9cise de fin Titre Tous les filmsAvec un titreSans titre Type Films uniquementDessins anim\u00e9s uniquementFilms + dessins anim\u00e9sFilms et \u0153uvres audiovisuelles (large) Tri Plus r\u00e9centsPlus anciensTitre A-Z R\u00e9sultats par page Nombre maxi de r\u00e9sultats Langue(s) du titre Chargement des langues disponibles dans la base&#8230; \ud83c\udf10 Toutes les langues (ignore la liste ci-dessus) La liste ci-dessus est construite \u00e0 partir des langues r\u00e9ellement pr\u00e9sentes dans les libell\u00e9s de films sur Wikidata (\u00e9chantillon). Maintenez Ctrl (Cmd sur Mac) pour en s\u00e9lectionner plusieurs. Cochez \u00ab Toutes les langues \u00bb pour ne faire aucun tri par langue. \u271d\ufe0f Films chr\u00e9tiens \/ religieux (genre \/ sujet Wikidata) \u00c9largir la recherche (mots-cl\u00e9s dans le titre) Chr\u00e9tien Religieux Biblique Spirituel Exclure les films contenant ces mots (titre) &nbsp; \u23f9 Annuler &nbsp; \ud83e\uddf9 R\u00e9initialiser Aller \u00e0 la page &nbsp; \ud83d\udcc4 Aller \u00e0 la page &nbsp; \u2b07\ufe0f Exporter CSV &nbsp; \ud83d\udd04 Rechercher &nbsp; \u25b6\ufe0f S\u00e9lection sur YouTube &nbsp; \ud83c\udfa6 Vue Cin\u00e9ma Pr\u00eat. \u25c0 Pr\u00e9c\u00e9dent Page 1 \/ 1 Suivant \u25b6<\/p>\n","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"site-sidebar-layout":"no-sidebar","site-content-layout":"","ast-site-content-layout":"full-width-container","site-content-style":"unboxed","site-sidebar-style":"unboxed","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"disabled","ast-breadcrumbs-content":"","ast-featured-img":"disabled","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"enabled","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":"","_members_access_role":[],"_members_access_error":""},"class_list":["post-31793","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/allianceenjesus.com\/index.php?rest_route=\/wp\/v2\/pages\/31793","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/allianceenjesus.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/allianceenjesus.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/allianceenjesus.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/allianceenjesus.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=31793"}],"version-history":[{"count":37,"href":"https:\/\/allianceenjesus.com\/index.php?rest_route=\/wp\/v2\/pages\/31793\/revisions"}],"predecessor-version":[{"id":31831,"href":"https:\/\/allianceenjesus.com\/index.php?rest_route=\/wp\/v2\/pages\/31793\/revisions\/31831"}],"wp:attachment":[{"href":"https:\/\/allianceenjesus.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=31793"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}