templates/user/video.html.twig line 1

Open in your IDE?
  1. {% extends 'base.html.twig' %}
  2. {% block title %}
  3.     {{ video.influencer.name|title }}
  4.     Nude Video #{{ video.id }} - Share-Nude
  5. {% endblock %}
  6. {% block description %}Nude video #{{ video.id }} of {{ video.influencer.name|title }} MYM posted {% if video.user.username != 'Admin' %}by {{ video.user.username }} {% endif %}on {{ video.createdAt|date('Y-m-d') }}. More leaks of {{ video.influencer.name|title }} Onlyfans on Share-Nude{% endblock %}
  7. {% block meta %}
  8.     <meta content="{{ video.influencer.name|title }} Nude Video #{{ video.id }}" property="og:title"/>
  9.     <meta content="Nude video #{{ video.id }} of {{ video.influencer.name|title }}{% if video.user.username != 'Admin' %} posted by {{ video.user.username }}{% endif %} on Share-Nude. More videos of {{ video.influencer.name|title }} on Share-Nude" property="og:description"/>
  10.     <meta content="Share-Nude" property="og:site_name"/>
  11.     <meta content="en_EN" property="og:locale"/>
  12.     <meta content="video.other" property="og:type"/>
  13.     <meta content="{{ video.embed }}" property="og:video:url"/>
  14.     <meta content="{{ video.embed }}" property="og:video:secure_url"/>
  15.     <meta content="https://share-nude.com{{ app.request.requestUri }}" property="og:url"/>
  16.     {% if video.webp is not null %}
  17.         <meta content="https://share-nude.com{{ asset('images/influencer/' ~ video.influencer.slug ~ '/306/' ~ video.webp) }}" property="og:image"/>
  18.     {% else %}
  19.         <meta content="https://share-nude.com{{ asset('images/influencer/' ~ video.influencer.slug ~ '/306/' ~ video.slug) }}" property="og:image"/>
  20.     {% endif %}
  21.     <meta name="twitter:card" content="summary_large_image"/>
  22.     <meta name="twitter:title" content="{{ video.influencer.name|title }} Nude Video #{{ video.id }}"/>
  23.     <meta name="twitter:description" content="Nude video #{{ video.id }} of {{ video.influencer.name|title }} on Share-Nude"/>
  24.     <meta name="twitter:image" content="https://share-nude.com{{ asset('images/influencer/' ~ video.influencer.slug ~ '/306/' ~ (video.webp ?? video.slug)) }}"/>
  25.     <link rel="canonical" href="{{sharenude|raw}}/v/{{ video.influencer.slug }}/{{ video.id }}"/>
  26.     {# Schema.org JSON-LD - VideoObject #}
  27.     <script type="application/ld+json">
  28.     {
  29.         "@context": "https://schema.org",
  30.         "@type": "VideoObject",
  31.         "name": "{{ video.influencer.name|title }} Nude Video #{{ video.id }}",
  32.         "description": "Nude video of {{ video.influencer.name|title }}{% if video.influencer.onlyfans|length > 0 %} ({{ video.influencer.onlyfans }} OnlyFans){% endif %}{% if video.influencer.mym|length > 0 %} ({{ video.influencer.mym }} MYM){% endif %} posted on Share-Nude",
  33.         "thumbnailUrl": "{{sharenude|raw}}{{ asset('images/influencer/' ~ video.influencer.slug ~ '/306/' ~ (video.webp ?? video.slug)) }}",
  34.         "uploadDate": "{{ video.createdAt|date('c') }}",
  35.         "url": "{{sharenude|raw}}/v/{{ video.influencer.slug }}/{{ video.id }}",
  36.         "embedUrl": "{{ video.embed }}",
  37.         "author": {
  38.             "@type": "Person",
  39.             "name": "{{ video.influencer.name|title }}",
  40.             "url": "{{sharenude|raw}}/i/{{ video.influencer.slug }}"
  41.         },
  42.         "publisher": {
  43.             "@type": "Organization",
  44.             "name": "Share-Nude",
  45.             "url": "{{sharenude|raw}}",
  46.             "logo": {
  47.                 "@type": "ImageObject",
  48.                 "url": "{{sharenude|raw}}{{ asset('sharenude.png') }}"
  49.             }
  50.         },
  51.         "interactionStatistic": {
  52.             "@type": "InteractionCounter",
  53.             "interactionType": "https://schema.org/ViewAction",
  54.             "userInteractionCount": {{ video.views }}
  55.         },
  56.         "commentCount": {{ comments|length }},
  57.         "comment": [
  58.             {% for c in comments|slice(0, 10) %}
  59.             {
  60.                 "@type": "Comment",
  61.                 "author": {"@type": "Person", "name": "{{ c.user.username|e('js') }}"},
  62.                 "dateCreated": "{{ c.createdAt|date('c') }}",
  63.                 "text": "{{ c.comment|e('js') }}"
  64.             }{% if not loop.last %},{% endif %}
  65.             {% endfor %}
  66.         ]
  67.     }
  68.     </script>
  69.     {# BreadcrumbList for navigation #}
  70.     <script type="application/ld+json">
  71.     {
  72.         "@context": "https://schema.org",
  73.         "@type": "BreadcrumbList",
  74.         "itemListElement": [
  75.             {
  76.                 "@type": "ListItem",
  77.                 "position": 1,
  78.                 "name": "Home",
  79.                 "item": "{{sharenude|raw}}"
  80.             },
  81.             {
  82.                 "@type": "ListItem",
  83.                 "position": 2,
  84.                 "name": "{{ video.influencer.name|title }}",
  85.                 "item": "{{sharenude|raw}}/i/{{ video.influencer.slug }}"
  86.             },
  87.             {
  88.                 "@type": "ListItem",
  89.                 "position": 3,
  90.                 "name": "Video #{{ video.id }}",
  91.                 "item": "{{sharenude|raw}}/v/{{ video.influencer.slug }}/{{ video.id }}"
  92.             }
  93.         ]
  94.     }
  95.     </script>
  96. {% endblock %}
  97. {% block body %}
  98.     <div class="container">
  99.         {% set breadcrumbs = [
  100.             {'label': video.influencer.name|title, 'url': path('app_influencer', {'slug': video.influencer.slug})},
  101.             {'label': 'Video #' ~ video.id, 'url': path('user_video', {'slug': video.influencer.slug, 'id': video.id})}
  102.         ] %}
  103.         {{ include('partials/breadcrumb.html.twig') }}
  104.         <div class="row">
  105.             <div class="col-lg-12 mx-auto my-3 text-center white">
  106.                 <h1>{{ video.influencer.name|title }} Nude Video #{{ video.id }}</h1>
  107.                 {{ include ('flash.html.twig') }}
  108.                 <!-- VIP Promo Banner -->
  109.                 <div class="vip-promo-alert">
  110.                     <a href="https://share-nude-vip.com" target="_blank" rel="noopener nofollow" class="vip-promo-link">
  111.                         <span class="vip-icon">🌟</span>
  112.                         <span class="vip-text">Share-Nude VIP - Toutes les vidĂ©os exclusives sans pub pour seulement 1€</span>
  113.                         <span class="vip-arrow">→</span>
  114.                     </a>
  115.                 </div>
  116.                 <div class="ratio ratio-16x9 my-3 position-relative" id="video-player-container">
  117.                     <!-- Preroll Overlay -->
  118.                     <div id="preroll-overlay" class="preroll-overlay">
  119.                         <div id="preroll-loader" class="preroll-loader">
  120.                             <div class="preroll-spinner"></div>
  121.                             <p>Loading ad...</p>
  122.                         </div>
  123.                         <video id="preroll-video" class="preroll-video" playsinline preload="auto">
  124.                             <!-- Video source will be set dynamically by JavaScript -->
  125.                         </video>
  126.                         <button id="preroll-play-pause" class="preroll-play-pause" style="display: none;">
  127.                             <i class="fas fa-pause"></i>
  128.                         </button>
  129.                         <div id="preroll-timer" class="preroll-timer" style="display: none;">
  130.                             Skip ad in <span id="timer-seconds">10</span>s
  131.                         </div>
  132.                         <button id="skip-preroll-btn" class="skip-preroll-btn" style="display: none;">
  133.                             Skip Ad
  134.                         </button>
  135.                         <div id="preroll-clickable" class="preroll-clickable"></div>
  136.                     </div>
  137.                     <!-- Real Video (hidden initially) -->
  138.                     <iframe id="real-video" width="600" height="480" src="{{ video.embed }}" scrolling="no" frameborder="0" allowfullscreen="true" style="display: none;"></iframe>
  139.                 </div>
  140.                 {{ include('ads.html.twig') }}
  141.                 {% if video.description|length > 0 %}
  142.                     <p>{{ video.description }}</p>
  143.                 {% endif %}
  144.                 {% if app.user %}
  145.                     <p>
  146.                         {% if is_granted('ROLE_ADMIN') %}
  147.                             <a href="{{ path('user_edit_video', {id:video.id}) }}" class="btn btn-secondary">Edit</a>
  148.                         {% endif %}
  149.                         {% if app.user == video.user or is_granted('ROLE_ADMIN') %}
  150.                             <a href="{{ path('user_remove_video', {id:video.id}) }}" onclick="return confirm('Are you sure you want to delete this video ?');" class="btn btn-danger">Delete</a>
  151.                         {% endif %}
  152.                     </p>
  153.                 {% endif %}
  154.                 <p class="text-muted">Posted
  155.                     {{ video.createdAt|ago }}
  156.                     {% if video.user.username != 'Admin' and video.user.ip is not null %}by
  157.                     <a href="{{ path('user_user', {username: video.user.username}) }}">{{ video.user.username }}</a>{% endif %}<br>{{ video.views|number_format }}
  158.                     <i class="fa-solid fa-eye"></i>
  159.                 </p>
  160.                 <a href="{{path('app_influencer', {slug:video.influencer.slug})}}" title="More {{ video.influencer.name|title }} photos and videos">
  161.                 {{ video.influencer.name }}{% if video.influencer.onlyfans|length > 0 %} / {{ video.influencer.onlyfans }} Onlyfans{% endif %}{% if video.influencer.mym|length > 0 %} / {{ video.influencer.mym }} MyM{% endif %}
  162.                 <br>
  163.                     {% if video.influencer.mainPhoto is not null %}
  164.                         <picture>
  165.                             {% if video.influencer.mainPhoto.webp is not null %}<source srcset="{{sharenude|raw}}{{ asset('images/influencer/' ~ video.influencer.slug ~ '/196/' ~ video.influencer.mainPhoto.webp ~ '') }}" type="image/webp">{% endif %}
  166.                             <img loading="lazy" src="{{sharenude|raw}}{{ asset('images/influencer/' ~ video.influencer.slug ~ '/196/' ~ video.influencer.mainPhoto.slug ~ '') }}" class="rounded-circle" title="{{ video.influencer.name|title }} photo" alt="{{ video.influencer.name|title }} photo"/>
  167.                         </picture>
  168.                     {% endif %}
  169.                 </a>
  170.                 <br>
  171.                 {% if video.influencer.videos|length > 1 %}
  172.                     <h2 id="more-videos-title">{{ video.influencer.videos|length -1 }} More {{ video.influencer.name|title }} videos</h2>
  173.                 {% endif %}
  174.                 <div id="more-videos-container" class="row mt-4" data-influencer-id="{{ video.influencer.id }}" data-video-id="{{ video.id }}" data-influencer-name="{{ video.influencer.name|title }}"></div>
  175.                 <div id="load-more-videos-button-container" class="text-center mt-4">
  176.                     <button id="load-more-videos-btn" class="btn btn-primary">
  177.                         Show more {{ video.influencer.name|title }} videos
  178.                     </button>
  179.                 </div>
  180.                 {{ include('comments.html.twig') }}
  181.                 <p class="text-muted mt-4">
  182.                     Is this "{{ video.influencer.name|title }}" video yours and was shared without your consent?
  183.                     <a href="mailto:[email protected]?subject=[DMCA on Share-Nude] Unauthorized Content Removal Request - {{ video.influencer.name|title }} &body=Url : {{sharenude|raw}}    /v/{{ video.influencer.slug }}/{{ video.id }}%0D%0APlease provide details about the content in question">
  184.                         <br>
  185.                         <button class="btn btn-sm btn-outline-danger"> Click here to reach out to us for removal.</button>
  186.                     </a>
  187.                     <br>
  188.                     Video will be deleted within fews days.
  189.                 </p>
  190.             </div>
  191.         </div>
  192.     </div>
  193. {% endblock %}
  194. {% block javascripts %}
  195.     <script>
  196.         // Preroll Ad System
  197.         function initPreroll() {
  198.             const prerollOverlay = document.getElementById('preroll-overlay');
  199.             const prerollVideo = document.getElementById('preroll-video');
  200.             const skipBtn = document.getElementById('skip-preroll-btn');
  201.             const realVideo = document.getElementById('real-video');
  202.             const prerollClickable = document.getElementById('preroll-clickable');
  203.             const timerElement = document.getElementById('preroll-timer');
  204.             const timerSeconds = document.getElementById('timer-seconds');
  205.             const playPauseBtn = document.getElementById('preroll-play-pause');
  206.             const prerollLoader = document.getElementById('preroll-loader');
  207.             if (!prerollOverlay || !prerollVideo || !skipBtn || !realVideo || !timerElement || !timerSeconds || !playPauseBtn || !prerollLoader) return;
  208.             // Array of preroll videos with tracking names
  209.             const prerollVideos = [
  210.                 { file: 'julie.MOV', name: 'julie' },
  211.                 { file: '004_fr.mp4', name: '004_fr' },
  212.                 { file: '006_fr.mp4', name: '006_fr' },
  213.                 { file: 'Afroditee-fr.mp4', name: 'Afroditee-fr' },
  214.                 { file: 'sn-vip.mp4', name: 'sn-vip' }
  215.             ];
  216.             // Randomly select a video (rand 4)
  217.             const selectedVideo = prerollVideos[Math.floor(Math.random() * prerollVideos.length)];
  218.             // Set video source dynamically
  219.             prerollVideo.src = '{{ asset('') }}' + selectedVideo.file;
  220.             // Build tracking URL - julie uses direct Telegram link, sn-vip uses VIP access, others use tracking
  221.             let trackingUrl;
  222.             if (selectedVideo.name === 'julie') {
  223.                 trackingUrl = 'https://t.me/m/I74uPFutOTY1';
  224.             } else if (selectedVideo.name === 'sn-vip') {
  225.                 trackingUrl = 'https://acces.share-nude-vip.com';
  226.             } else {
  227.                 trackingUrl = 'https://go.mavrtracktor.com?sourceId=sharenude&creativeId=preroll&campaignId=' + selectedVideo.name + '&userId=ff9cd0158a2d244c452cbcbc061440b1763a55f068539e459404ba4934dae07b';
  228.             }
  229.             let countdown = 10;
  230.             let isPlaying = true;
  231.             let loaderHidden = false;
  232.             // Function to hide loader and show controls
  233.             function showControls() {
  234.                 if (loaderHidden) return; // Already hidden
  235.                 loaderHidden = true;
  236.                 prerollLoader.style.display = 'none';
  237.                 playPauseBtn.style.display = 'flex';
  238.                 timerElement.style.display = 'block';
  239.             }
  240.             // Update play/pause button icon
  241.             function updatePlayPauseIcon() {
  242.                 const icon = playPauseBtn.querySelector('i');
  243.                 if (isPlaying) {
  244.                     icon.className = 'fas fa-pause';
  245.                 } else {
  246.                     icon.className = 'fas fa-play';
  247.                 }
  248.             }
  249.             // Start countdown timer
  250.             function startCountdown() {
  251.                 currentCountdownInterval = setInterval(() => {
  252.                     countdown--;
  253.                     timerSeconds.textContent = countdown;
  254.                     if (countdown <= 0) {
  255.                         clearInterval(currentCountdownInterval);
  256.                         timerElement.style.display = 'none';
  257.                         skipBtn.style.display = 'block';
  258.                     }
  259.                 }, 1000);
  260.             }
  261.             // Function to start video playback
  262.             async function startVideo() {
  263.                 try {
  264.                     // Try to play with sound first
  265.                     prerollVideo.muted = false;
  266.                     await prerollVideo.play();
  267.                     showControls();
  268.                     clearTimeout(currentPrerollTimeout);
  269.                     isPlaying = true;
  270.                     updatePlayPauseIcon();
  271.                     startCountdown();
  272.                 } catch (error) {
  273.                     // If autoplay with sound fails (Chrome policy), try muted
  274.                     try {
  275.                         prerollVideo.muted = true;
  276.                         await prerollVideo.play();
  277.                         showControls();
  278.                         clearTimeout(currentPrerollTimeout);
  279.                         isPlaying = true;
  280.                         updatePlayPauseIcon();
  281.                         startCountdown();
  282.                     } catch (mutedError) {
  283.                         clearTimeout(currentPrerollTimeout);
  284.                         hidePreroll();
  285.                     }
  286.                 }
  287.             }
  288.             // Wait for video to be ready before playing
  289.             let videoStarted = false;
  290.             function onVideoReady() {
  291.                 if (videoStarted) return; // Prevent double start
  292.                 videoStarted = true;
  293.                 startVideo();
  294.             }
  295.             // Try to play immediately, with retries
  296.             let playAttempts = 0;
  297.             const maxAttempts = 20;
  298.             async function tryPlay() {
  299.                 if (videoStarted) return;
  300.                 playAttempts++;
  301.                 try {
  302.                     // Check if video has any data
  303.                     if (prerollVideo.readyState >= 1) {
  304.                         onVideoReady();
  305.                         return;
  306.                     }
  307.                 } catch (e) {}
  308.                 // Retry after delay if not started
  309.                 if (playAttempts < maxAttempts && !videoStarted) {
  310.                     setTimeout(tryPlay, 250);
  311.                 }
  312.             }
  313.             // Event listeners as backup
  314.             prerollVideo.addEventListener('canplay', onVideoReady, { once: true });
  315.             prerollVideo.addEventListener('loadedmetadata', onVideoReady, { once: true });
  316.             // Force reload and start trying to play
  317.             prerollVideo.load();
  318.             setTimeout(tryPlay, 100);
  319.             // Safety timeout: skip preroll after 5 seconds if video doesn't start
  320.             currentPrerollTimeout = setTimeout(() => {
  321.                 if (!videoStarted) {
  322.                     hidePreroll();
  323.                 }
  324.             }, 5000);
  325.             // Play/Pause button handler
  326.             playPauseBtn.addEventListener('click', (e) => {
  327.                 e.preventDefault();
  328.                 e.stopPropagation();
  329.                 if (isPlaying) {
  330.                     // Pause video and timer
  331.                     prerollVideo.pause();
  332.                     clearInterval(currentCountdownInterval);
  333.                     isPlaying = false;
  334.                 } else {
  335.                     // Resume video and timer
  336.                     prerollVideo.play();
  337.                     startCountdown();
  338.                     isPlaying = true;
  339.                 }
  340.                 updatePlayPauseIcon();
  341.             });
  342.             // Function to hide preroll and show real video
  343.             function hidePreroll() {
  344.                 prerollOverlay.style.display = 'none';
  345.                 realVideo.style.display = 'block';
  346.                 prerollVideo.pause();
  347.                 clearInterval(currentCountdownInterval);
  348.                 clearInterval(currentPollInterval);
  349.                 clearTimeout(currentPrerollTimeout);
  350.             }
  351.             // Skip button click handler
  352.             skipBtn.addEventListener('click', (e) => {
  353.                 e.preventDefault();
  354.                 e.stopPropagation();
  355.                 hidePreroll();
  356.             });
  357.             // Clickable area handler - open tracking link and hide preroll
  358.             prerollClickable.addEventListener('click', (e) => {
  359.                 e.preventDefault();
  360.                 window.open(trackingUrl, '_blank', 'noopener,noreferrer');
  361.                 hidePreroll();
  362.             });
  363.             // When preroll ends, show real video automatically
  364.             prerollVideo.addEventListener('ended', hidePreroll);
  365.             // Handle video errors - skip preroll if video fails to load
  366.             prerollVideo.addEventListener('error', hidePreroll);
  367.         }
  368.         // Variables globales pour stocker les handlers et permettre le nettoyage
  369.         let currentScrollHandler = null;
  370.         let currentTouchMoveHandler = null;
  371.         let currentPrerollTimeout = null;
  372.         let currentCountdownInterval = null;
  373.         let currentPollInterval = null;
  374.         // Fonction pour nettoyer les event listeners et timeouts
  375.         function cleanupEventListeners() {
  376.             if (currentScrollHandler) {
  377.                 window.removeEventListener('scroll', currentScrollHandler);
  378.                 currentScrollHandler = null;
  379.             }
  380.             if (currentTouchMoveHandler) {
  381.                 window.removeEventListener('touchmove', currentTouchMoveHandler);
  382.                 currentTouchMoveHandler = null;
  383.             }
  384.             // Nettoyer les timeouts et intervals du preroll
  385.             if (currentPrerollTimeout) {
  386.                 clearTimeout(currentPrerollTimeout);
  387.                 currentPrerollTimeout = null;
  388.             }
  389.             if (currentCountdownInterval) {
  390.                 clearInterval(currentCountdownInterval);
  391.                 currentCountdownInterval = null;
  392.             }
  393.             if (currentPollInterval) {
  394.                 clearInterval(currentPollInterval);
  395.                 currentPollInterval = null;
  396.             }
  397.             // Mettre en pause la vidĂ©o preroll si elle existe
  398.             const prerollVideo = document.getElementById('preroll-video');
  399.             if (prerollVideo) {
  400.                 prerollVideo.pause();
  401.             }
  402.         }
  403.         function initVideoPage() {
  404.             // Nettoyer les listeners de la page prĂ©cĂ©dente
  405.             cleanupEventListeners();
  406.             // RĂ©cupĂ©rer les Ă©lĂ©ments Ă  chaque fois car ils peuvent changer avec Swup
  407.             function getElements() {
  408.                 return {
  409.                     container: document.getElementById('more-videos-container'),
  410.                     loadMoreBtn: document.getElementById('load-more-videos-btn'),
  411.                     loadMoreBtnContainer: document.getElementById('load-more-videos-button-container')
  412.                 };
  413.             }
  414.             const elements = getElements();
  415.             if (!elements.container) return;
  416.             // RĂ©cupĂ©rer dynamiquement les IDs et le nom depuis les attributs data (pour Ă©viter les conflits avec Swup)
  417.             const influencerId = elements.container.getAttribute('data-influencer-id');
  418.             const videoId = elements.container.getAttribute('data-video-id');
  419.             const influencerName = elements.container.getAttribute('data-influencer-name');
  420.             if (!influencerId || !videoId || !influencerName) {
  421.                 console.error('Missing influencer ID, video ID or influencer name');
  422.                 return;
  423.             }
  424.             // RĂ©initialiser TOUTES les variables Ă  chaque appel pour Ă©viter les conflits entre pages
  425.             let nextPage = 1;
  426.             let isLoading = false;
  427.             let hasMoreVideos = true;
  428.             const loadedVideoIds = new Set();
  429.             const basePath = '/load-more-influencer-videos';
  430.             // Vider le conteneur pour Ă©viter d'afficher les vidĂ©os de la page prĂ©cĂ©dente
  431.             elements.container.innerHTML = '';
  432.             // Supprimer les anciens event listeners en clonant le bouton
  433.             if (elements.loadMoreBtn) {
  434.                 const newBtn = elements.loadMoreBtn.cloneNode(true);
  435.                 elements.loadMoreBtn.parentNode.replaceChild(newBtn, elements.loadMoreBtn);
  436.             }
  437.             // Supprimer l'ancien listener de scroll s'il existe
  438.             if (window._videoScrollHandler) {
  439.                 window.removeEventListener('scroll', window._videoScrollHandler);
  440.                 window.removeEventListener('touchmove', window._videoScrollHandler);
  441.             }
  442.             function updateButtonVisibility() {
  443.                 const elements = getElements();
  444.                 // RĂ©cupĂ©rer le nom de l'influenceur depuis les attributs data
  445.                 const currentInfluencerName = elements.container.getAttribute('data-influencer-name');
  446.                 if (elements.loadMoreBtnContainer) {
  447.                     if (!hasMoreVideos) {
  448.                         elements.loadMoreBtnContainer.style.display = 'none';
  449.                     } else {
  450.                         elements.loadMoreBtnContainer.style.display = 'block';
  451.                     }
  452.                 }
  453.                 if (elements.loadMoreBtn) {
  454.                     elements.loadMoreBtn.disabled = isLoading || !hasMoreVideos;
  455.                     if (isLoading) {
  456.                         elements.loadMoreBtn.textContent = 'Loading...';
  457.                     } else {
  458.                         elements.loadMoreBtn.textContent = 'Show more ' + currentInfluencerName + ' videos';
  459.                     }
  460.                 }
  461.             }
  462.             function buildUrl(page) {
  463.                 return basePath + '/' + page + '?influencer=' + influencerId + '&video=' + videoId;
  464.             }
  465.             function handleScroll() {
  466.                 if (isLoading || !hasMoreVideos) return;
  467.                 const footer = document.getElementById('footer') ? document.getElementById('footer').offsetHeight : 0;
  468.                 const scrollPosition = window.innerHeight + window.scrollY;
  469.                 const documentHeight = document.body.offsetHeight;
  470.                 if (scrollPosition >= documentHeight - 600 - footer) {
  471.                     loadMoreVideos();
  472.                 }
  473.             }
  474.             // Stocker le handler pour pouvoir le supprimer plus tard
  475.             window._videoScrollHandler = handleScroll;
  476.             function getVideoIdFromElement(element) {
  477.                 const link = element.querySelector('a[href*="/v/"]');
  478.                 if (link) {
  479.                     const href = link.getAttribute('href');
  480.                     const match = href.match(/\/v\/[^\/]+\/(\d+)/);
  481.                     if (match) {
  482.                         return match[1];
  483.                     }
  484.                 }
  485.                 return null;
  486.             }
  487.             function loadMoreVideos() {
  488.                 if (isLoading || !hasMoreVideos) return;
  489.                 isLoading = true;
  490.                 updateButtonVisibility();
  491.                 const url = buildUrl(nextPage);
  492.                 fetch(url)
  493.                     .then(response => response.text())
  494.                     .then(data => {
  495.                         const elements = getElements();
  496.                         if (data.trim() === '') {
  497.                             hasMoreVideos = false;
  498.                             isLoading = false;
  499.                             updateButtonVisibility();
  500.                             return;
  501.                         }
  502.                         const tempDiv = document.createElement('div');
  503.                         tempDiv.innerHTML = data;
  504.                         const newElements = Array.from(tempDiv.querySelectorAll('.col-lg-3'));
  505.                         if (elements.container && newElements.length > 0) {
  506.                             let addedCount = 0;
  507.                             newElements.forEach(element => {
  508.                                 const vidId = getVideoIdFromElement(element);
  509.                                 if (!vidId || !loadedVideoIds.has(vidId)) {
  510.                                     elements.container.appendChild(element);
  511.                                     if (vidId) {
  512.                                         loadedVideoIds.add(vidId);
  513.                                     }
  514.                                     addedCount++;
  515.                                 }
  516.                             });
  517.                             if (newElements.length < 4) {
  518.                                 hasMoreVideos = false;
  519.                             } else if (addedCount === 0) {
  520.                                 hasMoreVideos = false;
  521.                             } else {
  522.                                 nextPage += 1;
  523.                             }
  524.                         } else {
  525.                             hasMoreVideos = false;
  526.                         }
  527.                         isLoading = false;
  528.                         updateButtonVisibility();
  529.                     })
  530.                     .catch(error => {
  531.                         console.error('Error loading videos:', error);
  532.                         isLoading = false;
  533.                         hasMoreVideos = false;
  534.                         updateButtonVisibility();
  535.                     });
  536.             }
  537.             // GĂ©rer le clic sur le bouton avec plusieurs types d'Ă©vĂ©nements pour iOS
  538.             const elementsAfterClone = getElements();
  539.             if (elementsAfterClone.loadMoreBtn) {
  540.                 elementsAfterClone.loadMoreBtn.addEventListener('click', loadMoreVideos);
  541.                 elementsAfterClone.loadMoreBtn.addEventListener('touchend', function(e) {
  542.                     e.preventDefault();
  543.                     loadMoreVideos();
  544.                 });
  545.             }
  546.             // Stocker et attacher l'Ă©vĂ©nement de scroll avec plusieurs types pour iOS
  547.             currentScrollHandler = handleScroll;
  548.             currentTouchMoveHandler = handleScroll;
  549.             window.addEventListener('scroll', currentScrollHandler, { passive: true });
  550.             window.addEventListener('touchmove', currentTouchMoveHandler, { passive: true });
  551.             // Initialiser la visibilitĂ© du bouton
  552.             updateButtonVisibility();
  553.         }
  554.         // Variable pour Ă©viter les doubles initialisations
  555.         let isInitializing = false;
  556.         // Fonction wrapper pour initialiser avec vĂ©rification
  557.         function safeInit() {
  558.             // Ă‰viter les doubles initialisations simultanĂ©es
  559.             if (isInitializing) return;
  560.             isInitializing = true;
  561.             // VĂ©rifier que nous sommes sur une page vidĂ©o avant d'initialiser le preroll
  562.             const prerollOverlay = document.getElementById('preroll-overlay');
  563.             if (prerollOverlay) {
  564.                 initPreroll();
  565.             }
  566.             initVideoPage();
  567.             // Reset après un dĂ©lai
  568.             setTimeout(() => {
  569.                 isInitializing = false;
  570.             }, 500);
  571.         }
  572.         // Initialiser au chargement de la page
  573.         if (document.readyState === 'loading') {
  574.             document.addEventListener('DOMContentLoaded', safeInit);
  575.         } else {
  576.             safeInit();
  577.         }
  578.         // RĂ©initialiser après chaque changement de page Swup
  579.         if (typeof Swup !== 'undefined') {
  580.             // Nettoyer avant le changement de page
  581.             document.addEventListener('swup:willReplaceContent', function() {
  582.                 cleanupEventListeners();
  583.             });
  584.             // Utiliser uniquement animationInDone pour Ă©viter les doubles appels
  585.             document.addEventListener('swup:animationInDone', function() {
  586.                 setTimeout(safeInit, 250);
  587.             });
  588.         }
  589.     </script>
  590. {% endblock %}