// KoalaSync Landing Page Logic document.addEventListener('DOMContentLoaded', () => { const safeGetLocalStorage = (key) => { try { return localStorage.getItem(key); } catch (_) { return null; } }; const safeSetLocalStorage = (key, val) => { try { localStorage.setItem(key, val); } catch (_) { return; } }; // Scroll Reveal Logic (IntersectionObserver for performance) const revealElements = document.querySelectorAll('[data-reveal]'); const revealObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('revealed'); revealObserver.unobserve(entry.target); } }); }, { rootMargin: '0px 0px -150px 0px', threshold: 0.1 }); revealElements.forEach(el => revealObserver.observe(el)); // Auto-update URL hash as user scrolls through sections // (preserves position across language switches) const sectionObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { history.replaceState(null, null, '#' + entry.target.id); } }); }, { threshold: 0.3 }); document.querySelectorAll('section[id], header[id]').forEach(el => sectionObserver.observe(el)); // Navbar scroll effect const nav = document.querySelector('nav'); window.addEventListener('scroll', () => { if (window.scrollY > 50) { nav.style.padding = '0.75rem 0'; nav.style.background = 'rgba(15, 23, 42, 0.9)'; } else { nav.style.padding = '1rem 0'; nav.style.background = 'rgba(30, 41, 59, 0.7)'; } }); // Invite Detection & Bridge const checkInvite = () => { const isJoinPage = window.location.pathname.includes('join'); // Dev Simulation Mode via URL Search Parameter (?dev=success) or Hash (#dev=success / #devsuccess) const urlParams = new URLSearchParams(window.location.search); let devMode = urlParams.get('dev'); if (!devMode) { const hashClean = window.location.hash.startsWith('#') ? window.location.hash.substring(1) : window.location.hash; const hashParams = new URLSearchParams(hashClean); devMode = hashParams.get('dev'); } if (!devMode) { if (window.location.hash.includes('devsuccess') || window.location.search.includes('devsuccess')) devMode = 'success'; if (window.location.hash.includes('devfailure') || window.location.search.includes('devfailure')) devMode = 'failure'; } if (isJoinPage && devMode) { setTimeout(() => { const displayRoom = document.getElementById('display-room-id'); const actions = document.getElementById('join-actions'); if (displayRoom) displayRoom.textContent = 'DEV-ROOM'; if (actions) { actions.innerHTML = `
Simulating connection (DEV)...Verbindung wird simuliert (DEV)...

Simulating status event in 1.5 seconds.Status-Event wird in 1,5 Sekunden simuliert.

`; setTimeout(() => { window.dispatchEvent(new CustomEvent('KOALASYNC_STATUS', { detail: { success: devMode === 'success', message: devMode === 'failure' ? 'Simulated Connection Timeout!' : '' } })); }, 1500); } }, 600); return; } // Use a short timeout to let the bridge script initialize its dataset attribute setTimeout(() => { const isInstalled = document.documentElement.dataset.koalasyncInstalled === 'true'; if (window.location.hash.startsWith('#join:')) { const parts = window.location.hash.split(':'); if (parts.length >= 3) { const roomId = parts[1]; const password = parts[2]; const serverFlag = parts[3] || '0'; const serverUrl = parts[4] ? decodeURIComponent(parts[4]) : ''; if (isJoinPage) { const displayRoom = document.getElementById('display-room-id'); const actions = document.getElementById('join-actions'); if (displayRoom) displayRoom.textContent = roomId; if (actions) { if (!isInstalled) { const isFirefox = navigator.userAgent.includes('Firefox'); if (isFirefox) { actions.innerHTML = `
Firefox GET IT ON MOZILLA ADD-ONSIM FIREFOX ADD-ON STORE HERUNTERLADEN Download via GitHubÜber GitHub herunterladen

The extension is required to join and sync videos. Die Erweiterung ist erforderlich, um beizutreten und Videos zu synchronisieren.

