function setupTextPreview(el) { if (el.dataset.expanded === "true") return; const text = el.dataset.fulltext; const words = text.split(' '); const lineHeight = parseFloat(getComputedStyle(el).lineHeight); const maxHeight = lineHeight * 10; el.innerHTML = ''; const visible = document.createElement('span'); const hidden = document.createElement('span'); const dots = document.createElement('span'); const link = document.createElement('a'); dots.textContent = '\u2026'; // saubere Ellipse link.href = '#'; link.textContent = 'weiterlesen'; link.style.marginLeft = '0.25em'; link.style.color = '#256fe9'; link.style.textDecoration = 'underline'; link.style.cursor = 'pointer'; el.appendChild(visible); let cutoff = words.length, current = ''; for (let i = 0; i < words.length; i++) { visible.textContent = current + words[i] + ' '; if (el.scrollHeight > maxHeight) { cutoff = i; break; } current += words[i] + ' '; } visible.textContent = words.slice(0, cutoff).join(' '); if (cutoff < words.length) { el.append(dots, link); for (let i = cutoff; i < words.length; i++) { const span = document.createElement('span'); span.textContent = (i === cutoff ? ' ' : '') + words[i] + ' '; span.style.opacity = 0; span.style.transition = 'opacity 0.3s ease'; hidden.appendChild(span); } el.appendChild(hidden); el.style.maxHeight = `${maxHeight + 30}px`; el.style.overflow = 'hidden'; el.style.transition = 'max-height 0.6s ease'; link.addEventListener('click', e => { e.preventDefault(); dots.remove(); link.remove(); el.dataset.expanded = "true"; el.style.maxHeight = `${el.scrollHeight + 10}px`; hidden.querySelectorAll('span').forEach((s, i) => { setTimeout(() => (s.style.opacity = 1), i * 50); }); }); } else { el.textContent = text; } } // ---- Neu: Helper & gezielte Initialisierung ---- function isVisible(el) { if (!el) return false; const style = getComputedStyle(el); if (style.display === 'none' || style.visibility === 'hidden') return false; // offsetParent ist bei display:none null (meist ausreichend) return el.offsetParent !== null || style.position === 'fixed'; } function initPreviewOnce(el) { if (el.dataset.inited === "true") return; // schon initialisiert? -> nix tun el.dataset.fulltext = (el.dataset.fulltext || el.textContent.trim()); setupTextPreview(el); el.dataset.inited = "true"; } // Initialisiere nur anfangs sichtbare .text-preview document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('.text-preview').forEach(el => { if (isVisible(el)) initPreviewOnce(el); }); }); // Auf Fensteränderung: nur bereits initialisierte, nicht expandierte neu berechnen window.addEventListener('resize', () => { document.querySelectorAll('.text-preview').forEach(el => { if (el.dataset.inited === "true" && el.dataset.expanded !== "true") { // Rebuild ohne Reset: fulltext bleibt, nur neu aufbauen setupTextPreview(el); } }); }); // WICHTIG: Klick auf .review-trigger -> danach neu sichtbar gewordene .text-preview initialisieren document.addEventListener('click', (e) => { const trigger = e.target.closest('.review-trigger'); if (!trigger) return; // Falls der Trigger selbst Content einblendet (Klasse toggelt o.ä.), // warten wir bis zum nächsten Frame, damit CSS wirkt: requestAnimationFrame(() => { // Optional: Eingrenzen auf einen bestimmten Container, wenn vorhanden: const targetSel = trigger.getAttribute('data-target'); // z.B. "#more-reviews" const scope = targetSel ? document.querySelector(targetSel) : document; if (!scope) return; // Nur neu sichtbar gewordene und noch nicht inited Elemente bearbeiten scope.querySelectorAll('.text-preview').forEach(el => { if (!isVisible(el)) return; if (el.dataset.inited === "true") return; initPreviewOnce(el); }); }); });