{"id":198,"date":"2021-04-26T12:05:31","date_gmt":"2021-04-26T12:05:31","guid":{"rendered":"https:\/\/cancun.famlevy.nl\/?page_id=198"},"modified":"2025-06-10T16:55:49","modified_gmt":"2025-06-10T14:55:49","slug":"video-archief-2024","status":"publish","type":"page","link":"https:\/\/cancun.famlevy.nl\/en\/video-archief-2024\/","title":{"rendered":"Video Archief 2024"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"198\" class=\"elementor elementor-198\" data-elementor-post-type=\"page\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-c64d8f0 elementor-section-stretched elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"c64d8f0\" data-element_type=\"section\" data-e-type=\"section\" data-settings=\"{&quot;stretch_section&quot;:&quot;section-stretched&quot;,&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-wider\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-4e01bf3\" data-id=\"4e01bf3\" 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<div class=\"elementor-element elementor-element-4378267 e-flex e-con-boxed e-con e-parent\" data-id=\"4378267\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-61324f8 elementor-widget elementor-widget-text-editor\" data-id=\"61324f8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<div style=\"text-align: center; padding: 0 10px;\">\n  <h2 style=\"word-wrap: break-word; font-size: 6vw; line-height: 1.2;\">\n    REIS<span style=\"color: #f3f600;\">VERSLAG<\/span><br>\n    <span style=\"color: #f3f600;\">CANCUN<\/span>2024\n  <\/h2>\n  <p style=\"font-weight: bold;\">\n    Hier is de volledig gemonteerde video van 2024. Veel kijkplezier!!\n  <\/p>\n<\/div>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-e0ba26a elementor-widget-divider--view-line elementor-widget elementor-widget-divider\" data-id=\"e0ba26a\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"divider.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-divider\">\n\t\t\t<span class=\"elementor-divider-separator\">\n\t\t\t\t\t\t<\/span>\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-4a643c5 elementor-widget elementor-widget-video\" data-id=\"4a643c5\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;video_type&quot;:&quot;hosted&quot;,&quot;controls&quot;:&quot;yes&quot;}\" data-widget_type=\"video.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"e-hosted-video elementor-wrapper elementor-open-inline\">\n\t\t\t\t\t<video class=\"elementor-video\" src=\"https:\/\/cancun.famlevy.nl\/wp-content\/uploads\/2025\/06\/cancun-2024-final-v2.mp4\" controls=\"\" preload=\"metadata\" poster=\"https:\/\/cancun.famlevy.nl\/wp-content\/uploads\/2025\/06\/cancun-2024-thumbnail.png\"><\/video>\n\t\t\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-ed9cb06 elementor-widget elementor-widget-spacer\" data-id=\"ed9cb06\" 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-1a2363f elementor-widget-divider--view-line elementor-widget elementor-widget-divider\" data-id=\"1a2363f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"divider.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-divider\">\n\t\t\t<span class=\"elementor-divider-separator\">\n\t\t\t\t\t\t<\/span>\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-bee9895 elementor-widget elementor-widget-text-editor\" data-id=\"bee9895\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<div style=\"text-align: center; padding: 0 10px;\">\n  <h2 style=\"word-wrap: break-word; font-size: 6vw; line-height: 1.2;\">\n    VIDEO<span style=\"color: #f3f600;\">GALLERIJ<\/span><br>\n    <span style=\"color: #f3f600;\">CANCUN<\/span>2024\n  <\/h2>\n  <p style=\"font-weight: bold;\">\n    Hier kun je onze avonturen van 2024 bekijken! Altijd leuk voor de herinnering natuurlijk.\n  <\/p>\n<\/div>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-c7c1f22 elementor-widget-divider--view-line elementor-widget elementor-widget-divider\" data-id=\"c7c1f22\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"divider.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-divider\">\n\t\t\t<span class=\"elementor-divider-separator\">\n\t\t\t\t\t\t<\/span>\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-045a924 elementor-widget elementor-widget-html\" data-id=\"045a924\" 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<!-- ELEMENTOR PRO OPTIMIZED VIDEO GALLERY -->\n<!-- Paste this code into your Elementor Pro HTML widget -->\n\n<div id=\"video-gallery\" class=\"elementor-video-gallery\">\n    <!-- Videos will be dynamically loaded here -->\n<\/div>\n\n<style>\n\/* Elementor-specific Video Gallery Styles *\/\n.elementor-video-gallery {\n    display: grid;\n    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n    gap: 25px;\n    padding: 20px 0;\n    width: 100%;\n    max-width: 100%;\n}\n\n\/* Ensure compatibility with Elementor container *\/\n.elementor-widget-html .elementor-video-gallery {\n    margin: 0;\n    padding: 20px 0;\n}\n\n.elementor-video-item {\n    position: relative;\n    background: #ffffff;\n    border-radius: 12px;\n    overflow: hidden;\n    box-shadow: 0 4px 15px rgba(0,0,0,0.1);\n    transition: all 0.3s ease;\n    border: 1px solid #e0e0e0;\n}\n\n.elementor-video-item:hover {\n    transform: translateY(-5px);\n    box-shadow: 0 8px 25px rgba(0,0,0,0.15);\n}\n\n.elementor-video-placeholder {\n    width: 100%;\n    height: 250px;\n    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    color: white;\n    font-size: 16px;\n    position: relative;\n    cursor: pointer;\n    user-select: none;\n    font-family: inherit;\n}\n\n.elementor-video-placeholder::before {\n    content: \"\u25b6\";\n    font-size: 60px;\n    color: rgba(255,255,255,0.9);\n    margin-bottom: 15px;\n    transition: transform 0.3s ease;\n    line-height: 1;\n}\n\n.elementor-video-placeholder:hover::before {\n    transform: scale(1.15);\n}\n\n.elementor-video-placeholder.loading::before {\n    content: \"\u27f3\";\n    animation: elementor-spin 1s linear infinite;\n}\n\n.elementor-video-placeholder.error {\n    background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);\n    cursor: default;\n}\n\n.elementor-video-placeholder.error::before {\n    content: \"\u26a0\";\n    font-size: 48px;\n    transform: none;\n}\n\n@keyframes elementor-spin {\n    from { transform: rotate(0deg); }\n    to { transform: rotate(360deg); }\n}\n\n.elementor-video-item video {\n    width: 100%;\n    height: auto;\n    display: block;\n    background: #000;\n    border-radius: 0;\n}\n\n.elementor-video-meta {\n    padding: 18px 20px;\n    background: #ffffff;\n    border-top: 1px solid #f0f0f0;\n    font-size: 14px;\n    color: #333333;\n    font-weight: 500;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    flex-wrap: wrap;\n    gap: 10px;\n    font-family: inherit;\n}\n\n.elementor-video-info {\n    display: flex;\n    flex-direction: column;\n    gap: 4px;\n    flex: 1;\n}\n\n.elementor-video-date {\n    font-weight: 600;\n    color: #2c3e50;\n}\n\n.elementor-video-time {\n    font-size: 13px;\n    color: #7f8c8d;\n}\n\n.elementor-video-status {\n    font-size: 11px;\n    padding: 4px 10px;\n    border-radius: 15px;\n    background: #ecf0f1;\n    color: #7f8c8d;\n    white-space: nowrap;\n    font-weight: 500;\n    text-transform: uppercase;\n    letter-spacing: 0.5px;\n}\n\n.elementor-video-status.loaded {\n    background: #d5f4e6;\n    color: #27ae60;\n}\n\n.elementor-video-status.loading {\n    background: #fef9e7;\n    color: #f39c12;\n}\n\n.elementor-video-status.error {\n    background: #fadbd8;\n    color: #e74c3c;\n}\n\n.elementor-video-error {\n    padding: 20px;\n    text-align: center;\n    color: #e74c3c;\n    background: rgba(248, 215, 218, 0.3);\n    font-family: inherit;\n}\n\n\/* Loading animations *\/\n.elementor-fade-in {\n    animation: elementorFadeIn 0.6s ease-in;\n}\n\n@keyframes elementorFadeIn {\n    from { opacity: 0; transform: translateY(10px); }\n    to { opacity: 1; transform: translateY(0); }\n}\n\n\/* Responsive design for Elementor *\/\n@media (max-width: 1024px) {\n    .elementor-video-gallery {\n        grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n        gap: 20px;\n    }\n}\n\n@media (max-width: 768px) {\n    .elementor-video-gallery {\n        grid-template-columns: 1fr;\n        gap: 15px;\n        padding: 15px 0;\n    }\n    \n    .elementor-video-placeholder {\n        height: 200px;\n        font-size: 14px;\n    }\n    \n    .elementor-video-placeholder::before {\n        font-size: 48px;\n    }\n    \n    .elementor-video-meta {\n        padding: 15px 18px;\n        font-size: 13px;\n        flex-direction: column;\n        align-items: flex-start;\n        gap: 8px;\n    }\n    \n    .elementor-video-status {\n        align-self: flex-end;\n    }\n}\n\n@media (max-width: 480px) {\n    .elementor-video-gallery {\n        gap: 12px;\n        padding: 10px 0;\n    }\n    \n    .elementor-video-placeholder {\n        height: 180px;\n        font-size: 13px;\n    }\n    \n    .elementor-video-meta {\n        padding: 12px 15px;\n    }\n}\n\n\/* Elementor editor compatibility *\/\n.elementor-editor-active .elementor-video-gallery {\n    min-height: 200px;\n}\n\n\/* No videos message styling *\/\n.elementor-no-videos {\n    text-align: center;\n    padding: 60px 20px;\n    color: #7f8c8d;\n    font-family: inherit;\n}\n\n.elementor-no-videos-icon {\n    font-size: 48px;\n    margin-bottom: 20px;\n    opacity: 0.7;\n}\n\n.elementor-no-videos-title {\n    font-size: 20px;\n    font-weight: 600;\n    margin-bottom: 10px;\n    color: #2c3e50;\n}\n\n.elementor-no-videos-text {\n    font-size: 16px;\n    line-height: 1.5;\n}\n\n\/* Error message styling *\/\n.elementor-error-container {\n    text-align: center;\n    padding: 60px 20px;\n    font-family: inherit;\n}\n\n.elementor-error-box {\n    background: #fadbd8;\n    color: #e74c3c;\n    padding: 30px;\n    border-radius: 12px;\n    display: inline-block;\n    border: 1px solid #f5b7b1;\n}\n\n.elementor-error-icon {\n    font-size: 48px;\n    margin-bottom: 20px;\n}\n\n.elementor-error-title {\n    font-size: 18px;\n    font-weight: 600;\n    margin-bottom: 10px;\n}\n\n.elementor-error-text {\n    margin-bottom: 20px;\n    line-height: 1.5;\n}\n\n.elementor-error-button {\n    padding: 12px 24px;\n    background: #3498db;\n    color: white;\n    border: none;\n    border-radius: 6px;\n    cursor: pointer;\n    font-size: 14px;\n    font-weight: 500;\n    transition: background 0.3s ease;\n}\n\n.elementor-error-button:hover {\n    background: #2980b9;\n}\n<\/style>\n\n<script>\nclass ElementorVideoLoader {\n    constructor(options = {}) {\n        \/\/ Configuration optimized for Elementor\n        this.config = {\n            maxLoadedVideos: options.maxLoadedVideos || 6, \/\/ Lower for Elementor\n            loadMargin: options.loadMargin || '150px',\n            unloadMargin: options.unloadMargin || '-250px',\n            enableRecycling: options.enableRecycling !== false,\n            enableClickToLoad: options.enableClickToLoad !== false,\n            retryAttempts: options.retryAttempts || 2,\n            ...options\n        };\n        \n        \/\/ Core properties\n        this.gallery = document.getElementById('video-gallery');\n        this.observer = null;\n        this.unloadObserver = null;\n        this.videoData = [];\n        this.loadedVideos = new Map();\n        this.loadingQueue = new Set();\n        this.retryCount = new Map();\n        \n        \/\/ Initialize if gallery exists\n        if (this.gallery) {\n            this.init();\n        } else {\n            console.warn('Elementor Video Gallery: video-gallery element not found');\n        }\n    }\n\n    init() {\n        \/\/ Add Elementor-specific class\n        this.gallery.classList.add('elementor-video-gallery');\n        \n        this.setupIntersectionObservers();\n        this.loadVideoData();\n        this.setupElementorCompatibility();\n    }\n\n    setupElementorCompatibility() {\n        \/\/ Handle Elementor editor mode\n        if (window.elementorFrontend) {\n            window.elementorFrontend.hooks.addAction('frontend\/element_ready\/widget', () => {\n                \/\/ Reinitialize if needed when Elementor refreshes\n                if (!this.gallery.querySelector('.elementor-video-item')) {\n                    this.loadVideoData();\n                }\n            });\n        }\n        \n        \/\/ Handle WordPress admin bar\n        const adminBar = document.getElementById('wpadminbar');\n        if (adminBar) {\n            const adminBarHeight = adminBar.offsetHeight;\n            this.config.loadMargin = `${150 + adminBarHeight}px`;\n        }\n    }\n\n    setupIntersectionObservers() {\n        \/\/ Observer for loading videos\n        const loadOptions = {\n            root: null,\n            rootMargin: this.config.loadMargin,\n            threshold: 0.1\n        };\n\n        this.observer = new IntersectionObserver((entries) => {\n            entries.forEach(entry => {\n                if (entry.isIntersecting) {\n                    const videoId = entry.target.dataset.videoId;\n                    if (videoId && !this.loadedVideos.has(videoId) && !this.loadingQueue.has(videoId)) {\n                        this.loadVideo(entry.target, videoId);\n                    }\n                }\n            });\n        }, loadOptions);\n\n        \/\/ Observer for unloading videos (if recycling is enabled)\n        if (this.config.enableRecycling) {\n            const unloadOptions = {\n                root: null,\n                rootMargin: this.config.unloadMargin,\n                threshold: 0\n            };\n\n            this.unloadObserver = new IntersectionObserver((entries) => {\n                entries.forEach(entry => {\n                    if (!entry.isIntersecting) {\n                        const videoId = entry.target.dataset.videoId;\n                        if (videoId && this.loadedVideos.has(videoId) && this.loadedVideos.size > this.config.maxLoadedVideos) {\n                            setTimeout(() => {\n                                if (!entry.isIntersecting && this.loadedVideos.size > this.config.maxLoadedVideos) {\n                                    this.unloadVideo(entry.target, videoId);\n                                }\n                            }, 2000);\n                        }\n                    }\n                });\n            }, unloadOptions);\n        }\n    }\n\n    async loadVideoData() {\n        try {\n            \/\/ Show loading state\n            this.gallery.innerHTML = '<div style=\"text-align: center; padding: 40px; color: #7f8c8d;\">Video\\'s worden geladen...<\/div>';\n            \n            const response = await fetch('\/wp-json\/wp\/v2\/media?per_page=100&order=desc');\n            \n            if (!response.ok) {\n                throw new Error(`HTTP error! status: ${response.status}`);\n            }\n            \n            const data = await response.json();\n            \n            \/\/ Filter and sort videos\n            this.videoData = data\n                .filter(item => {\n                    const date = new Date(item.date);\n                    const year = date.getFullYear();\n                    return year === 2024 &&\n                           item.media_type === 'file' &&\n                           item.mime_type === 'video\/mp4' &&\n                           item.source_url &&\n                           item.source_url.includes('cancunblog-');\n                })\n                .sort((a, b) => new Date(a.date) - new Date(b.date));\n\n            if (this.videoData.length === 0) {\n                this.showNoVideosMessage();\n                return;\n            }\n\n            this.createVideoPlaceholders();\n            \n        } catch (error) {\n            console.error('Elementor Video Gallery Error:', error);\n            this.showErrorMessage(error.message);\n        }\n    }\n\n    createVideoPlaceholders() {\n        \/\/ Clear existing content\n        this.gallery.innerHTML = '';\n        \n        \/\/ Create document fragment for better performance\n        const fragment = document.createDocumentFragment();\n        \n        this.videoData.forEach((item, index) => {\n            const videoContainer = this.createVideoPlaceholder(item, index);\n            fragment.appendChild(videoContainer);\n        });\n        \n        \/\/ Append all at once\n        this.gallery.appendChild(fragment);\n        \n        \/\/ Start observing all containers\n        this.gallery.querySelectorAll('.elementor-video-item').forEach(container => {\n            this.observer.observe(container);\n            if (this.unloadObserver) {\n                this.unloadObserver.observe(container);\n            }\n        });\n    }\n\n    createVideoPlaceholder(item, index) {\n        const videoContainer = document.createElement('div');\n        videoContainer.classList.add('elementor-video-item');\n        videoContainer.dataset.videoId = index;\n\n        \/\/ Create placeholder\n        const placeholder = document.createElement('div');\n        placeholder.classList.add('elementor-video-placeholder');\n        placeholder.innerHTML = `\n            <div style=\"text-align: center;\">\n                <div style=\"margin-bottom: 10px; font-weight: 600;\">\n                    ${this.config.enableClickToLoad ? 'Klik om video te laden' : 'Video wordt geladen...'}\n                <\/div>\n                <div style=\"font-size: 12px; opacity: 0.8;\">\n                    ${this.config.enableClickToLoad ? 'Of scroll om automatisch te laden' : 'Even geduld...'}\n                <\/div>\n            <\/div>\n        `;\n\n        \/\/ Add click handler if enabled\n        if (this.config.enableClickToLoad) {\n            placeholder.addEventListener('click', () => {\n                if (!this.loadedVideos.has(index.toString()) && !this.loadingQueue.has(index.toString())) {\n                    this.loadVideo(videoContainer, index.toString());\n                }\n            });\n        }\n\n        \/\/ Create metadata with Elementor styling\n        const date = new Date(item.date);\n        const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };\n        const formattedDate = date.toLocaleDateString('nl-NL', options);\n        const timeString = date.toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit' });\n\n        const metaDataBar = document.createElement('div');\n        metaDataBar.classList.add('elementor-video-meta');\n        metaDataBar.innerHTML = `\n            <div class=\"elementor-video-info\">\n                <div class=\"elementor-video-date\">${formattedDate}<\/div>\n                <div class=\"elementor-video-time\">${timeString}<\/div>\n            <\/div>\n            <div class=\"elementor-video-status\">Niet geladen<\/div>\n        `;\n\n        videoContainer.appendChild(placeholder);\n        videoContainer.appendChild(metaDataBar);\n\n        return videoContainer;\n    }\n\n    async loadVideo(container, videoId) {\n        const item = this.videoData[videoId];\n        if (!item || !item.source_url) return;\n\n        this.loadingQueue.add(videoId);\n        \n        const placeholder = container.querySelector('.elementor-video-placeholder');\n        const statusElement = container.querySelector('.elementor-video-status');\n        \n        if (!placeholder || !statusElement) return;\n\n        \/\/ Update status\n        statusElement.textContent = 'Laden...';\n        statusElement.className = 'elementor-video-status loading';\n\n        \/\/ Show loading state\n        placeholder.classList.add('loading');\n        placeholder.innerHTML = `\n            <div style=\"text-align: center;\">\n                <div style=\"margin-bottom: 10px; font-weight: 600;\">Video wordt geladen...<\/div>\n                <div style=\"font-size: 12px; opacity: 0.8;\">Even geduld...<\/div>\n            <\/div>\n        `;\n\n        try {\n            \/\/ Create video element with Elementor compatibility\n            const videoElement = document.createElement('video');\n            videoElement.setAttribute('controls', '');\n            videoElement.setAttribute('preload', 'metadata');\n            videoElement.setAttribute('playsinline', '');\n            videoElement.setAttribute('webkit-playsinline', ''); \/\/ iOS compatibility\n            \n            \/\/ Add loading and error handlers\n            const loadPromise = new Promise((resolve, reject) => {\n                const timeoutId = setTimeout(() => {\n                    reject(new Error('Video load timeout'));\n                }, 15000); \/\/ 15 second timeout\n                \n                const loadHandler = () => {\n                    clearTimeout(timeoutId);\n                    videoElement.removeEventListener('loadedmetadata', loadHandler);\n                    videoElement.removeEventListener('error', errorHandler);\n                    resolve();\n                };\n                \n                const errorHandler = (e) => {\n                    clearTimeout(timeoutId);\n                    videoElement.removeEventListener('loadedmetadata', loadHandler);\n                    videoElement.removeEventListener('error', errorHandler);\n                    reject(new Error('Video failed to load'));\n                };\n                \n                videoElement.addEventListener('loadedmetadata', loadHandler);\n                videoElement.addEventListener('error', errorHandler);\n            });\n\n            \/\/ Set source\n            videoElement.src = item.source_url;\n            \n            \/\/ Wait for video to load\n            await loadPromise;\n            \n            \/\/ Smooth transition\n            placeholder.style.opacity = '0';\n            placeholder.style.transition = 'opacity 0.4s ease';\n            \n            setTimeout(() => {\n                if (placeholder.parentNode) {\n                    container.insertBefore(videoElement, placeholder);\n                    placeholder.remove();\n                    videoElement.classList.add('elementor-fade-in');\n                }\n                \n                statusElement.textContent = 'Geladen';\n                statusElement.className = 'elementor-video-status loaded';\n            }, 400);\n\n            \/\/ Store loaded video\n            this.loadedVideos.set(videoId, {\n                element: videoElement,\n                container: container,\n                lastAccessed: Date.now()\n            });\n\n            \/\/ Stop observing for loading\n            this.observer.unobserve(container);\n            \n            \/\/ Reset retry count\n            this.retryCount.delete(videoId);\n\n        } catch (error) {\n            console.error('Elementor Video Gallery - Error loading video:', error);\n            \n            \/\/ Implement retry logic\n            const retries = this.retryCount.get(videoId) || 0;\n            if (retries < this.config.retryAttempts) {\n                this.retryCount.set(videoId, retries + 1);\n                setTimeout(() => {\n                    this.loadingQueue.delete(videoId);\n                    this.loadVideo(container, videoId);\n                }, 2000 * (retries + 1)); \/\/ Exponential backoff\n            } else {\n                this.handleVideoError(container, item.source_url);\n            }\n        } finally {\n            this.loadingQueue.delete(videoId);\n        }\n    }\n\n    unloadVideo(container, videoId) {\n        if (!this.config.enableRecycling) return;\n        \n        const videoData = this.loadedVideos.get(videoId);\n        if (!videoData) return;\n\n        const { element } = videoData;\n        \n        \/\/ Pause video\n        if (!element.paused) {\n            element.pause();\n        }\n\n        \/\/ Create new placeholder\n        const placeholder = document.createElement('div');\n        placeholder.classList.add('elementor-video-placeholder');\n        placeholder.innerHTML = `\n            <div style=\"text-align: center;\">\n                <div style=\"margin-bottom: 10px; font-weight: 600;\">Klik om video te herladen<\/div>\n                <div style=\"font-size: 12px; opacity: 0.8;\">Video werd uitgeladen om geheugen te besparen<\/div>\n            <\/div>\n        `;\n\n        \/\/ Add click handler\n        placeholder.addEventListener('click', () => {\n            if (!this.loadedVideos.has(videoId) && !this.loadingQueue.has(videoId)) {\n                this.loadVideo(container, videoId);\n            }\n        });\n\n        \/\/ Replace video\n        container.insertBefore(placeholder, element);\n        element.remove();\n\n        \/\/ Update status\n        const statusElement = container.querySelector('.elementor-video-status');\n        if (statusElement) {\n            statusElement.textContent = 'Uitgeladen';\n            statusElement.className = 'elementor-video-status';\n        }\n\n        \/\/ Remove from loaded videos\n        this.loadedVideos.delete(videoId);\n\n        \/\/ Resume observing\n        this.observer.observe(container);\n    }\n\n    handleVideoError(container, videoUrl) {\n        const placeholder = container.querySelector('.elementor-video-placeholder');\n        const statusElement = container.querySelector('.elementor-video-status');\n        \n        if (placeholder) {\n            placeholder.classList.add('error');\n            placeholder.innerHTML = `\n                <div style=\"text-align: center;\">\n                    <div style=\"margin-bottom: 10px; font-weight: 600;\">\u26a0\ufe0f Video kon niet worden geladen<\/div>\n                    <div style=\"font-size: 12px;\">\n                        <a href=\"${videoUrl}\" target=\"_blank\" style=\"color: white; text-decoration: underline;\">\n                            Direct bekijken\n                        <\/a>\n                    <\/div>\n                <\/div>\n            `;\n            placeholder.classList.remove('loading');\n        }\n\n        if (statusElement) {\n            statusElement.textContent = 'Fout';\n            statusElement.className = 'elementor-video-status error';\n        }\n    }\n\n    showNoVideosMessage() {\n        this.gallery.innerHTML = `\n            <div class=\"elementor-no-videos\">\n                <div class=\"elementor-no-videos-icon\">\ud83d\udcf9<\/div>\n                <div class=\"elementor-no-videos-title\">Geen video's gevonden<\/div>\n                <div class=\"elementor-no-videos-text\">Er zijn nog geen livestreams uit 2024 beschikbaar.<\/div>\n            <\/div>\n        `;\n    }\n\n    showErrorMessage(errorDetails = '') {\n        this.gallery.innerHTML = `\n            <div class=\"elementor-error-container\">\n                <div class=\"elementor-error-box\">\n                    <div class=\"elementor-error-icon\">\u26a0\ufe0f<\/div>\n                    <div class=\"elementor-error-title\">Fout bij laden<\/div>\n                    <div class=\"elementor-error-text\">Er is een fout opgetreden bij het laden van de video's.<\/div>\n                    ${errorDetails ? `<div style=\"font-size: 12px; opacity: 0.8; margin-bottom: 15px;\">${errorDetails}<\/div>` : ''}\n                    <button class=\"elementor-error-button\" onclick=\"location.reload()\">\n                        Pagina vernieuwen\n                    <\/button>\n                <\/div>\n            <\/div>\n        `;\n    }\n\n    \/\/ Public method to destroy the loader\n    destroy() {\n        if (this.observer) {\n            this.observer.disconnect();\n        }\n        if (this.unloadObserver) {\n            this.unloadObserver.disconnect();\n        }\n        this.loadedVideos.clear();\n        this.loadingQueue.clear();\n        this.retryCount.clear();\n    }\n}\n\n\/\/ Initialize when DOM is ready (Elementor compatible)\nfunction initElementorVideoGallery() {\n    if (document.getElementById('video-gallery')) {\n        window.elementorVideoLoader = new ElementorVideoLoader({\n            maxLoadedVideos: 6,        \/\/ Conservative for Elementor\n            loadMargin: '150px',       \/\/ Smaller margin for better UX\n            unloadMargin: '-250px',    \/\/ Unload when further away\n            enableRecycling: true,     \/\/ Enable memory management\n            enableClickToLoad: true,   \/\/ Allow manual loading\n            retryAttempts: 2           \/\/ Retry failed loads\n        });\n    }\n}\n\n\/\/ Multiple initialization methods for Elementor compatibility\nif (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', initElementorVideoGallery);\n} else {\n    initElementorVideoGallery();\n}\n\n\/\/ Elementor frontend compatibility\nif (window.elementorFrontend) {\n    window.elementorFrontend.hooks.addAction('frontend\/element_ready\/widget', initElementorVideoGallery);\n}\n\n\/\/ Cleanup on page unload\nwindow.addEventListener('beforeunload', function() {\n    if (window.elementorVideoLoader) {\n        window.elementorVideoLoader.destroy();\n    }\n});\n<\/script>\n\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1c59584 elementor-widget-divider--view-line elementor-widget elementor-widget-divider\" data-id=\"1c59584\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"divider.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-divider\">\n\t\t\t<span class=\"elementor-divider-separator\">\n\t\t\t\t\t\t<\/span>\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\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>REISVERSLAG CANCUN2024 Hier is de volledig gemonteerde video van 2024. Veel kijkplezier!! https:\/\/cancun.famlevy.nl\/wp-content\/uploads\/2025\/06\/cancun-2024-final-v2.mp4 VIDEOGALLERIJ CANCUN2024 Hier kun je onze avonturen van 2024 bekijken! Altijd leuk voor<span class=\"excerpt-hellip\"> [\u2026]<\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"open","ping_status":"closed","template":"","meta":{"rs_blank_template":"","rs_page_bg_color":"","slide_template_v7":"","footnotes":""},"class_list":["post-198","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/cancun.famlevy.nl\/en\/wp-json\/wp\/v2\/pages\/198","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cancun.famlevy.nl\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/cancun.famlevy.nl\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/cancun.famlevy.nl\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cancun.famlevy.nl\/en\/wp-json\/wp\/v2\/comments?post=198"}],"version-history":[{"count":80,"href":"https:\/\/cancun.famlevy.nl\/en\/wp-json\/wp\/v2\/pages\/198\/revisions"}],"predecessor-version":[{"id":1281,"href":"https:\/\/cancun.famlevy.nl\/en\/wp-json\/wp\/v2\/pages\/198\/revisions\/1281"}],"wp:attachment":[{"href":"https:\/\/cancun.famlevy.nl\/en\/wp-json\/wp\/v2\/media?parent=198"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}