// KoalaSync Landing Page Logic
document.addEventListener('DOMContentLoaded', () => {
// Scroll Reveal Logic
const revealElements = document.querySelectorAll('[data-reveal]');
const revealOnScroll = () => {
const windowHeight = window.innerHeight;
revealElements.forEach(el => {
const elementTop = el.getBoundingClientRect().top;
const revealPoint = 150;
if (elementTop < windowHeight - revealPoint) {
el.classList.add('revealed');
}
});
};
// Initial check
revealOnScroll();
// Scroll listener
window.addEventListener('scroll', revealOnScroll);
// 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.html');
// 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 = `
🦊
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 = `
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.html');
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');
if (success) {
if (icon) icon.textContent = '✅';
const isDE = document.documentElement.classList.contains('lang-de');
title.textContent = isDE ? 'Erfolgreich!' : 'Success!';
let count = 3;
const updateCountdown = () => {
const closingMsg = isDE
? `Du bist dem Raum beigetreten.
Dieser Tab schließt sich in ${count} Sekunden...`
: `You joined the room.
This tab will close in ${count} seconds...`;
desc.innerHTML = closingMsg;
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);
}
};
updateCountdown();
const closeLabel = isDE ? 'TAB JETZT SCHLIESSEN' : 'CLOSE TAB NOW';
actions.innerHTML = ``;
} else {
if (icon) icon.textContent = '❌';
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 3s...
';
setTimeout(() => window.close(), 3000);
} 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}`;
}
} 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) {
hamburger.addEventListener('click', () => {
navLinks.classList.toggle('open');
});
}
// 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.endsWith('join.html');
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}`;
}
});
});
checkInvite();
updateDynamicVersion();
});