// ======================================== // Paraguay LLC & SRL — Main JS // ======================================== const API_URL = 'https://n8n.odoo4projects.com/webhook/paraguay/login'; const NEWSLETTER_URL = 'https://n8n.odoo4projects.com/webhook/paraguay/newsletter'; const USER_URL = 'https://n8n.odoo4projects.com/webhook/paraguay/user'; const PROFILE_URL = 'https://n8n.odoo4projects.com/webhook/paraguay/profile'; const WIKI_URL = 'https://n8n.odoo4projects.com/webhook/paraguay/wiki'; const COMMUNITY_URL = 'https://n8n.odoo4projects.com/webhook/paraguay/community'; const SCHEDULE_URL = 'https://n8n.odoo4projects.com/webhook/paraguay/schedule'; let currentLang = 'de'; document.addEventListener('DOMContentLoaded', () => { currentLang = document.body.dataset.lang || 'de'; checkSession(); loadCommunityStats(); // Smooth scroll nav links document.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const target = document.querySelector(link.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth' }); } // Close mobile nav document.getElementById('nav-menu').classList.remove('open'); }); }); // Mobile nav toggle const navToggle = document.getElementById('nav-toggle'); const navMenu = document.getElementById('nav-menu'); if (navToggle) { navToggle.addEventListener('click', () => { navMenu.classList.toggle('open'); }); } // Navbar scroll effect window.addEventListener('scroll', () => { const nav = document.getElementById('main-nav'); if (nav) { nav.classList.toggle('scrolled', window.scrollY > 20); } }); // Language toggle document.querySelectorAll('.lang-btn').forEach(btn => { btn.addEventListener('click', () => { const lang = btn.dataset.lang; setLanguage(lang); // Reload content in new language if (isLoggedIn) { if (document.querySelector('.notebook') || document.getElementById('members-list')) loadUsers(); if (document.getElementById('wiki-grid')) loadWiki(); } }); }); // Member search const memberSearch = document.getElementById('member-search'); if (memberSearch) { memberSearch.addEventListener('input', (e) => { filterMembers(e.target.value); }); } // Wiki search const wikiSearch = document.getElementById('wiki-search'); if (wikiSearch) { wikiSearch.addEventListener('input', (e) => { searchWiki(e.target.value); }); } // Close modals on overlay click document.querySelectorAll('.modal-overlay').forEach(overlay => { overlay.addEventListener('click', (e) => { if (e.target === overlay) { overlay.classList.remove('active'); } }); }); // Close modals on Escape document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { document.querySelectorAll('.modal-overlay.active').forEach(m => m.classList.remove('active')); } }); // Login button const loginBtn = document.getElementById('login-btn'); if (loginBtn) { loginBtn.addEventListener('click', () => { openLoginModal(); }); } // ======================================== // SCROLL-TRIGGERED ANIMATIONS // ======================================== // Fade-in sections on scroll (Intersection Observer) initScrollReveal(); // Ripple effect on buttons initRippleEffects(); // Fade in hero image (prevents blink) initHeroImageFade(); }); // ======================================== // HERO IMAGE FADE IN — prevents blink // ======================================== function initHeroImageFade() { const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; const heroImage = document.querySelector('.hero-image'); if (!heroImage) return; if (prefersReducedMotion) { heroImage.classList.add('loaded'); return; } if (heroImage.complete && heroImage.naturalHeight > 0) { requestAnimationFrame(() => heroImage.classList.add('loaded')); return; } heroImage.addEventListener('load', () => requestAnimationFrame(() => heroImage.classList.add('loaded')), { once: true }); heroImage.addEventListener('error', () => requestAnimationFrame(() => heroImage.classList.add('loaded')), { once: true }); } // ======================================== // SCROLL REVEAL (Intersection Observer) // ======================================== function initScrollReveal() { // Respect reduced motion preference const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (prefersReducedMotion) return; const observerOptions = { root: null, rootMargin: '0px 0px -60px 0px', threshold: 0.1 }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); // Only animate once } }); }, observerOptions); // Observe all sections for reveal animation document.querySelectorAll('.section, .telegram-banner, #newsletter-section').forEach(section => { section.style.opacity = '0'; section.style.transform = 'translateY(20px)'; section.style.transition = 'opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1), transform 0.6s cubic-bezier(0.16, 1, 0.3, 1)'; observer.observe(section); }); // Observe individual interactive elements for staggered reveals const staggerElements = [ '.section-title', '.stat', '.about-card', '.wiki-card-new', '.member-card', '.date-card', '.newsletter-card', '.btn-cta', '.hero-title', '.hero-subtitle' ]; staggerElements.forEach(selector => { document.querySelectorAll(selector).forEach(el => { el.style.opacity = '0'; el.style.transition = 'opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1), transform 0.6s cubic-bezier(0.16, 1, 0.3, 1)'; observer.observe(el); }); }); // Hero elements fade in on load (not scroll) if (window.location.hash === '' || window.location.hash === '#hero') { document.querySelectorAll('.hero-title, .hero-subtitle, .btn-cta').forEach((el, i) => { setTimeout(() => { el.classList.add('visible'); el.style.opacity = ''; el.style.transform = ''; }, 300 + i * 150); }); // Also observe hero stats document.querySelectorAll('.stat').forEach((el, i) => { setTimeout(() => { el.classList.add('visible'); el.style.opacity = ''; el.style.transform = ''; }, 400 + i * 100); }); } } // ======================================== // RIPPLE EFFECT ON BUTTONS // ======================================== function initRippleEffects() { document.querySelectorAll('.btn, .lang-btn').forEach(btn => { btn.addEventListener('click', function(e) { const rect = this.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const ripple = document.createElement('span'); ripple.style.cssText = ` position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.4); width: 0; height: 0; left: ${x}px; top: ${y}px; transform: translate(-50%, -50%); pointer-events: none; animation: rippleExpand 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards; `; this.style.position = this.style.position || 'relative'; this.style.overflow = 'hidden'; this.appendChild(ripple); setTimeout(() => ripple.remove(), 600); }); }); } // ======================================== // LANGUAGE // ======================================== function setLanguage(lang) { currentLang = lang; document.body.dataset.lang = lang; document.querySelectorAll('.lang-btn').forEach(b => { b.classList.toggle('active', b.dataset.lang === lang); }); } function isDE() { return currentLang !== 'en'; } // ======================================== // COMMUNITY STATS // ======================================== async function loadCommunityStats() { try { const response = await fetch(COMMUNITY_URL); if (!response.ok) throw new Error('Failed to fetch'); const json = await response.json(); // API returns an array: [{"users": 4, "topics": 27, "posts": 58}] const d = Array.isArray(json) ? json[0] : json; if (d.users !== undefined) { animateCounter('stat-members', d.users); } if (d.posts !== undefined) { animateCounter('stat-posts', d.posts); } if (d.topics !== undefined) { animateCounter('stat-wiki', d.topics); } } catch (error) { console.error('Failed to load community stats:', error); } } function animateCounter(elementId, targetNumber) { const element = document.getElementById(elementId); if (!element) return; let current = 0; const duration = 1500; // ms const steps = 40; const increment = targetNumber / steps; const stepTime = duration / steps; const timer = setInterval(() => { current += increment; if (current >= targetNumber) { element.textContent = targetNumber; clearInterval(timer); } else { element.textContent = Math.floor(current); } }, stepTime); } // ======================================== // AUTH / SESSION // ======================================== let isLoggedIn = false; function isSessionValid() { const sid = getCookie('sessionid'); return sid.length > 0; } function checkSession() { isLoggedIn = isSessionValid(); if (isLoggedIn) { document.body.dataset.loggedIn = 'true'; document.getElementById('login-btn').style.display = 'none'; document.getElementById('logout-btn').style.display = 'inline-flex'; document.getElementById('profile-btn').style.display = 'inline-flex'; document.getElementById('telegram-banner').style.display = 'none'; document.getElementById('main-content').style.display = 'block'; document.getElementById('newsletter-section').style.display = 'block'; loadUsers(); loadWiki(); loadDates(); } else { document.body.dataset.loggedIn = 'false'; document.getElementById('login-btn').style.display = 'inline-flex'; document.getElementById('logout-btn').style.display = 'none'; document.getElementById('profile-btn').style.display = 'none'; document.getElementById('telegram-banner').style.display = 'block'; document.getElementById('main-content').style.display = 'none'; document.getElementById('newsletter-section').style.display = 'none'; } } // ======================================== // LOGIN MODAL // ======================================== function openLoginModal() { document.getElementById('login-modal-overlay').classList.add('active'); // Reset to step 1 document.querySelectorAll('.login-step').forEach(s => s.classList.remove('active')); document.getElementById('login-step-1').classList.add('active'); document.getElementById('telegram-user').value = ''; document.getElementById('verification-code').value = ''; } function closeLoginModal() { document.getElementById('login-modal-overlay').classList.remove('active'); } // ======================================== // PROFILE MODAL // ======================================== function openProfileModal() { const sid = getCookie('sessionid'); if (!sid) { alert('Bitte anmelden.'); return; } fetch(PROFILE_URL + '?sessionid=' + encodeURIComponent(sid)) .then(r => r.json()) .then(user => { document.getElementById('profile-username-display').textContent = user.username || ''; const rank = getRank(user.posts || 0); const badge = document.getElementById('profile-rank-badge'); badge.innerHTML = `${rank.emoji} ${rank.title}`; document.getElementById('profile-company').value = user.company || ''; document.getElementById('profile-email').value = user.email || ''; document.getElementById('profile-homepage').value = user.homepage || ''; document.getElementById('profile-image').value = user.image || ''; document.getElementById('profile-location').value = user.location || ''; document.getElementById('profile-description').value = user.Description || ''; document.getElementById('profile-publish').checked = true; document.getElementById('profile-modal-overlay').classList.add('active'); }) .catch(() => alert('Fehler beim Laden des Profils.')); } function closeProfileModal() { document.getElementById('profile-modal-overlay').classList.remove('active'); } function saveProfile() { const sid = getCookie('sessionid'); fetch(PROFILE_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: document.getElementById('profile-username-display').textContent, company: document.getElementById('profile-company').value.trim(), email: document.getElementById('profile-email').value, homepage: document.getElementById('profile-homepage').value, image: document.getElementById('profile-image').value, location: document.getElementById('profile-location').value, Description: document.getElementById('profile-description').value, sessionid: sid, publish: document.getElementById('profile-publish').checked }) }) .then(r => r.ok ? alert('Profil gespeichert!') : alert('Fehler beim Speichern.')) .catch(() => alert('Fehler bei der Verbindung.')); } // ======================================== // USER DETAIL MODAL // ======================================== function openUserDetailModal(user) { const avatar = document.getElementById('user-detail-avatar'); const avatarImg = avatar.querySelector('img'); const avatarInitial = document.getElementById('user-detail-initial'); if (user.image) { if (avatarImg) { avatarImg.src = user.image; avatarImg.alt = user.username; avatarImg.style.display = ''; } else { const img = document.createElement('img'); img.src = user.image; img.alt = user.username; img.style.display = ''; avatar.appendChild(img); } avatarInitial.textContent = ''; } else { if (avatarImg) avatarImg.style.display = 'none'; avatarInitial.textContent = (user.username || '?').charAt(0).toUpperCase(); } document.getElementById('user-detail-username').textContent = '@' + (user.username || 'Unknown'); const rank = getRank(user.posts || 0); document.getElementById('user-detail-rank-badge').className = `rank-badge rank-${rank.level}`; document.getElementById('user-detail-rank-badge').textContent = rank.emoji + ' ' + rank.title; document.getElementById('user-detail-company').textContent = user.company || ''; const emailEl = document.getElementById('user-detail-email'); emailEl.innerHTML = user.email ? `${user.email}` : '-'; const hpEl = document.getElementById('user-detail-homepage'); const hp = user.homepage || ''; hpEl.innerHTML = hp ? `${hp}` : '-'; document.getElementById('user-detail-location').textContent = user.location || '-'; document.getElementById('user-detail-description').innerHTML = (user.Description || '-').replace(/\n/g, '
'); document.getElementById('user-detail-modal-overlay').classList.add('active'); } function closeUserDetailModal() { document.getElementById('user-detail-modal-overlay').classList.remove('active'); } // ======================================== // LOGIN FLOW // ======================================== function sendTelegramUser() { const user = document.getElementById('telegram-user').value.trim(); if (!user) { alert('Bitte geben Sie einen Telegram-Nutzernamen ein.'); return; } const btn = event.target; btn.disabled = true; btn.textContent = isDE() ? 'Sende...' : 'Sending...'; fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: user }) }) .then(r => r.json()) .then(data => { if (data.sessionid) { setCookie('sessionid', data.sessionid, 1); document.getElementById('login-step-1').classList.remove('active'); document.getElementById('login-step-3').classList.add('active'); } else if (data.status === 'code send') { document.getElementById('login-step-1').classList.remove('active'); document.getElementById('login-step-2').classList.add('active'); } else { alert(isDE() ? 'Benutzername nicht gefunden. Bitte über Telegram Bot anmelden.' : 'Username not found. Please sign up via Telegram bot.'); } }) .catch(() => { btn.disabled = false; alert(isDE() ? 'Verbindungsfehler' : 'Connection error'); }); } function verifyCode() { const code = document.getElementById('verification-code').value.trim(); if (!code) { alert('Bitte geben Sie den Code ein.'); return; } fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: document.getElementById('telegram-user').value, code: code, sessionid: getCookie('sessionid') }) }) .then(r => r.json()) .then(data => { if (data.sessionid) { setCookie('sessionid', data.sessionid, 1); checkSession(); closeLoginModal(); } else { alert(isDE() ? 'Ungültiger Code' : 'Invalid code'); } }) .catch(() => alert(isDE() ? 'Verbindungsfehler' : 'Connection error')); } function submitLoginData() { const name = document.getElementById('full-name').value.trim(); const llc = document.getElementById('llc-name').value.trim(); if (!name || !llc) { alert(isDE() ? 'Bitte Name und LLC eintragen' : 'Please enter name and LLC'); return; } fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: document.getElementById('telegram-user').value, name: name, llc: llc }) }) .then(r => r.json()) .then(data => { if (data.sessionid) { setCookie('sessionid', data.sessionid, 1); checkSession(); closeLoginModal(); } else { alert(isDE() ? 'Registrierung fehlgeschlagen' : 'Registration failed'); } }) .catch(() => alert(isDE() ? 'Verbindungsfehler' : 'Connection error')); } function logout() { setCookie('sessionid', '', -1); checkSession(); } // ======================================== // NEWSLETTER // ======================================== function subscribeNewsletter() { const email = document.getElementById('newsletter-email').value.trim(); if (!email) { alert(isDE() ? 'Bitte E-Mail eingeben' : 'Please enter email'); return; } fetch(NEWSLETTER_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email }) }) .then(r => { if (r.ok) { document.getElementById('newsletter-form').classList.add('hidden'); document.getElementById('newsletter-success').classList.remove('hidden'); } else { alert(isDE() ? 'Fehler beim Abonnieren' : 'Error subscribing'); } }) .catch(() => alert(isDE() ? 'Verbindungsfehler' : 'Connection error')); } // ======================================== // USERS / MEMBERS // ======================================== async function loadUsers() { try { const response = await fetch(USER_URL); if (!response.ok) throw new Error('Failed to fetch'); const data = await response.json(); const container = document.getElementById('members-list'); if (data.data && Array.isArray(data.data)) { container.innerHTML = ''; container.style.display = 'grid'; data.data.forEach(user => { const card = createMemberCard(user); container.appendChild(card); }); // Update stats will be handled by loadCommunityStats } else { container.innerHTML = '

