// KoalaSync Landing Page Logic document.addEventListener('DOMContentLoaded', () => { // 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)); // 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)'; } }); // Smooth scroll for anchors document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); // 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.html' + 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 response = await fetch('version.json'); 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('.nav-links'); if (hamburger && navLinks) { // Initialize accessibility attribute hamburger.setAttribute('aria-expanded', 'false'); hamburger.addEventListener('click', () => { const isOpen = navLinks.classList.toggle('open'); hamburger.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); }); } // Language Selection Umschalter const toggleLanguage = (e) => { if (e) e.preventDefault(); const html = document.documentElement; const currentIsEnglish = html.classList.contains('lang-en'); const newLang = currentIsEnglish ? 'de' : 'en'; html.classList.remove('lang-en', 'lang-de'); html.classList.add('lang-' + newLang); html.lang = newLang; localStorage.setItem('koala_lang', newLang); // Update titles dynamically based on page var path = window.location.pathname; var isIndex = path === '/' || path.endsWith('index.html') || path.split('/').pop() === ''; var isJoin = path.includes('join'); if (isIndex) { const titles = { en: 'KoalaSync | Real-time Video Synchronization for Friends', de: 'KoalaSync | Echtzeit-Video-Synchronisation für Freunde' }; document.title = titles[newLang] || titles.en; } else if (isJoin) { const titles = { en: 'Join Room | KoalaSync', de: 'Raum beitreten | KoalaSync' }; document.title = titles[newLang] || titles.en; } }; document.querySelectorAll('.lang-toggle').forEach(btn => { btn.addEventListener('click', toggleLanguage); }); // 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.innerHTML = `${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'; }); } }; detectBrowserAndElevateBadge(); checkInvite(); updateDynamicVersion(); });