{"id":99486,"date":"2025-11-14T10:02:58","date_gmt":"2025-11-14T09:02:58","guid":{"rendered":"https:\/\/www.light-style.eu\/mon-compte\/votre-galerie-de-selection\/"},"modified":"2026-08-28T17:51:28","modified_gmt":"2026-08-28T15:51:28","slug":"votre-galerie-de-selection","status":"publish","type":"page","link":"https:\/\/www.light-style.eu\/fr\/mon-compte\/votre-galerie-de-selection\/","title":{"rendered":"Votre galerie !"},"content":{"rendered":"\n<h3 class=\"wp-block-heading has-text-align-center has-luminous-vivid-amber-color has-text-color has-link-color wp-elements-1\"><strong>Bienvenue dans votre galerie photo personnelle ! &#x2728;<\/strong><\/h3>\n\n<p class=\"has-text-align-center wp-block-paragraph\">Ravi de vous accueillir ! Nous avons rassembl\u00e9 pour vous les plus beaux moments de votre s\u00e9ance photo. <\/p>\n\n<p class=\"has-text-align-center wp-block-paragraph\">Prenez tout votre temps, installez-vous confortablement et s\u00e9lectionnez vos clich\u00e9s pr\u00e9f\u00e9r\u00e9s.<\/p>\n\n<p class=\"has-text-align-center has-luminous-vivid-amber-color has-text-color has-link-color wp-elements-2 wp-block-paragraph\"><strong>\u00ab Les photos sont des \u0153uvres d\u2019art \u2013 on ne les appr\u00e9cie pas au format timbre-poste. Pour une profondeur d\u2019image et une brillance des couleurs optimales, nous recommandons une consultation sur tablette ou ordinateur. \u00bb<\/strong><\/p>\n\n<p class=\"has-luminous-vivid-amber-color has-text-color has-link-color wp-elements-3 wp-block-paragraph\"><strong>Bon \u00e0 savoir :<\/strong><\/p>\n\n<ul class=\"wp-block-list\">\n<li><strong>S\u00e9lectionnez ensemble :<\/strong> Vous pouvez facilement partager le lien de votre galerie avec votre famille ou vos amis, en utilisant le deuxi\u00e8me mot de passe, afin que personne ne puisse modifier vos favoris ou commander des photos en votre nom. <\/li>\n\n\n\n<li><strong>Sauvegarde automatique :<\/strong> Votre s\u00e9lection est directement enregistr\u00e9e en arri\u00e8re-plan \u2013 vous pouvez fermer la galerie \u00e0 tout moment et reprendre votre navigation plus tard.<\/li>\n\n\n\n<li><strong>Tout sous les yeux :<\/strong> Dans l\u2019aper\u00e7u, vous pouvez voir \u00e0 tout moment combien d\u2019images vous avez choisies et quelles options suppl\u00e9mentaires sont \u00e0 votre disposition.<\/li>\n<\/ul>\n\n<p class=\"has-text-align-center wp-block-paragraph\">D\u00e8s que vous avez marqu\u00e9 vos favoris, cliquez simplement en bas sur <strong>\u00ab V\u00e9rifier la s\u00e9lection &amp; envoyer \u00bb<\/strong> \u2013 nous nous occuperons ensuite de la retouche finale pour vous !<\/p>\n\n<p class=\"has-text-align-center has-luminous-vivid-amber-color has-text-color has-link-color wp-elements-4 wp-block-paragraph\"><strong>Les options suppl\u00e9mentaires facultatives, telles que la retouche (selon le type de photo), vous seront affich\u00e9es dans le processus de commande si elles sont pr\u00e9vues, et vous pourrez les y ajouter.<\/strong><\/p>\n\n<p class=\"has-text-align-center has-black-color has-cyan-bluish-gray-background-color has-text-color has-background has-link-color wp-elements-5 wp-block-paragraph\"><strong>NOTE : La s\u00e9lection et la d\u00e9s\u00e9lection d&rsquo;une photo (le petit c\u0153ur) prend 1 \u00e0 2 secondes. Par s\u00e9curit\u00e9. Le petit c\u0153ur devient rouge lorsque le serveur a confirm\u00e9 votre s\u00e9lection. Ainsi, rien n&rsquo;est perdu.   <\/strong><\/p>\n\n<p class=\"wp-block-paragraph\">\u200bViel Freude beim St\u00f6bern und Ausw\u00e4hlen! &#x1f4f8;<\/p>\n            <div class=\"ls-login-wrapper\">\n                                                <form method=\"post\" class=\"ls-login-form\" id=\"ls-login-form\">\n                    <h3 style=\"margin-top:0; text-align:center; color:#fff;\">Kunden-Login<\/h3>\n                                        <input type=\"hidden\" name=\"ls_login\" value=\"1\">\n                    <p><label>Benutzername<\/label><input type=\"text\" name=\"ls_user\" required class=\"ls-input-field\"><\/p>\n                    <p><label>Passwort<\/label><input type=\"password\" name=\"ls_pass\" required class=\"ls-input-field\"><\/p>\n                                        <p><button type=\"submit\" class=\"ls-btn\" style=\"width:100%;\">Galerie \u00f6ffnen<\/button><\/p>\n                                    <\/form>\n                <p style=\"text-align:center; margin-top:10px;\">\n                    <a href=\"#\" id=\"ls-forgot-password-toggle\" style=\"color:#c5a059; font-size:13px; text-decoration:underline;\">Passwort vergessen?<\/a>\n                <\/p>\n                <form method=\"post\" id=\"ls-forgot-password-form\" style=\"display:none; margin-top:10px; border-top:1px solid #333; padding-top:15px;\">\n                    <p style=\"font-size:12px; color:#aaa;\">Bitte gib deinen Benutzernamen ein - wir informieren dann das Studio, das sich zeitnah bei dir meldet.<\/p>\n                    <p><label>Benutzername<\/label><input type=\"text\" name=\"ls_forgot_username\" required class=\"ls-input-field\"><\/p>\n                    <p><button type=\"submit\" name=\"ls_forgot_password\" class=\"ls-btn\" style=\"width:100%; background:#444; color:#fff; text-shadow:none; box-shadow:none; border-color:#333;\">Studio benachrichtigen<\/button><\/p>\n                <\/form>\n            <\/div>\n            <script>\n                (function() {\n                    var toggle = document.getElementById('ls-forgot-password-toggle');\n                    var form = document.getElementById('ls-forgot-password-form');\n                    if (toggle && form) {\n                        toggle.addEventListener('click', function(e) {\n                            e.preventDefault();\n                            form.style.display = (form.style.display === 'none' || !form.style.display) ? 'block' : 'none';\n                        });\n                    }\n                })();\n            <\/script>\n                                <style>\n            .ls-gallery-container { font-family: sans-serif; color: #333; }\n            .ls-logout { color: #c5a059; text-decoration: none; font-weight: bold; }\n            .ls-logout:hover { text-decoration: underline; }\n            .ls-tab-btn { background: #f1f1f1; border: none; padding: 10px 18px; font-weight: bold; cursor: pointer; border-radius: 4px 4px 0 0; }\n            .ls-tab-btn.ls-tab-active { background: linear-gradient(180deg, #ecd08f 0%, #c5a059 45%, #a3803d 100%); color: #3a2c0f; text-shadow: 0 1px 0 rgba(255,255,255,0.3); }\n            .ls-login-wrapper { max-width: 400px; margin: 60px auto; background: #1a1a1a; padding: 30px; border-radius: 8px; border: 1px solid #333; color: #fff; }\n            .ls-input-field { width: 100%; padding: 10px; margin-top: 5px; background: #222; border: 1px solid #444; color: #fff; border-radius: 4px; box-sizing: border-box; }\n            \/* Metallischer Gold-Look: mehrstufiger Verlauf simuliert Lichtreflexion auf einer\n               polierten Oberfl\u00e4che, statt einer flachen Einheitsfarbe. *\/\n            .ls-btn {\n                background: linear-gradient(180deg, #f3d99a 0%, #dcb469 22%, #c5a059 45%, #a3803d 65%, #c5a059 85%, #e8c877 100%);\n                color: #3a2c0f;\n                border: 1px solid #8a6a2f;\n                padding: 12px;\n                font-weight: bold;\n                border-radius: 4px;\n                cursor: pointer;\n                text-shadow: 0 1px 0 rgba(255,255,255,0.35);\n                box-shadow: 0 2px 6px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.4);\n                transition: filter 0.15s ease, transform 0.15s ease;\n            }\n            .ls-btn:hover { filter: brightness(1.08); transform: translateY(-1px); }\n            .ls-btn:active { filter: brightness(0.95); transform: translateY(0); }\n            \n            .ls-thumb-card.ls-active-thumb {\n                border: 3px solid #c5a059 !important;\n                box-shadow: 0 0 20px 5px rgba(197, 160, 89, 1.0), inset 0 0 10px rgba(197, 160, 89, 0.7) !important;\n                transform: scale(1.08);\n                z-index: 20;\n                background-color: #fffdf0 !important;\n            }\n            .ls-thumb-card.ls-active-thumb::after {\n                content: \"\u2713 Aktiv\";\n                position: absolute;\n                top: -9px;\n                left: 50%;\n                transform: translateX(-50%);\n                background: #c5a059;\n                color: #fff;\n                font-size: 9px;\n                font-weight: bold;\n                padding: 1px 6px;\n                border-radius: 3px;\n                z-index: 25;\n                box-shadow: 0 2px 5px rgba(0,0,0,0.4);\n                white-space: nowrap;\n            }\n\n            @keyframes ls-spin {\n                to { transform: rotate(360deg); }\n            }\n\n            \/* Miniaturbild-Leiste: Standard 2 Spalten bei schmaler Breite (Tablet\/kleinere\n               Fenster), auf breiten Bildschirmen mehr Platz und 3 Spalten - dort ist neben der\n               Gro\u00dfansicht ohnehin genug Raum ungenutzt. Breite\/Spaltenzahl liegen bewusst hier\n               (statt nur inline im PHP), damit die Media Query greifen kann. *\/\n            .ls-thumbs-sidebar {\n                width: 240px;\n                display: grid;\n                grid-template-columns: repeat(2, 1fr);\n                gap: 8px;\n            }\n            @media (min-width: 1400px) {\n                .ls-thumbs-sidebar {\n                    width: 340px;\n                    grid-template-columns: repeat(3, 1fr);\n                }\n            }\n\n            .ls-gallery-container img {\n                -webkit-user-drag: none;\n                user-select: none;\n                -webkit-user-select: none;\n                -moz-user-select: none;\n                -ms-user-select: none;\n            }\n            .ls-img-wrapper { position: relative; display: inline-block; }\n            .ls-img-shield { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 5; background: transparent; }\n\n            \/* Mobile Responsiveness: Sidebar + Gro\u00dfbild untereinander statt nebeneinander,\n               Miniaturen-Raster passt sich der Breite an, Kopfzeile\/Tabs brechen sauber um. *\/\n            @media (max-width: 782px) {\n                .ls-split-layout { flex-direction: column !important; gap: 15px !important; }\n                .ls-thumbs-sidebar { width: 100% !important; max-height: 55vh !important; grid-template-columns: repeat(4, 1fr) !important; }\n                .ls-main-viewer { width: 100%; box-sizing: border-box; }\n                .ls-header { flex-wrap: wrap; gap: 10px; }\n                .ls-tab-navigation { flex-wrap: wrap; }\n                .ls-tab-btn { flex-grow: 1; text-align: center; }\n            }\n            @media (max-width: 480px) {\n                .ls-thumbs-sidebar { grid-template-columns: repeat(3, 1fr) !important; max-height: 45vh !important; }\n                .ls-login-wrapper { margin: 20px auto !important; padding: 20px !important; max-width: 92% !important; }\n            }\n        <\/style>\n        <script>\n        const ajaxUrl = 'https:\/\/www.light-style.eu\/studiosite\/wp-admin\/admin-ajax.php';\n        const lsNonce = '';\n                \/\/ Vollst\u00e4ndige, sortierte Dateiliste (unabh\u00e4ngig von der Seitenaufteilung) - wird f\u00fcr\n        \/\/ Vor\/Zur\u00fcck-Navigation, Diashow und die Endnummer-Suche gebraucht, da diese \u00fcber die\n        \/\/ gesamte Galerie hinweg funktionieren sollen, nicht nur innerhalb der aktuell\n        \/\/ geladenen Seite der Miniaturbild-Leiste.\n        const lsAllFiles = [];\n        const lsIsViewOnly = false;\n        \/\/ Aktuelle Favoriten als Set (unabh\u00e4ngig davon, ob die passende Miniaturbild-Karte\n        \/\/ gerade im DOM geladen ist) - wird von der Diashow f\u00fcr den Herz-Button-Status genutzt\n        \/\/ und bei jedem Umschalten live mitgepflegt.\n        const lsFavoritedFiles = new Set([]);\n        let lsCurrentFileIndex = 0;\n        if (lsCurrentFileIndex < 0) lsCurrentFileIndex = 0;\n\n        \/\/ \"Gerade online\"-Puls f\u00fcrs Admin-Backend: solange diese Seite ge\u00f6ffnet ist, alle\n        \/\/ 60 Sekunden einen kleinen Zeitstempel-Ping senden (siehe handle_heartbeat()).\n        function lsSendHeartbeat() {\n            if (!lsNonce) return;\n            const fd = new FormData();\n            fd.append('action', 'ls_heartbeat');\n            fd.append('nonce', lsNonce);\n            fetch(ajaxUrl, { method: 'POST', body: fd }).catch(() => {});\n        }\n        if (lsNonce) {\n            lsSendHeartbeat();\n            setInterval(lsSendHeartbeat, 60000);\n        }\n\n        \/\/ --- Seitenaufteilung der Miniaturbild-Leiste (bei sehr gro\u00dfen Galerien) ---\n        let lsCurrentThumbsPage = 1;\n        let lsThumbsShowingAll = false;\n\n        function lsGeneratePendingThumbsInView() {\n            \/\/ Sucht alle aktuell sichtbaren Platzhalter (Vorschaubild fehlt noch) und l\u00e4sst sie\n            \/\/ im Hintergrund in kleinen H\u00e4ppchen erzeugen - f\u00fcllt sich nach und nach selbst auf,\n            \/\/ ohne dass der Kunde etwas daf\u00fcr tun muss oder die Seite blockiert wird.\n            const pending = Array.from(document.querySelectorAll('.ls-thumb-pending')).slice(0, 12);\n            if (pending.length === 0) return;\n\n            const files = pending.map(el => el.getAttribute('data-file'));\n            const fd = new FormData();\n            fd.append('action', 'ls_customer_generate_pending_thumbs');\n            fd.append('nonce', lsNonce);\n            files.forEach(f => fd.append('files[]', f));\n\n            fetch(ajaxUrl, { method: 'POST', body: fd })\n                .then(r => r.json())\n                .then(resp => {\n                    if (resp.success && resp.data.results) {\n                        Object.keys(resp.data.results).forEach(file => {\n                            const url = resp.data.results[file];\n                            document.querySelectorAll('.ls-thumb-pending[data-file=\"' + CSS.escape(file) + '\"]').forEach(placeholder => {\n                                const img = document.createElement('img');\n                                img.src = url;\n                                img.loading = 'lazy';\n                                img.alt = '';\n                                img.style.cssText = 'width:100%; height:70px; object-fit:contain; display:block; border-radius:2px; background:#f0f0f0;';\n                                placeholder.replaceWith(img);\n                            });\n                        });\n                    }\n                    \/\/ Weiter in kleinen H\u00e4ppchen, solange noch Platzhalter \u00fcbrig sind.\n                    if (document.querySelectorAll('.ls-thumb-pending').length > 0) {\n                        setTimeout(lsGeneratePendingThumbsInView, 400);\n                    }\n                })\n                .catch(() => {});\n        }\n        \/\/ Kurz nach dem Laden starten, statt sofort - l\u00e4sst der Seite Zeit, zuerst ganz normal\n        \/\/ aufzubauen.\n        setTimeout(lsGeneratePendingThumbsInView, 800);\n\n        function lsLoadThumbsPage(page, showAll, afterLoad) {\n            const sidebar = document.getElementById('ls-thumbs-sidebar');\n            const pageInfo = document.getElementById('ls-thumbs-page-info');\n            const prevBtn = document.getElementById('ls-thumbs-prev-btn');\n            const nextBtn = document.getElementById('ls-thumbs-next-btn');\n            const showAllBtn = document.getElementById('ls-thumbs-show-all-btn');\n            if (!sidebar) return;\n\n            if (pageInfo) pageInfo.textContent = showAll ? 'Lade alle Fotos...' : ('Lade Seite ' + page + '...');\n\n            const fd = new FormData();\n            fd.append('action', 'ls_get_gallery_photos_page');\n            fd.append('nonce', lsNonce);\n            fd.append('page', page);\n            if (showAll) fd.append('show_all', '1');\n\n            fetch(ajaxUrl, { method: 'POST', body: fd })\n                .then(r => r.json())\n                .then(resp => {\n                    if (!resp.success) {\n                        if (pageInfo) pageInfo.textContent = 'Fehler beim Laden.';\n                        if (typeof afterLoad === 'function') afterLoad(false);\n                        return;\n                    }\n                    const d = resp.data;\n                    sidebar.innerHTML = d.html;\n                    lsCurrentThumbsPage = d.current_page;\n                    lsThumbsShowingAll = d.show_all;\n\n                    if (pageInfo) {\n                        pageInfo.textContent = d.show_all\n                            ? ('Alle ' + d.total_files + ' Fotos angezeigt')\n                            : ('Seite ' + d.current_page + ' von ' + d.total_pages + ' (' + d.total_files + ' Fotos)');\n                    }\n                    if (prevBtn) prevBtn.disabled = d.show_all || d.current_page <= 1;\n                    if (nextBtn) {\n                        nextBtn.disabled = d.show_all || d.current_page >= d.total_pages;\n                        nextBtn.style.display = d.show_all ? 'none' : 'inline-block';\n                    }\n                    if (showAllBtn) {\n                        showAllBtn.textContent = d.show_all ? '\ud83d\udcd6 Zur\u00fcck zur Seitenansicht (50 pro Seite)' : ('\ud83d\udcd6 Alle ' + d.total_files + ' Fotos auf einer Seite anzeigen');\n                    }\n                    const jumpInputEl = document.getElementById('ls-thumbs-jump-input');\n                    if (jumpInputEl && !d.show_all) jumpInputEl.value = d.current_page;\n\n                    \/\/ Neu geladene Fotos landen ganz oben in der Liste - kein aktives Foto mehr\n                    \/\/ markiert, das ist auf einer neuen Seite ohnehin nicht mehr aussagekr\u00e4ftig.\n                    setTimeout(lsGeneratePendingThumbsInView, 300);\n\n                    if (typeof afterLoad === 'function') afterLoad(true);\n                })\n                .catch(() => {\n                    if (pageInfo) pageInfo.textContent = 'Netzwerkfehler beim Laden.';\n                    if (typeof afterLoad === 'function') afterLoad(false);\n                });\n        }\n\n        document.addEventListener('DOMContentLoaded', function() {\n            const prevBtn = document.getElementById('ls-thumbs-prev-btn');\n            const nextBtn = document.getElementById('ls-thumbs-next-btn');\n            const showAllBtn = document.getElementById('ls-thumbs-show-all-btn');\n            const jumpBtn = document.getElementById('ls-thumbs-jump-btn');\n            const jumpInput = document.getElementById('ls-thumbs-jump-input');\n\n            if (prevBtn) {\n                prevBtn.addEventListener('click', function() {\n                    if (lsCurrentThumbsPage > 1) lsLoadThumbsPage(lsCurrentThumbsPage - 1, false);\n                });\n            }\n            if (nextBtn) {\n                nextBtn.addEventListener('click', function() {\n                    lsLoadThumbsPage(lsCurrentThumbsPage + 1, false);\n                });\n            }\n            if (showAllBtn) {\n                showAllBtn.addEventListener('click', function() {\n                    if (lsThumbsShowingAll) {\n                        lsLoadThumbsPage(1, false);\n                    } else {\n                        lsLoadThumbsPage(1, true);\n                    }\n                });\n            }\n            if (jumpBtn && jumpInput) {\n                function lsDoJump() {\n                    const maxPage = parseInt(jumpInput.getAttribute('max'), 10) || 1;\n                    let target = parseInt(jumpInput.value, 10) || 1;\n                    target = Math.max(1, Math.min(target, maxPage));\n                    jumpInput.value = target;\n                    lsLoadThumbsPage(target, false);\n                }\n                jumpBtn.addEventListener('click', lsDoJump);\n                jumpInput.addEventListener('keydown', function(e) {\n                    if (e.key === 'Enter') { e.preventDefault(); lsDoJump(); }\n                });\n            }\n        });\n\n        function lsSwitchTab(tabName) {\n            document.querySelectorAll('.ls-tab-btn').forEach(btn => {\n                btn.classList.remove('ls-tab-active');\n                if (btn.getAttribute('data-tab') === tabName) {\n                    btn.classList.add('ls-tab-active');\n                }\n            });\n            const sectionAll = document.getElementById('ls-tab-content-all');\n            const sectionReorder = document.getElementById('ls-tab-content-reorder');\n            if (tabName === 'all') {\n                if (sectionAll) sectionAll.style.display = 'block';\n                if (sectionReorder) sectionReorder.style.display = 'none';\n            } else if (tabName === 'reorder') {\n                if (sectionAll) sectionAll.style.display = 'none';\n                if (sectionReorder) sectionReorder.style.display = 'block';\n            }\n        }\n\n        function lsHighlightThumb(filename) {\n            document.querySelectorAll('.ls-thumb-card').forEach(card => {\n                if (card.getAttribute('data-file') === filename) {\n                    card.classList.add('ls-active-thumb');\n                } else {\n                    card.classList.remove('ls-active-thumb');\n                }\n            });\n            const fnEl = document.getElementById('ls-active-image-filename');\n            if (fnEl) fnEl.textContent = filename;\n\n            \/\/ Zuschauer-Zugang: den Wandbild-Wunsch-Mail-Link auf das gerade angesehene Foto\n            \/\/ aktualisieren, damit der Dateiname immer zum tats\u00e4chlich angezeigten Bild passt.\n            const mailtoLink = document.getElementById('ls-viewer-mailto-link');\n            if (mailtoLink) {\n                const subjectPrefix = mailtoLink.getAttribute('data-subject-prefix') || 'Wandbild-Wunsch';\n                const body = 'Hallo,\\n\\nich habe folgendes Foto gesehen und w\u00fcrde es gerne als Wandbild\/Poster bestellen:\\n\\nDateiname: ' + filename + '\\nGew\u00fcnschtes Format: \\n\\nViele Gr\u00fc\u00dfe';\n                mailtoLink.href = 'mailto:order@light-style.eu?subject=' + encodeURIComponent(subjectPrefix) + '&body=' + encodeURIComponent(body);\n            }\n        }\n\n        function lsBuildPreviewUrl(filename) {\n            const galleryEl = document.querySelector('.ls-gallery-container');\n            const galleryId = galleryEl ? galleryEl.getAttribute('data-gallery') : '';\n            return ajaxUrl + '?action=ls_serve_photo&gallery=' + encodeURIComponent(galleryId) + '&size=preview&file=' + encodeURIComponent(filename);\n        }\n\n        \/\/ Zentrale Funktion zum Anzeigen eines Fotos im Gro\u00dfbild - von Klick auf eine\n        \/\/ Miniatur, den Vor\/Zur\u00fcck-Buttons, der Endnummer-Suche und der Diashow gemeinsam\n        \/\/ genutzt, damit sich das Verhalten \u00fcberall exakt gleich anf\u00fchlt.\n        function lsLoadPhotoByFilename(filename) {\n            const idx = lsAllFiles.indexOf(filename);\n            if (idx !== -1) lsCurrentFileIndex = idx;\n\n            const s = document.getElementById('ls-active-image-status');\n            if (s) { s.textContent = '\u23f3 Wird geladen...'; s.style.color = '#fff'; s.style.display = 'block'; }\n            const d = document.getElementById('ls-image-error-diagnosis');\n            if (d) d.style.display = 'none';\n            const activeImg = document.getElementById('ls-active-image');\n            if (activeImg) activeImg.src = lsBuildPreviewUrl(filename);\n            lsHighlightThumb(filename);\n        }\n\n        \/\/ Umschalter \"Nur Favoriten durchbl\u00e4ttern\" - wirkt sowohl auf die normale Vor\/Zur\u00fcck-\n        \/\/ Navigation als auch auf die Diashow. lsCurrentFileIndex bleibt dabei immer ein Index\n        \/\/ in lsAllFiles (der vollst\u00e4ndigen Liste); zur Navigation wird bei aktivem Filter\n        \/\/ stattdessen die gefilterte Liste herangezogen und danach zur\u00fcck auf den passenden\n        \/\/ Index in lsAllFiles gemappt.\n        let lsOnlyFavoritesMode = false;\n        function lsGetActiveFileList() {\n            return lsOnlyFavoritesMode ? lsAllFiles.filter(function(f) { return lsFavoritedFiles.has(f); }) : lsAllFiles;\n        }\n        function lsGetNextIndex(fromIndex, direction) {\n            const list = lsGetActiveFileList();\n            if (list.length === 0) return fromIndex;\n            const currentFilename = lsAllFiles[fromIndex];\n            let pos = list.indexOf(currentFilename);\n            pos = (pos === -1) ? 0 : (pos + direction);\n            if (pos < 0) pos = list.length - 1;\n            if (pos >= list.length) pos = 0;\n            return lsAllFiles.indexOf(list[pos]);\n        }\n\n        \/\/ Bl\u00e4ttert im Gro\u00dfbild zum vorherigen\/n\u00e4chsten Foto der GESAMTEN Galerie (nicht nur\n        \/\/ der aktuell in der Seitenleiste geladenen Seite).\n        function lsNavigatePhoto(direction) {\n            if (lsAllFiles.length === 0) return;\n            const idx = lsGetNextIndex(lsCurrentFileIndex, direction);\n            lsCurrentFileIndex = idx;\n            lsLoadPhotoByFilename(lsAllFiles[idx]);\n        }\n\n        \/\/ Endnummer-Suche: findet ein Foto, dessen Dateiname (vor der Dateiendung) mit der\n        \/\/ eingegebenen Zahl endet - z.B. \"139\" findet \"...-139.jpg\" oder \"IMG_0139.jpg\".\n        \/\/ Reicht die Endnummer, ganz ohne den kompletten Dateinamen tippen zu m\u00fcssen.\n        function lsSearchByNumber() {\n            const input = document.getElementById('ls-photo-search-input');\n            const statusEl = document.getElementById('ls-photo-search-status');\n            if (!input) return;\n            const query = input.value.trim().replace(\/^-+\/, '');\n            if (!query) return;\n\n            const match = lsAllFiles.find(function(fn) {\n                const nameOnly = fn.replace(\/\\.[^.]+$\/, '');\n                return nameOnly === query || nameOnly.endsWith('-' + query) || nameOnly.endsWith('_' + query) || nameOnly.endsWith(query);\n            });\n\n            if (!match) {\n                if (statusEl) { statusEl.textContent = 'Kein Foto mit dieser Nummer gefunden.'; statusEl.style.color = '#c0392b'; }\n                return;\n            }\n\n            if (statusEl) { statusEl.textContent = '\u2713 Gefunden: ' + match; statusEl.style.color = '#2e7d32'; }\n            lsLoadPhotoByFilename(match);\n\n            \/\/ Falls das gefundene Foto auf einer anderen Seite der Miniaturbild-Leiste liegt,\n            \/\/ automatisch dorthin wechseln (alle anzeigen), damit es auch dort sichtbar\/markiert ist.\n            const idx = lsAllFiles.indexOf(match);\n            const perPage = 50;\n            const targetPage = Math.floor(idx \/ perPage) + 1;\n            if (typeof lsLoadThumbsPage === 'function' && targetPage !== lsCurrentThumbsPage) {\n                lsLoadThumbsPage(targetPage, false);\n            }\n\n            document.getElementById('ls-active-image').scrollIntoView({ behavior: 'smooth', block: 'center' });\n        }\n\n        \/\/ Wird ausgel\u00f6st, wenn das Gro\u00dfbild nicht laden konnte. Stellt dieselbe Anfrage per\n        \/\/ fetch() nach und zeigt den tats\u00e4chlichen Server-Status samt Antworttext direkt auf\n        \/\/ der Seite an - so l\u00e4sst sich der genaue Fehler auch ganz ohne Entwicklertools (F12)\n        \/\/ ablesen, z.B. ob eine Firewall\/Sicherheitsregel mit eigener Fehlerseite antwortet.\n        function lsImageLoadError(imgSrc) {\n            const statusEl = document.getElementById('ls-active-image-status');\n            if (statusEl) {\n                statusEl.textContent = '\u26a0\ufe0f Bild konnte nicht geladen werden.';\n                statusEl.style.color = '#c0392b';\n            }\n\n            const diagEl = document.getElementById('ls-image-error-diagnosis');\n            if (!diagEl || !imgSrc) return;\n\n            diagEl.style.display = 'block';\n            diagEl.textContent = 'Pr\u00fcfe genaue Fehlerursache...';\n\n            fetch(imgSrc, { cache: 'no-store' })\n                .then(response => {\n                    return response.text().then(bodyText => {\n                        let info = 'HTTP-Status: ' + response.status + ' ' + response.statusText + '\\n';\n                        info += 'Content-Type der Antwort: ' + (response.headers.get('content-type') || '(unbekannt)') + '\\n';\n                        info += '\\n--- Antwort vom Server (erste 2500 Zeichen) ---\\n';\n                        info += bodyText.substring(0, 2500);\n                        diagEl.textContent = info;\n                    });\n                })\n                .catch(err => {\n                    diagEl.textContent = 'Die Anfrage konnte gar nicht erst gesendet werden (Netzwerkfehler): ' + err.message;\n                });\n        }\n\n        function lsOpenLightbox(imgSrc) {\n            const lbModal = document.getElementById('ls-lightbox-modal');\n            const lbImg = document.getElementById('ls-lightbox-large-img');\n            const lbStatus = document.getElementById('ls-lightbox-status');\n            if (lbModal && lbImg && imgSrc) {\n                if (lbStatus) {\n                    lbStatus.textContent = '\u23f3 Wird geladen...';\n                    lbStatus.style.color = '#c5a059';\n                    lbStatus.style.display = 'block';\n                }\n                lbImg.src = imgSrc;\n                lbModal.style.display = 'flex';\n            }\n        }\n\n        function getSelectedServicesData() {\n            const data = {};\n            document.querySelectorAll('.ls-service-checkbox:checked').forEach(cb => {\n                const title = cb.getAttribute('data-title');\n                const scope = cb.getAttribute('data-scope');\n                if (scope === 'per_image') {\n                    const favCards = document.querySelectorAll('.ls-thumb-card');\n                    const files = [];\n                    favCards.forEach(card => {\n                        if (card.getAttribute('data-ordered') !== '1' && card.getAttribute('data-favorite') === 'true') {\n                            files.push(card.getAttribute('data-file'));\n                        }\n                    });\n                    data[title] = { files: files };\n                } else {\n                    data[title] = { global: true };\n                }\n            });\n            return data;\n        }\n\n        document.addEventListener('DOMContentLoaded', function() {\n            document.addEventListener('contextmenu', function(e) {\n                if (e.target.tagName === 'IMG' || e.target.classList.contains('ls-img-shield')) {\n                    e.preventDefault();\n                }\n            });\n            document.addEventListener('dragstart', function(e) {\n                if (e.target.tagName === 'IMG') { e.preventDefault(); }\n            });\n\n            \/\/ Zentrale Funktion zum Setzen\/Entfernen eines Favoriten - funktioniert auch f\u00fcr\n            \/\/ Fotos, deren Miniaturbild-Karte gerade NICHT geladen ist (z.B. weil eine andere\n            \/\/ Seite der Seitenaufteilung aktiv ist, oder aus der Diashow heraus). Aktualisiert\n            \/\/ eine ggf. vorhandene Karte zus\u00e4tzlich optisch mit, falls sie im DOM ist.\n            function lsToggleFavoriteByFilename(file, makeFavorite, onDone) {\n                const fd = new FormData();\n                fd.append('action', 'ls_toggle_favorite');\n                fd.append('nonce', lsNonce);\n                fd.append('file', file);\n                fd.append('fav_action', makeFavorite ? 'add' : 'remove');\n\n                fetch(ajaxUrl, { method: 'POST', body: fd })\n                .then(r => r.json())\n                .then(resp => {\n                    if (resp.success) {\n                        if (makeFavorite) { lsFavoritedFiles.add(file); } else { lsFavoritedFiles.delete(file); }\n                        const card = document.querySelector('.ls-thumb-card[data-file=\"' + CSS.escape(file) + '\"]');\n                        if (card) {\n                            const btn = card.querySelector('.ls-fav-toggle-btn');\n                            if (makeFavorite) {\n                                if (btn) { btn.style.background = '#c5a059'; btn.textContent = '\u2764\ufe0f'; }\n                                card.style.borderColor = '#c5a059';\n                                card.setAttribute('data-favorite', 'true');\n                            } else {\n                                if (btn) { btn.style.background = 'rgba(0,0,0,0.6)'; btn.textContent = '\ud83e\udd0d'; }\n                                card.style.borderColor = '#ddd';\n                                card.setAttribute('data-favorite', 'false');\n                            }\n                        }\n                        const countEl = document.getElementById('ls-fav-selection-count');\n                        if (countEl) countEl.textContent = resp.data.count;\n                        const grandTotalEl = document.getElementById('ls-live-grand-total');\n                        if (grandTotalEl && resp.data.grand_total !== undefined) {\n                            grandTotalEl.textContent = new Intl.NumberFormat('de-DE', {minimumFractionDigits:2, maximumFractionDigits:2}).format(resp.data.grand_total) + ' \u20ac';\n                        }\n                    }\n                    if (typeof onDone === 'function') onDone(resp.success);\n                })\n                .catch(() => { if (typeof onDone === 'function') onDone(false); });\n            }\n\n            \/\/ Event-Delegation statt direkter Bindung an jeden einzelnen Button - so\n            \/\/ funktioniert der Favoriten-Button auch f\u00fcr Foto-Karten, die erst sp\u00e4ter per\n            \/\/ Seitenaufteilung (Paginierung) nachgeladen werden, ohne die Bindung erneut\n            \/\/ ausf\u00fchren zu m\u00fcssen.\n            document.addEventListener('click', function(e) {\n                const btn = e.target.closest('.ls-fav-toggle-btn');\n                if (!btn) return;\n                e.stopPropagation();\n                const file = btn.getAttribute('data-file');\n                const card = btn.closest('.ls-thumb-card');\n                const isCurrentlyFav = card.getAttribute('data-favorite') === 'true';\n                lsToggleFavoriteByFilename(file, !isCurrentlyFav);\n            });\n\n            const wandbildToggleBtn = document.getElementById('ls-wandbild-toggle-btn');\n            const wandbildPanel = document.getElementById('ls-wandbild-panel');\n            if (wandbildToggleBtn && wandbildPanel) {\n                wandbildToggleBtn.addEventListener('click', function() {\n                    const isOpening = (wandbildPanel.style.display === 'none' || !wandbildPanel.style.display);\n                    wandbildPanel.style.display = isOpening ? 'block' : 'none';\n                    if (isOpening) {\n                        \/\/ Automatisch zum gerade ge\u00f6ffneten Bereich scrollen - sonst geht er\n                        \/\/ leicht unter, da er weiter unten auf der Seite aufklappt.\n                        setTimeout(function() {\n                            wandbildPanel.scrollIntoView({ behavior: 'smooth', block: 'center' });\n                            \/\/ Kurzes Aufblitzen des Rahmens lenkt den Blick zus\u00e4tzlich dorthin.\n                            wandbildPanel.style.transition = 'box-shadow 0.4s ease';\n                            wandbildPanel.style.boxShadow = '0 0 0 3px rgba(197,160,89,0.6)';\n                            setTimeout(function() { wandbildPanel.style.boxShadow = ''; }, 900);\n                        }, 50);\n                    }\n                });\n            }\n\n            document.querySelectorAll('.ls-wandbild-select').forEach(function(sel) {\n                sel.addEventListener('change', function() {\n                    const file = sel.getAttribute('data-file');\n                    const row = sel.closest('.ls-wandbild-row');\n                    const statusEl = row ? row.querySelector('.ls-wandbild-row-status') : null;\n                    const hasSelection = (sel.value !== '');\n                    if (statusEl) { statusEl.textContent = '\u23f3 ...'; statusEl.style.color = '#888'; }\n\n                    const fd = new FormData();\n                    fd.append('action', 'ls_toggle_wandbild_selection');\n                    fd.append('nonce', lsNonce);\n                    fd.append('file', file);\n                    fd.append('option', sel.value);\n\n                    fetch(ajaxUrl, { method: 'POST', body: fd })\n                    .then(r => r.json())\n                    .then(resp => {\n                        if (resp.success) {\n                            if (statusEl) { statusEl.textContent = hasSelection ? '\u2713 Ausgew\u00e4hlt' : ''; statusEl.style.color = '#e8c877'; }\n                            if (row) {\n                                row.style.borderColor = hasSelection ? '#c5a059' : '#2a2a2a';\n                                row.style.background = hasSelection ? 'rgba(197,160,89,0.08)' : 'rgba(255,255,255,0.02)';\n                            }\n                            const grandTotalEl = document.getElementById('ls-live-grand-total');\n                            if (grandTotalEl && resp.data.grand_total !== undefined) {\n                                grandTotalEl.textContent = new Intl.NumberFormat('de-DE', {minimumFractionDigits:2, maximumFractionDigits:2}).format(resp.data.grand_total) + ' \u20ac';\n                            }\n                        } else {\n                            if (statusEl) { statusEl.textContent = '\u26a0 Fehler'; statusEl.style.color = '#a00'; }\n                        }\n                    })\n                    .catch(() => {\n                        if (statusEl) { statusEl.textContent = '\u26a0 Fehler'; statusEl.style.color = '#a00'; }\n                    });\n                });\n            });\n\n            function lsFormatEta(seconds) {\n                if (!isFinite(seconds) || seconds <= 0) return '';\n                if (seconds < 60) return Math.ceil(seconds) + ' Sek.';\n                return Math.ceil(seconds \/ 60) + ' Min.';\n            }\n\n            function lsSetupChunkedZipDownload(btn, statusDiv, ajaxAction) {\n                if (!btn || !statusDiv) return;\n                btn.addEventListener('click', function() {\n                    btn.disabled = true;\n                    const originalBtnText = btn.textContent;\n                    btn.textContent = '\u23f3 Wird vorbereitet...';\n                    const startTime = Date.now();\n\n                    function runZipChunk(offset) {\n                        const fd = new FormData();\n                        fd.append('action', ajaxAction);\n                        fd.append('nonce', lsNonce);\n                        fd.append('offset', offset);\n\n                        fetch(ajaxUrl, { method: 'POST', body: fd })\n                        .then(r => r.json())\n                        .then(resp => {\n                            if (!resp.success) {\n                                btn.disabled = false;\n                                btn.textContent = originalBtnText;\n                                statusDiv.innerHTML = '<span style=\"color:#a00;\">Fehler: ' + (resp.data || 'Unbekannter Fehler') + '<\/span>';\n                                return;\n                            }\n                            const d = resp.data;\n                            const pct = d.total > 0 ? Math.round((d.processed \/ d.total) * 100) : 100;\n                            const elapsedSec = (Date.now() - startTime) \/ 1000;\n                            const rate = elapsedSec > 0 ? (d.processed \/ elapsedSec) : 0;\n                            const remaining = Math.max(0, d.total - d.processed);\n                            const etaSec = rate > 0 ? (remaining \/ rate) : 0;\n                            const etaText = lsFormatEta(etaSec);\n\n                            statusDiv.innerHTML = '<span style=\"color:#ccc;\">\ud83d\udce6 ' + d.processed + ' von ' + d.total + ' Fotos gepackt (' + pct + '%)' + (etaText ? ' - noch ca. ' + etaText : '') + '...<\/span>';\n\n                            if (d.has_more) {\n                                runZipChunk(d.next_offset);\n                            } else {\n                                btn.disabled = false;\n                                btn.textContent = originalBtnText;\n                                statusDiv.innerHTML = '<span style=\"color:#2e7d32;\">\u2714 Fertig! Download startet...<\/span>';\n                                window.location.href = d.download_url;\n                            }\n                        })\n                        .catch(() => {\n                            btn.disabled = false;\n                            btn.textContent = originalBtnText;\n                            statusDiv.innerHTML = '<span style=\"color:#a00;\">Netzwerkfehler beim Packen des Archivs.<\/span>';\n                        });\n                    }\n\n                    runZipChunk(0);\n                });\n            }\n\n            \/\/ --- Endnummer-Suche ---\n            const onlyFavToggle = document.getElementById('ls-only-favorites-toggle');\n            if (onlyFavToggle) {\n                onlyFavToggle.addEventListener('change', function() {\n                    lsOnlyFavoritesMode = onlyFavToggle.checked;\n                });\n            }\n\n            \/\/ Eigener, von \"Nur Favoriten durchbl\u00e4ttern\" (Navigation\/Diashow) getrennter\n            \/\/ Schalter: blendet Nicht-Favoriten direkt im Thumbnail-Raster aus, wirkt sich NICHT\n            \/\/ auf Vor\/Zur\u00fcck-Navigation oder die Diashow aus. Nutzt das bereits vorhandene\n            \/\/ data-favorite-Attribut jeder Karte (siehe render_customer_thumb_card()).\n            function lsApplyFavoritesTileFilter(showOnlyFav) {\n                document.querySelectorAll('#ls-thumbs-sidebar .ls-thumb-card').forEach(function(card) {\n                    if (!showOnlyFav) {\n                        card.style.display = card.getAttribute('data-ordered') === '1' && !false ? 'none' : 'block';\n                        return;\n                    }\n                    card.style.display = card.getAttribute('data-favorite') === 'true' ? 'block' : 'none';\n                });\n            }\n\n            const thumbsOnlyFavToggle = document.getElementById('ls-thumbs-only-favorites-toggle');\n            const thumbsFavLoadingEl = document.getElementById('ls-thumbs-fav-loading');\n            if (thumbsOnlyFavToggle) {\n                thumbsOnlyFavToggle.addEventListener('change', function() {\n                    const showOnlyFav = thumbsOnlyFavToggle.checked;\n\n                    \/\/ Bei seitenweise aufgeteilten gro\u00dfen Galerien liegen nicht alle Favoriten\n                    \/\/ zwangsl\u00e4ufig auf der aktuell geladenen Seite - deshalb bei Aktivierung\n                    \/\/ zuerst alle Seiten nachladen (nutzt denselben Mechanismus wie der\n                    \/\/ \"Alle Fotos auf einer Seite anzeigen\"-Button), erst danach filtern. War\n                    \/\/ die Seite schon vollst\u00e4ndig geladen, entf\u00e4llt dieser Zwischenschritt.\n                    if (showOnlyFav && !lsThumbsShowingAll) {\n                        \/\/ Deutlich sichtbarer Lade-Hinweis, w\u00e4hrend im Hintergrund alle Seiten\n                        \/\/ nachgeladen werden - bei sehr gro\u00dfen Galerien (z.B. 1000+ Hochzeitsfotos)\n                        \/\/ kann das sp\u00fcrbar dauern, ohne diesen Hinweis k\u00f6nnte es wie ein H\u00e4nger wirken.\n                        if (thumbsFavLoadingEl) thumbsFavLoadingEl.style.display = 'inline-block';\n                        thumbsOnlyFavToggle.disabled = true;\n\n                        lsLoadThumbsPage(1, true, function(success) {\n                            if (thumbsFavLoadingEl) thumbsFavLoadingEl.style.display = 'none';\n                            thumbsOnlyFavToggle.disabled = false;\n                            if (success) {\n                                lsApplyFavoritesTileFilter(true);\n                            } else {\n                                thumbsOnlyFavToggle.checked = false;\n                            }\n                        });\n                        return;\n                    }\n\n                    lsApplyFavoritesTileFilter(showOnlyFav);\n                });\n            }\n\n            const searchBtn = document.getElementById('ls-photo-search-btn');\n            const searchInput = document.getElementById('ls-photo-search-input');\n            if (searchBtn) searchBtn.addEventListener('click', lsSearchByNumber);\n            if (searchInput) {\n                searchInput.addEventListener('keydown', function(e) {\n                    if (e.key === 'Enter') { e.preventDefault(); lsSearchByNumber(); }\n                });\n            }\n\n            \/\/ --- Diashow: gro\u00dfformatig, mit sanftem \u00dcberblenden (kein Springen zwischen\n            \/\/ Hoch-\/Querformat, da der Rahmen eine feste Gr\u00f6\u00dfe hat und nur das Bild darin per\n            \/\/ object-fit:contain skaliert wird) ---\n            (function() {\n                const modal = document.getElementById('ls-slideshow-modal');\n                const imgA = document.getElementById('ls-slideshow-img-a');\n                const imgB = document.getElementById('ls-slideshow-img-b');\n                const filenameEl = document.getElementById('ls-slideshow-filename');\n                const favBtn = document.getElementById('ls-slideshow-fav-btn');\n                const startBtn = document.getElementById('ls-slideshow-start-btn');\n                const prevBtn = document.getElementById('ls-slideshow-prev-btn');\n                const nextBtn = document.getElementById('ls-slideshow-next-btn');\n                const playPauseBtn = document.getElementById('ls-slideshow-playpause-btn');\n                const closeBtn = document.getElementById('ls-slideshow-close-btn');\n                if (!modal || !imgA || !imgB || !startBtn) return;\n\n                let activeImg = imgA;\n                let inactiveImg = imgB;\n                let slideIndex = 0;\n                let autoTimer = null;\n                let isPlaying = true;\n                const AUTO_ADVANCE_MS = 5000;\n\n                function updateFavButton() {\n                    if (!favBtn) return;\n                    const isFav = lsFavoritedFiles.has(lsAllFiles[slideIndex]);\n                    const iconEl = document.getElementById('ls-slideshow-fav-icon');\n                    if (iconEl) iconEl.textContent = isFav ? '\u2764\ufe0f' : '\ud83e\udd0d';\n                    favBtn.style.background = isFav ? 'rgba(197,160,89,0.25)' : 'rgba(255,255,255,0.08)';\n                    favBtn.style.borderColor = isFav ? '#c5a059' : '#555';\n                }\n\n                function showSlide(index) {\n                    if (lsAllFiles.length === 0) return;\n                    if (index < 0) index = lsAllFiles.length - 1;\n                    if (index >= lsAllFiles.length) index = 0;\n                    slideIndex = index;\n                    lsCurrentFileIndex = index;\n                    const filename = lsAllFiles[index];\n\n                    inactiveImg.onload = function() {\n                        activeImg.style.opacity = '0';\n                        inactiveImg.style.opacity = '1';\n                        const tmp = activeImg;\n                        activeImg = inactiveImg;\n                        inactiveImg = tmp;\n                    };\n                    inactiveImg.src = lsBuildPreviewUrl(filename);\n                    if (filenameEl) filenameEl.textContent = filename;\n                    updateFavButton();\n                    lsHighlightThumb(filename);\n                    const mainImg = document.getElementById('ls-active-image');\n                    if (mainImg) mainImg.src = lsBuildPreviewUrl(filename);\n                }\n\n                function startAutoAdvance() {\n                    stopAutoAdvance();\n                    autoTimer = setInterval(function() { showSlide(lsGetNextIndex(slideIndex, 1)); }, AUTO_ADVANCE_MS);\n                }\n                function stopAutoAdvance() {\n                    if (autoTimer) { clearInterval(autoTimer); autoTimer = null; }\n                }\n                function restartAutoAdvanceIfPlaying() {\n                    if (isPlaying) startAutoAdvance();\n                }\n\n                function openSlideshow() {\n                    modal.style.display = 'flex';\n                    document.body.style.overflow = 'hidden';\n                    isPlaying = true;\n                    if (playPauseBtn) playPauseBtn.textContent = '\u23f8 Pause';\n                    slideIndex = lsCurrentFileIndex;\n                    \/\/ Falls \"Nur Favoriten\" aktiv ist, aber das gerade angezeigte Foto kein\n                    \/\/ Favorit ist, gleich zum n\u00e4chsten passenden Foto springen.\n                    if (lsOnlyFavoritesMode && !lsFavoritedFiles.has(lsAllFiles[slideIndex])) {\n                        slideIndex = lsGetNextIndex(slideIndex, 1);\n                    }\n                    const filename = lsAllFiles[slideIndex];\n                    activeImg.style.opacity = '0';\n                    inactiveImg.style.opacity = '0';\n                    activeImg.onload = function() { activeImg.style.opacity = '1'; };\n                    activeImg.src = lsBuildPreviewUrl(filename);\n                    if (filenameEl) filenameEl.textContent = filename;\n                    updateFavButton();\n                    startAutoAdvance();\n                }\n\n                function closeSlideshow() {\n                    modal.style.display = 'none';\n                    document.body.style.overflow = '';\n                    stopAutoAdvance();\n                }\n\n                startBtn.addEventListener('click', openSlideshow);\n                if (closeBtn) closeBtn.addEventListener('click', closeSlideshow);\n                if (prevBtn) prevBtn.addEventListener('click', function() { stopAutoAdvance(); showSlide(lsGetNextIndex(slideIndex, -1)); restartAutoAdvanceIfPlaying(); });\n                if (nextBtn) nextBtn.addEventListener('click', function() { stopAutoAdvance(); showSlide(lsGetNextIndex(slideIndex, 1)); restartAutoAdvanceIfPlaying(); });\n                if (playPauseBtn) {\n                    playPauseBtn.addEventListener('click', function() {\n                        isPlaying = !isPlaying;\n                        playPauseBtn.textContent = isPlaying ? '\u23f8 Pause' : '\u25b6 Weiter';\n                        if (isPlaying) startAutoAdvance(); else stopAutoAdvance();\n                    });\n                }\n                if (favBtn) {\n                    favBtn.addEventListener('click', function() {\n                        const filename = lsAllFiles[slideIndex];\n                        const makeFav = !lsFavoritedFiles.has(filename);\n                        lsToggleFavoriteByFilename(filename, makeFav, function() { updateFavButton(); });\n                    });\n                }\n\n                document.addEventListener('keydown', function(e) {\n                    if (modal.style.display !== 'flex') return;\n                    if (e.key === 'Escape') closeSlideshow();\n                    if (e.key === 'ArrowLeft') { stopAutoAdvance(); showSlide(lsGetNextIndex(slideIndex, -1)); restartAutoAdvanceIfPlaying(); }\n                    if (e.key === 'ArrowRight') { stopAutoAdvance(); showSlide(lsGetNextIndex(slideIndex, 1)); restartAutoAdvanceIfPlaying(); }\n                });\n            })();\n\n            lsSetupChunkedZipDownload(document.getElementById('ls-download-zip-btn'), document.getElementById('ls-download-status'), 'ls_prepare_download_zip_chunk');\n            lsSetupChunkedZipDownload(document.getElementById('ls-download-favorites-zip-btn'), document.getElementById('ls-download-favorites-status'), 'ls_prepare_favorites_zip_chunk');\n\n            \/\/ Zuschauer-Zugang: das gerade aktive\/angeklickte Foto einzeln herunterladen (nicht\n            \/\/ per AJAX, sondern ein echter Datei-Download \u00fcber den serverseitigen Endpunkt).\n            const downloadSingleBtn = document.getElementById('ls-download-single-photo-btn');\n            if (downloadSingleBtn) {\n                downloadSingleBtn.addEventListener('click', function() {\n                    const filenameEl = document.getElementById('ls-active-image-filename');\n                    const file = filenameEl ? filenameEl.textContent.trim() : '';\n                    const statusEl = document.getElementById('ls-download-single-status');\n                    if (!file) {\n                        if (statusEl) statusEl.textContent = 'Kein Foto ausgew\u00e4hlt.';\n                        return;\n                    }\n                    const galleryEl = document.querySelector('.ls-gallery-container');\n                    const galleryId = galleryEl ? galleryEl.getAttribute('data-gallery') : '';\n                    const url = ajaxUrl + '?action=ls_download_single_photo&gallery=' + encodeURIComponent(galleryId) + '&file=' + encodeURIComponent(file);\n                    if (statusEl) statusEl.textContent = '\u2714 Download gestartet...';\n                    window.location.href = url;\n                });\n            }\n\n            const submitFirstBtn = document.getElementById('ls-submit-first-order-btn');\n            const summaryModal = document.getElementById('ls-checkout-summary-modal');\n            const confirmOrderBtn = document.getElementById('ls-confirm-order-btn');\n            const submitReorderBtn = document.getElementById('ls-submit-reorder-btn');\n            const fileDropdown = document.getElementById('ls-checkout-file-dropdown');\n            const thumbPreview = document.getElementById('ls-checkout-thumb-preview');\n            const shippingCheckbox = document.getElementById('ls-shipping-checkbox');\n            const agbCheckbox = document.getElementById('ls-agb-checkbox');\n\n            if (fileDropdown && thumbPreview) {\n                fileDropdown.addEventListener('change', function() {\n                    const baseUrl = fileDropdown.dataset.baseUrl || '';\n                    if (fileDropdown.value) {\n                        thumbPreview.src = baseUrl + encodeURIComponent(fileDropdown.value);\n                    }\n                });\n            }\n\n            function fetchAndUpdateCheckoutSummary() {\n                const shippingChecked = shippingCheckbox?.checked || false;\n                const servicesData = getSelectedServicesData();\n\n                const fd = new FormData();\n                fd.append('action', 'ls_calculate_checkout_summary');\n                fd.append('nonce', lsNonce);\n                fd.append('shipping_selected', shippingChecked ? '1' : '0');\n                fd.append('services_data_json', JSON.stringify(servicesData));\n\n                return fetch(ajaxUrl, { method: 'POST', body: fd })\n                .then(r => r.json())\n                .then(resp => {\n                    if (resp.success) {\n                        const data = resp.data;\n                        const tbody = document.getElementById('ls-summary-tbody');\n                        tbody.innerHTML = '';\n\n                        if (fileDropdown) {\n                            fileDropdown.innerHTML = '';\n                            fileDropdown.dataset.baseUrl = data.upload_base_url;\n                            if (data.selected_files && data.selected_files.length > 0) {\n                                data.selected_files.forEach((file, idx) => {\n                                    const opt = document.createElement('option');\n                                    opt.value = file;\n                                    opt.textContent = (idx + 1) + '. ' + file;\n                                    fileDropdown.appendChild(opt);\n                                });\n                                if (thumbPreview) {\n                                    thumbPreview.src = data.upload_base_url + encodeURIComponent(data.selected_files[0]);\n                                }\n                            } else {\n                                const opt = document.createElement('option');\n                                opt.textContent = 'Keine Fotos ausgew\u00e4hlt';\n                                fileDropdown.appendChild(opt);\n                                if (thumbPreview) thumbPreview.src = '';\n                            }\n                        }\n\n                        data.items.forEach(item => {\n                            const tr = document.createElement('tr');\n                            tr.style.borderBottom = '1px solid #333';\n                            tr.innerHTML = `\n                                <td style=\"padding:6px;\">${item.title}<\/td>\n                                <td style=\"padding:6px; text-align:center;\">${item.qty}<\/td>\n                                <td style=\"padding:6px; text-align:right;\">${new Intl.NumberFormat('de-DE', {minimumFractionDigits:2, maximumFractionDigits:2}).format(item.unit_price)} \u20ac<\/td>\n                                <td style=\"padding:6px; text-align:right;\">${new Intl.NumberFormat('de-DE', {minimumFractionDigits:2, maximumFractionDigits:2}).format(item.total)} \u20ac<\/td>\n                            `;\n                            tbody.appendChild(tr);\n                        });\n\n                        const totalsBox = document.getElementById('ls-summary-totals-box');\n                        const vatText = data.is_business ? `zzgl. ${data.vat_rate}% MwSt.` : `inkl. MwSt. (${data.vat_rate}%)`;\n                        totalsBox.innerHTML = `\n                            Netto-Betrag: <strong>${new Intl.NumberFormat('de-DE', {minimumFractionDigits:2, maximumFractionDigits:2}).format(data.netto_total)} \u20ac<\/strong><br>\n                            ${vatText}: <strong>${new Intl.NumberFormat('de-DE', {minimumFractionDigits:2, maximumFractionDigits:2}).format(data.vat_amount)} \u20ac<\/strong><br>\n                            Gesamtbetrag (Brutto): <strong>${new Intl.NumberFormat('de-DE', {minimumFractionDigits:2, maximumFractionDigits:2}).format(data.grand_total)} \u20ac<\/strong>\n                        `;\n                    }\n                });\n            }\n\n            if (submitFirstBtn && summaryModal) {\n                submitFirstBtn.addEventListener('click', function() {\n                    const count = parseInt(document.getElementById('ls-fav-selection-count').textContent || '0');\n                    if (count <= 0) {\n                        alert('Du hast noch keine Favoriten (\u2764\ufe0f) ausgew\u00e4hlt!');\n                        return;\n                    }\n\n                    submitFirstBtn.disabled = true;\n                    const originalText = submitFirstBtn.textContent;\n                    submitFirstBtn.textContent = '\u23f3 Wird berechnet...';\n\n                    fetchAndUpdateCheckoutSummary().then(() => {\n                        submitFirstBtn.disabled = false;\n                        submitFirstBtn.textContent = originalText;\n                        summaryModal.style.display = 'flex';\n                    });\n                });\n            }\n\n            if (shippingCheckbox) {\n                shippingCheckbox.addEventListener('change', fetchAndUpdateCheckoutSummary);\n            }\n            document.querySelectorAll('.ls-service-checkbox').forEach(cb => {\n                cb.addEventListener('change', fetchAndUpdateCheckoutSummary);\n            });\n\n            if (confirmOrderBtn) {\n                confirmOrderBtn.addEventListener('click', function() {\n                    const agbChecked = agbCheckbox?.checked || false;\n                    const statusDiv = document.getElementById('ls-order-submit-status');\n\n                    if (!agbChecked) {\n                        statusDiv.innerHTML = '<span style=\"color:#a00;\">Bitte akzeptiere die AGB.<\/span>';\n                        return;\n                    }\n\n                    confirmOrderBtn.disabled = true;\n                    confirmOrderBtn.textContent = '\u23f3 Wird \u00fcbermittelt...';\n\n                    const shippingChecked = shippingCheckbox?.checked || false;\n                    const servicesData = getSelectedServicesData();\n                    const selectedRating = document.querySelector('input[name=\"ls_customer_rating\"]:checked')?.value || '';\n                    const feedbackText = document.getElementById('ls-customer-feedback')?.value || '';\n\n                    const fd = new FormData();\n                    fd.append('action', 'ls_submit_order');\n                    fd.append('nonce', lsNonce);\n                    fd.append('agb_accepted', '1');\n                    fd.append('shipping_selected', shippingChecked ? '1' : '0');\n                    fd.append('services_data_json', JSON.stringify(servicesData));\n                    fd.append('rating', selectedRating);\n                    fd.append('feedback', feedbackText);\n\n                    fetch(ajaxUrl, { method: 'POST', body: fd })\n                    .then(r => r.json())\n                    .then(resp => {\n                        confirmOrderBtn.disabled = false;\n                        confirmOrderBtn.textContent = 'Jetzt verbindlich bestellen';\n                        if (resp.success) {\n                            statusDiv.innerHTML = '<span style=\"color:#2e7d32;\">\u2714 ' + resp.data + '<\/span>';\n                            setTimeout(() => { location.reload(); }, 2000);\n                        } else {\n                            statusDiv.innerHTML = '<span style=\"color:#a00;\">Fehler: ' + (resp.data || 'Unbekannter Fehler') + '<\/span>';\n                        }\n                    });\n                });\n            }\n\n            \/\/ ---- Nachbestell-Warenkorb (Checkout \u00e4hnlich der Erstbestellung, ohne Smileys) ----\n            const reorderCart = [];\n            const addReorderItemBtn = document.getElementById('ls-add-reorder-item-btn');\n            const reorderCartWrapper = document.getElementById('ls-reorder-cart-wrapper');\n            const reorderCartTbody = document.getElementById('ls-reorder-cart-tbody');\n            const reorderCartSubtotalEl = document.getElementById('ls-reorder-cart-subtotal');\n            const reorderSummaryModal = document.getElementById('ls-reorder-summary-modal');\n            const reorderShippingCheckbox = document.getElementById('ls-reorder-shipping-checkbox');\n            const reorderAgbCheckbox = document.getElementById('ls-reorder-agb-checkbox');\n            const confirmReorderBtn = document.getElementById('ls-confirm-reorder-btn');\n            const reorderFileSelect = document.getElementById('ls-reorder-file-select');\n            const reorderFileThumb = document.getElementById('ls-reorder-file-thumb');\n\n            if (reorderFileSelect && reorderFileThumb) {\n                reorderFileSelect.addEventListener('change', function() {\n                    const baseUrl = reorderFileSelect.dataset.baseUrl || '';\n                    if (reorderFileSelect.value) {\n                        reorderFileThumb.src = baseUrl + encodeURIComponent(reorderFileSelect.value);\n                    }\n                });\n            }\n\n            function formatEuro(val) {\n                return new Intl.NumberFormat('de-DE', {minimumFractionDigits:2, maximumFractionDigits:2}).format(val) + ' \u20ac';\n            }\n\n            function renderReorderCart() {\n                if (!reorderCartTbody) return;\n                reorderCartTbody.innerHTML = '';\n                let subtotal = 0;\n\n                reorderCart.forEach((item, idx) => {\n                    const lineTotal = item.price * item.qty;\n                    subtotal += lineTotal;\n                    const tr = document.createElement('tr');\n                    tr.style.borderBottom = '1px solid #333';\n                    tr.innerHTML = `\n                        <td style=\"padding:6px;\">${item.file}<\/td>\n                        <td style=\"padding:6px;\">${item.option}<\/td>\n                        <td style=\"padding:6px; text-align:center;\">${item.qty}<\/td>\n                        <td style=\"padding:6px; text-align:right;\">${formatEuro(lineTotal)}<\/td>\n                        <td style=\"padding:6px; text-align:right;\"><button type=\"button\" class=\"ls-reorder-remove-item\" data-idx=\"${idx}\" style=\"background:#444; color:#fff; border:none; border-radius:4px; padding:4px 8px; cursor:pointer;\">Entfernen<\/button><\/td>\n                    `;\n                    reorderCartTbody.appendChild(tr);\n                });\n\n                if (reorderCartSubtotalEl) reorderCartSubtotalEl.textContent = formatEuro(subtotal);\n                if (reorderCartWrapper) reorderCartWrapper.style.display = reorderCart.length > 0 ? 'block' : 'none';\n            }\n\n            if (addReorderItemBtn) {\n                addReorderItemBtn.addEventListener('click', function() {\n                    const fileSelect = document.getElementById('ls-reorder-file-select');\n                    const itemSelect = document.getElementById('ls-reorder-item-select');\n                    const qtyInput = document.getElementById('ls-reorder-qty');\n                    if (!fileSelect || !itemSelect || !qtyInput) return;\n\n                    const selectedFile = fileSelect.value;\n                    const selectedOption = itemSelect.value;\n                    const selectedPrice = parseFloat(itemSelect.options[itemSelect.selectedIndex].getAttribute('data-price')) || 0;\n                    const qty = parseInt(qtyInput.value, 10) || 1;\n\n                    reorderCart.push({ file: selectedFile, option: selectedOption, price: selectedPrice, qty: qty });\n                    renderReorderCart();\n                    qtyInput.value = '1';\n                });\n            }\n\n            if (reorderCartTbody) {\n                reorderCartTbody.addEventListener('click', function(e) {\n                    const btn = e.target.closest('.ls-reorder-remove-item');\n                    if (!btn) return;\n                    const idx = parseInt(btn.getAttribute('data-idx'));\n                    reorderCart.splice(idx, 1);\n                    renderReorderCart();\n                });\n            }\n\n            function fetchAndUpdateReorderSummary() {\n                const shippingChecked = reorderShippingCheckbox?.checked || false;\n\n                const fd = new FormData();\n                fd.append('action', 'ls_calculate_reorder_summary');\n                fd.append('nonce', lsNonce);\n                fd.append('shipping_selected', shippingChecked ? '1' : '0');\n                fd.append('cart_items_json', JSON.stringify(reorderCart));\n\n                return fetch(ajaxUrl, { method: 'POST', body: fd })\n                .then(r => r.json())\n                .then(resp => {\n                    if (resp.success) {\n                        const data = resp.data;\n                        const tbody = document.getElementById('ls-reorder-summary-tbody');\n                        tbody.innerHTML = '';\n\n                        data.items.forEach(item => {\n                            const tr = document.createElement('tr');\n                            tr.style.borderBottom = '1px solid #333';\n                            tr.innerHTML = `\n                                <td style=\"padding:6px;\">${item.title}<\/td>\n                                <td style=\"padding:6px; text-align:center;\">${item.qty}<\/td>\n                                <td style=\"padding:6px; text-align:right;\">${formatEuro(item.unit_price)}<\/td>\n                                <td style=\"padding:6px; text-align:right;\">${formatEuro(item.total)}<\/td>\n                            `;\n                            tbody.appendChild(tr);\n                        });\n\n                        const totalsBox = document.getElementById('ls-reorder-summary-totals-box');\n                        const vatText = data.is_business ? `zzgl. ${data.vat_rate}% MwSt.` : `inkl. MwSt. (${data.vat_rate}%)`;\n                        totalsBox.innerHTML = `\n                            Netto-Betrag: <strong>${formatEuro(data.netto_total)}<\/strong><br>\n                            ${vatText}: <strong>${formatEuro(data.vat_amount)}<\/strong><br>\n                            Gesamtbetrag (Brutto): <strong>${formatEuro(data.grand_total)}<\/strong>\n                        `;\n                    }\n                });\n            }\n\n            if (submitReorderBtn && reorderSummaryModal) {\n                submitReorderBtn.addEventListener('click', function() {\n                    if (reorderCart.length === 0) {\n                        alert('Bitte f\u00fcge zuerst mindestens eine Position zu deinem Warenkorb hinzu.');\n                        return;\n                    }\n\n                    submitReorderBtn.disabled = true;\n                    const originalText = submitReorderBtn.textContent;\n                    submitReorderBtn.textContent = 'Wird berechnet...';\n\n                    fetchAndUpdateReorderSummary().then(() => {\n                        submitReorderBtn.disabled = false;\n                        submitReorderBtn.textContent = originalText;\n                        reorderSummaryModal.style.display = 'flex';\n                    });\n                });\n            }\n\n            if (reorderShippingCheckbox) {\n                reorderShippingCheckbox.addEventListener('change', fetchAndUpdateReorderSummary);\n            }\n\n            if (confirmReorderBtn) {\n                confirmReorderBtn.addEventListener('click', function() {\n                    const agbChecked = reorderAgbCheckbox?.checked || false;\n                    const statusDiv = document.getElementById('ls-reorder-order-submit-status');\n\n                    if (!agbChecked) {\n                        statusDiv.innerHTML = '<span style=\"color:#a00;\">Bitte akzeptiere die AGB.<\/span>';\n                        return;\n                    }\n\n                    confirmReorderBtn.disabled = true;\n                    confirmReorderBtn.textContent = 'Wird \u00fcbermittelt...';\n\n                    const shippingChecked = reorderShippingCheckbox?.checked || false;\n\n                    const fd = new FormData();\n                    fd.append('action', 'ls_submit_reorder');\n                    fd.append('nonce', lsNonce);\n                    fd.append('agb_accepted', '1');\n                    fd.append('shipping_selected', shippingChecked ? '1' : '0');\n                    reorderCart.forEach((item, idx) => {\n                        fd.append('items[' + idx + '][file]', item.file);\n                        fd.append('items[' + idx + '][option]', item.option);\n                        fd.append('items[' + idx + '][price]', item.price);\n                        fd.append('items[' + idx + '][qty]', item.qty);\n                    });\n\n                    fetch(ajaxUrl, { method: 'POST', body: fd })\n                    .then(r => r.json())\n                    .then(resp => {\n                        confirmReorderBtn.disabled = false;\n                        confirmReorderBtn.textContent = 'Jetzt verbindlich nachbestellen';\n                        if (resp.success) {\n                            statusDiv.innerHTML = '<span style=\"color:#2e7d32;\">' + resp.data + '<\/span>';\n                            setTimeout(() => { location.reload(); }, 2000);\n                        } else {\n                            statusDiv.innerHTML = '<span style=\"color:#a00;\">Fehler: ' + (resp.data || 'Unbekannter Fehler') + '<\/span>';\n                        }\n                    });\n                });\n            }\n        });\n        <\/script>\n        \n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Bienvenue dans votre galerie photo personnelle ! &#x2728; Ravi de vous accueillir ! Nous avons rassembl\u00e9 pour vous les plus beaux moments de votre s\u00e9ance photo. Prenez tout votre temps, installez-vous confortablement et s\u00e9lectionnez vos clich\u00e9s pr\u00e9f\u00e9r\u00e9s. \u00ab Les photos sont des \u0153uvres d\u2019art \u2013 on ne les appr\u00e9cie pas au format timbre-poste. Pour une&hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":66682,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"jetpack_seo_schema_type":"","ngg_post_thumbnail":0,"footnotes":""},"class_list":["post-99486","page","type-page","status-publish","hentry","entry","no-media"],"jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.light-style.eu\/fr\/wp-json\/wp\/v2\/pages\/99486","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.light-style.eu\/fr\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.light-style.eu\/fr\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.light-style.eu\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.light-style.eu\/fr\/wp-json\/wp\/v2\/comments?post=99486"}],"version-history":[{"count":0,"href":"https:\/\/www.light-style.eu\/fr\/wp-json\/wp\/v2\/pages\/99486\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.light-style.eu\/fr\/wp-json\/wp\/v2\/pages\/66682"}],"wp:attachment":[{"href":"https:\/\/www.light-style.eu\/fr\/wp-json\/wp\/v2\/media?parent=99486"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}