' + (isDE() ? 'Keine Mitglieder gefunden' : 'No members found') + '

'; } } catch (error) { const container = document.getElementById('members-list'); container.innerHTML = '

' + (isDE() ? 'Fehler beim Laden' : 'Error loading') + '

'; } } function createMemberCard(user) { const card = document.createElement('div'); card.className = 'member-card'; card.setAttribute('role', 'button'); card.setAttribute('tabindex', '0'); const rank = getRank(user.posts || 0); const avatarContent = user.image ? `${user.username}` : `${(user.username || '?').charAt(0).toUpperCase()}`; card.innerHTML = `
${avatarContent}

${user.username || 'N/A'}

${user.company || ''}

${user.location ? `

📍 ${user.location}

` : ''} ${user.posts ? `

${user.posts} ${isDE() ? 'Beiträge' : 'posts'}

` : ''} ${rank.emoji} ${rank.title}
`; card.addEventListener('click', (e) => { e.stopPropagation(); openUserDetailModal(user); }); card.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openUserDetailModal(user); } }); return card; } function filterMembers(term) { const cards = document.querySelectorAll('.member-card'); const t = term.toLowerCase(); cards.forEach(card => { const text = card.textContent.toLowerCase(); card.style.display = text.includes(t) ? 'flex' : 'none'; }); } // ======================================== // RANKS // ======================================== function getRank(posts) { if (posts >= 500) return { level: 'legend', title: isDE() ? 'Legende' : 'Legend', emoji: '👑' }; if (posts >= 200) return { level: 'expert', title: isDE() ? 'Experte' : 'Expert', emoji: '🏆' }; if (posts >= 100) return { level: 'veteran', title: isDE() ? 'Veteran' : 'Veteran', emoji: '💎' }; if (posts >= 50) return { level: 'contributor', title: isDE() ? 'Mitwirkender' : 'Contributor', emoji: '⭐' }; if (posts >= 30) return { level: 'member', title: isDE() ? 'Mitglied' : 'Member', emoji: '🌳' }; if (posts >= 5) return { level: 'beginner', title: isDE() ? 'Einsteiger' : 'Beginner', emoji: '🌿' }; return { level: 'rookie', title: isDE() ? 'Anfänger' : 'Rookie', emoji: '🌱' }; } // ======================================== // TOPIC COLORS — each topic gets a distinct color // ======================================== const TOPIC_COLORS = { '🌎 Internationales Geschäft': { bg: '#E0F2F1', text: '#00695C', border: '#00695C' }, '🏦 Banken & Finanzen Filialen': { bg: '#E3F2FD', text: '#1565C0', border: '#1565C0' }, '🏦 Banken & Finanzen allgmein': { bg: '#E8EAF6', text: '#283593', border: '#283593' }, '💰 Steuern': { bg: '#FFF3E0', text: '#E65100', border: '#E65100' }, '💻 Software & Digital Operations': { bg: '#EDE7F6', text: '#4527A0', border: '#4527A0' }, '📑 Buchhaltung': { bg: '#E8F5E9', text: '#2E7D32', border: '#2E7D32' } }; function getTopicColor(topic) { // Try exact match first if (TOPIC_COLORS[topic]) return TOPIC_COLORS[topic]; // Try matching without emoji const cleanTopic = topic.replace(/^[^\s]+\s/, ''); for (const key of Object.keys(TOPIC_COLORS)) { const cleanKey = key.replace(/^[^\s]+\s/, ''); if (cleanTopic === cleanKey) return TOPIC_COLORS[key]; } // Fallback: generate color from topic string let hash = 0; for (let i = 0; i < topic.length; i++) { hash = topic.charCodeAt(i) + ((hash << 5) - hash); } const hues = [200, 180, 240, 30, 140, 60]; // blue, teal, purple, orange, green, amber const idx = Math.abs(hash) % hues.length; const color = hues[idx]; return { bg: `hsl(${color}, 70%, 92%)`, text: `hsl(${color}, 60%, 35%)`, border: `hsl(${color}, 60%, 35%)` }; } // ======================================== // WIKI // ======================================== // ======================================== let wikiData = []; async function loadWiki() { try { const response = await fetch(WIKI_URL); if (!response.ok) throw new Error('Failed to fetch'); const result = await response.json(); if (result.data && Array.isArray(result.data)) { wikiData = result.data; renderWikiCards(wikiData); // Wiki count will be loaded from community webhook } else { document.getElementById('wiki-grid').innerHTML = '

' + (isDE() ? 'Keine Wiki-Daten' : 'No wiki data') + '

'; } } catch (error) { document.getElementById('wiki-grid').innerHTML = '

' + (isDE() ? 'Fehler beim Laden' : 'Error loading') + '

'; } } function renderWikiCards(data) { const grid = document.getElementById('wiki-grid'); grid.innerHTML = ''; if (data.length === 0) { grid.innerHTML = '

' + (isDE() ? 'Keine Ergebnisse gefunden' : 'No results found') + '

'; return; } // Sort by updatedAt descending (newest first) const sorted = [...data].sort((a, b) => new Date(b.updatedAt || b.createdAt) - new Date(a.updatedAt || a.createdAt)); sorted.forEach((item, idx) => { const card = document.createElement('div'); card.className = 'wiki-card-new stagger-' + (idx % 4 + 1); card.setAttribute('role', 'button'); card.setAttribute('tabindex', '0'); const topic = currentLang === 'en' && item.topic_en ? item.topic_en : (item.topic || 'Unknown'); const topicColor = getTopicColor(topic); // Get excerpt from first subtopic