`; } else { actions.innerHTML = `
Chrome GET IT ON CHROME WEBSTOREIM CHROME WEB STORE HERUNTERLADEN Download via GitHubÜber GitHub herunterladen

The extension is required to join and sync videos. Die Erweiterung ist erforderlich, um beizutreten und Videos zu synchronisieren.

`; } } else { actions.innerHTML = `
Joining room automatically...Raum wird automatisch betreten...

Your extension is taking care of it.Deine Erweiterung kümmert sich darum.

`; // AUTO-TRIGGER JOIN setTimeout(() => { window.dispatchEvent(new CustomEvent('KOALASYNC_JOIN_REQUEST', { detail: { roomId, password, useCustomServer: serverFlag === '1', serverUrl: serverUrl } })); }, 500); } } } else { // Fallback banner for index.html if (!document.getElementById('koala-banner')) { const banner = document.createElement('div'); banner.className = 'invite-banner'; banner.id = 'koala-banner'; const container = document.createElement('div'); container.className = 'container'; container.style.cssText = 'display:flex; justify-content:space-between; align-items:center;'; const inviteSpan = document.createElement('span'); inviteSpan.appendChild(document.createTextNode('🎫 Invitation for ')); const boldRoom = document.createElement('b'); boldRoom.textContent = roomId; inviteSpan.appendChild(boldRoom); inviteSpan.appendChild(document.createTextNode(' detected!')); const joinLink = document.createElement('a'); joinLink.href = 'join' + window.location.hash; joinLink.className = 'btn-banner'; joinLink.textContent = 'OPEN JOIN PAGE'; container.appendChild(inviteSpan); container.appendChild(joinLink); banner.appendChild(container); document.body.prepend(banner); } } // Global listener for Join Button document.addEventListener('click', (e) => { if (e.target && e.target.id === 'webJoinBtn') { e.target.textContent = 'JOINING...'; e.target.disabled = true; window.dispatchEvent(new CustomEvent('KOALASYNC_JOIN_REQUEST', { detail: { roomId, password, useCustomServer: serverFlag === '1', serverUrl: serverUrl } })); } }); } } }, 600); // 600ms delay to ensure bridge.js has set the dataset }; // Listen for status from Extension window.addEventListener('KOALASYNC_STATUS', (e) => { const { success, message } = e.detail; const isJoinPage = window.location.pathname.includes('join'); if (isJoinPage) { const icon = document.getElementById('join-status-icon'); const title = document.getElementById('join-title'); const actions = document.getElementById('join-actions'); const desc = document.getElementById('join-desc'); const ring = document.getElementById('status-ring'); if (success) { if (ring) { ring.classList.remove('active-pulse'); ring.style.display = 'none'; } if (icon) { icon.innerHTML = 'Success'; icon.style.transform = 'scale(1)'; } const isDE = document.documentElement.classList.contains('lang-de'); title.textContent = isDE ? 'Erfolgreich!' : 'Success!'; desc.innerHTML = isDE ? 'Verbunden!
Wähle jetzt einen Video-Tab in der Erweiterung aus.' : 'Connected!
Now select a video tab in the extension.'; let count = 3; const updateCountdown = () => { if (count <= 0) { window.close(); desc.textContent = isDE ? 'Beitritt erfolgreich! Du kannst diesen Tab jetzt manuell schließen.' : 'Joined successfully! You can close this tab manually.'; } else { count--; setTimeout(updateCountdown, 1000); } }; setTimeout(updateCountdown, 1000); const closeLabel = isDE ? 'TAB JETZT SCHLIESSEN' : 'CLOSE TAB NOW'; actions.innerHTML = `
`; } else { if (ring) { ring.classList.remove('active-pulse'); ring.style.display = 'none'; } if (icon) { icon.innerHTML = 'Error'; icon.style.transform = 'scale(1)'; } const isDE = document.documentElement.classList.contains('lang-de'); title.textContent = isDE ? 'Fehler' : 'Error'; desc.textContent = isDE ? `Beitritt fehlgeschlagen: ${message}` : `Join failed: ${message}`; const retryLabel = isDE ? 'ERNEUT VERSUCHEN' : 'TRY AGAIN'; actions.innerHTML = `
`; } } else { const banner = document.getElementById('koala-banner'); if (banner) { if (success) { banner.style.background = 'var(--success)'; banner.innerHTML = '
✅ Joined! This tab will close in 2s...
'; setTimeout(() => window.close(), 2000); } else { banner.style.background = 'var(--error)'; banner.innerHTML = ''; const errDiv = document.createElement('div'); errDiv.className = 'container'; errDiv.textContent = '❌ Error: ' + message; banner.appendChild(errDiv); } } } }); const updateDynamicVersion = async () => { try { const versionPath = document.documentElement.lang === 'en' ? 'version.json' : '../version.json'; const response = await fetch(versionPath); if (!response.ok) return; const data = await response.json(); const { version, date } = data; if (!version || !date) return; const releaseDate = new Date(date); const now = new Date(); const diffMs = now - releaseDate; const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffMins = Math.floor(diffMs / (1000 * 60)); let relativeTimeEn = ''; let relativeTimeDe = ''; if (diffDays > 0) { relativeTimeEn = `${diffDays} ${diffDays === 1 ? 'day' : 'days'} ago`; relativeTimeDe = `vor ${diffDays} ${diffDays === 1 ? 'Tag' : 'Tagen'}`; } else if (diffHours > 0) { relativeTimeEn = `${diffHours} ${diffHours === 1 ? 'hour' : 'hours'} ago`; relativeTimeDe = `vor ${diffHours} ${diffHours === 1 ? 'Stunde' : 'Stunden'}`; } else if (diffMins > 0) { relativeTimeEn = `${diffMins} ${diffMins === 1 ? 'minute' : 'minutes'} ago`; relativeTimeDe = `vor ${diffMins} ${diffMins === 1 ? 'Minute' : 'Minuten'}`; } else { relativeTimeEn = 'just now'; relativeTimeDe = 'gerade eben'; } const badgeEn = document.querySelector('.version-text-en'); const badgeDe = document.querySelector('.version-text-de'); if (badgeEn) { badgeEn.textContent = `v${version} OUT NOW • ${relativeTimeEn}`; } if (badgeDe) { badgeDe.textContent = `v${version} JETZT VERFÜGBAR • ${relativeTimeDe}`; } // Update Schema.org structured data dynamically const schemaScript = document.getElementById('schema-software'); if (schemaScript) { try { const schema = JSON.parse(schemaScript.textContent); schema.softwareVersion = version; schemaScript.textContent = JSON.stringify(schema, null, 2); } catch (err) { console.warn('Failed to dynamically update schema version:', err); } } } catch (e) { console.warn('Failed to fetch dynamic version info:', e); } }; // Extension Mockup Tab Switcher const mockTabs = document.querySelectorAll('.mock-tab'); const mockScreens = document.querySelectorAll('.mock-screen'); mockTabs.forEach(tab => { tab.addEventListener('click', () => { mockTabs.forEach(t => t.classList.remove('active')); mockScreens.forEach(s => s.classList.remove('active')); tab.classList.add('active'); const targetId = tab.getAttribute('data-target'); const targetScreen = document.getElementById(targetId); if (targetScreen) { targetScreen.classList.add('active'); } }); }); // Terminal Tab Switcher const termTabBtns = document.querySelectorAll('.terminal-tab-btn'); const termPanes = document.querySelectorAll('.terminal-pane'); termTabBtns.forEach(btn => { btn.addEventListener('click', () => { termTabBtns.forEach(b => b.classList.remove('active')); termPanes.forEach(p => p.classList.remove('active')); btn.classList.add('active'); const targetPaneId = btn.getAttribute('data-tab'); const targetPane = document.getElementById(targetPaneId); if (targetPane) { targetPane.classList.add('active'); } }); }); // Terminal Clipboard Copy const copyBtn = document.querySelector('.terminal-copy-btn'); if (copyBtn) { copyBtn.addEventListener('click', () => { const activePane = document.querySelector('.terminal-pane.active'); if (!activePane) return; const codeElement = activePane.querySelector('code'); if (!codeElement) return; const textToCopy = codeElement.innerText || codeElement.textContent; navigator.clipboard.writeText(textToCopy).then(() => { const isDE = document.documentElement.classList.contains('lang-de'); const originalHTML = copyBtn.innerHTML; copyBtn.innerHTML = isDE ? '✅ Kopiert!' : '✅ Copied!'; copyBtn.disabled = true; setTimeout(() => { copyBtn.innerHTML = originalHTML; copyBtn.disabled = false; }, 2000); }).catch(err => { console.error('Failed to copy text: ', err); }); }); } // Mobile Hamburger Menu Toggle const hamburger = document.querySelector('.hamburger'); const navLinks = document.querySelector('#primary-nav'); if (hamburger && navLinks) { hamburger.setAttribute('aria-expanded', 'false'); const open = () => { navLinks.classList.add('open'); hamburger.setAttribute('aria-expanded', 'true'); document.addEventListener('keydown', onEsc); }; const close = () => { navLinks.classList.remove('open'); hamburger.setAttribute('aria-expanded', 'false'); document.removeEventListener('keydown', onEsc); }; const toggle = () => navLinks.classList.contains('open') ? close() : open(); const onEsc = (e) => { if (e.key === 'Escape') close(); }; hamburger.addEventListener('click', toggle); navLinks.querySelectorAll('a').forEach(a => a.addEventListener('click', close)); } // Dynamically localize home links on root dynamic pages (impressum, datenschutz, join) const localizeHomeLinks = () => { const activeLang = safeGetLocalStorage('koala_lang') || (navigator.language.startsWith('de') ? 'de' : 'en'); const path = window.location.pathname; const pathSegments = path.split('/'); const isSubdir = pathSegments.some(seg => ['de', 'fr', 'es', 'pt-BR', 'ru', 'it', 'pl', 'tr', 'nl', 'ja', 'ko', 'pt'].includes(seg)); // Only need to do this dynamic rewrite if we are NOT already inside a localized subdirectory if (!isSubdir) { const homeLinks = document.querySelectorAll('a[href="./"], a[href="de/"], a[href="fr/"], a[href="es/"], a[href="pt-BR/"], a[href="ru/"], a[href="it/"], a[href="pl/"], a[href="tr/"], a[href="nl/"], a[href="ja/"], a[href="ko/"], a[href="pt/"]'); homeLinks.forEach(link => { link.href = (activeLang === 'en') ? './' : `${activeLang}/`; }); } }; // Modern Language Selector Navigation and State Toggling const handleLanguageChange = (e) => { const select = e.currentTarget; const newLang = select.value; const path = window.location.pathname; // Save the user's preference safeSetLocalStorage('koala_lang', newLang); const isLegalImprint = path.includes('impressum') || path.includes('imprint'); const isLegalPrivacy = path.includes('datenschutz') || path.includes('privacy'); if (isLegalImprint) { let target; const hasHtml = path.endsWith('.html'); if (newLang === 'de') { target = hasHtml ? 'de/impressum.html' : 'de/impressum'; if (path.includes('/de/')) target = hasHtml ? 'impressum.html' : 'impressum'; } else { target = hasHtml ? 'imprint.html' : 'imprint'; if (path.includes('/de/')) target = hasHtml ? '../imprint.html' : '../imprint'; } window.location.href = target + window.location.hash; return; } else if (isLegalPrivacy) { let target; const hasHtml = path.endsWith('.html'); if (newLang === 'de') { target = hasHtml ? 'de/datenschutz.html' : 'de/datenschutz'; if (path.includes('/de/')) target = hasHtml ? 'datenschutz.html' : 'datenschutz'; } else { target = hasHtml ? 'privacy.html' : 'privacy'; if (path.includes('/de/')) target = hasHtml ? '../privacy.html' : '../privacy'; } window.location.href = target + window.location.hash; return; } // Determine if we are on a static landing page versus a dynamic utility page const isIndex = !path.includes('join'); if (isIndex) { // Static navigation: Route to correct subdirectory const pathSegments = path.split('/'); const isSubdir = pathSegments.some(seg => ['de', 'fr', 'es', 'pt-BR', 'ru', 'it', 'pl', 'tr', 'nl', 'ja', 'ko', 'pt'].includes(seg)); let targetPath; if (newLang === 'en') { if (isSubdir) { targetPath = '../'; } else { targetPath = './'; } } else { if (isSubdir) { // Switching from one language subdirectory to another (e.g., /de/ to /fr/) targetPath = '../' + newLang + '/'; } else { // Switching from root (English) to a language subdirectory (e.g., / to /fr/) targetPath = newLang + '/'; } } window.location.href = targetPath + window.location.hash; } else { // Dynamic page: Toggle classes and update elements dynamically without navigating away const html = document.documentElement; html.classList.remove('lang-en', 'lang-de', 'lang-fr', 'lang-es', 'lang-pt-br', 'lang-ru', 'lang-it', 'lang-pl', 'lang-tr', 'lang-nl', 'lang-ja', 'lang-ko', 'lang-pt'); // Fallback dynamic pages to 'en' if 'de' is not chosen (since fr/es markup is not present) const activeDisplayLang = (newLang === 'de') ? 'de' : 'en'; html.classList.add('lang-' + activeDisplayLang); html.lang = activeDisplayLang; // Sync all selects on the page to the new value document.querySelectorAll('.lang-dropdown').forEach(sel => { sel.value = newLang; }); // Update titles dynamically const isJoin = path.includes('join'); if (isJoin) { const titles = { en: 'Join Room | KoalaSync', de: 'Raum beitreten | KoalaSync' }; document.title = titles[activeDisplayLang] || titles.en; } // Localize home links dynamically localizeHomeLinks(); } }; // Dynamically adjust language select width to fit the selected option's text length const adjustDropdownWidth = () => { document.querySelectorAll('.lang-dropdown').forEach(select => { const tempSpan = document.createElement('span'); const style = window.getComputedStyle(select); tempSpan.style.fontFamily = style.fontFamily; tempSpan.style.fontSize = style.fontSize; tempSpan.style.fontWeight = style.fontWeight; tempSpan.style.visibility = 'hidden'; tempSpan.style.position = 'absolute'; tempSpan.style.whiteSpace = 'nowrap'; const activeOption = select.options[select.selectedIndex]; if (activeOption) { tempSpan.textContent = activeOption.textContent; document.body.appendChild(tempSpan); const textWidth = tempSpan.getBoundingClientRect().width; select.style.width = (textWidth + 18) + 'px'; document.body.removeChild(tempSpan); } }); }; // Register change event listener for the dropdowns document.querySelectorAll('.lang-dropdown').forEach(select => { select.addEventListener('change', (e) => { handleLanguageChange(e); adjustDropdownWidth(); }); }); // Initialize language select elements to show the current preferred language const initLanguageSelectorValue = () => { const savedLang = safeGetLocalStorage('koala_lang'); const browserLang = navigator.language.startsWith('de') ? 'de' : 'en'; const activePref = savedLang || browserLang; document.querySelectorAll('.lang-dropdown').forEach(select => { select.value = activePref; }); adjustDropdownWidth(); }; // Impressum Email Obfuscation Click Reveal document.querySelectorAll('.email-reveal').forEach(el => { el.addEventListener('click', function() { const user = this.getAttribute('data-user'); const domain = this.getAttribute('data-domain'); if (user && domain) { this.textContent = `${user}@${domain}`; } }); }); // Automated Store/Local Badge Linking based on User-Agent const detectBrowserAndElevateBadge = () => { const isFirefox = navigator.userAgent.includes('Firefox'); const isChrome = navigator.userAgent.includes('Chrome') || navigator.userAgent.includes('Chromium'); const chromeBtns = document.querySelectorAll('.btn-primary'); const firefoxBtns = document.querySelectorAll('.btn-firefox'); if (isFirefox && chromeBtns.length > 0 && firefoxBtns.length > 0) { // User is on Firefox: Elevate Firefox button to primary, make Chrome secondary chromeBtns.forEach(btn => { btn.classList.remove('btn-primary'); btn.classList.add('btn-secondary'); }); firefoxBtns.forEach(btn => { // Put Firefox first in visual order btn.style.order = '-1'; // Add subtle focus scale effect btn.style.transform = 'scale(1.05)'; btn.addEventListener('mouseleave', () => { btn.style.transform = 'scale(1)'; }); btn.addEventListener('mouseenter', () => { btn.style.transform = 'scale(1.05) translateY(-2px)'; }); }); } else if (isChrome && chromeBtns.length > 0 && firefoxBtns.length > 0) { // User is on Chrome: Make Firefox secondary firefoxBtns.forEach(btn => { btn.classList.remove('btn-firefox'); btn.classList.add('btn-secondary'); btn.style.color = 'var(--text)'; btn.style.background = 'var(--card)'; btn.style.border = '1px solid var(--glass-border)'; btn.style.boxShadow = 'none'; }); } // Handle Step 1 Landing Page Download Badges & Nav Badge setTimeout(() => { const isInstalled = document.documentElement.dataset.koalasyncInstalled === 'true'; // Nav Badge Logic const navBadge = document.getElementById('nav-extension-status'); if (isInstalled && navBadge) { navBadge.style.display = 'inline-flex'; } const illusChrome = document.querySelectorAll('.illus-store-btn.chrome'); const illusFirefox = document.querySelectorAll('.illus-store-btn.firefox'); if (isFirefox && illusFirefox.length > 0) { illusFirefox.forEach(btn => { btn.style.order = '-1'; if (!isInstalled) { btn.classList.add('install-breathe'); btn.style.cursor = 'pointer'; btn.onclick = () => window.open('https://addons.mozilla.org/de/firefox/addon/koalasync/', '_blank', 'noopener'); } }); illusChrome.forEach(btn => { btn.style.opacity = '0.5'; btn.style.transform = 'scale(0.95)'; }); } else if (isChrome && illusChrome.length > 0) { illusChrome.forEach(btn => { btn.style.order = '-1'; if (!isInstalled) { btn.classList.add('install-breathe'); btn.style.cursor = 'pointer'; btn.onclick = () => window.open('https://chromewebstore.google.com/detail/koalasync/obbnmkmlaaddodakcbdljknjpagklifc', '_blank', 'noopener'); } }); illusFirefox.forEach(btn => { btn.style.opacity = '0.5'; btn.style.transform = 'scale(0.95)'; }); } // Pulse main hero CTA buttons via Web Animations API // (avoids CSS transition/inline-style conflicts from mouse handlers) if (!isInstalled) { const heroBtns = document.querySelectorAll(isFirefox ? '.btn-firefox' : (isChrome ? '.btn-primary' : null)); if (heroBtns && heroBtns.length > 0) { heroBtns.forEach(btn => { const isFF = btn.classList.contains('btn-firefox'); const glowColor = isFF ? 'rgba(249, 115, 22, ' : 'rgba(99, 102, 241, '; btn.animate([ { transform: 'scale(1)', boxShadow: `0 0 15px ${glowColor}0.2)` }, { transform: 'scale(1.05)', boxShadow: `0 0 25px ${glowColor}0.5)` }, { transform: 'scale(1)', boxShadow: `0 0 15px ${glowColor}0.2)` } ], { duration: 2500, iterations: Infinity, easing: 'ease-in-out' }); }); } } }, 600); }; detectBrowserAndElevateBadge(); checkInvite(); updateDynamicVersion(); localizeHomeLinks(); initLanguageSelectorValue(); });