Български | Català | Deutsche | Hrvatski | Čeština | Dansk | Nederlandse | English | Eesti keel | Français | Ελληνικά | Magyar | Italiano | Latviski | Norsk | Polski | Português | Română | Русский | Српски | Slovenský | Slovenščina | Español | Svenska | Türkçe | 汉语 | 日本語 |
P

meta title

Aktiver Satz
Datum der Informationsaktualisierung: 2026/05/20

📊 Zusammenfassung: meta title

📋
Suchhäufigkeit: 26 841
Für diesen Ausdruck sind keine morphologischen Daten oder Definitionen verfügbar.

Positionen in Google

Suchphrasen - Google

🔍
Position Domain Seite Aktionen
1 blog.ringostat.com /ru/kak-pravilno-nap...
Titel
Как правильно написать мета-теги Title, Description, ...
Zuletzt aktualisiert
N / A
Seitenautorität
N / A
Verkehr: N / A
Backlinks: N / A
Soziale Anteile: N / A
Ladezeit: N / A
Snippet-Vorschau:
Как правильно написать мета-теги Title, Description, ...
16 нояб. 2020 г. — Что такое мета-теги Title , Description, Keywords. Мета-теги — это элементы HTML-кода, которые сообщают поисковым системам информацию о ...
2 mchost.ru /articles/o-meta-teg...
Titel
Основные мета-теги в SEO
Zuletzt aktualisiert
N / A
Seitenautorität
N / A
Verkehr: N / A
Backlinks: N / A
Soziale Anteile: N / A
Ladezeit: N / A
Snippet-Vorschau:
Основные мета-теги в SEO
9 окт. 2023 г. — Метатег — это заголовок страницы, ее название , которое отображается на вкладке в верхней части браузерного окна, когда посетитель ... </div> </div> </td> </tr> <!-- Main result row --> <tr> <td> <span class="badge badge-success"> 3 </span> </td> <td> <a href="/de/domain/www.seobility.net/" > www.seobility.net </a> </td> <td>/en/wiki/Meta_Title</td> <td> <button class="show-page-btn" onclick="togglePageDetails(this)"> Seitendetails anzeigen </button> </td> </tr> <!-- Page details row --> <tr class="page-details-row"> <td colspan="4"> <div class="page-details" id="page-details-google-3"> <div class="page-details-grid"> <div class="page-detail-item"> <div class="page-detail-label">Vollständige URL</div> <div class="page-detail-value"> <a href="https://www.seobility.net/en/wiki/Meta_Title" class="page-url" target="_blank" rel="nofollow"> https://www.seobility.net/en/wiki/Meta_Title </a> </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Titel</div> <div class="page-detail-value"> Meta Title - Definition + Best Practices - Seobility Wiki </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Zuletzt aktualisiert</div> <div class="page-detail-value"> N / A </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Seitenautorität</div> <div class="page-detail-value"> N / A </div> </div> </div> <div class="page-metrics"> <div class="page-metric"> <span class="page-detail-label">Verkehr:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Backlinks:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Soziale Anteile:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Ladezeit:</span> <span class="page-metric-value"> N / A </span> </div> </div> <div class="snippet" style="margin-top: 1rem;"> <strong>Snippet-Vorschau:</strong><br> <noindex><a href="https://https://www.seobility.net/en/wiki/Meta_Title" rel="nofollow" target="_blank">Meta Title - Definition + Best Practices - Seobility Wiki</a></noindex><br>A meta title (also called title tag) is an element in the head section of an HTML document that defines the title of each page of a website. </div> </div> </td> </tr> <!-- Main result row --> <tr> <td> <span class="badge badge-warning"> 4 </span> </td> <td> <a href="/de/domain/rookee.ru/" > rookee.ru </a> </td> <td>/glossary/title-meta...</td> <td> <button class="show-page-btn" onclick="togglePageDetails(this)"> Seitendetails anzeigen </button> </td> </tr> <!-- Page details row --> <tr class="page-details-row"> <td colspan="4"> <div class="page-details" id="page-details-google-4"> <div class="page-details-grid"> <div class="page-detail-item"> <div class="page-detail-label">Vollständige URL</div> <div class="page-detail-value"> <a href="https://rookee.ru/glossary/title-meta-teg/" class="page-url" target="_blank" rel="nofollow"> https://rookee.ru/glossary/title-meta-teg/ </a> </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Titel</div> <div class="page-detail-value"> Мета-тег title и его оптимизация </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Zuletzt aktualisiert</div> <div class="page-detail-value"> N / A </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Seitenautorität</div> <div class="page-detail-value"> N / A </div> </div> </div> <div class="page-metrics"> <div class="page-metric"> <span class="page-detail-label">Verkehr:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Backlinks:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Soziale Anteile:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Ladezeit:</span> <span class="page-metric-value"> N / A </span> </div> </div> <div class="snippet" style="margin-top: 1rem;"> <strong>Snippet-Vorschau:</strong><br> <noindex><a href="https://https://rookee.ru/glossary/title-meta-teg/" rel="nofollow" target="_blank">Мета-тег title и его оптимизация</a></noindex><br>18 мая 2018 г. — Тег title — это базовый элемент HTML-разметки , который в лаконичной форме демонстрирует пользователям и поисковым роботам основную смысловую и ... </div> </div> </td> </tr> <!-- Main result row --> <tr> <td> <span class="badge badge-warning"> 5 </span> </td> <td> <a href="/de/domain/kolibri-online.com/" > kolibri-online.com </a> </td> <td>/en/magazine/create-...</td> <td> <button class="show-page-btn" onclick="togglePageDetails(this)"> Seitendetails anzeigen </button> </td> </tr> <!-- Page details row --> <tr class="page-details-row"> <td colspan="4"> <div class="page-details" id="page-details-google-5"> <div class="page-details-grid"> <div class="page-detail-item"> <div class="page-detail-label">Vollständige URL</div> <div class="page-detail-value"> <a href="https://kolibri-online.com/en/magazine/create-optimal-meta-titles-and-descriptions/" class="page-url" target="_blank" rel="nofollow"> https://kolibri-online.com/en/magazine/create-optimal-meta-titles-and-descriptions/ </a> </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Titel</div> <div class="page-detail-value"> Create perfect meta titles and descriptions </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Zuletzt aktualisiert</div> <div class="page-detail-value"> N / A </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Seitenautorität</div> <div class="page-detail-value"> N / A </div> </div> </div> <div class="page-metrics"> <div class="page-metric"> <span class="page-detail-label">Verkehr:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Backlinks:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Soziale Anteile:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Ladezeit:</span> <span class="page-metric-value"> N / A </span> </div> </div> <div class="snippet" style="margin-top: 1rem;"> <strong>Snippet-Vorschau:</strong><br> <noindex><a href="https://https://kolibri-online.com/en/magazine/create-optimal-meta-titles-and-descriptions/" rel="nofollow" target="_blank">Create perfect meta titles and descriptions</a></noindex><br>10 авг. 2023 г. — Meta titles and meta descriptions act as a billboard for your website in search engine results pages – a sort of digital business card or welcome sign. </div> </div> </td> </tr> <!-- Main result row --> <tr> <td> <span class="badge badge-warning"> 6 </span> </td> <td> <a href="/de/domain/www.mango-office.ru/" > www.mango-office.ru </a> </td> <td>/journal/glossary/me...</td> <td> <button class="show-page-btn" onclick="togglePageDetails(this)"> Seitendetails anzeigen </button> </td> </tr> <!-- Page details row --> <tr class="page-details-row"> <td colspan="4"> <div class="page-details" id="page-details-google-6"> <div class="page-details-grid"> <div class="page-detail-item"> <div class="page-detail-label">Vollständige URL</div> <div class="page-detail-value"> <a href="https://www.mango-office.ru/journal/glossary/metategi/" class="page-url" target="_blank" rel="nofollow"> https://www.mango-office.ru/journal/glossary/metategi/ </a> </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Titel</div> <div class="page-detail-value"> Что такое метатеги, Title и Description </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Zuletzt aktualisiert</div> <div class="page-detail-value"> N / A </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Seitenautorität</div> <div class="page-detail-value"> N / A </div> </div> </div> <div class="page-metrics"> <div class="page-metric"> <span class="page-detail-label">Verkehr:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Backlinks:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Soziale Anteile:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Ladezeit:</span> <span class="page-metric-value"> N / A </span> </div> </div> <div class="snippet" style="margin-top: 1rem;"> <strong>Snippet-Vorschau:</strong><br> <noindex><a href="https://https://www.mango-office.ru/journal/glossary/metategi/" rel="nofollow" target="_blank">Что такое метатеги, Title и Description</a></noindex><br>21 дек. 2021 г. — Метатеги — это служебные элементы кода, которые содержат информацию о странице . Обычно они располагаются в заголовке, в границах тегов <head> и ... </div> </div> </td> </tr> <!-- Main result row --> <tr> <td> <span class="badge badge-warning"> 7 </span> </td> <td> <a href="/de/domain/www.w3schools.com/" > www.w3schools.com </a> </td> <td>/tags/tag_meta.asp</td> <td> <button class="show-page-btn" onclick="togglePageDetails(this)"> Seitendetails anzeigen </button> </td> </tr> <!-- Page details row --> <tr class="page-details-row"> <td colspan="4"> <div class="page-details" id="page-details-google-7"> <div class="page-details-grid"> <div class="page-detail-item"> <div class="page-detail-label">Vollständige URL</div> <div class="page-detail-value"> <a href="https://www.w3schools.com/tags/tag_meta.asp" class="page-url" target="_blank" rel="nofollow"> https://www.w3schools.com/tags/tag_meta.asp </a> </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Titel</div> <div class="page-detail-value"> HTML meta tag </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Zuletzt aktualisiert</div> <div class="page-detail-value"> N / A </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Seitenautorität</div> <div class="page-detail-value"> N / A </div> </div> </div> <div class="page-metrics"> <div class="page-metric"> <span class="page-detail-label">Verkehr:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Backlinks:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Soziale Anteile:</span> <span class="page-metric-value"> N / A </span> </div> <div class="page-metric"> <span class="page-detail-label">Ladezeit:</span> <span class="page-metric-value"> N / A </span> </div> </div> <div class="snippet" style="margin-top: 1rem;"> <strong>Snippet-Vorschau:</strong><br> <noindex><a href="https://https://www.w3schools.com/tags/tag_meta.asp" rel="nofollow" target="_blank">HTML meta tag</a></noindex><br>The <meta> tag defines metadata about an HTML document . Metadata is data (information) about data. <meta> tags always go inside the <head> element. </div> </div> </td> </tr> </tbody> </table> </div> <div class="pagination" id="googlePagination"> <button disabled onclick="loadSearchDomains('google', 0)">«</button> <button class="active" onclick="loadSearchDomains('google', 1)">1</button> <button disabled onclick="loadSearchDomains('google', 2)">»</button> </div> </div> </div> <script> google.charts.load('current', { 'packages': ['corechart', 'line'], 'callback': drawChart_google}); function drawChart_google() { var data = new google.visualization.DataTable(); data.addColumn('date', 'Tag'); data.addColumn("number", "meta title");data.addColumn("number", "meta title");data.addColumn("number", "meta title");data.addColumn("number", "meta title");data.addColumn("number", "meta title");data.addColumn("number", "meta title");data.addColumn("number", "meta title"); data.addRows([ [new Date(2026, 6, 14),1,2,3,4,5,6,7,], ]); var options = { chart: { title: 'Top-Domains in google Suchergebnisse für Phrase: "meta title"', subtitle: 'Verlauf der Domainposition' }, width: '100%', height: 400, curveType: 'function', legend: { position: 'bottom' }, pointSize: 5, pointsVisible: true, interpolateNulls: true, selectionMode: 'multiple', vAxis: { direction: '-1', title: 'Position', }, hAxis: { title: 'Datum', }, }; var chart = new google.visualization.LineChart(document.getElementById("chart_div_google")); chart.draw(data, options); } </script> <!-- Phrase SEO Article --> <!-- Words Definitions --> <!-- Additional Services --> <div class="card" id="additional-services"> <div class="card-header"> <h2 class="card-title">Zusätzliche Dienstleistungen</h2> <div class="card-icon">💎</div> </div> <div class="card-content"> <div class="info-grid"> <div class="info-item"> <span class="popup-trigger" alt="Vollständiges SEO-Audit mit detaillierten Empfehlungen"> <button class="cta-button" data-modal="paymentModal" data-title="Vollständiges SEO-Audit" data-text="Vollständiges SEO-Audit mit detaillierten Empfehlungen zur Verbesserung der Suchmaschinenpositionen." data-price="99.99"> Vollständiges SEO-Audit </button> <span class="popup-text"></span> </span> </div> <div class="info-item"> <span class="popup-trigger" alt="Monatliche Positionsüberwachung für 100 Schlüsselphrasen"> <button class="cta-button" data-modal="paymentModal" data-title="Positionsüberwachung" data-text="Monatliche Positionsüberwachung für 100 Schlüsselphrasen in Google und Yandex." data-price="49.99"> Positionsüberwachung </button> <span class="popup-text"></span> </span> </div> <div class="info-item"> <span class="popup-trigger" alt="Konkurrenzanalyse und Identifizierung ihrer Werbestrategien"> <button class="cta-button" data-modal="paymentModal" data-title="Konkurrenzanalyse" data-text="Konkurrenzanalyse und Identifizierung ihrer Werbestrategien." data-price="79.99"> Konkurrenzanalyse </button> <span class="popup-text"></span> </span> </div> </div> </div> </div> <!-- JavaScript for AJAX Pagination --> <script> // Apply filters function function applyPhraseFilters(engine) { loadSearchDomains(engine, 1); } // Load search domains for phrase function loadSearchDomains(engine, page) { const domainFilter = document.getElementById(engine + 'DomainFilter').value; const pageFilter = document.getElementById(engine + 'PageFilter').value; const phrase_id = document.getElementById('phrase_id').value; const data = { type: 'search_domains', engine: engine, page: page, phrase_id: phrase_id, // Send the phrase domain_filter: domainFilter, page_filter: pageFilter }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { renderSearchDomains(engine, response.data); renderPagination(engine, response.pagination); } else { showToast('Fehler' + ':'+response.error); } }); } // Render domain pagination specifically for phrase pages function renderDomainPagination(engine, pagination) { const paginationContainer = document.getElementById(engine + 'Pagination'); paginationContainer.innerHTML = ''; const { current_page, total_pages, has_previous, has_next } = pagination; // Previous button const prevButton = document.createElement('button'); prevButton.innerHTML = '«'; prevButton.disabled = !has_previous; prevButton.addEventListener('click', () => { if (has_previous) loadSearchDomains(engine, current_page - 1); }); paginationContainer.appendChild(prevButton); // Page buttons for (let i = 1; i <= total_pages; i++) { const pageButton = document.createElement('button'); pageButton.textContent = i; pageButton.classList.toggle('active', i === current_page); pageButton.addEventListener('click', () => { if (i !== current_page) loadSearchDomains(engine, i); }); paginationContainer.appendChild(pageButton); } // Next button const nextButton = document.createElement('button'); nextButton.innerHTML = '»'; nextButton.disabled = !has_next; nextButton.addEventListener('click', () => { if (has_next) loadSearchDomains(engine, current_page + 1); }); paginationContainer.appendChild(nextButton); } // Get badge class based on position function getPositionBadgeClass(position) { if (position <= 3) return 'badge-success'; if (position <= 10) return 'badge-warning'; return 'badge-danger'; } // Toggle snippet visibility function toggleSnippet(button) { const snippet = button.nextElementSibling; const isVisible = snippet.style.display === 'block'; snippet.style.display = isVisible ? 'none' : 'block'; button.textContent = isVisible ? 'Ausschnitt anzeigen' : 'Ausschnitt ausblenden'; } </script> <script> // Toggle page details visibility function togglePageDetails(button) { const row = button.closest('tr'); const detailsRow = row.nextElementSibling; const pageDetails = detailsRow.querySelector('.page-details'); // Close all other open page details document.querySelectorAll('.page-details').forEach(detail => { if (detail !== pageDetails) { detail.classList.remove('active'); } }); // Update all button texts document.querySelectorAll('.show-page-btn').forEach(btn => { if (btn !== button) { btn.textContent = 'Seitendetails anzeigen'; } }); // Toggle current page details if (pageDetails.classList.contains('active')) { pageDetails.classList.remove('active'); button.textContent = 'Seitendetails anzeigen'; } else { pageDetails.classList.add('active'); button.textContent = 'Seitendetails ausblenden'; } } // Close all page details when clicking outside document.addEventListener('click', function(e) { if (!e.target.closest('.show-page-btn') && !e.target.closest('.page-details')) { document.querySelectorAll('.page-details').forEach(detail => { detail.classList.remove('active'); }); document.querySelectorAll('.show-page-btn').forEach(btn => { btn.textContent = 'Seitendetails anzeigen'; }); } }); // Update the renderSearchDomains function to include show page buttons function renderSearchDomains(engine, data) { const tbody = document.getElementById(engine + 'PhrasesBody'); tbody.innerHTML = ''; data.forEach((item, index) => { const row = document.createElement('tr'); // Format position with change let positionHtml = `<span class="badge ${getPositionBadgeClass(item.position)}">${item.position}</span>`; if (item.change !== null && item.change !== 0) { const changeClass = item.change > 0 ? 'positive' : 'negative'; const changeSymbol = item.change > 0 ? '+' : ''; positionHtml += `<span class="position-change ${changeClass}">(${changeSymbol}${item.change})</span>`; } row.innerHTML = ` <td>${positionHtml}</td> <td><noindex><a href="/de/domain/${item.domain}/" rel="nofollow">${item.domain}</a></noindex></td> <td>${item.page}</td> <td> <button class="show-page-btn" onclick="togglePageDetails(this)">Seitendetails anzeigen</button> </td> `; tbody.appendChild(row); // Add page details row const detailsRow = document.createElement('tr'); detailsRow.className = 'page-details-row'; detailsRow.innerHTML = ` <td colspan="4"> <div class="page-details" id="page-details-${engine}-${index}"> <div class="page-details-grid"> <div class="page-detail-item"> <div class="page-detail-label">Full URL</div> <div class="page-detail-value"> <a href="${item.full_url || '#'}" class="page-url" target="_blank" rel="nofollow"> ${item.full_url || 'N/A'} </a> </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Title</div> <div class="page-detail-value">${item.page_title || 'N/A'}</div> </div> <div class="page-detail-item"> <div class="page-detail-label">Last Updated</div> <div class="page-detail-value">${item.last_updated || 'N/A'}</div> </div> <div class="page-detail-item"> <div class="page-detail-label">Page Authority</div> <div class="page-detail-value">${item.page_authority || 'N/A'}</div> </div> </div> <div class="page-metrics"> <div class="page-metric"> <span class="page-detail-label">Traffic:</span> <span class="page-metric-value">${item.traffic || 'N/A'}</span> </div> <div class="page-metric"> <span class="page-detail-label">Backlinks:</span> <span class="page-metric-value">${item.backlinks || 'N/A'}</span> </div> <div class="page-metric"> <span class="page-detail-label">Social Shares:</span> <span class="page-metric-value">${item.social_shares || 'N/A'}</span> </div> <div class="page-metric"> <span class="page-detail-label">Load Time:</span> <span class="page-metric-value">${item.load_time || 'N/A'}</span> </div> </div> <div class="snippet" style="margin-top: 1rem;"> <strong>Snippet Preview:</strong><br> ${item.snippet || 'No snippet available'} </div> </div> </td> `; tbody.appendChild(detailsRow); }); } </script> <script> (function() { 'use strict'; // --- Configuration --- const STORAGE_KEY = 'serpuls_phrase_sections_state'; const SECTIONS_TO_COLLAPSE = [ { id: 'morphological-lemmas', title: 'Morphologische Deckspelzen', btnText: 'Lemmas' }, { id: 'google-search-section', title: 'Suchphrasen - Google', btnText: 'Google' }, { id: 'yandex-search-section', title: 'Suchphrasen - Yandex', btnText: 'Yandex' }, { id: 'book-samples-section', title: 'Buchbeispiele mit Phrase', btnText: 'Bücher' }, { id: 'additional-services', title: 'Zusätzliche Dienstleistungen', btnText: 'Dienstleistungen' } ]; // --- Helper Functions --- function getSectionIdFromTitle(title) { if (title.includes('Morphologische Deckspelzen')) return 'morphological-lemmas'; if (title.includes('Suchphrasen - Google')) return 'google-search-section'; if (title.includes('Suchphrasen - Yandex')) return 'yandex-search-section'; if (title.includes('Zusätzliche Dienstleistungen')) return 'additional-services'; return null; } function saveSectionState(sectionId, isCollapsed) { const savedState = localStorage.getItem(STORAGE_KEY); const state = savedState ? JSON.parse(savedState) : {}; state[sectionId] = isCollapsed; localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } function loadSectionState(sectionId) { const savedState = localStorage.getItem(STORAGE_KEY); if (!savedState) return false; const state = JSON.parse(savedState); return state[sectionId] || false; } // --- Create Floating TOC --- function createFloatingTOC() { // Find all major sections (cards with a card-header) const sections = Array.from(document.querySelectorAll('.card')).map(card => { const headerEl = card.querySelector('.card-header'); if (!headerEl) return null; const titleEl = headerEl.querySelector('.card-title'); if (!titleEl) return null; const title = titleEl.innerText.trim(); // Skip sections that are too small or not needed const skipTitles = [ ]; let shouldSkip = false; for (const skipTitle of skipTitles) { if (title.includes(skipTitle) || title === skipTitle) { shouldSkip = true; break; } } if (shouldSkip) return null; // Create a unique ID for the section if it doesn't have one let id = card.id; if (!id) { id = 'section-' + Math.random().toString(36).substr(2, 8); card.id = id; } return { id, title, element: card }; }).filter(s => s !== null); if (sections.length < 2) return; // Create TOC container const tocContainer = document.createElement('div'); tocContainer.id = 'floating-toc'; tocContainer.className = 'floating-toc'; tocContainer.innerHTML = ` <div class="floating-toc-header"> <span>📑 Inhaltsverzeichnis</span> <button id="toc-toggle-btn" class="toc-toggle-btn" aria-label="Inhaltsverzeichnis einklappen">−</button> </div> <div id="toc-links-container" class="toc-links-container"> ${sections.map(section => `<a href="#${section.id}" class="toc-link" data-id="${section.id}">${escapeHtml(section.title)}</a>`).join('')} </div> `; document.body.appendChild(tocContainer); // Toggle TOC visibility const toggleBtn = document.getElementById('toc-toggle-btn'); const linksContainer = document.getElementById('toc-links-container'); let isTocExpanded = true; toggleBtn.addEventListener('click', () => { if (isTocExpanded) { linksContainer.style.display = 'none'; toggleBtn.textContent = '+'; tocContainer.classList.add('collapsed'); } else { linksContainer.style.display = 'block'; toggleBtn.textContent = '−'; tocContainer.classList.remove('collapsed'); } isTocExpanded = !isTocExpanded; }); // Highlight active section on scroll const tocLinks = document.querySelectorAll('.toc-link'); const sectionElements = sections.map(s => s.element); function updateActiveLink() { let current = ''; const scrollPosition = window.scrollY + 100; for (const section of sectionElements) { const sectionTop = section.offsetTop; const sectionBottom = sectionTop + section.offsetHeight; if (scrollPosition >= sectionTop && scrollPosition < sectionBottom) { current = section.id; break; } } tocLinks.forEach(link => { link.classList.remove('active'); if (link.getAttribute('data-id') === current) { link.classList.add('active'); } }); } window.addEventListener('scroll', updateActiveLink); updateActiveLink(); // Smooth scroll tocLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const targetId = link.getAttribute('data-id'); const targetElement = document.getElementById(targetId); if (targetElement) { targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); } // --- Add Collapsible Buttons to Sections --- function addCollapsibleButtons() { SECTIONS_TO_COLLAPSE.forEach(section => { // Find the card by its title let targetCard = null; const cards = document.querySelectorAll('.card'); for (const card of cards) { const titleEl = card.querySelector('.card-title'); if (titleEl && titleEl.innerText.trim() === section.title) { targetCard = card; break; } } if (!targetCard) return; // Set ID if not exists if (!targetCard.id) targetCard.id = section.id; // Create button const button = document.createElement('button'); button.className = 'collapse-section-btn'; button.setAttribute('data-section-id', section.id); button.setAttribute('aria-label', 'Abschnitt ein-/ausblenden'); // Get initial state const isCollapsed = loadSectionState(section.id); if (isCollapsed) { button.innerHTML = '➕'; targetCard.classList.add('section-collapsed'); const content = targetCard.querySelector('.card-content'); if (content) content.style.display = 'none'; } else { button.innerHTML = '➖'; targetCard.classList.remove('section-collapsed'); const content = targetCard.querySelector('.card-content'); if (content) content.style.display = ''; } // Insert button into card-header const cardHeader = targetCard.querySelector('.card-header'); if (cardHeader) { const iconDiv = cardHeader.querySelector('.card-icon'); if (iconDiv) { cardHeader.insertBefore(button, iconDiv); } else { cardHeader.appendChild(button); } // Add click handler button.addEventListener('click', (e) => { e.stopPropagation(); const sectionId = button.getAttribute('data-section-id'); const card = document.getElementById(sectionId); if (!card) return; const content = card.querySelector('.card-content'); if (!content) return; if (card.classList.contains('section-collapsed')) { // Expand content.style.display = ''; card.classList.remove('section-collapsed'); button.innerHTML = '➖'; saveSectionState(sectionId, false); } else { // Collapse content.style.display = 'none'; card.classList.add('section-collapsed'); button.innerHTML = '➕'; saveSectionState(sectionId, true); } }); } }); } // --- Create Back to Top Button --- function createBackToTopButton() { const btn = document.createElement('button'); btn.id = 'back-to-top'; btn.innerHTML = '↑'; btn.setAttribute('aria-label', 'Zurück nach oben'); btn.className = 'back-to-top-btn'; document.body.appendChild(btn); window.addEventListener('scroll', () => { if (window.scrollY > 500) { btn.classList.add('visible'); } else { btn.classList.remove('visible'); } }); btn.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); } // --- Improve Table Responsiveness (add scroll hints) --- function improveTables() { const tables = document.querySelectorAll('.data-table, .tasks-table'); tables.forEach(table => { const container = table.closest('.table-responsive') || table.parentElement; if (container && !container.querySelector('.scroll-hint')) { const hint = document.createElement('div'); hint.className = 'scroll-hint'; hint.innerHTML = '← → Wischen Sie, um mehr anzuzeigen'; hint.style.cssText = ` display: none; text-align: center; font-size: 0.7rem; color: var(--gray); margin-top: 0.5rem; padding: 0.25rem; background: rgba(67, 97, 238, 0.05); border-radius: 12px; `; container.style.position = 'relative'; container.appendChild(hint); // Show hint if table is wider than container function checkScroll() { if (container.scrollWidth > container.clientWidth) { hint.style.display = 'block'; } else { hint.style.display = 'none'; } } checkScroll(); window.addEventListener('resize', checkScroll); container.addEventListener('scroll', () => { hint.style.opacity = '0.5'; setTimeout(() => { hint.style.opacity = '1'; }, 300); }); } }); } // --- Escape HTML helper --- function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // --- Add custom styles for new elements --- function addCustomStyles() { const style = document.createElement('style'); style.textContent = ` /* Floating TOC */ .floating-toc { position: fixed; top: 80px; right: 20px; z-index: 1000; background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); border: 1px solid #e9ecef; width: 260px; max-width: calc(100% - 40px); transition: all 0.3s ease; backdrop-filter: blur(8px); background: rgba(255,255,255,0.95); } .floating-toc-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; background: linear-gradient(135deg, var(--primary), var(--secondary)); color: white; border-radius: 12px 12px 0 0; font-weight: 600; font-size: 0.9rem; cursor: pointer; } .toc-toggle-btn { background: none; border: none; color: white; font-size: 1.2rem; cursor: pointer; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: background 0.2s; } .toc-toggle-btn:hover { background: rgba(255,255,255,0.2); } .floating-toc.collapsed { width: auto; } .floating-toc.collapsed .floating-toc-header { border-radius: 12px; } .toc-links-container { max-height: 400px; overflow-y: auto; padding: 8px 0; } .toc-link { display: block; padding: 8px 15px; color: var(--dark); text-decoration: none; font-size: 0.85rem; transition: all 0.2s; border-left: 3px solid transparent; } .toc-link:hover { background: rgba(67, 97, 238, 0.05); color: var(--primary); padding-left: 20px; } .toc-link.active { background: rgba(67, 97, 238, 0.1); border-left-color: var(--primary); color: var(--primary); font-weight: 500; } /* Collapse button in cards */ .collapse-section-btn { background: none; border: none; font-size: 1.1rem; cursor: pointer; width: 28px; height: 28px; border-radius: 6px; display: inline-flex; align-items: center; justify-content: center; transition: all 0.2s; color: var(--gray); margin-right: 8px; } .collapse-section-btn:hover { background: rgba(67, 97, 238, 0.1); color: var(--primary); } .card.section-collapsed .card-header { border-bottom: none; } /* Back to Top Button */ .back-to-top-btn { position: fixed; bottom: 30px; right: 30px; width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, var(--primary), var(--secondary)); color: white; border: none; cursor: pointer; font-size: 1.5rem; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: all 0.3s ease; z-index: 999; box-shadow: 0 4px 12px rgba(67, 97, 238, 0.4); } .back-to-top-btn.visible { opacity: 1; visibility: visible; } .back-to-top-btn:hover { transform: translateY(-3px); box-shadow: 0 6px 16px rgba(67, 97, 238, 0.5); } /* Mobile adjustments */ @media (max-width: 768px) { .floating-toc { top: auto; bottom: 20px; right: 20px; left: auto; width: 48px; overflow: hidden; } .floating-toc .floating-toc-header span { display: none; } .floating-toc .floating-toc-header { justify-content: center; padding: 12px; } .floating-toc .toc-toggle-btn { margin: 0; } .floating-toc:not(.collapsed) { width: 260px; left: auto; right: 20px; bottom: 20px; top: auto; } .floating-toc:not(.collapsed) .floating-toc-header span { display: inline; } .floating-toc:not(.collapsed) .floating-toc-header { justify-content: space-between; } .back-to-top-btn { bottom: 80px; right: 20px; width: 40px; height: 40px; font-size: 1.2rem; } .scroll-hint { display: block !important; font-size: 0.65rem; } } /* Smooth scroll behavior */ html { scroll-behavior: smooth; } /* Better card transitions */ .card .card-content { transition: display 0.2s ease; } /* RTL support for languages like Arabic/Hebrew */ [dir="rtl"] .floating-toc { right: auto; left: 20px; } [dir="rtl"] .back-to-top-btn { right: auto; left: 30px; } [dir="rtl"] .collapse-section-btn { margin-right: 0; margin-left: 8px; } `; document.head.appendChild(style); } // --- Initialize everything when DOM is ready --- function init() { addCustomStyles(); createFloatingTOC(); addCollapsibleButtons(); createBackToTopButton(); improveTables(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })(); </script></div> <!-- Footer --> <footer> <div class="footer-container"> <div class="footer-content"> <div class="footer-section"> <h3>About serpuls.com</h3> <div class="footer-info"> <p>Professionelles Tool zur Suchmaschinenanalyse, Positionsüberwachung und SEO-Optimierung.</p> <p style="margin-top: 1rem;"><strong>Skynet360 LP.</strong></p> <p><strong>Firmennr:</strong> SL027564</p> <p><strong>Adresse:</strong> Suite 1 4 Queen Street, Edinburgh, Scotland, EH2 1JE</p> </div> </div> <div class="footer-section"> <h3>Quicklinks</h3> <ul class="footer-links"> <li><a href="/de/">Heim</a></li> <li><a href="/de/profile/">Profil</a></li> <li><a href="/de/tasks/">Überwachungsaufgaben</a></li> <li><a href="/de/api_docs/">API-Dokumentation</a></li> <li><a href="/de/team/">Unser Team</a></li> </ul> </div> <div class="footer-section"> <h3>Ressourcen</h3> <ul class="footer-links"> <li><a href="/de/blogs/">Blog</a></li> <li><a href="/de/faq/">FAQ</a></li> <li><a href="/de/support/">Unterstützung</a></li> </ul> </div> <div class="footer-section"> <h3>Kontakte</h3> <div class="footer-info"> <p>E-Mail: <a href="mailto:info@serpuls.com">info@serpuls.com</a></p> <p>Telegramm: <a href="https://t.me/serpulse_support">@serpulse_support</a></p> <div class="social-links"> <a href="#" class="social-link" title="Telegramm">📱</a> <a href="https://github.com/SkyNetJose/serpuls" class="social-link" title="GitHub">💻</a> <a href="#" class="social-link" title="Twitter">🐦</a> </div> </div> </div> </div> <div class="footer-bottom"> <p class="copyright">© 2026 serpuls.com. Alle Rechte vorbehalten. Skynet360 LP.</p> <div class="footer-bottom-links"> <a href="/de/privacy/">Datenschutzrichtlinie</a> <a href="/de/terms/">Nutzungsbedingungen</a> <a href="/de/agreement/">Benutzervereinbarung</a> <a href="/de/cookie/">Cookie-Richtlinie</a> </div> </div> </div> </footer> <!-- Updated Payment Modal HTML --> <div class="modal" id="paymentModal"> <div class="modal-content"> <span class="close-btn">×</span> <div class="modal-success" id="paymentSuccess"> <div class="success-icon">✓</div> <h3>Zahlung erfolgreich!</h3> <p>Vielen Dank für Ihre Bestellung. Wir werden uns in Kürze mit Ihnen in Verbindung setzen.</p> <button class="cta-button" onclick="closeModal('paymentModal')">Schließen</button> </div> <form id="paymentForm" onsubmit="return processPayment(event)"> <h2 class="modal-title" id="modalTitle">Bezahlung für den Service</h2> <p class="modal-text" id="modalText">Leistungsbeschreibung</p> <!-- Hidden fields for payment type and ID --> <input type="hidden" id="paymentType" name="payment_type" value=""> <input type="hidden" id="paymentItemId" name="payment_item_id" value=""> <!-- Payment Options --> <div class="payment-options" id="paymentOptions"> <!-- Options will be dynamically generated here --> </div> <!-- Manual Price Input (Optional) - Now dynamically shown based on config --> <div class="form-group" id="manualPriceGroup" style="display: none;"> <label for="manualPrice">Betrag eingeben (USD):</label> <input type="number" id="manualPrice" class="form-control" placeholder="Betrag eingeben" min="1" max="10000" step="0.01" oninput="updateManualPrice(this.value)"> <div class="error-message" id="manualPriceError"></div> </div> <!-- Custom Price Option Toggle - Now dynamically shown --> <div class="form-group" id="customPriceToggle" style="display: none; margin-bottom: 1rem;"> <label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;"> <input type="radio" id="customPriceRadio" name="paymentOption" value="custom" onchange="toggleCustomPriceSelection(this.checked)"> Geben Sie einen benutzerdefinierten Betrag ein </label> </div> <div class="form-group"> <label for="modalName">Name:</label> <input type="text" id="modalName" class="form-control" placeholder="Geben Sie Ihren Namen ein" required> <div class="error-message" id="modalNameError"></div> </div> <div class="form-group"> <label for="modalEmail">E-Mail:</label> <input type="email" id="modalEmail" class="form-control" placeholder="Geben Sie Ihre E-Mail-Adresse ein" required> <div class="error-message" id="modalEmailError"></div> </div> <p class="modal-price" id="modalPrice">Preis: $0.00</p> <button type="submit" class="cta-button" id="payButton">Zahlen $0.00</button> </form> </div> </div> <!-- Redirect Modal (for iframe) --> <div class="modal" id="redirectModal"> <div class="modal-content modal-large"> <span class="close-btn">×</span> <h2 class="modal-title">Zahlung</h2> <iframe id="redirectIframe" class="modal-iframe" src=""></iframe> </div> </div> <!-- Auth Modal --> <div class="modal" id="authModal"> <div class="modal-content"> <span class="close-btn">×</span> <div class="auth-tabs"> <div class="auth-tab active" data-tab="login">Login</div> <div class="auth-tab" data-tab="register">Registrieren</div> </div> <div class="auth-form active" id="loginForm"> <h2 class="modal-title">Melden Sie sich bei Ihrem Konto an</h2> <!-- Regular Login --> <div class="form-group"> <label for="loginEmail">E-Mail:</label> <input type="email" id="loginEmail" class="form-control" placeholder="Geben Sie Ihre E-Mail-Adresse ein"> <div class="error-message" id="loginEmailError"></div> </div> <div class="form-group"> <label for="loginPassword">Passwort:</label> <input type="password" id="loginPassword" class="form-control" placeholder="Geben Sie Ihr Passwort ein"> <div class="error-message" id="loginPasswordError"></div> </div> <button class="cta-button" style="width: 100%;" onclick="processLogin()">Login</button> <!-- Telegram Login Option --> <div style="text-align: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #e9ecef;"> <p style="margin-bottom: 1rem; color: var(--gray);">Oder melden Sie sich per Telegram an</p> <button class="cta-button" style="background: #0088cc; width: 100%;" onclick="loginWithTelegram()"> <span style="margin-right: 0.5rem;">📱</span> Login per Telegram </button> </div> </div> <div class="auth-form" id="registerForm"> <h2 class="modal-title">Registrieren Sie sich per Telegram</h2> <!-- Registration Form --> <div class="form-group"> <label for="registerName">Name:</label> <input type="text" id="registerName" class="form-control" placeholder="Geben Sie Ihren Namen ein" required> <div class="error-message" id="registerNameError"></div> </div> <div class="form-group"> <label for="registerEmail">E-Mail:</label> <input type="email" id="registerEmail" class="form-control" placeholder="Geben Sie Ihre E-Mail-Adresse ein" required> <div class="error-message" id="registerEmailError"></div> </div> <button class="cta-button" style="background: #0088cc; width: 100%; padding: 1rem; font-size: 1.1rem;" onclick="startTelegramRegistration()"> <span style="margin-right: 0.5rem;">🚀</span> Setzen Sie die Registrierung im Telegram fort </button> <div class="how-it-works"> <h4 class="how-it-works-title">So funktioniert es:</h4> <ol class="how-it-works-list"> <li>Geben Sie oben Ihren Namen und Ihre E-Mail-Adresse ein</li> <li>Klicken Sie auf die Schaltfläche, um Telegram zu öffnen</li> <li>Vollständige Registrierung im Bot (Telefonnummer erforderlich)</li> <li>Erhalten Sie Zugangsdaten per E-Mail</li> </ol> </div> </div> </div> </div> <!-- Telegram Code Input Modal --> <div class="modal" id="telegramCodeModal"> <div class="modal-content"> <span class="close-btn" onclick="closeModal('telegramCodeModal')">×</span> <div class="centered-content"> <div class="success-icon">🔐</div> <h2 class="modal-title">Bestätigungscode</h2> <p class="modal-text">Der Code wurde an Telegram gesendet. Geben Sie es unten ein:</p> <div class="form-group"> <input type="text" id="telegramLoginCode" class="form-control verify-code-input" placeholder="0000000" maxlength="7" style="font-size: 1.5rem; letter-spacing: 0.5rem;"> <div class="error-message" id="telegramLoginCodeError"></div> </div> <button class="cta-button" style="width: 100%; margin-top: 1rem;" onclick="verifyTelegramLoginCode()"> Bestätigen Sie die Anmeldung </button> <div class="timer-container" id="loginCodeTimer"> Code gültig für: <span id="loginTimer">05:00</span> </div> </div> </div> </div> <!-- Updated Telegram Verification Modal --> <div class="modal" id="telegramVerifyModal"> <div class="modal-content"> <span class="close-btn" onclick="closeTelegramVerifyModal()">×</span> <div class="centered-content"> <div class="success-icon">📱</div> <h2 class="modal-title">Vollständige Registrierung im Telegram</h2> <p class="modal-text" id="telegramModalText"> Telegram wurde in einem neuen Tab geöffnet.<br> Wenn der Bot nicht automatisch geöffnet wurde, verwenden Sie die Schaltfläche unten oder scannen Sie den QR-Code. </p> <!-- QR Code Container --> <div id="qrCodeContainer" style="margin: 1.5rem 0; text-align: center;"> <div id="qrcode" style="display: inline-block;"></div> <p style="font-size: 0.9rem; color: var(--gray); margin-top: 0.5rem;"> Scannen Sie den QR-Code, um den Bot zu öffnen </p> </div> <!-- Timer and Status --> <div id="telegramTimer" class="timer-container"> Warte auf Bestätigung... <span id="timer">05:00</span> </div> <!-- Action Buttons --> <div style="display: flex; gap: 1rem; margin-top: 1.5rem; flex-wrap: wrap; justify-content: center;"> <button class="cta-button" style="background: #0088cc;" onclick="openTelegramBot()" id="openTelegramBtn"> 📱 Öffnen Sie Telegram erneut </button> <button class="cta-button secondary" onclick="refreshTelegramQR()" id="refreshQRBtn"> 🔄 QR-Code aktualisieren </button> </div> <!-- Status Info --> <div class="info-box" style="margin-top: 1.5rem;"> <p style="margin: 0; font-size: 0.9rem;"> <strong>So funktioniert es:</strong><br> 1. Klicken Sie auf „Telegram öffnen“ oder scannen Sie den QR-Code<br> 2. Klicken Sie im Bot auf START und teilen Sie Ihre Telefonnummer mit<br> 3. Der Bestätigungsstatus wird automatisch aktualisiert </p> </div> </div> </div> </div> <!-- Email Verification Modal --> <div class="modal" id="emailVerifyModal"> <div class="modal-content"> <span class="close-btn" onclick="closeEmailVerifyModal()">×</span> <div class="centered-content"> <div class="success-icon">📧</div> <h2 class="modal-title">E-Mail-Verifizierung</h2> <p class="modal-text">Wir haben eine Bestätigungs-E-Mail an Ihre Adresse gesendet</p> <!-- Email Verification Input --> <div class="form-group"> <label for="emailVerifyToken">Bestätigungscode:</label> <input type="text" id="emailVerifyToken" class="form-control" placeholder="Geben Sie den Code aus der E-Mail ein" style="text-align: center; font-size: 1.1rem;"> <div class="error-message" id="emailVerifyTokenError"></div> </div> <button class="cta-button" style="width: 100%; margin-bottom: 1rem;" onclick="verifyEmailToken()"> E-Mail bestätigen </button> <button id="resendEmailBtn" class="cta-button secondary" style="width: 100%;" onclick="resendEmailVerification()" disabled> Code erneut senden (<span id="resendTimer">300</span>) </button> <!-- Auto-check status --> <div id="emailCheckStatus" style="margin-top: 1rem; display: none;"> <p style="color: var(--gray); font-size: 0.9rem;"> 🔍 Überprüfungsstatus wird überprüft... </p> </div> </div> </div> </div> <!-- Image reusable Modal --> <div class="modal" id="imgModal"> <div class="modal-content"> <span class="close-btn">×</span> <img id="modalImg" src="" style="max-width:90%; max-height:90%; margin-top:40px; border-radius:8px;"> </div> </div> <!--Loading overlay --> <div class="loading-overlay" id="loadingOverlay"> <div class="loading-spinner"></div> </div> <div class="toast" id="toast"></div> <script> // Get language from URL path like /en/about or /ru/products function getLangFromUrl() { const path = window.location.pathname; const match = path.match(/^\/([a-z]{2})(\/|$)/i); return match ? match[1] : 'en'; // default to 'en' if no language code found } // Store in global variable window.currentLang = getLangFromUrl(); // Show loading overlay function showLoading() { document.getElementById('loadingOverlay').classList.add('active'); } // Hide loading overlay function hideLoading() { document.getElementById('loadingOverlay').classList.remove('active'); } // Show toast notification function showToast(message, type = 'info') { const toast = document.getElementById('toast'); toast.textContent = message; toast.className = 'toast ' + type; toast.classList.add('show'); setTimeout(() => { toast.classList.remove('show'); }, 3000); } // Check user login status on page load document.addEventListener('DOMContentLoaded', function() { checkUserLoginStatus(); }); // Function to check if user is logged in function checkUserLoginStatus() { const userInfoElement = document.getElementById('userInfo'); const guestButtonsElement = document.getElementById('guestButtons'); const userNameElement = document.getElementById('userName'); // Show loading state or keep defaults while checking if (guestButtonsElement) { guestButtonsElement.style.display = 'flex'; } if (userInfoElement) { userInfoElement.style.display = 'none'; } const data = { type: 'check_user_logined' }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { if (response.user_logined === 1) { // User is logged in if (userInfoElement) { userInfoElement.style.display = 'block'; } if (guestButtonsElement) { guestButtonsElement.style.display = 'none'; } if (userNameElement) { // Use logined_user_name if available, otherwise show default userNameElement.textContent = response.logined_user_name || 'Profil'; } // Optionally store minimal info in localStorage for quick access // but don't rely on it for auth state if (response.logined_user_id) { localStorage.setItem('user_id', response.logined_user_id); } if (response.logined_user_name) { localStorage.setItem('user_name', response.logined_user_name); } } else { // User is not logged in if (userInfoElement) { userInfoElement.style.display = 'none'; } if (guestButtonsElement) { guestButtonsElement.style.display = 'flex'; } // Clear any previously stored user data localStorage.removeItem('user_id'); localStorage.removeItem('user_name'); localStorage.removeItem('user_info'); } } else { console.error('Fehler beim Überprüfen des Anmeldestatus:', response.error); // On error, default to guest view if (userInfoElement) { userInfoElement.style.display = 'none'; } if (guestButtonsElement) { guestButtonsElement.style.display = 'flex'; } } }, false); // Don't show loading overlay for this quick check } // Logout function function logout() { // Send logout request to server const data = { type: 'logout' }; sendRequest('/ajax/', data, function(response) { // Clear all user data from localStorage regardless of response localStorage.removeItem('user_info'); localStorage.removeItem('user_id'); localStorage.removeItem('user_name'); localStorage.removeItem('current_user_token'); // Update UI checkUserLoginStatus(); // Redirect to main page window.location.href = '/'; }); } </script> <script> // Mobile menu toggle - Combined approach document.getElementById('hamburger').addEventListener('click', function() { const mobileMenu = document.getElementById('mobileMenu'); mobileMenu.classList.toggle('active'); }); // Close mobile menu when clicking on links or buttons document.querySelectorAll('.nav-links a, .auth-buttons button').forEach(element => { element.addEventListener('click', function() { document.getElementById('mobileMenu').classList.remove('active'); }); }); // Close mobile menu when clicking outside document.addEventListener('click', function(event) { const mobileMenu = document.getElementById('mobileMenu'); const hamburger = document.getElementById('hamburger'); const navContainer = document.querySelector('.nav-container'); if (!navContainer.contains(event.target) && mobileMenu.classList.contains('active')) { mobileMenu.classList.remove('active'); } }); // Simple animation on scroll document.addEventListener('DOMContentLoaded', function() { const cards = document.querySelectorAll('.card, .domain-header, .chart-container, .lemma-card'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; } }); }, { threshold: 0.0001, // Optional: keep small percentage rootMargin: '350px 0px 350px 0px' // Trigger 100px before element enters }); cards.forEach(card => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; card.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; observer.observe(card); }); }); // Toggle links visibility function toggleLinks(className, btn, max_count = 5) { const list = document.getElementById(className); const items = list.querySelectorAll('li'); const hiddenItems = list.querySelectorAll('li.hidden'); if (hiddenItems.length > 0) { // Show all list.querySelectorAll('li.hidden').forEach(el => el.classList.remove('hidden')); btn.textContent = 'Verstecken'; } else { // Hide all beyond the first 5 items.forEach((el, index) => { if (index >= max_count) el.classList.add('hidden'); }); btn.textContent = 'Mehr anzeigen'; } } // Toggle raw data visibility function toggleRawData(elementId, btn) { const element = document.getElementById(elementId); if (element.style.maxHeight === '200px') { element.style.maxHeight = 'none'; btn.textContent = 'Vollständige Daten ausblenden'; } else { element.style.maxHeight = '200px'; btn.textContent = 'Vollständige Daten anzeigen'; } } </script> <script> // Payment Modal // Global variable to store payment config let paymentConfig = { allowCustomAmount: false, minAmount: 1, maxAmount: 1000 }; // Payment options functionality document.addEventListener('DOMContentLoaded', function() { // Replace the entire payment options event listener with this: document.addEventListener('change', function(e) { if (e.target && e.target.name === 'paymentOption') { // Update selected state for regular options document.querySelectorAll('.payment-option').forEach(opt => { opt.classList.remove('selected'); }); // Handle custom price radio separately if (e.target.id === 'customPriceRadio') { // For custom price, we don't have a .payment-option wrapper // Just update the custom price toggle UI document.querySelectorAll('.payment-option').forEach(opt => { opt.classList.remove('selected'); }); // You might want to add visual feedback for custom price selection const customPriceToggle = document.getElementById('customPriceToggle'); if (customPriceToggle) { customPriceToggle.style.backgroundColor = 'rgba(67, 97, 238, 0.1)'; customPriceToggle.style.borderLeft = '3px solid var(--primary)'; customPriceToggle.style.padding = '0.5rem'; customPriceToggle.style.borderRadius = 'var(--border-radius)'; } } else { // For regular options, highlight the .payment-option container const paymentOption = e.target.closest('.payment-option'); if (paymentOption) { paymentOption.classList.add('selected'); // Reset custom price toggle styling if it was highlighted const customPriceToggle = document.getElementById('customPriceToggle'); if (customPriceToggle) { customPriceToggle.style.backgroundColor = ''; customPriceToggle.style.borderLeft = ''; customPriceToggle.style.padding = ''; } } } // Update price (this should work for all options) updatePaymentPrice(e.target.value); } }); }); // Update the updatePaymentPrice function to handle custom price: function updatePaymentPrice(price) { const modalPrice = document.getElementById('modalPrice'); const payButton = document.getElementById('payButton'); const customPriceRadio = document.getElementById('customPriceRadio'); // Uncheck custom price radio if regular option is selected if (customPriceRadio && customPriceRadio.checked === false) { document.getElementById('manualPriceGroup').style.display = 'none'; // Reset custom price toggle styling const customPriceToggle = document.getElementById('customPriceToggle'); if (customPriceToggle) { customPriceToggle.style.backgroundColor = ''; customPriceToggle.style.borderLeft = ''; customPriceToggle.style.padding = ''; } } modalPrice.textContent = 'Preis: $' + parseFloat(price).toFixed(2); payButton.textContent = 'Zahlen $' + parseFloat(price).toFixed(2); payButton.disabled = false; payButton.style.opacity = '1'; } // Generate payment options based on config function generatePaymentOptions(optionsData, paymentType = 'balance', paymentItemId = '', config = {}) { const paymentOptionsContainer = document.getElementById('paymentOptions'); const paymentTypeInput = document.getElementById('paymentType'); const paymentItemIdInput = document.getElementById('paymentItemId'); const customPriceToggle = document.getElementById('customPriceToggle'); const manualPriceGroup = document.getElementById('manualPriceGroup'); // Set hidden fields paymentTypeInput.value = paymentType; paymentItemIdInput.value = paymentItemId; // Store config globally paymentConfig = { allowCustomAmount: config.allowCustomAmount || false, minAmount: config.minAmount || 1, maxAmount: config.maxAmount || 1000 }; // Clear container paymentOptionsContainer.innerHTML = ''; // Generate regular options if (optionsData && optionsData.length > 0) { optionsData.forEach((option, index) => { const optionElement = document.createElement('div'); optionElement.className = 'payment-option'; optionElement.innerHTML = ` <input type="radio" id="option${index}" name="paymentOption" value="${option.price}" data-label="${option.label}" ${option.default ? 'checked' : ''} onchange="updatePaymentPrice('${option.price}')"> <label for="option${index}">${option.label} - $${option.price}</label> `; paymentOptionsContainer.appendChild(optionElement); }); paymentOptionsContainer.style.display = 'block'; } else { paymentOptionsContainer.style.display = 'none'; } // Show/hide custom amount option based on config if (paymentConfig.allowCustomAmount) { customPriceToggle.style.display = 'block'; // Update manual price input constraints document.getElementById('manualPrice').min = paymentConfig.minAmount; document.getElementById('manualPrice').max = paymentConfig.maxAmount; document.getElementById('manualPrice').placeholder = 'Aus $${paymentConfig.minAmount} Zu $${paymentConfig.maxAmount}'; } else { customPriceToggle.style.display = 'none'; manualPriceGroup.style.display = 'none'; } // Select first option by default if none is marked as default if (optionsData && optionsData.length > 0) { const defaultOption = paymentOptionsContainer.querySelector('input[type="radio"][checked]') || paymentOptionsContainer.querySelector('input[type="radio"]'); if (defaultOption) { defaultOption.checked = true; defaultOption.closest('.payment-option').classList.add('selected'); updatePaymentPrice(defaultOption.value); } } else if (paymentConfig.allowCustomAmount) { // If no options but custom amount is allowed, show custom by default document.getElementById('customPriceRadio').checked = true; toggleCustomPriceSelection(true); } } // Also update the toggleCustomPriceSelection function: function toggleCustomPriceSelection(isSelected) { const manualPriceGroup = document.getElementById('manualPriceGroup'); const paymentOptions = document.getElementById('paymentOptions'); const customPriceRadio = document.getElementById('customPriceRadio'); if (isSelected) { manualPriceGroup.style.display = 'block'; // Deselect all regular options document.querySelectorAll('input[name="paymentOption"]').forEach(radio => { if (radio.id !== 'customPriceRadio') { radio.checked = false; const paymentOption = radio.closest('.payment-option'); if (paymentOption) { paymentOption.classList.remove('selected'); } } }); // Visual feedback for custom price selection const customPriceToggle = document.getElementById('customPriceToggle'); if (customPriceToggle) { customPriceToggle.style.backgroundColor = 'rgba(67, 97, 238, 0.1)'; customPriceToggle.style.borderLeft = '3px solid var(--primary)'; customPriceToggle.style.padding = '0.5rem'; customPriceToggle.style.borderRadius = 'var(--border-radius)'; customPriceToggle.style.transition = 'var(--transition)'; } // Focus on manual price input setTimeout(() => { const manualInput = document.getElementById('manualPrice'); manualInput.focus(); // Update price if there's already a value if (manualInput.value) { updateManualPrice(manualInput.value); } else { // Set default to min amount updateManualPrice(paymentConfig.minAmount); manualInput.value = paymentConfig.minAmount; } }, 100); } else { manualPriceGroup.style.display = 'none'; // Reset custom price toggle styling const customPriceToggle = document.getElementById('customPriceToggle'); if (customPriceToggle) { customPriceToggle.style.backgroundColor = ''; customPriceToggle.style.borderLeft = ''; customPriceToggle.style.padding = ''; } } } // Update price from manual input in real-time function updateManualPrice(price) { if (!price || price < paymentConfig.minAmount || price > paymentConfig.maxAmount) { const modalPrice = document.getElementById('modalPrice'); const payButton = document.getElementById('payButton'); if (price && (price < paymentConfig.minAmount || price > paymentConfig.maxAmount)) { modalPrice.textContent = 'Preis: aus $${paymentConfig.minAmount} Zu $${paymentConfig.maxAmount}'; payButton.textContent = 'Ungültiger Betrag'; payButton.disabled = true; payButton.style.opacity = '0.6'; } else { modalPrice.textContent = 'Preis: $0.00'; payButton.textContent = 'Zahlen $0.00'; payButton.disabled = true; payButton.style.opacity = '0.6'; } return; } const modalPrice = document.getElementById('modalPrice'); const payButton = document.getElementById('payButton'); modalPrice.textContent = 'Preis: $' + parseFloat(price).toFixed(2); payButton.textContent = 'Zahlen $' + parseFloat(price).toFixed(2); payButton.disabled = false; payButton.style.opacity = '1'; } const paymentModal = document.getElementById('paymentModal'); const paymentCloseBtn = paymentModal.querySelector('.close-btn'); // Elements inside modal const modalTitle = document.getElementById('modalTitle'); const modalText = document.getElementById('modalText'); const modalPrice = document.getElementById('modalPrice'); const payButton = document.getElementById('payButton'); const paymentForm = document.getElementById('paymentForm'); const paymentSuccess = document.getElementById('paymentSuccess'); // Updated modal opening handler document.querySelectorAll('[data-modal="paymentModal"]').forEach(trigger => { trigger.addEventListener('click', function(e) { e.preventDefault(); // Get data attributes const title = this.getAttribute('data-title'); const text = this.getAttribute('data-text'); const price = this.getAttribute('data-price'); const paymentType = this.getAttribute('data-payment-type') || 'balance'; const paymentItemId = this.getAttribute('data-payment-item-id') || ''; // Get payment config from data attributes const allowCustomAmount = this.getAttribute('data-allow-custom') === 'true'; const minAmount = parseFloat(this.getAttribute('data-min-amount')) || 1; const maxAmount = parseFloat(this.getAttribute('data-max-amount')) || 1000; // Get payment options from data attribute const optionsData = this.getAttribute('data-options'); let paymentOptions = []; if (optionsData) { try { paymentOptions = JSON.parse(optionsData); } catch (e) { console.error('Fehler beim Parsen der Zahlungsoptionen:', e); } } // Fill modal content document.getElementById('modalTitle').textContent = title; document.getElementById('modalText').textContent = text; // Generate payment options with config const config = { allowCustomAmount: allowCustomAmount, minAmount: minAmount, maxAmount: maxAmount }; generatePaymentOptions(paymentOptions, paymentType, paymentItemId, config); // If no options and custom amount not allowed, use default price if (paymentOptions.length === 0 && !allowCustomAmount) { updatePaymentPrice(price); } // Reset form resetForm('paymentModal'); document.getElementById('paymentForm').style.display = 'block'; document.getElementById('paymentSuccess').style.display = 'none'; // Show modal document.getElementById('paymentModal').style.display = 'flex'; }); }); // Helper function to reset form function resetPaymentForm() { document.getElementById('modalName').value = ''; document.getElementById('modalEmail').value = ''; document.getElementById('manualPrice').value = ''; if (document.getElementById('customPriceRadio')) { document.getElementById('customPriceRadio').checked = false; } clearErrors(['nameError', 'emailError', 'manualPriceError']); } // Close payment modal paymentCloseBtn.addEventListener('click', function() { paymentModal.style.display = 'none'; }); // Close on outside click window.addEventListener('click', function(e) { if (e.target === paymentModal) { paymentModal.style.display = 'none'; } }); // Fill tooltip with alt text automatically document.querySelectorAll('.popup-trigger').forEach(trigger => { const altText = trigger.getAttribute('alt'); const popupText = trigger.querySelector('.popup-text'); if (popupText && altText) { popupText.textContent = altText; } }); </script> <script> // Fill tooltip with alt text automatically document.querySelectorAll('.popup-trigger').forEach(trigger => { const altText = trigger.getAttribute('alt'); const popupText = trigger.querySelector('.popup-text'); if (popupText && altText) { popupText.textContent = altText; } }); // Auth Modal const authModal = document.getElementById('authModal'); const authCloseBtn = authModal.querySelector('.close-btn'); const loginBtn = document.getElementById('loginBtn'); const registerBtn = document.getElementById('registerBtn'); const authTabs = document.querySelectorAll('.auth-tab'); const authForms = document.querySelectorAll('.auth-form'); // Open auth modal loginBtn.addEventListener('click', function() { authModal.style.display = 'flex'; switchAuthTab('login'); resetForm('authModal'); }); registerBtn.addEventListener('click', function() { authModal.style.display = 'flex'; switchAuthTab('register'); resetForm('authModal'); }); // Close auth modal authCloseBtn.addEventListener('click', function() { authModal.style.display = 'none'; }); // Close on outside click window.addEventListener('click', function(e) { if (e.target === authModal) { authModal.style.display = 'none'; } }); // Switch auth tabs function switchAuthTab(tabName) { authTabs.forEach(tab => { if (tab.getAttribute('data-tab') === tabName) { tab.classList.add('active'); } else { tab.classList.remove('active'); } }); authForms.forEach(form => { if (form.id === tabName + 'Form') { form.classList.add('active'); } else { form.classList.remove('active'); } }); } // Add click events to auth tabs authTabs.forEach(tab => { tab.addEventListener('click', function() { const tabName = this.getAttribute('data-tab'); switchAuthTab(tabName); resetForm('authModal'); }); }); // Reset form and errors function resetForm(modalType) { if (modalType === 'paymentModal') { document.getElementById('modalName').value = ''; document.getElementById('modalEmail').value = ''; clearErrors(['nameError', 'emailError']); } else if (modalType === 'authModal') { document.getElementById('loginEmail').value = ''; document.getElementById('loginPassword').value = ''; document.getElementById('registerName').value = ''; document.getElementById('registerEmail').value = ''; if(document.getElementById('registerPassword'))document.getElementById('registerPassword').value = ''; if(document.getElementById('registerConfirmPassword'))document.getElementById('registerConfirmPassword').value = ''; clearErrors(['loginEmailError', 'loginPasswordError', 'registerNameError', 'registerEmailError', 'registerPasswordError', 'registerConfirmPasswordError']); } } // Clear error messages function clearErrors(errorIds) { errorIds.forEach(id => { const errorElement = document.getElementById(id); if (errorElement) { errorElement.textContent = ''; errorElement.classList.remove('show'); const inputElement = document.getElementById(id.replace('Error', '')); if (inputElement) { inputElement.classList.remove('error'); } } }); } // Show error message function showError(fieldId, message) { const errorElement = document.getElementById(fieldId + 'Error'); const inputElement = document.getElementById(fieldId); if (errorElement) { errorElement.textContent = message; errorElement.classList.add('show'); } if (inputElement) { inputElement.classList.add('error'); } } // Enhanced close modal function for general use function closeModal(modalId) { // Clear timers based on which modal is being closed switch(modalId) { case 'telegramVerifyModal': clearTelegramTimers(); break; case 'emailVerifyModal': clearEmailTimers(); break; case 'authModal': // Clear any potential timers in auth modal break; } document.getElementById(modalId).style.display = 'none'; } // Enhanced processPayment function function processPayment(event) { if (event) event.preventDefault(); const name = document.getElementById('modalName').value; const email = document.getElementById('modalEmail').value; const paymentType = document.getElementById('paymentType').value; const paymentItemId = document.getElementById('paymentItemId').value; // Get price - check if using custom amount or selected option let price; let optionLabel = ''; const customPriceRadio = document.getElementById('customPriceRadio'); if (customPriceRadio && customPriceRadio.checked) { // Custom amount price = document.getElementById('manualPrice').value; optionLabel = 'custom_amount'; // Validate custom amount if (!price || price < paymentConfig.minAmount || price > paymentConfig.maxAmount) { showError('manualPrice', 'Der Betrag muss zwischen liegen $${paymentConfig.minAmount} Und $${paymentConfig.maxAmount}'); return false; } } else { // Regular option const selectedOption = document.querySelector('input[name="paymentOption"]:checked'); if (selectedOption) { price = selectedOption.value; optionLabel = selectedOption.getAttribute('data-label') || 'selected_option'; } else { // Fallback to default price price = document.getElementById('modalPrice').textContent.match(/\d+\.\d+/)?.[0] || '0'; optionLabel = 'default'; } } const service = document.getElementById('modalTitle').textContent; // Clear previous errors clearErrors(['nameError', 'emailError', 'manualPriceError']); // Validate form let hasErrors = false; if (!name) { showError('modalName', 'Bitte geben Sie Ihren Namen ein'); hasErrors = true; } if (!email) { showError('modalEmail', 'Bitte geben Sie Ihre E-Mail-Adresse ein'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('modalEmail', 'Bitte geben Sie eine gültige E-Mail-Adresse ein'); hasErrors = true; } if (hasErrors) return false; // Prepare data for API request const data = { type: 'payment', name: name, email: email, price: parseFloat(price).toFixed(2), service: service, payment_type: paymentType, payment_item_id: paymentItemId, option: optionLabel, is_custom_amount: (customPriceRadio && customPriceRadio.checked) ? 1 : 0 }; // Send request to API sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { if (response.redirect_url) { handlePaymentRedirect(response.redirect_url, response.redirect_type); } else { // Show success message document.getElementById('paymentForm').style.display = 'none'; document.getElementById('paymentSuccess').style.display = 'block'; } } else { // Show error message alert('Fehler: ' + response.error); } }); return false; } // Handle payment redirect function handlePaymentRedirect(redirectUrl, redirectType) { switch (redirectType) { case 'new_window': window.open(redirectUrl, '_blank'); closeModal('paymentModal'); break; case 'iframe': document.getElementById('redirectIframe').src = redirectUrl; closeModal('paymentModal'); document.getElementById('redirectModal').style.display = 'flex'; break; case 'default': default: window.location.href = redirectUrl; break; } } // Process login function processLogin() { const email = document.getElementById('loginEmail').value; const password = document.getElementById('loginPassword').value; // Clear previous errors clearErrors(['loginEmail', 'loginPassword']); // Validate form let hasErrors = false; if (!email) { showError('loginEmail', 'Bitte geben Sie Ihre E-Mail-Adresse ein'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('loginEmail', 'Bitte geben Sie eine gültige E-Mail-Adresse ein'); hasErrors = true; } if (!password) { showError('loginPassword', 'Bitte geben Sie Ihr Passwort ein'); hasErrors = true; } if (hasErrors) return; // Prepare data for API request const data = { type: 'login', email: email, password: password }; // Send request to API sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { // Close modal and redirect or show success closeModal('authModal'); alert('Anmeldung erfolgreich!'); // Redirect to profile window.location.href = '/' + window.currentLang + '/profile/'; } else { // Show error message alert('Fehler: ' + response.error); } }); } // Process registration function processRegistration() { const name = document.getElementById('registerName').value; const email = document.getElementById('registerEmail').value; const password = document.getElementById('registerPassword').value; const confirmPassword = document.getElementById('registerConfirmPassword').value; // Clear previous errors clearErrors(['registerName', 'registerEmail', 'registerPassword', 'registerConfirmPassword']); // Validate form let hasErrors = false; if (!name) { showError('registerName', 'Bitte geben Sie Ihren Namen ein'); hasErrors = true; } if (!email) { showError('registerEmail', 'Bitte geben Sie Ihre E-Mail-Adresse ein'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('registerEmail', 'Bitte geben Sie eine gültige E-Mail-Adresse ein'); hasErrors = true; } if (!password) { showError('registerPassword', 'Bitte erstellen Sie ein Passwort'); hasErrors = true; } else if (password.length < 6) { showError('registerPassword', 'Das Passwort muss mindestens 6 Zeichen lang sein'); hasErrors = true; } if (!confirmPassword) { showError('registerConfirmPassword', 'Bitte bestätigen Sie Ihr Passwort'); hasErrors = true; } else if (password !== confirmPassword) { showError('registerConfirmPassword', 'Passwörter stimmen nicht überein'); hasErrors = true; } if (hasErrors) return; // Prepare data for API request const data = { type: 'registration', name: name, email: email, password: password }; // Send request to API sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { // Close modal and redirect or show success closeModal('authModal'); alert('Registrierung erfolgreich!'); // Here you would typically redirect or update UI } else { // Show error message alert('Fehler: ' + response.error); } }); } // Send AJAX request function sendRequest(url, data, callback,need_loading_overlay=true) { // Show loading overlay when request starts if(need_loading_overlay)showLoading(); //console.log("XMLHttpRequest started"); const xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onreadystatechange = function() { //console.log("XMLHttpRequest onready fired, readyState:", xhr.readyState); if (xhr.readyState === 4) { // Hide loading overlay when request completes if(need_loading_overlay)hideLoading(); //console.log("Request complete, status:", xhr.status); //console.log("responseText:", xhr.responseText); if (xhr.status === 200) { let is_good = true; let response; // Declare response variable outside try block try { response = JSON.parse(xhr.responseText); } catch (e) { is_good=false; console.error("JSON parse error:", e); callback({status: 'error', error: 'Ungültige Antwort vom Server'}); } if(is_good) callback(response); } else { callback({status: 'error', error: 'Serverfehler: ' + xhr.status}); } } }; xhr.send(JSON.stringify(data)); } // Close redirect modal document.getElementById('redirectModal').querySelector('.close-btn').addEventListener('click', function() { document.getElementById('redirectModal').style.display = 'none'; }); // Mock chart data (in a real implementation, you would use Google Charts) function initCharts() { // This is a placeholder for actual chart implementation //console.log('Charts would be initialized here with Google Charts API'); } // Image modal functionality document.addEventListener('DOMContentLoaded', function() { // Image modal const imgModal = document.getElementById('imgModal'); const modalImg = document.getElementById('modalImg'); const imgCloseBtn = imgModal.querySelector('.close-btn'); // Open image modal document.querySelectorAll('[data-modal="imgModal"]').forEach(img => { img.addEventListener('click', function() { modalImg.src = this.src; imgModal.style.display = 'flex'; }); }); // Close image modal imgCloseBtn.addEventListener('click', function() { imgModal.style.display = 'none'; }); // Close on outside click window.addEventListener('click', function(e) { if (e.target === imgModal) { imgModal.style.display = 'none'; } }); }); </script> <script> //console.log("herei"); // Analysis functionality document.addEventListener('DOMContentLoaded', function() { //console.log("DOMContentLoaded"); const analyzeBtn = document.getElementById('analyzeNowBtn'); const analysisStatus = document.getElementById('analysisStatus'); if (analyzeBtn) { analyzeBtn.addEventListener('click', function() { //console.log("click"); // Show captcha modal showCaptchaModal(); }); } }); function showCaptchaModal() { //console.log("showCaptchaModal"); // Create modal for captcha const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'captchaModal'; modal.style.display = 'flex'; modal.innerHTML = ` <div class="modal-content"> <span class="close-btn" onclick="closeCaptchaModal()">×</span> <h2 class="modal-title">Bestätigen Sie, dass Sie kein Roboter sind</h2> <p class="modal-text">Geben Sie den Text aus dem Bild ein:</p> <div class="captcha-container"> <img src="/captcha.php?t=` + new Date().getTime() + `" alt="CAPTCHA" class="captcha-image" id="captchaImage"> <button type="button" class="refresh-captcha" onclick="refreshCaptcha()">Code aktualisieren</button> <input type="text" class="captcha-input" id="captchaInput" placeholder="Code eingeben" maxlength="6"> </div> <button class="cta-button" onclick="submitAnalysis()" style="margin-top: 1rem;">Zur Analyse einreichen</button> </div> `; document.body.appendChild(modal); } function closeCaptchaModal() { const modal = document.getElementById('captchaModal'); if (modal) { modal.remove(); } } function refreshCaptcha() { const captchaImage = document.getElementById('captchaImage'); if (captchaImage) { // Add timestamp to prevent caching captchaImage.src = '/captcha.php?t=' + new Date().getTime(); } } function submitAnalysis() { const captchaInput = document.getElementById('captchaInput'); const captchaCode = captchaInput ? captchaInput.value.trim() : ''; const analysisStatus = document.getElementById('analysisStatus'); if (!captchaCode) { alert('Bitte geben Sie den Code aus dem Bild ein'); return; } // Show loading state analysisStatus.textContent = 'Anfrage wird gesendet...'; analysisStatus.className = 'analysis-status'; analysisStatus.style.display = 'block'; // Prepare data for API request type = "none"; if(document.getElementById('domain_name')){ type = "domain_analysis"; sendData = document.getElementById('domain_name').value; } if(document.getElementById('phrase')){ type = "phrase_analysis"; sendData = document.getElementById('phrase').value; } if(type=="none"){ // Show error message closeCaptchaModal(); analysisStatus.textContent = 'Fehler: Typ ist keiner'; analysisStatus.className = 'analysis-status error'; analysisStatus.style.display = 'block'; return false; } const data = { type: type, data: sendData, captcha_code: captchaCode }; // Send request to API sendRequest('/ajax/', data, function(response) { // Close captcha modal closeCaptchaModal(); if (response.status === 'ok') { // Show success message analysisStatus.textContent = 'Ihre Anfrage wurde gesendet. Bitte warten Sie auf die Domain-Bestätigung'; analysisStatus.className = 'analysis-status success'; } else { // Show error message analysisStatus.textContent = 'Fehler: ' + response.error; analysisStatus.className = 'analysis-status error'; // Refresh captcha on error if (response.refresh_captcha) { setTimeout(refreshCaptcha, 500); } } analysisStatus.style.display = 'block'; // Clear captcha input if (captchaInput) { captchaInput.value = ''; } }); } </script> <script> // Full screenshot functionality function openFullScreenshot(src) { const modal = document.getElementById('fullScreenshotModal'); const img = document.getElementById('fullScreenshotImg'); img.src = src; modal.style.display = 'flex'; // Prevent body scroll when modal is open document.body.style.overflow = 'hidden'; } function closeFullScreenshot() { const modal = document.getElementById('fullScreenshotModal'); modal.style.display = 'none'; // Restore body scroll document.body.style.overflow = 'auto'; } function downloadScreenshot() { const img = document.getElementById('fullScreenshotImg'); const link = document.createElement('a'); link.download = `screenshot-${document.getElementById('domain_name').value}-${new Date().getTime()}.jpg`; link.href = img.src; link.click(); } // Close modal on outside click document.addEventListener('click', function(e) { const modal = document.getElementById('fullScreenshotModal'); if (e.target === modal) { closeFullScreenshot(); } }); // Close modal on Escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { closeFullScreenshot(); } }); </script> <script> // Global variables to track intervals let telegramStatusInterval = null; let telegramTimerInterval = null; let resendTimerInterval = null; let emailCheckInterval = null; // Global variable to store QR code instance let qrcodeInstance = null; function startTelegramRegistration(name=false,email=false) { if(!name)name = document.getElementById('registerName').value.trim(); if(!email)email = document.getElementById('registerEmail').value.trim(); // Clear previous errors clearErrors(['registerName', 'registerEmail']); // Validate form let hasErrors = false; if (!name) { showError('registerName', 'Bitte geben Sie Ihren Namen ein'); hasErrors = true; } if (!email) { showError('registerEmail', 'Bitte geben Sie Ihre E-Mail-Adresse ein'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('registerEmail', 'Bitte geben Sie eine gültige E-Mail-Adresse ein'); hasErrors = true; } if (hasErrors) return; // Prepare data for API request const data = { type: 'telegram_registration_start', name: name, email: email }; //console.log("telegram_registration_start request"); // Send request to API sendRequest('/ajax/', data, function(response) { //console.log("telegram_registration_start callback"); if (response.status === 'ok') { // Store user token localStorage.setItem('current_user_token', response.user_token); // Immediately open Telegram bot in new tab if (response.deep_link) { window.open(response.deep_link, '_blank'); } // Show Telegram verification modal with deep link and QR code showTelegramVerifyModal(response.bot_username, response.telegram_token, response.deep_link,response.telegram_token_expires_remaining); // Clear form clearRegistrationForm(); // Close auth modal closeModal('authModal'); //YM reach goal send ym(109200701,'reachGoal','registration_start'); } else if (response.status === 'continue_email_verification') { //!! // User exists, Telegram verified, but email not verified localStorage.setItem('current_user_token', response.user_token); // Auto-resend email and show verification modal autoResendEmailAndShowModal(response.user_token); // Clear form clearRegistrationForm(); // Close auth modal closeModal('authModal'); } else { alert('Fehler1: ' + response.error); } }); } // Auto-resend email and show verification modal function autoResendEmailAndShowModal(userToken) { // First, try to resend the email const data = { type: 'resend_email_verification', user_token: userToken }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { // Email sent successfully, show verification modal with full timer //showEmailVerifyModal(); safeShowEmailVerifyModal(); } else { // Handle resend error - use cooldown_remaining from response if (response.cooldown_remaining !== undefined) { // Use the exact remaining seconds from the response const remainingSeconds = Math.max(1, Math.floor(response.cooldown_remaining)); // Show verification modal with synchronized timer showEmailVerifyModalWithCooldown(remainingSeconds); } else { // Other error - show modal with default timer //showEmailVerifyModal(); safeShowEmailVerifyModal(); alert('Fehler: ' + response.error); } } }); } // Enhanced resend email verification function function resendEmailVerification() { const userToken = localStorage.getItem('current_user_token'); const resendBtn = document.getElementById('resendEmailBtn'); if (!resendBtn) { alert('Fehler: Schaltfläche nicht gefunden. Bitte aktualisieren Sie die Seite.'); return; } // Prevent multiple clicks if (resendBtn.disabled) { return; } // Immediately disable button and show loading state resendBtn.disabled = true; resendBtn.style.opacity = '0.7'; resendBtn.innerHTML = 'Senden...'; const data = { type: 'resend_email_verification', user_token: userToken }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { // Success - restart timer AND restart email check interval setupResendButtonForTimer(); startResendTimer(300); // CRITICAL FIX: Restart the email check interval restartEmailCheckInterval(); // Update status message const statusElement = document.getElementById('emailCheckStatus'); if (statusElement) { statusElement.innerHTML = '<p style="color: var(--primary);">🔍 Neuer Code gesendet. Überprüfung wird überprüft...</p>'; statusElement.className = 'email-check-status status-checking'; statusElement.style.display = 'block'; } alert('Der neue Bestätigungscode wurde an Ihre E-Mail-Adresse gesendet'); } else { // Handle different error cases if (response.cooldown_remaining !== undefined) { const remainingSeconds = Math.max(1, Math.floor(response.cooldown_remaining)); setupResendButtonForTimer(); startResendTimer(remainingSeconds); // CRITICAL FIX: Even on cooldown, restart the email check interval restartEmailCheckInterval(); const remainingMinutes = Math.ceil(remainingSeconds / 60); // Update status message const statusElement = document.getElementById('emailCheckStatus'); if (statusElement) { statusElement.innerHTML = `<p style="color: #856404;">⏰ Neuer Code kann erneut eingesandt werden ${remainingMinutes} Minuten. Vorherige Codeüberprüfung wird überprüft...</p>`; statusElement.style.display = 'block'; } alert('Neuer Code kann erneut eingesandt werden ' + remainingMinutes + ' Minuten.'); } else { alert('Fehler: ' + response.error); // Re-enable button on other errors resendBtn.disabled = false; resendBtn.style.opacity = '1'; resendBtn.innerHTML = 'Code erneut senden'; } } }); } function restartEmailCheckInterval() { const userToken = localStorage.getItem('current_user_token'); let attempts = 0; const maxAttempts = 30; // Clear existing interval if any if (emailCheckInterval) { clearInterval(emailCheckInterval); emailCheckInterval = null; } // Update status to show checking updateEmailCheckStatus('checking', 'Überprüfungsstatus wird überprüft...'); // Start new interval emailCheckInterval = setInterval(function() { if (attempts >= maxAttempts) { if (emailCheckInterval) { clearInterval(emailCheckInterval); emailCheckInterval = null; } updateEmailCheckStatus('timeout', 'Überprüfen Sie die Zeitüberschreitung. Bitte aktualisieren Sie die Seite.'); return; } checkEmailVerificationStatus(userToken); attempts++; }, 10000); } // Setup resend button for timer display function setupResendButtonForTimer() { const resendBtn = document.getElementById('resendEmailBtn'); if (!resendBtn) return; resendBtn.disabled = true; resendBtn.style.opacity = '0.7'; // Ensure timer element exists let timerElement = document.getElementById('resendTimer'); if (!timerElement) { timerElement = document.createElement('span'); timerElement.id = 'resendTimer'; resendBtn.appendChild(timerElement); } // Set initial timer display resendBtn.innerHTML = 'Code erneut senden (<span id="resendTimer">5:00</span>)'; } function showEmailVerifyModalWithCooldown(remainingSeconds) { // Clear previous input const emailVerifyToken = document.getElementById('emailVerifyToken'); if (emailVerifyToken) { emailVerifyToken.value = ''; } clearErrors(['emailVerifyToken']); // Setup button for timer setupResendButtonForTimer(); // Start timer with remaining time startResendTimer(remainingSeconds); // Start email check interval startEmailCheckInterval(); // Show modal document.getElementById('emailVerifyModal').style.display = 'flex'; // Show info message const statusElement = document.getElementById('emailCheckStatus'); if (statusElement) { const remainingMinutes = Math.ceil(remainingSeconds / 60); statusElement.innerHTML = `<p style="color: #856404;">⏰ Neuer Code kann erneut eingesandt werden ${remainingMinutes} Minuten</p>`; statusElement.className = 'email-check-status status-checking'; statusElement.style.display = 'block'; } } // Safe function to show email verification modal function safeShowEmailVerifyModal() { // Clear previous input const emailVerifyToken = document.getElementById('emailVerifyToken'); if (emailVerifyToken) { emailVerifyToken.value = ''; } clearErrors(['emailVerifyToken']); // Setup button for timer setupResendButtonForTimer(); // Start fresh timer startResendTimer(300); // Start email check interval startEmailCheckInterval(); // Show modal document.getElementById('emailVerifyModal').style.display = 'flex'; } // Safe function to clear registration form function clearRegistrationForm() { const nameField = document.getElementById('registerName'); const emailField = document.getElementById('registerEmail'); if (nameField) nameField.value = ''; if (emailField) emailField.value = ''; // Only clear password fields if they exist (for traditional registration) const passwordField = document.getElementById('registerPassword'); const confirmPasswordField = document.getElementById('registerConfirmPassword'); if (passwordField) passwordField.value = ''; if (confirmPasswordField) confirmPasswordField.value = ''; // Clear any status messages const statusElement = document.getElementById('registrationStatus'); if (statusElement) { statusElement.innerHTML = ''; statusElement.style.display = 'none'; } } // Enhanced showTelegramVerifyModal function function showTelegramVerifyModal(botUsername, telegramToken, deepLink,telegram_token_expires_remaining) { // Set Telegram bot deep link const telegramUrl = deepLink || `https://t.me/${botUsername}?start=${telegramToken}`; window.telegramBotUrl = telegramUrl; // Generate QR code generateQRCode(telegramUrl); // Update the "Open Telegram" button const openTelegramBtn = document.getElementById('openTelegramBtn'); if (openTelegramBtn) { openTelegramBtn.onclick = function() { window.open(telegramUrl, '_blank'); }; } // Update modal text const modalText = document.getElementById('telegramModalText'); if (modalText) { modalText.innerHTML = ` Telegram wurde in einem neuen Tab geöffnet.<br> Wenn der Bot nicht automatisch geöffnet wurde, verwenden Sie die Schaltfläche unten oder scannen Sie den QR-Code. `; } // Start timer //console.log("start timer:"+telegram_token_expires_remaining); startTelegramTimer(telegram_token_expires_remaining); // Start status checking startTelegramStatusCheck(); // Show modal document.getElementById('telegramVerifyModal').style.display = 'flex'; } // Generate QR code using qrcodejs library function generateQRCode(url) { const qrcodeElement = document.getElementById('qrcode'); // Clear previous QR code if exists if (qrcodeInstance) { qrcodeInstance.clear(); qrcodeElement.innerHTML = ''; } try { // Create new QR code instance qrcodeInstance = new QRCode(qrcodeElement, { text: url, width: 200, height: 200, colorDark: "#000000", colorLight: "#ffffff", correctLevel: QRCode.CorrectLevel.H }); //console.log('QR code generated successfully'); } catch (error) { console.error('Die Generierung des QR-Codes ist fehlgeschlagen:', error); // Fallback: show message to use the button instead qrcodeElement.innerHTML = ` <div style="color: var(--warning); font-size: 0.9rem; padding: 1rem;"> <p>QR-Code nicht verfügbar. Verwenden Sie die Schaltfläche „Telegramm öffnen“.</p> </div> `; } } // Refresh QR code function refreshTelegramQR() { if (window.telegramBotUrl) { const refreshBtn = document.getElementById('refreshQRBtn'); const originalText = refreshBtn.innerHTML; refreshBtn.innerHTML = '🔄 Erfrischend...'; refreshBtn.disabled = true; // Regenerate QR code generateQRCode(window.telegramBotUrl); setTimeout(() => { refreshBtn.innerHTML = originalText; refreshBtn.disabled = false; }, 500); } } // Open Telegram bot in new tab function openTelegramBot() { if (window.telegramBotUrl) { window.open(window.telegramBotUrl, '_blank'); } } function startTelegramStatusCheck() { const userToken = localStorage.getItem('current_user_token'); // Clear existing interval if any if (telegramStatusInterval) { clearInterval(telegramStatusInterval); } // Check status every 5 seconds telegramStatusInterval = setInterval(function() { checkTelegramVerificationStatus(userToken); }, 5000); } // Update the checkTelegramVerificationStatus to clear timers on success function checkTelegramVerificationStatus(userToken) { const data = { type: 'check_telegram_verification', user_token: userToken }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { if (response.telegram_verified) { // Telegram verified successfully clearTelegramTimers(); // Show success message const timerContainer = document.getElementById('telegramTimer'); timerContainer.innerHTML = '✅ Telegramm erfolgreich verifiziert!'; timerContainer.className = 'timer-container'; timerContainer.style.background = '#d4edda'; timerContainer.style.color = '#155724'; //YM reach goal send ym(109200701,'reachGoal','registration_tg_success'); // Auto-proceed to email verification after 2 seconds setTimeout(function() { closeTelegramVerifyModal(); safeShowEmailVerifyModal(); }, 2000); } } },false); } // Enhanced timer functions that store interval IDs function startTelegramTimer(duration) { let timerContainer = document.getElementById('telegramTimer'); let timerElement = document.getElementById('timer'); // Check if timer was expired and reset if needed if (timerContainer && timerContainer.classList.contains('timer-expired')) { //console.log('Timer has expired - resetting UI'); timerContainer.classList.remove('timer-expired'); timerContainer.innerHTML = 'Warte auf Bestätigung... <span id="timer">05:00</span>'; // Update timerElement reference after resetting HTML timerElement = document.getElementById('timer'); } let timer = duration; // Clear existing timer if any if (telegramTimerInterval) { clearInterval(telegramTimerInterval); } telegramTimerInterval = setInterval(function() { const minutes = parseInt(timer / 60, 10); const seconds = parseInt(timer % 60, 10); if (timerElement) { timerElement.textContent = minutes + ":" + (seconds < 10 ? "0" + seconds : seconds); } if (--timer < 0) { clearTelegramTimers(); if (timerElement) { timerElement.textContent = "00:00"; } if (timerContainer) { timerContainer.classList.add('timer-expired'); timerContainer.innerHTML = 'Zeit abgelaufen. Bitte fangen Sie von vorne an.'; } // Auto-close after 3 seconds setTimeout(function() { closeTelegramVerifyModal(); }, 3000); } }, 1000); } // Enhanced close function to clean up QR code function closeTelegramVerifyModal() { // Clear QR code instance if (qrcodeInstance) { qrcodeInstance.clear(); qrcodeInstance = null; } // Clear container const qrcodeElement = document.getElementById('qrcode'); if (qrcodeElement) { qrcodeElement.innerHTML = ''; } // Clear timers clearTelegramTimers(); // Hide modal document.getElementById('telegramVerifyModal').style.display = 'none'; } // Verify Telegram code function verifyTelegramCode() { const code = document.getElementById('telegramVerifyCode').value.trim(); const userToken = localStorage.getItem('current_user_token'); // Clear previous errors clearErrors(['telegramVerifyCode']); if (!code) { showError('telegramVerifyCode', 'Bitte geben Sie den Bestätigungscode ein'); return; } if (code.length !== 7) { showError('telegramVerifyCode', 'Der Code muss 7 Ziffern enthalten'); return; } // Prepare data for API request const data = { type: 'verify_telegram_code', user_token: userToken, verify_code: code }; // Send request to API sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { // Stop timer clearInterval(telegramTimer); // Close Telegram modal closeTelegramVerifyModal(); // Show email verification modal //showEmailVerifyModal(); safeShowEmailVerifyModal(); } else { showError('telegramVerifyCode', response.error); } }); } // Update the modal opening calls function showEmailVerifyModal() { safeShowEmailVerifyModal(); } // Enhanced start resend timer function function startResendTimer(duration) { let timer = duration; // Clear existing timer if any if (resendTimerInterval) { clearInterval(resendTimerInterval); } // Update timer display immediately updateResendTimerDisplay(timer); resendTimerInterval = setInterval(function() { timer--; updateResendTimerDisplay(timer); if (timer <= 0) { if (resendTimerInterval) { clearInterval(resendTimerInterval); resendTimerInterval = null; } enableResendButton(); } }, 1000); } // Safe function to update timer display function updateResendTimerDisplay(timer) { const timerElement = document.getElementById('resendTimer'); const resendBtn = document.getElementById('resendEmailBtn'); if (!timerElement || !resendBtn) { console.warn('Timer-Elemente wurden während der Aktualisierung nicht gefunden'); return; } const minutes = Math.floor(timer / 60); const seconds = timer % 60; const timeString = minutes + ":" + (seconds < 10 ? "0" + seconds : seconds); timerElement.textContent = timeString; // Update button text while preserving the timer element resendBtn.innerHTML = 'Code erneut senden (<span id="resendTimer">' + timeString + '</span>)'; } // Helper function to update timer display function updateTimerDisplay(timerElement, timer) { if (!timerElement) return; const minutes = Math.floor(timer / 60); const seconds = timer % 60; timerElement.textContent = minutes + ":" + (seconds < 10 ? "0" + seconds : seconds); } // Enable resend button when timer expires function enableResendButton() { const resendBtn = document.getElementById('resendEmailBtn'); if (!resendBtn) { console.error('Die Schaltfläche „Erneut senden“ wurde nicht gefunden'); return; } resendBtn.disabled = false; resendBtn.style.opacity = '1'; // Set final state without timer resendBtn.innerHTML = 'Code erneut senden'; // Re-create the timer element for future use const timerSpan = document.createElement('span'); timerSpan.id = 'resendTimer'; timerSpan.style.display = 'none'; // Hide when not active resendBtn.appendChild(timerSpan); } // Enhanced email check interval function startEmailCheckInterval() { restartEmailCheckInterval(); // Just call the new function } // Update the checkEmailVerificationStatus to clear interval on success function checkEmailVerificationStatus(userToken) { const data = { type: 'check_email_verification', user_token: userToken }; sendRequest('/ajax/', data, function(response) { const statusElement = document.getElementById('emailCheckStatus'); if (response.status === 'ok') { if (response.email_verified) { // Email verified successfully if (emailCheckInterval) { clearInterval(emailCheckInterval); emailCheckInterval = null; } if (statusElement) { statusElement.innerHTML = '<p style="color: #155724;">✅ E-Mail erfolgreich bestätigt!</p>'; statusElement.className = 'email-check-status status-success'; } //YM reach goal send ym(109200701,'reachGoal','registration_email_success'); // Auto-redirect after 2 seconds setTimeout(function() { completeRegistration(); }, 2000); } else { if (statusElement) { statusElement.style.display = 'block'; statusElement.innerHTML = '<p style="color: var(--primary);">🔍 Überprüfungsstatus wird überprüft...</p>'; statusElement.className = 'email-check-status status-checking'; } } } else { // Handle error response if (statusElement) { statusElement.innerHTML = `<p style="color: #721c24;">❌ Fehler bei der Verifizierungsprüfung: ${response.error}</p>`; statusElement.className = 'email-check-status status-error'; } } },false); } // Verify email token manually function verifyEmailToken() { const token = document.getElementById('emailVerifyToken').value.trim(); const userToken = localStorage.getItem('current_user_token'); // Clear previous errors clearErrors(['emailVerifyToken']); if (!token) { showError('emailVerifyToken', 'Bitte geben Sie den Bestätigungscode ein'); return; } const data = { type: 'verify_email_token', user_token: userToken, email_token: token }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { completeRegistration(); } else { showError('emailVerifyToken', response.error); } }); } // Complete registration and login function completeRegistration() { const userToken = localStorage.getItem('current_user_token'); const data = { type: 'complete_registration', user_token: userToken }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { // Store user info localStorage.setItem('user_info', JSON.stringify(response.user)); // Close modal closeEmailVerifyModal(); //YM send reachGoal ym(109200701,'reachGoal','registration_complete'); // Redirect to profile window.location.href = '/' + window.currentLang + '/profile/'; } else { alert('Fehler: ' + response.error); } }); } function closeEmailVerifyModal() { // Clear all email-related intervals if (resendTimerInterval) { clearInterval(resendTimerInterval); resendTimerInterval = null; } if (emailCheckInterval) { clearInterval(emailCheckInterval); emailCheckInterval = null; } document.getElementById('emailVerifyModal').style.display = 'none'; } // Add this to check registration status when email is entered document.getElementById('registerEmail').addEventListener('blur', function() { const email = this.value.trim(); if(statusElement = document.getElementById('registrationStatus')){ statusElement.innerHTML = ''; } if (email && /\S+@\S+\.\S+/.test(email)) { checkRegistrationStatus(email); } }); function checkRegistrationStatus(email) { const data = { type: 'check_registration_status', email: email }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { const status = response.registration_status; let statusElement = document.getElementById('registrationStatus'); // Remove 'const' declaration if (!statusElement) { // Create status element if it doesn't exist statusElement = document.createElement('div'); // Now we're assigning to the existing variable statusElement.id = 'registrationStatus'; statusElement.style.marginTop = '1rem'; statusElement.style.padding = '0.75rem'; statusElement.style.borderRadius = 'var(--border-radius)'; statusElement.style.fontSize = '0.9rem'; const registerForm = document.getElementById('registerForm'); const submitButton = registerForm.querySelector('button[onclick="startTelegramRegistration()"]'); registerForm.insertBefore(statusElement, submitButton.nextSibling); } // Remove the duplicate declaration of statusElement here // const statusElement = document.getElementById('registrationStatus'); // ← This line is removed switch(status) { case 'completed': statusElement.innerHTML = '✅ Diese E-Mail ist bereits registriert. <a href="#" onclick="switchAuthTab(\'login\')">Login</a>'; statusElement.style.background = '#d4edda'; statusElement.style.color = '#155724'; break; case 'email_pending': statusElement.innerHTML = '📧 Anmeldung fast abgeschlossen! E-Mail-Bestätigung erforderlich.'; statusElement.style.background = '#fff3cd'; statusElement.style.color = '#856404'; break; case 'telegram_pending': statusElement.innerHTML = '📱 Setzen Sie die Registrierung im Telegram fort.'; statusElement.style.background = '#cce5ff'; statusElement.style.color = '#004085'; break; case 'incomplete': statusElement.innerHTML = '🔄 Registrierung fortsetzen.'; statusElement.style.background = '#e2e3e5'; statusElement.style.color = '#383d41'; break; default: statusElement.innerHTML = ''; } } },false); } // Enhanced email check status function function updateEmailCheckStatus(type, message) { const statusElement = document.getElementById('emailCheckStatus'); if (!statusElement) return; statusElement.style.display = 'block'; switch(type) { case 'checking': statusElement.innerHTML = '<p style="color: var(--primary);">🔍 Überprüfungsstatus wird überprüft...</p>'; statusElement.className = 'email-check-status status-checking'; break; case 'success': statusElement.innerHTML = '<p style="color: #155724;">✅ ' + message + '</p>'; statusElement.className = 'email-check-status status-success'; break; case 'error': statusElement.innerHTML = '<p style="color: #721c24;">❌ ' + message + '</p>'; statusElement.className = 'email-check-status status-error'; break; case 'timeout': statusElement.innerHTML = '<p style="color: #856404;">⏰ ' + message + '</p>'; statusElement.className = 'email-check-status status-error'; break; } } // Helper function to clear all Telegram timers function clearTelegramTimers() { if (telegramTimerInterval) { clearInterval(telegramTimerInterval); telegramTimerInterval = null; } if (telegramStatusInterval) { clearInterval(telegramStatusInterval); telegramStatusInterval = null; } } // Helper function to clear all email timers function clearEmailTimers() { if (resendTimerInterval) { clearInterval(resendTimerInterval); resendTimerInterval = null; } if (emailCheckInterval) { clearInterval(emailCheckInterval); emailCheckInterval = null; } } </script> <script> // Login with Telegram function function loginWithTelegram() { const email = document.getElementById('loginEmail').value.trim(); // Clear previous errors clearErrors(['loginEmail']); // Validate email if (!email) { showError('loginEmail', 'Bitte geben Sie Ihre E-Mail-Adresse ein'); return; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('loginEmail', 'Bitte geben Sie eine gültige E-Mail-Adresse ein'); return; } // Check if user exists and is verified checkUserForTelegramLogin(email); } // Check if user exists and is verified for Telegram login function checkUserForTelegramLogin(email) { const data = { type: 'check_user_for_telegram_login', email: email }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { if(response.user_exists && response.telegram_verified && response.email_verified){ // User exists and Telegram verified and email verifed- generate login link generateTelegramLoginLink(email); }else if (response.user_exists && response.telegram_verified) { // User exists and Telegram verified //alert('Для входа через Telegram необходимо сначала завершить регистрацию подтвердите email'); startTelegramRegistration("noneedname",email); } else if (response.user_exists && !response.telegram_verified) { // User exists but Telegram not verified //alert('Для входа через Telegram необходимо сначала завершить регистрацию в Telegram'); startTelegramRegistration("noneedname",email); } else { // User doesn't exist alert('Benutzer mit dieser E-Mail wurde nicht gefunden. Bitte registrieren Sie sich.'); } } else { alert('Fehler: ' + response.error); } }); } // Generate Telegram login code function generateTelegramLoginLink(email) { const data = { type: 'generate_telegram_login_link', email: email }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { // Store user token localStorage.setItem('current_user_token', response.user_token); // Show Telegram verification modal for login showTelegramLoginModal(response.bot_username, response.deep_link, response.telegram_token_expires_remaining); // Close auth modal closeModal('authModal'); } else { alert('Fehler: ' + response.error); } }); } // Show Telegram login modal function showTelegramLoginModal(botUsername, deepLink, expiresRemaining) { // Set Telegram bot deep link const telegramUrl = deepLink || `https://t.me/${botUsername}?start=login_${Date.now()}`; window.telegramBotUrl = telegramUrl; //open forced window.open(telegramUrl, '_blank'); // Generate QR code generateQRCode(telegramUrl); // Update modal text for login const modalText = document.getElementById('telegramModalText'); if (modalText) { modalText.innerHTML = ` Telegramm zum Anmelden geöffnet.<br> Wenn der Bot nicht automatisch geöffnet wurde, verwenden Sie die Schaltfläche unten oder scannen Sie den QR-Code. `; } // Update modal title const modalTitle = document.querySelector('#telegramVerifyModal .modal-title'); if (modalTitle) { modalTitle.textContent = 'Login per Telegram'; } // Start timer startTelegramTimer(expiresRemaining || 300); // Start status checking for login startTelegramLoginStatusCheck(); // Show modal document.getElementById('telegramVerifyModal').style.display = 'flex'; } // Start Telegram login status check function startTelegramLoginStatusCheck() { const userToken = localStorage.getItem('current_user_token'); // Clear existing interval if any if (telegramStatusInterval) { clearInterval(telegramStatusInterval); } // Check status every 5 seconds telegramStatusInterval = setInterval(function() { checkTelegramLoginStatus(userToken); }, 5000); } // Check Telegram login status function checkTelegramLoginStatus(userToken) { const data = { type: 'check_telegram_login_status', user_token: userToken }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { if (response.login_code_sended ) { // Login code received - show code input modal clearTelegramTimers(); showTelegramCodeInputModal(response.verify_code); } else if (response.login_completed) { // Login completed successfully clearTelegramTimers(); completeTelegramLogin(); } } }); } // Show Telegram code input modal function showTelegramCodeInputModal(verifyCode) { // Close Telegram verify modal closeTelegramVerifyModal(); // Create or show code input modal let codeModal = document.getElementById('telegramCodeModal'); // Start code timer startLoginCodeTimer(300); // Show modal codeModal.style.display = 'flex'; } // Start login code timer function startLoginCodeTimer(duration) { let timer = duration; const timerElement = document.getElementById('loginTimer'); if (!timerElement) return; // Clear existing timer if any if (window.loginCodeTimer) { clearInterval(window.loginCodeTimer); } window.loginCodeTimer = setInterval(function() { const minutes = parseInt(timer / 60, 10); const seconds = parseInt(timer % 60, 10); timerElement.textContent = minutes + ":" + (seconds < 10 ? "0" + seconds : seconds); if (--timer < 0) { clearInterval(window.loginCodeTimer); timerElement.textContent = "00:00"; // Show expired message const timerContainer = document.getElementById('loginCodeTimer'); if (timerContainer) { timerContainer.classList.add('timer-expired'); timerContainer.innerHTML = 'Code abgelaufen. Bitte fangen Sie von vorne an.'; } } }, 1000); } // Verify Telegram login code function verifyTelegramLoginCode() { const userToken = localStorage.getItem('current_user_token'); const codeInput = document.getElementById('telegramLoginCode'); const code = codeInput ? codeInput.value.trim() : ''; // Clear previous errors clearErrors(['telegramLoginCode']); if (!code) { showError('telegramLoginCode', 'Bitte geben Sie den Bestätigungscode ein'); return; } if (code.length !== 7) { showError('telegramLoginCode', 'Der Code muss 7 Ziffern enthalten'); return; } const data = { type: 'verify_telegram_login_code', user_token: userToken, verify_code: code }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { // Clear timer if (window.loginCodeTimer) { clearInterval(window.loginCodeTimer); } localStorage.setItem('user_info', JSON.stringify(response.user)); // Close any open modals closeModal('telegramCodeModal'); closeModal('telegramVerifyModal'); // Show success message and redirect alert('Anmeldung erfolgreich!'); window.location.href = '/' + window.currentLang + '/profile/'; } else { showError('telegramLoginCode', response.error); } }); } </script> <!-- Add this JavaScript before closing body tag --> <script> // Animation on scroll document.addEventListener('DOMContentLoaded', function() { const fadeElements = document.querySelectorAll('.fade-in'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); } }); }, { threshold: 0.1 }); fadeElements.forEach(element => { observer.observe(element); }); // Add click handlers for project CTAs document.querySelectorAll('.project-cta').forEach(button => { button.addEventListener('click', function() { const projectTitle = this.closest('.project-card').querySelector('.project-title').textContent; // alert(`Exploring: ${projectTitle}`); // In production, this would navigate to the appropriate page }); }); }); </script> <script> // Cookie consent management (function() { // Configuration const COOKIE_CONSENT_KEY = 'serpuls_cookie_consent'; const COOKIE_EXPIRY_DAYS = 365; // Create cookie consent banner if not already accepted function initCookieConsent() { // Check if user has already made a choice const consent = getCookieConsent(); if (consent !== null) { // User already made a choice, apply preferences applyCookiePreferences(consent); return; } // Create banner createCookieBanner(); } // Create the cookie banner HTML function createCookieBanner() { const banner = document.createElement('div'); banner.id = 'cookie-consent-banner'; banner.style.cssText = ` position: fixed; bottom: 0; left: 0; right: 0; background: white; box-shadow: 0 -4px 20px rgba(0,0,0,0.15); padding: 1.5rem; z-index: 9999; border-top: 3px solid var(--primary, #4361ee); animation: slideUp 0.5s ease; `; // Add animation style if not exists if (!document.getElementById('cookie-animation-style')) { const style = document.createElement('style'); style.id = 'cookie-animation-style'; style.textContent = ` @keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } } `; document.head.appendChild(style); } banner.innerHTML = ` <div style="max-width: 1200px; margin: 0 auto; display: flex; flex-wrap: wrap; gap: 2rem; align-items: center; justify-content: space-between;"> <div style="flex: 2; min-width: 280px;"> <h3 style="margin: 0 0 0.5rem 0; color: var(--dark, #212529); font-size: 1.25rem;">🍪 Cookie-Einwilligung</h3> <p style="margin: 0; color: var(--gray, #6c757d); line-height: 1.5;"> Wir verwenden Cookies, um Ihr Surferlebnis zu verbessern, personalisierte Inhalte bereitzustellen und unseren Datenverkehr zu analysieren. Indem Sie auf „Alle akzeptieren“ klicken, stimmen Sie der Verwendung von Cookies zu. Lesen Sie unsere <a href="/en/cookie/" style="color: var(--primary, #4361ee); text-decoration: underline;">Cookie-Richtlinie</a> Und <a href="/en/privacy/" style="color: var(--primary, #4361ee); text-decoration: underline;">Datenschutzrichtlinie</a>. </p> </div> <div style="display: flex; gap: 1rem; flex-wrap: wrap; align-items: center;"> <button id="cookie-accept-all" style="padding: 0.75rem 1.5rem; background: linear-gradient(135deg, var(--primary, #4361ee), var(--secondary, #7209b7)); color: white; border: none; border-radius: var(--border-radius, 8px); font-weight: 600; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 6px rgba(67,97,238,0.3);"> Alle akzeptieren </button> <button id="cookie-accept-essential" style="padding: 0.75rem 1.5rem; background: white; color: var(--dark, #212529); border: 1px solid var(--gray, #6c757d); border-radius: var(--border-radius, 8px); font-weight: 600; cursor: pointer; transition: all 0.3s ease;"> Nur essenziell </button> <button id="cookie-settings" style="padding: 0.75rem 1.5rem; background: transparent; color: var(--primary, #4361ee); border: none; font-weight: 600; cursor: pointer; text-decoration: underline;"> Anpassen </button> </div> </div> `; document.body.appendChild(banner); // Add event listeners document.getElementById('cookie-accept-all').addEventListener('click', function() { acceptAllCookies(); }); document.getElementById('cookie-accept-essential').addEventListener('click', function() { acceptEssentialCookies(); }); document.getElementById('cookie-settings').addEventListener('click', function() { showCookieSettings(); }); } // Show cookie settings modal function showCookieSettings() { // Remove existing modal if any const existingModal = document.getElementById('cookie-settings-modal'); if (existingModal) existingModal.remove(); const modal = document.createElement('div'); modal.id = 'cookie-settings-modal'; modal.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 10000; animation: fadeIn 0.3s ease; `; modal.innerHTML = ` <div style="background: white; border-radius: var(--border-radius, 8px); max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; box-shadow: 0 10px 25px rgba(0,0,0,0.2);"> <div style="padding: 1.5rem; border-bottom: 1px solid #e9ecef;"> <h3 style="margin: 0; color: var(--dark, #212529);">Cookie-Einstellungen</h3> </div> <div style="padding: 1.5rem;"> <p style="color: var(--gray, #6c757d); margin-bottom: 1.5rem;">Wählen Sie aus, welche Cookies Sie zulassen. Sie können diese Einstellungen jederzeit ändern.</p> <div style="margin-bottom: 1rem; padding: 1rem; background: #f8f9fa; border-radius: var(--border-radius, 8px);"> <label style="display: flex; align-items: center; gap: 0.75rem; cursor: not-allowed;"> <input type="checkbox" checked disabled style="width: 18px; height: 18px;"> <div> <strong style="color: var(--dark, #212529);">Unverzichtbare Cookies</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Erforderlich für die Funktion der Website. Kann nicht deaktiviert werden.</p> </div> </label> </div> <div style="margin-bottom: 1rem; padding: 1rem; background: #f8f9fa; border-radius: var(--border-radius, 8px);"> <label style="display: flex; align-items: center; gap: 0.75rem; cursor: pointer;"> <input type="checkbox" id="cookie-pref-analytics" checked style="width: 18px; height: 18px;"> <div> <strong style="color: var(--dark, #212529);">Analyse-Cookies</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Helfen Sie uns zu verstehen, wie Besucher mit unserer Website interagieren.</p> </div> </label> </div> <div style="margin-bottom: 1rem; padding: 1rem; background: #f8f9fa; border-radius: var(--border-radius, 8px);"> <label style="display: flex; align-items: center; gap: 0.75rem; cursor: pointer;"> <input type="checkbox" id="cookie-pref-functional" checked style="width: 18px; height: 18px;"> <div> <strong style="color: var(--dark, #212529);">Funktionale Cookies</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Merken Sie sich Ihre Vorlieben und Einstellungen.</p> </div> </label> </div> <div style="margin-bottom: 1rem; padding: 1rem; background: #f8f9fa; border-radius: var(--border-radius, 8px);"> <label style="display: flex; align-items: center; gap: 0.75rem; cursor: pointer;"> <input type="checkbox" id="cookie-pref-targeting" checked style="width: 18px; height: 18px;"> <div> <strong style="color: var(--dark, #212529);">Targeting-Cookies</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Wird zur Bereitstellung relevanter Inhalte und Anzeigen verwendet.</p> </div> </label> </div> </div> <div style="padding: 1.5rem; border-top: 1px solid #e9ecef; display: flex; gap: 1rem; justify-content: flex-end;"> <button id="cookie-save-preferences" style="padding: 0.75rem 1.5rem; background: linear-gradient(135deg, var(--primary, #4361ee), var(--secondary, #7209b7)); color: white; border: none; border-radius: var(--border-radius, 8px); font-weight: 600; cursor: pointer;"> Einstellungen speichern </button> <button id="cookie-close-modal" style="padding: 0.75rem 1.5rem; background: white; color: var(--dark, #212529); border: 1px solid var(--gray, #6c757d); border-radius: var(--border-radius, 8px); font-weight: 600; cursor: pointer;"> Stornieren </button> </div> </div> `; document.body.appendChild(modal); // Close modal handlers document.getElementById('cookie-close-modal').addEventListener('click', function() { modal.remove(); }); document.getElementById('cookie-save-preferences').addEventListener('click', function() { const preferences = { essential: true, // Always true analytics: document.getElementById('cookie-pref-analytics').checked, functional: document.getElementById('cookie-pref-functional').checked, targeting: document.getElementById('cookie-pref-targeting').checked }; saveCookiePreferences(preferences); modal.remove(); removeCookieBanner(); }); // Click outside to close modal.addEventListener('click', function(e) { if (e.target === modal) { modal.remove(); } }); } // Accept all cookies function acceptAllCookies() { const preferences = { essential: true, analytics: true, functional: true, targeting: true }; saveCookiePreferences(preferences); removeCookieBanner(); } // Accept only essential cookies function acceptEssentialCookies() { const preferences = { essential: true, analytics: false, functional: false, targeting: false }; saveCookiePreferences(preferences); removeCookieBanner(); } // Save cookie preferences function saveCookiePreferences(preferences) { // Save to cookie const expiryDate = new Date(); expiryDate.setDate(expiryDate.getDate() + COOKIE_EXPIRY_DAYS); const cookieValue = JSON.stringify(preferences); document.cookie = `${COOKIE_CONSENT_KEY}=${encodeURIComponent(cookieValue)}; expires=${expiryDate.toUTCString()}; path=/; SameSite=Lax`; // Apply preferences applyCookiePreferences(preferences); // Store in localStorage for quick access localStorage.setItem(COOKIE_CONSENT_KEY, cookieValue); // Optional: send event to analytics if (typeof window.gtag !== 'undefined' && preferences.analytics) { window.gtag('consent', 'update', { 'analytics_storage': 'granted' }); } } // Get cookie consent from cookie or localStorage function getCookieConsent() { // Try to get from cookie first const match = document.cookie.match(new RegExp('(^| )' + COOKIE_CONSENT_KEY + '=([^;]+)')); if (match) { try { return JSON.parse(decodeURIComponent(match[2])); } catch (e) { console.error('Fehler beim Parsen der Cookie-Zustimmung:', e); } } // Try localStorage const stored = localStorage.getItem(COOKIE_CONSENT_KEY); if (stored) { try { return JSON.parse(stored); } catch (e) { console.error('Fehler beim Parsen der localStorage-Zustimmung:', e); } } return null; } // Apply cookie preferences (enable/disable features) function applyCookiePreferences(preferences) { if (!preferences) return; // Example: Disable analytics if not consented if (!preferences.analytics) { // Disable Google Analytics if present if (typeof window.gtag !== 'undefined') { window.gtag('consent', 'default', { 'analytics_storage': 'denied' }); } // Optionally remove existing analytics cookies // This is handled by the analytics script itself usually } // Disable targeting cookies if (!preferences.targeting) { // Disable ad-related tracking // Remove Facebook pixel, etc. } // Store preferences in data attribute for other scripts to check document.documentElement.setAttribute('data-cookie-consent', JSON.stringify(preferences)); // Dispatch event for other scripts to react window.dispatchEvent(new CustomEvent('cookieConsentChanged', { detail: preferences })); } // Remove cookie banner function removeCookieBanner() { const banner = document.getElementById('cookie-consent-banner'); if (banner) { banner.style.animation = 'slideDown 0.5s ease forwards'; setTimeout(() => { if (banner.parentNode) banner.remove(); }, 500); // Add slideDown animation if not exists if (!document.getElementById('cookie-animation-slidedown')) { const style = document.createElement('style'); style.id = 'cookie-animation-slidedown'; style.textContent = ` @keyframes slideDown { from { transform: translateY(0); } to { transform: translateY(100%); } } `; document.head.appendChild(style); } } } // Add a floating button to reopen cookie settings (optional) function addCookieSettingsButton() { const btn = document.createElement('button'); btn.id = 'cookie-settings-button'; btn.innerHTML = '🍪'; btn.setAttribute('aria-label', 'Cookie-Einstellungen'); btn.style.cssText = ` position: fixed; bottom: 20px; left: 20px; width: 48px; height: 48px; border-radius: 50%; background: white; border: 1px solid var(--gray, #6c757d); box-shadow: var(--box-shadow, 0 4px 6px rgba(0,0,0,0.1)); cursor: pointer; font-size: 1.2rem; display: flex; align-items: center; justify-content: center; z-index: 9998; transition: var(--transition, all 0.3s ease); opacity: 0.8; `; btn.addEventListener('mouseenter', function() { this.style.opacity = '1'; this.style.transform = 'scale(1.1)'; }); btn.addEventListener('mouseleave', function() { this.style.opacity = '0.8'; this.style.transform = 'scale(1)'; }); btn.addEventListener('click', function() { showCookieSettings(); }); document.body.appendChild(btn); } // Initialize on DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initCookieConsent); } else { initCookieConsent(); } // Optional: Add settings button after consent is given window.addEventListener('cookieConsentChanged', function() { // Only show settings button if not already present if (!document.getElementById('cookie-settings-button')) { //addCookieSettingsButton(); } }); // If consent already exists, add settings button const existingConsent = getCookieConsent(); if (existingConsent && !document.getElementById('cookie-settings-button')) { document.addEventListener('DOMContentLoaded', function() { //addCookieSettingsButton(); }); } })(); </script> </body> </html>