Български | 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 | 汉语 | 日本語 |
N

news.nes.ru

Eingetragen
Screenshot news.nes.ru
Website-Screenshot

Russische Wirtschaftsschule ist ein Ort, an dem Ideen und Menschen geboren werdenNeuigkeiten von der Nachrichtenseite. nes. ru ist wie ein Fenster in die Welt, in der Wissenschaft auf Real...

Aktualisierungsdatum der Seitenanalyse: 2026/03/30 19:33:25
Datum der letzten Whois-Aktualisierung: 2026/06/06 21:02:14
Domänenstatus
Eingetragen
Bezahlt bis
08.06.2027
Erhältlich ab
10.07.2027

Website-Beschreibung

📝

Russische Wirtschaftsschule ist ein Ort, an dem Ideen und Menschen geboren werden

Neuigkeiten von der Nachrichtenseite. nes. ru ist wie ein Fenster in die Welt, in der Wissenschaft auf Realität trifft, wo Professoren über den Wettbewerb in China diskutieren und Studenten in aller Stille bei einem Picknick eine Karriere aufbauen. Ja, dies ist die Website von NES – der Russischen Wirtschaftsschule, aber nicht nur einer Bildungseinrichtung, sondern einer echten akademischen Gemeinschaft.

Der Geist ist sofort auf der Hauptseite zu spüren: Hier wird alles hervorgehoben – vom Hauptmenü bis zu den neuesten Nachrichten. Es gibt Abschnitte, die vielleicht wie eine gewöhnliche Reihe von Links erscheinen, aber wenn man genauer hinschaut, ist jeder von ihnen Teil eines größeren Systems. Über die Schule, Mission, Leute, Bewertungen. Das sind nicht nur Informationen, das ist Geschichte. Außerdem gibt es viele Details zum Campus, zum Stiftungsfonds und zu Kontakten. Nun ja, fast alles außer dem Telefon – es gibt keines. Aber es gibt noch viele andere Dinge.

Was ist hier los?

Die Seite lebt ihr eigenes Leben. Auf dem GURU-Portal werden Artikel veröffentlicht, in denen die Autoren Fragen stellen wie: Kann Wettbewerb der Wirtschaft schaden? Und ja, es stellt sich heraus, dass es möglich ist. Vor allem, wenn die Regierung zu aktiv eingreift. Beispiel – China. Es scheint, dass sich das ganze Land entwickelt, aber gleichzeitig gibt es Paradoxien. Ist es möglich, dass das scheinbar ideale System nicht so funktioniert, wie es sollte?

Es gibt noch andere Themen. Podcasts zum Beispiel. Wirtschaft nach Gehör. Die Gäste diskutieren über die Altgläubigen, ihre Ehrlichkeit, ihr Wort und ihr Vertrauen. Man kann stundenlang zuhören. Es stimmt, manchmal scheint es, als würden sie über das Geschäft reden, aber in Wirklichkeit reden sie über Moral. Interessant, nicht wahr? Wird Kapitalismus ohne Ethik schließlich nicht zum Chaos?

Oder hier ist ein anderes Beispiel – der Artikel Sei ein Mann!. Wir sprechen hier von Stereotypen. Männer streben danach, stark zu sein, sich zu überarbeiten, sich in die Arbeit zu stürzen, aber warum? Um sich selbst zu beweisen, dass sie wirklich Männer sind? Das betrifft aber auch Gesundheit, Beruf, Familie. Warum sind wir so gebaut? Vielleicht liegt der springende Punkt darin, dass die Gesellschaft uns dazu zwingt?

Bildung, Forschung, Leben

NES lehrt nicht nur. Hier gibt es Programme: Bachelor-, Master-, Workshop- und Aufbaustudiengänge. Es gibt sogar Online-Programme. Mini-MIF zum Beispiel. Oder ein Vermögensverwaltungsprogramm. Dies ist nicht nur ein Lehrplan – es ist ein Weg für diejenigen, die nicht nur ein Diplom machen, sondern die Welt verstehen wollen.

Hier finden auch Veranstaltungen statt. Picknick zum Beispiel. Zum zweiten Mal in Folge. Die Leute kommen, gehen, reden. Informell. Keine Vorträge. Aber es ist eine lebendige Gemeinschaft. Alles ist so, wie es sein sollte. Obwohl. vielleicht nicht ganz. Manchmal scheint es, dass jeder zu ernst ist. Nun ja, oder umgekehrt – zu lustig, um ehrlich zu sein.

Übrigens gab es im September ein Forum, auf dem Rektor Anton Suworow sprach. Über neue Lösungen für Wirtschaft und Regierung in Zeiten der Knappheit.Interessant, nicht wahr? Denn jeder von uns steht vor der Frage: Wo findet man Personal, Kapital, Ideen? Und hier, so stellt sich heraus, versuchen sie bereits, das Problem zu lösen.

Was für ein Portal ist GURU?

Das ist eine andere Geschichte. Es gibt Artikel, Kolumnen, Leselisten. Zum Beispiel Leseliste: Gesetze funktionieren wie Steuern und Preise. Es klingt seltsam, aber ja. Stellen Sie sich vor, das Recht sei eine Ware. Es wird verkauft, sein Preis hängt von der Nachfrage ab, Strafen sind Geldstrafen und Gerechtigkeit ist das Ergebnis eines Gleichgewichts von Anreizen. Nein, so einfach ist das nicht. Aber es ist interessant, nicht wahr? Aber die Leute studieren dort, auch wenn nicht an der NES.

Ergebnis

Neuigkeiten über Neuigkeiten. nes. ru ist nicht nur Information. Es ist Teil der akademischen Kultur. Für leere Stubs ist hier kein Platz. Keine Domain ist abgelaufen, die Seite ist vorübergehend nicht erreichbar – alles funktioniert. Er lebt einfach. Jeden Tag erscheint etwas Neues – entweder ein Artikel über den Wettbewerb, ein Podcast über Altgläubige oder ein Bericht über die Ergebnisse des Empfangs. Und das ist in Ordnung. So soll es sein.

Häufig gestellte Fragen

Was gibt es Neues? nes. ru?

Dies ist die Website der Russischen Wirtschaftsschule, auf der Nachrichten, Forschungsergebnisse, Expertenmeinungen, Materialien vom GURU-Portal und Informationen zu Schulungsprogrammen veröffentlicht werden.

Welche Programme gibt es an der NES?

Bachelor-Abschluss (BA), Master-Abschluss (MAMSc), Master-Studiengänge (DPO), Graduiertenschule, Online-Programme wie Mini-MIF und Master of Finance.

Was ist das GURU-Portal?

Dies ist eine von NES erstellte Plattform zur Veröffentlichung wissenschaftlicher Artikel, Analysen, Kolumnen und Materialien zu Wirtschaft, Gesellschaft und Kultur.

Wo können Sie sich über die Zulassungskampagne informieren?

Auf der News-Website. nes. ru wurden die Ergebnisse der Zulassungskampagne 2025 mit Daten zur Einschreibung von Studierenden in Bachelor- und Masterstudiengängen veröffentlicht.

Verfügt NES über Ereignisse?

Ja. Zum Beispiel ein traditionelles Picknick zu Beginn des Schuljahres, die Teilnahme des Rektors am Eastern Economic Forum, Reden auf Konferenzen und Seminaren.

.

SEO-Score
61.64%
90
Punktzahl erreicht
146
Maximale Punktzahl

Hauptinformationen

ℹ️
Titel: --> <title>Научные статьи, исследования, экспертное мнение | РЭШ
Beschreibung: Новости и события
Schlüsselwörter: Научные статьи, исследования, экспертное мнение, экономика, финансы, выпускники, студенты, абитуриенты
Seitenkodierung: utf-8
Seitendateigröße: 158 KB

Serverinformationen

🖥️
IP: 217.16.17.177
Standort: Russia,RU,55.7386,37.6068,Europe/Moscow
HTTP-Server: nginx
Codierung: utf-8

Meta-Tags-Liste

🏷️

Interne Links

🔗

Externe Links

🌐

Whois-Informationen

📄
domain_name: nes.ru
taxpayer_id: 7727016888
update_date: on
update_time:
creation_date: 2000-06-09T11:41:42Z
creation_time: 960550902

Whois-Rohdaten

📋
            domain:        NES.RU
nserver: ns3-l2.nic.ru.
nserver: ns4-cloud.nic.ru.
nserver: ns4-l2.nic.ru.
nserver: ns8-cloud.nic.ru.
nserver: ns8-l2.nic.ru.
state: REGISTERED, DELEGATED, VERIFIED
org: New Economic School, Non-Govemmental Education Institution of Higher Education
taxpayer-id: 7727016888
registrar: RU-CENTER-RU
admin-contact: https://www.nic.ru/whois
created: 2000-06-09T11:41:42Z
paid-till: 2027-06-08T21:00:00Z
free-date: 2027-07-10
source: TCI
Last updated on 2026-06-06T20:58:01Z

Robots.txt

🤖
			User-agent: Yandex 
Disallow: /*?
Disallow: /*?id=
Disallow: /assets/components/
Disallow: /core/
Disallow: /mgr/
Disallow: /*connectors/
Disallow: /*manager/
Disallow: /index.php
Disallow: /404
Disallow: /404.html
Disallow: /log/
Disallow: /maillog/
Disallow: /cg-bin/
Disallow: /templstes/
Disallow: /revolution/
Disallow: /scriptsl/
Disallow: /permit-covid
Disallow: /permit-covid.html1
Disallow: /permit-covid-staff
Disallow: /permit-covid-staff.html
Disallow: /permit-covid-MIF
Disallow: /permit-covid-MIF.html
Disallow: /permit-covid-MAE
Disallow: /permit-covid-MAE.html
Disallow: /permit-covid-BAE
Disallow: /permit-covid-BAE.html
Disallow: /permit-covid-MAF
Disallow: /permit-covid-MAF.html
Disallow: /permit-covid-MAEE
Disallow: /permit-covid-MAEE.html
Disallow: /permit-covid-MSEE
Disallow: /permit-covid-MSEE.html
Disallow: /permit-covid-EADA
Disallow: /permit-covid-EADA.html
Disallow: /permit-covid-MSF
Disallow: /permit-covid-MSF.html
Disallow: /rectorsearch
Disallow: /rectorsearch.html
Disallow: /rectorsearch-ad
Disallow: /rectorsearch-ad.html
Disallow: *?
Crawl-Delay: 5

User-agent: Googlebot
Disallow: /*?
Disallow: /*?id=
Disallow: /assets/components/
Disallow: /core/
Disallow: /mgr/
Disallow: /*connectors/
Disallow: /*manager/
Disallow: /index.php
Disallow: /404
Disallow: /404.html
Disallow: /log/
Disallow: /maillog/
Disallow: /cg-bin/
Disallow: /templstes/
Disallow: /revolution/
Disallow: /scriptsl/
Disallow: /permit-covid
Disallow: /permit-covid.html
Disallow: /permit-covid-staff
Disallow: /permit-covid-staff.html
Disallow: /permit-covid-MIF
Disallow: /permit-covid-MIF.html
Disallow: /permit-covid-MAE
Disallow: /permit-covid-MAE.html
Disallow: /permit-covid-BAE
Disallow: /permit-covid-BAE.html
Disallow: /permit-covid-MAF
Disallow: /permit-covid-MAF.html
Disallow: /permit-covid-MAEE
Disallow: /permit-covid-MAEE.html
Disallow: /permit-covid-MSEE
Disallow: /permit-covid-MSEE.html
Disallow: /permit-covid-EADA
Disallow: /permit-covid-EADA.html
Disallow: /permit-covid-MSF
Disallow: /permit-covid-MSF.html
Disallow: /rectorsearch
Disallow: /rectorsearch.html
Disallow: /rectorsearch-ad
Disallow: /rectorsearch-ad.html
Disallow: *?
Crawl-Delay: 5

User-agent: Googlebot-Image 
Disallow: /*?
Disallow: /*?id=
Disallow: /assets/components/
Disallow: /core/
Disallow: /mgr/
Disallow: /*connectors/
Disallow: /*manager/
Disallow: /index.php
Disallow: /404
Disallow: /404.html
Disallow: /log/
Disallow: /maillog/
Disallow: /cg-bin/
Disallow: /templstes/
Disallow: /revolution/
Disallow: /scriptsl/
Disallow: /permit-covid
Disallow: /permit-covid.html
Disallow: /permit-covid-staff
Disallow: /permit-covid-staff.html
Disallow: /permit-covid-MIF
Disallow: /permit-covid-MIF.html
Disallow: /permit-covid-MAE
Disallow: /permit-covid-MAE.html
Disallow: /permit-covid-BAE
Disallow: /permit-covid-BAE.html
Disallow: /permit-covid-MAF
Disallow: /permit-covid-MAF.html
Disallow: /permit-covid-MAEE
Disallow: /permit-covid-MAEE.html
Disallow: /permit-covid-MSEE
Disallow: /permit-covid-MSEE.html
Disallow: /permit-covid-EADA
Disallow: /permit-covid-EADA.html
Disallow: /permit-covid-MSF
Disallow: /permit-covid-MSF.html
Disallow: /rectorsearch
Disallow: /rectorsearch.html
Disallow: /rectorsearch-ad
Disallow: /rectorsearch-ad.html
Disallow: *?
Crawl-Delay: 5

User-agent: *
Disallow: /

Sitemap: https://www.nes.ru/sitemap.xml
Host: https://www.nes.ru/
        

SEO-Audit

🔍

Technisches SEO

Antwortcode
200
Status 200 OK – Seite wird korrekt geladen.
Zeichenkodierung
Page: utf-8, Header: utf-8
Konsistente Zeichenkodierung zwischen HTML und Headern.
Seitengröße
162717 bytes
Seitengröße für schnelles Laden akzeptabel.
!
Ressourcen
62 total
Zu viele Ressourcen. Optimieren und kombinieren Sie Dateien für eine bessere Leistung.
Hreflang-Tags
0 hreflang tags
Fügen Sie Hreflang-Tags hinzu, wenn Sie mehrsprachige Inhalte haben.
Robots.txt
Exists
Robots.txt-Datei gefunden.
Sitemap
Declared in robots.txt
Sitemap in robots.txt deklariert.
HTTPS
Yes
Sichere HTTPS-Verbindung aktiviert.
Kompression
gzip
Gzip- oder Zstd-Komprimierung für schnelleres Laden aktiviert.
Caching
no-store, no-cache, must-revalidate
Cache-Control-Header richtig gesetzt.
Seitengeschwindigkeit
1.7 ms
Hervorragende Ladegeschwindigkeit.

On-Page-SEO

!
Titel
--> Научные статьи, исследования, экспертное мнение | РЭШ</div><div class="audit-recommendation">Titel zu lang. Reduzieren Sie die Länge auf 30–60 Zeichen, um Kürzungen zu vermeiden.</div></div></div><div class="audit-item"><div class="audit-status status-fail">!</div><div class="audit-info"><div class="audit-name">Meta-Beschreibung</div><div class="audit-value">Новости и события Lenght:17</div><div class="audit-recommendation">Meta-Beschreibung zu kurz. Auf 100–160 Zeichen erweitern.</div></div></div><div class="audit-item"><div class="audit-status status-pass">✓</div><div class="audit-info"><div class="audit-name">H1-Überschrift</div><div class="audit-value">1 found - "Новости"</div><div class="audit-recommendation">Gut – einzelne H1-Überschrift gefunden.</div></div></div><div class="audit-item"><div class="audit-status status-fail">!</div><div class="audit-info"><div class="audit-name">Wortanzahl</div><div class="audit-value">383</div><div class="audit-recommendation">Inhalt etwas kurz. Erweitern Sie den Text auf über 500 Wörter für optimale SEO.</div></div></div><div class="audit-item"><div class="audit-status status-fail">!</div><div class="audit-info"><div class="audit-name">Kanonisches Tag</div><div class="audit-value"></div><div class="audit-recommendation">Fügen Sie ein kanonisches Tag hinzu, um Probleme mit doppeltem Inhalt zu vermeiden.</div></div></div><div class="audit-item"><div class="audit-status status-fail">!</div><div class="audit-info"><div class="audit-name">Doppelte Meta</div><div class="audit-value">{"yandex-verification":2,"format-detection":2}</div><div class="audit-recommendation">Entfernen Sie doppelte Meta-Tags, um Konflikte zu vermeiden.</div></div></div><div class="audit-item"><div class="audit-status status-pass">✓</div><div class="audit-info"><div class="audit-name">Schlüsselwörter</div><div class="audit-value">Научные статьи, исследования, экспертное мнение, экономика, финансы, выпускники, студенты, абитуриенты</div><div class="audit-recommendation">Meta-Schlüsselwörter festgelegt (Hinweis: werden von großen Suchmaschinen nicht verwendet).</div></div></div><h3 class="category-title" style="margin-top: 1.5rem;">Inhalt und UX</h3><div class="audit-item"><div class="audit-status status-fail">!</div><div class="audit-info"><div class="audit-name">Sprache</div><div class="audit-value"></div><div class="audit-recommendation">Fügen Sie dem <html>-Tag das lang-Attribut hinzu, um Barrierefreiheit und SEO zu gewährleisten.</div></div></div><div class="audit-item"><div class="audit-status status-fail">!</div><div class="audit-info"><div class="audit-name">Bilder</div><div class="audit-value">18 total, 5 missing ALT</div><div class="audit-recommendation">Fügen Sie Bildern ALT-Text hinzu, um Barrierefreiheit und SEO zu gewährleisten.</div></div></div><div class="audit-item"><div class="audit-status status-pass">✓</div><div class="audit-info"><div class="audit-name">Ansichtsfenster</div><div class="audit-value">width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no</div><div class="audit-recommendation">Viewport-Meta-Tag für mobile Geräte richtig eingestellt.</div></div></div><div class="audit-item"><div class="audit-status status-pass">✓</div><div class="audit-info"><div class="audit-name">Diagramm öffnen</div><div class="audit-value">All OG tags present.</div><div class="audit-recommendation">Alle wichtigen OpenGraph-Tags vorhanden.</div></div></div><div class="audit-item"><div class="audit-status status-pass">✓</div><div class="audit-info"><div class="audit-name">Strukturierte Daten</div><div class="audit-value">1 JSON-LD scripts</div><div class="audit-recommendation">Strukturierte Daten (JSON-LD) gefunden.</div></div></div></div></div> <!-- Load Google Charts at the top of the search engine section --> <script> // Load Google Charts google.charts.load('current', {'packages':['corechart', 'line']}); </script> <!-- Google Positions --> <div class="chart-container"> <h2 class="chart-title">Positionen in Google</h2> <div id="chart_div_google" style="width: 100%; height: 400px;"></div> </div> <!-- Google Search Phrases --> <div class="card"> <div class="card-header"> <h2 class="card-title">Suchphrasen - Google</h2> <div class="card-icon">🔍</div> </div> <div class="card-content"> <div class="filters"> <div class="filter-group"> <label class="filter-label" for="googlePhraseFilter">Phrase:</label> <input type="text" id="googlePhraseFilter" class="filter-input" placeholder="Nach Phrase filtern"> </div> <div class="filter-group"> <label class="filter-label" for="googlePageFilter">Seite:</label> <input type="text" id="googlePageFilter" class="filter-input" placeholder="Nach Seite filtern"> </div> <div class="filter-group"> <label class="filter-label"> </label> <button class="cta-button" onclick="applyFilters('google')">Filter anwenden</button> </div> </div> <div class="table-responsive"> <table class="data-table" id="googlePhrasesTable"> <thead> <tr> <th>Position</th> <th>Phrase</th> <th>Seite</th> <th>Ausschnitt</th> </tr> </thead> <tbody id="googlePhrasesBody"> <tr><td><span class="badge badge-success">1</span><span class="position-change positive">(+1)</span></td><td><noindex><a href="/de/phrase/%D0%BF%D0%BE%D1%87%D0%B5%D0%BC%D1%83-%D0%B6%D0%B5%D0%BD%D1%89%D0%B8%D0%BD-%D0%BC%D0%B0%D0%BB%D0%BE/" rel="nofollow">почему женщин мало</a></noindex></td><td>/news/Pamela-Campa-w...</td><td><button class="show-snippet-btn" onclick="toggleSnippet(this)">Ausschnitt anzeigen</button><div class="snippet" style="display: none;"><noindex><a href="https://news.nes.ru//news/Pamela-Campa-women-in-politics-lecture/" rel="nofollow" target="_blank">Почему женщины мало представлены в политике?</a></noindex><br>19 нояб. 2019 г. — Глубинная причина такой расстановки приоритетов – недостаток поддержки со стороны родителей и учителей , маленький опыт конкурентной борьбы и, ...</div></td></tr><tr><td><span class="badge badge-success">1</span><span class="position-change positive">(+3)</span></td><td><noindex><a href="/de/phrase/%D0%BF%D0%BE%D0%BB%D0%B8%D1%82%D0%B8%D0%BA%D0%B8-%D0%BC%D0%B5%D0%BD%D0%B5%D0%B5/" rel="nofollow">политики менее</a></noindex></td><td>/news/Pamela-Campa-w...</td><td><button class="show-snippet-btn" onclick="toggleSnippet(this)">Ausschnitt anzeigen</button><div class="snippet" style="display: none;"><noindex><a href="https://news.nes.ru//news/Pamela-Campa-women-in-politics-lecture/" rel="nofollow" target="_blank">Почему женщины мало представлены в политике?</a></noindex><br>19 нояб. 2019 г. — Глубинная причина такой расстановки приоритетов – недостаток поддержки со стороны родителей и учителей, маленький опыт конкурентной борьбы и, ...</div></td></tr><tr><td><span class="badge badge-success">1</span></td><td><noindex><a href="/de/phrase/%D0%BF%D1%80%D0%B8%D1%85%D0%BE%D0%B4%D0%B8%D1%82%D1%8C-%D0%B8%D1%82%D0%BE%D0%B3/" rel="nofollow">приходить итог</a></noindex></td><td>/news/kogda-ii-prixo...</td><td><button class="show-snippet-btn" onclick="toggleSnippet(this)">Ausschnitt anzeigen</button><div class="snippet" style="display: none;"><noindex><a href="https://news.nes.ru//news/kogda-ii-prixodit-v-sud-itogi-pervogo-masshtabnogo-eksperimenta/" rel="nofollow" target="_blank">Когда ИИ приходит в суд: итоги первого масштабного ...</a></noindex><br>10 июл. 2026 г. — Что получится, если дополнить судью роботом? Чем языковая модель может помочь правосудию? И готовы ли судьи доверить чат-ботам право ...</div></td></tr><tr><td><span class="badge badge-success">1</span></td><td><noindex><a href="/de/phrase/%D1%80%D0%B5%D1%88%D0%B8%D1%82%D0%B5-%D1%8D%D0%BA%D0%BE%D0%BD%D0%BE%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D1%83%D1%8E/" rel="nofollow">решите экономическую</a></noindex></td><td>/news/ne-tolko-vsero...</td><td><button class="show-snippet-btn" onclick="toggleSnippet(this)">Ausschnitt anzeigen</button><div class="snippet" style="display: none;"><noindex><a href="https://news.nes.ru//news/ne-tolko-vseros.-podrobnyij-gajd-resh-po-ekonomicheskim-olimpiadam/" rel="nofollow" target="_blank">Не только Всерос. Подробный гайд ...</a></noindex><br>30 нояб. 2021 г. — Большинство поступающих на Совместный бакалавриат РЭШ и ВШЭ – победители и призеры олимпиад по экономике. Как выбрать, в каких участвовать?</div></td></tr><tr><td><span class="badge badge-success">2</span></td><td><noindex><a href="/de/phrase/%D1%82%D0%B5%D0%BF%D0%BB%D1%83%D1%85%D0%B8%D0%BD-%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B0-%D0%B4%D0%B8%D0%B0%D0%BB%D0%BE%D0%B3/" rel="nofollow">теплухин тройка диалог</a></noindex></td><td>/news/pavel-tepluxin...</td><td><button class="show-snippet-btn" onclick="toggleSnippet(this)">Ausschnitt anzeigen</button><div class="snippet" style="display: none;"><noindex><a href="https://news.nes.ru//news/pavel-tepluxin-v-resh/&lang=ru" rel="nofollow" target="_blank">Павел Теплухин, один из основателей «Тройка Диалог</a></noindex><br>21 февр. 2018 г. — Президент Matrix Capital Павел Теплухин рассказал студентам и выпускникам РЭШ о взгляде изнутри на фондовые рынки России.</div></td></tr><tr><td><span class="badge badge-success">2</span></td><td><noindex><a href="/de/phrase/%D0%B2%D1%8B%D0%BF%D1%83%D1%81%D0%BA%D0%BD%D0%B8%D0%BA%D0%B8-%D0%BC%D0%B0%D0%B3%D0%B8%D1%81%D1%82%D1%80%D0%B0%D1%82%D1%83%D1%80%D1%8B/" rel="nofollow">выпускники магистратуры</a></noindex></td><td>/news/gde-rabotayut-...</td><td><button class="show-snippet-btn" onclick="toggleSnippet(this)">Ausschnitt anzeigen</button><div class="snippet" style="display: none;"><noindex><a href="https://news.nes.ru//news/gde-rabotayut-i-skolko-zarabatyivayut-vyipuskniki-magistraturyi-resh/" rel="nofollow" target="_blank">Где работают и сколько зарабатывают выпускники ...</a></noindex><br>7 февр. 2025 г. — Узнайте, на какие позиции и в какие компании трудоустраиваются выпускники магистратуры РЭШ сразу после выпуска. Ключевики: карьера после РЭШ ...</div></td></tr><tr><td><span class="badge badge-success">2</span></td><td><noindex><a href="/de/phrase/%D0%B6%D0%B5%D0%BD%D1%89%D0%B8%D0%BD%D1%8B-%D0%BC%D0%B0%D0%BB%D0%BE/" rel="nofollow">женщины мало</a></noindex></td><td>/news/Pamela-Campa-w...</td><td><button class="show-snippet-btn" onclick="toggleSnippet(this)">Ausschnitt anzeigen</button><div class="snippet" style="display: none;"><noindex><a href="https://news.nes.ru//news/Pamela-Campa-women-in-politics-lecture/" rel="nofollow" target="_blank">Почему женщины мало представлены в политике?</a></noindex><br>19 нояб. 2019 г. — Глубинная причина такой расстановки приоритетов – недостаток поддержки со стороны родителей и учителей, маленький опыт конкурентной борьбы и, ...</div></td></tr><tr><td><span class="badge badge-success">2</span></td><td><noindex><a href="/de/phrase/%D0%BC%D0%B0%D0%BB%D0%BE-%D0%BE%D1%82%D0%BB%D0%B8%D1%87%D0%B0%D0%B5%D1%82%D1%81%D1%8F/" rel="nofollow">мало отличается</a></noindex></td><td>/news/bolshe-biznesa...</td><td><button class="show-snippet-btn" onclick="toggleSnippet(this)">Ausschnitt anzeigen</button><div class="snippet" style="display: none;"><noindex><a href="https://news.nes.ru//news/bolshe-biznesa-menshe-sporta-chem-chm-po-futbolu-otlichaetsya-ot-drugix-podkast-ekonomika-na-slux/" rel="nofollow" target="_blank">Больше бизнеса, меньше спорта. Чем ЧМ по футболу ...</a></noindex><br>30 июн. 2026 г. — «Экономика на слух» не могла остаться в стороне от чемпионата мира по футболу 2026 г. На этом турнире число команд выросло с 32 до 48, появилось ...</div></td></tr><tr><td><span class="badge badge-success">2</span></td><td><noindex><a href="/de/phrase/%D0%BF%D0%BE%D0%BC%D0%BE%D0%B3%D0%B8%D1%82%D0%B5-16/" rel="nofollow">помогите 16</a></noindex></td><td>/news/na-16-m-semina...</td><td><button class="show-snippet-btn" onclick="toggleSnippet(this)">Ausschnitt anzeigen</button><div class="snippet" style="display: none;"><noindex><a href="https://news.nes.ru//news/na-16-m-seminare-po-ekonomicheskim-issledovaniyam-ekspertyi-obsudili-kak-bolshie-i-alternativnyie-dannyie-vliyayut-na-ekonomiku-i-finansovyij-sektor/" rel="nofollow" target="_blank">На 16-м семинаре по экономическим исследованиям ...</a></noindex><br>2 дня назад — На 16 -м семинаре по экономическим исследованиям эксперты обсудили, как большие и альтернативные данные влияют на экономику и финансовый сектор ...</div></td></tr><tr><td><span class="badge badge-success">2</span></td><td><noindex><a href="/de/phrase/%D0%B3%D0%B4%D0%B5-%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%B0%D1%8E%D1%82-%D0%BF%D0%BE%D1%81%D0%BB%D0%B5/" rel="nofollow">где работают после</a></noindex></td><td>/news/kariera-posle-...</td><td><button class="show-snippet-btn" onclick="toggleSnippet(this)">Ausschnitt anzeigen</button><div class="snippet" style="display: none;"><noindex><a href="https://news.nes.ru//news/kariera-posle-resh-gde-rabotayut-i-skolko-zarabatyivayut-vyipuskniki-magistraturyi-2025/" rel="nofollow" target="_blank">Где работают и сколько зарабатывают выпускники ...</a></noindex><br>3 февр. 2026 г. — Такой подход обеспечивает выпускникам широкий спектр карьерных возможностей в экономике, финансовом секторе, международных компаниях, ...</div></td></tr> </tbody> </table> </div> <div class="pagination" id="googlePagination"> <button disabled onclick="loadSearchPhrases('google', 0)">«</button> <button class="active" onclick="loadSearchPhrases('google', 1)">1</button> <button class="" onclick="loadSearchPhrases('google', 2)">2</button> <button class="" onclick="loadSearchPhrases('google', 3)">3</button> <button disabled="">...</button> <button class="" onclick="loadSearchPhrases('google', 12)">12</button> <button class="" onclick="loadSearchPhrases('google', 13)">13</button> <button onclick="loadSearchPhrases('google', 2)">»</button> </div> </div> </div> <script> // Google Charts for google google.charts.setOnLoadCallback(drawChart_google); function drawChart_google() { var data = new google.visualization.DataTable(); data.addColumn('date', 'Tag'); data.addColumn("number", "почему женщин мало");data.addColumn("number", "политики менее");data.addColumn("number", "приходить итог");data.addColumn("number", "решите экономическую");data.addColumn("number", "теплухин тройка диалог");data.addColumn("number", "выпускники магистратуры");data.addColumn("number", "женщины мало");data.addColumn("number", "мало отличается");data.addColumn("number", "помогите 16");data.addColumn("number", "где работают после"); data.addRows([ [new Date(2025, 9, 28),null,null,null,null,2,null,null,null,null,null,],[new Date(2025, 9, 30),null,null,null,null,null,null,2,null,null,null,],[new Date(2025, 10, 26),null,null,null,null,null,null,null,null,2,null,],[new Date(2025, 6, 1),2,null,null,null,null,null,null,null,null,null,],[new Date(2025, 6, 13),null,4,null,null,null,null,null,null,null,null,],[new Date(2025, 7, 10),null,null,null,null,null,2,null,null,null,null,],[new Date(2026, 6, 19),null,null,null,1,null,null,null,null,null,null,],[new Date(2026, 7, 13),null,null,null,null,null,null,null,2,null,null,],[new Date(2026, 7, 14),1,null,null,null,null,null,null,null,null,null,],[new Date(2026, 7, 17),null,null,null,null,null,null,null,null,null,2,],[new Date(2026, 7, 3),null,1,1,null,null,null,null,null,null,null,], ]); var options = { chart: { title: 'Suchbegriffe, bei denen die Domain ganz oben steht google Suchmaschine: "news.nes.ru"', subtitle: 'Verlauf der Phrasenposition' }, 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> <!-- Additional Services --> <div class="card" id="domain-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-payment-type="domain_seo_pay" data-payment-item-id="8754252" data-title="Vollständiges SEO-Audit news.nes.ru" 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-payment-type="unsupported" disabled title="Payment flow is not implemented for this service" data-payment-item-id="8754252" data-title="Positionsüberwachung news.nes.ru" 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-payment-type="domain_analiz_pay" data-payment-item-id="8754252" data-title="Konkurrenzanalyse news.nes.ru" data-text="Konkurrenzanalyse und Identifizierung ihrer Werbestrategien." data-price="79.99"> Konkurrenzanalyse </button> <span class="popup-text"></span> </span> </div> </div> </div> </div> <!-- Search Engine Positions --> <!-- Load Google Charts --> <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> <script> // Load Google Charts google.charts.load('current', {'packages':['corechart', 'line']}); </script> <script> // Google Charts loader (already in your HTML) google.charts.load('current', {'packages':['corechart', 'line']}); // Global variable to store chart instances let chartInstances = {}; // AJAX function for loading search phrases with pagination/filtering function loadSearchPhrases(engine, page) { const phraseFilterEl = document.getElementById(`${engine}PhraseFilter`); const pageFilterEl = document.getElementById(`${engine}PageFilter`); const phraseFilter = phraseFilterEl ? phraseFilterEl.value : ''; const pageFilter = pageFilterEl ? pageFilterEl.value : ''; const domain = document.getElementById('domain_name').value; const data = { type: 'search_phrases', engine: engine, page: page, domain: domain, phrase_filter: phraseFilter, page_filter: pageFilter }; // Show loading state for table const tbody = document.getElementById(`${engine}PhrasesBody`); if (tbody) { tbody.innerHTML = ` <tr> <td colspan="4" style="text-align: center; padding: 2rem;"> <div class="loading-spinner"></div> <p>Loading data...</p> </td> </tr> `; } // Show loading state for chart const chartContainer = document.getElementById(`chart_div_${engine}`); if (chartContainer) { chartContainer.innerHTML = ` <div style="text-align: center; padding: 4rem;"> <div class="loading-spinner"></div> <p>Updating chart...</p> </div> `; } sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { updateTableContent(engine, response.data); updatePagination(engine, response.pagination); // Generate and update chart with new data if (response.data && response.data.length > 0) { const chartData = generateChartData(response.data, engine); updateChart(engine, chartData); } else { // Hide chart if no data const chartContainerDiv = document.getElementById(`chart_container_${engine}`); if (chartContainerDiv) { chartContainerDiv.style.display = 'none'; } } } else { showErrorInTable(engine, response.error); } }); } // Generate chart data from API response function generateChartData(phrasesData, engine) { const chartData = { js_addColumn_str: '', js_dataRows_str: '' }; if (!phrasesData || phrasesData.length === 0) { return chartData; } // Parse all phrases and their history const allPhrases = []; const allHistory = []; phrasesData.forEach(item => { const pages = parsePageData(item.page_data); const titles = parseContentData(item.title_data); const snippets = parseContentData(item.snippet_data); const history = parsePositionHistory(item.position_history, pages, titles, snippets); if (history.length > 0) { allPhrases.push(item.phrase); allHistory.push({ phrase: item.phrase, history: history }); } }); if (allHistory.length === 0) { return chartData; } // Collect all unique days const allDays = new Set(); allHistory.forEach(item => { item.history.forEach(h => { allDays.add(h.day); }); }); // Sort days in ascending order const sortedDays = Array.from(allDays).sort((a, b) => a - b); // Generate columns chartData.js_addColumn_str = 'data.addColumn("date", "Day")\n'; allPhrases.forEach(phrase => { const label = phrase.length > 30 ? phrase.substring(0, 27) + '...' : phrase; chartData.js_addColumn_str += `data.addColumn("number", "${label}")\n`; }); // Count how many columns we should have const expectedColumnCount = 1 + allPhrases.length; // Generate data rows const dataRows = []; sortedDays.forEach(day => { // Convert day number to Date object const date = new Date(day * 86400 * 1000); // Create row array const row = [date]; // Add position for EACH phrase on this day allHistory.forEach(item => { const history = item.history; const entry = history.find(h => h.day === day); row.push(entry ? entry.position : null); }); // Adjust row length if needed if (row.length !== expectedColumnCount) { while (row.length < expectedColumnCount) { row.push(null); } while (row.length > expectedColumnCount) { row.pop(); } } dataRows.push(row); }); // Format rows for Google Charts if (dataRows.length > 0) { const rowStrings = dataRows.map(row => { const formattedRow = row.map((value, index) => { if (index === 0 && value instanceof Date) { const year = value.getFullYear(); const month = value.getMonth(); const day = value.getDate(); return `new Date(${year}, ${month}, ${day})`; } return value; }); return JSON.stringify(formattedRow); }); chartData.js_dataRows_str = rowStrings.join(',\n'); chartData.js_dataRows_str = chartData.js_dataRows_str.replace(/"new Date\((\d+), (\d+), (\d+)\)"/g, 'new Date($1, $2, $3)'); } return chartData; } // Update chart with new data function updateChart(engine, chartData) { if (!chartData || !chartData.js_dataRows_str || !chartData.js_addColumn_str) { console.warn(`No chart data for ${engine}`); const chartContainerDiv = document.getElementById(`chart_container_${engine}`); if (chartContainerDiv) { chartContainerDiv.style.display = 'none'; } return; } // Show chart container const chartContainerDiv = document.getElementById(`chart_container_${engine}`); if (chartContainerDiv) { chartContainerDiv.style.display = 'block'; } // Check if Google Charts is loaded if (typeof google === 'undefined' || typeof google.visualization === 'undefined') { console.warn('Google Charts not loaded yet'); google.charts.load('current', {'packages':['corechart', 'line']}); google.charts.setOnLoadCallback(function() { setTimeout(() => { drawChart(engine, chartData); }, 100); }); } else { setTimeout(() => { drawChart(engine, chartData); }, 100); } } // Draw chart function function drawChart(engine, chartData) { try { const chartContainer = document.getElementById(`chart_div_${engine}`); // Check if chart container exists if (!chartContainer) { console.warn(`Chart container for ${engine} not found`); return; } var data = new google.visualization.DataTable(); // Add date column data.addColumn('date', 'Day'); // Parse and add data columns const columns = chartData.js_addColumn_str.split('\n').filter(line => line.trim() !== ''); columns.forEach(col => { const match = col.match(/data\.addColumn\("(\w+)",\s*"([^"]+)"\)/); if (match) { const type = match[1]; const label = match[2]; if (type === 'date' && label === 'Day') { return; } data.addColumn(type, label); } }); // Parse data rows const rowsStr = chartData.js_dataRows_str.trim(); if (rowsStr && rowsStr !== '[]') { try { const rows = eval(`[${rowsStr}]`); // Adjust rows if needed const adjustedRows = rows.map(row => { if (!Array.isArray(row)) { return new Array(data.getNumberOfColumns()).fill(null); } if (row.length === data.getNumberOfColumns()) { return row; } const adjusted = new Array(data.getNumberOfColumns()).fill(null); // Copy available values const copyCount = Math.min(row.length, data.getNumberOfColumns()); for (let i = 0; i < copyCount; i++) { adjusted[i] = row[i]; } return adjusted; }); data.addRows(adjustedRows); } catch (e) { console.error(`Error parsing data rows:`, e); return; } } const domainName = document.getElementById('domain_name').value; const engineName = engine.charAt(0).toUpperCase() + engine.slice(1); var options = { chart: { title: `Search phrases for which the domain is at the top of the ${engine} search engine: "${domainName}"`, subtitle: 'Phrase position history' }, width: '100%', height: 400, curveType: 'function', legend: { position: 'bottom', maxLines: 3 }, pointSize: 3, pointsVisible: true, interpolateNulls: true, selectionMode: 'multiple', vAxis: { direction: -1, title: 'Position', viewWindow: { min: 1, max: 100 }, ticks: [ {v: 1, f: '1'}, {v: 10, f: '10'}, {v: 20, f: '20'}, {v: 30, f: '30'}, {v: 50, f: '50'}, {v: 100, f: '100'} ] }, hAxis: { title: 'Date', format: 'MMM yyyy' }, }; // Store chart instance for potential future updates const chart = new google.visualization.LineChart(chartContainer); chartInstances[engine] = chart; chart.draw(data, options); } catch (error) { console.error(`Error drawing chart for ${engine}:`, error); } } // Parse functions (same as before) function parsePageData(pageDataStr) { const pages = {}; if (!pageDataStr || pageDataStr.trim() === '') return pages; try { const pageItems = pageDataStr.split(';'); pageItems.forEach(item => { if (item.trim()) { const parts = item.split(':'); if (parts.length >= 2) { const id = parts[0].trim(); const page = parts.slice(1).join(':'); if (id) { pages[id] = decodeURIComponent(page).replace(/\+/g, ' '); } } } }); } catch (e) { console.error('Error parsing page data:', e); } return pages; } function parseContentData(contentDataStr) { const content = {}; if (!contentDataStr || contentDataStr.trim() === '') return content; try { const items = contentDataStr.split(';'); items.forEach(item => { if (item.trim()) { const parts = item.split(':'); if (parts.length >= 2) { const id = parts[0].trim(); const value = parts.slice(1).join(':'); if (id) { let decodedValue; try { decodedValue = decodeURIComponent(value); decodedValue = decodedValue.replace(/\\x([0-9A-Fa-f]{2})/g, (match, hex) => String.fromCharCode(parseInt(hex, 16)) ); } catch (e) { decodedValue = value; } content[id] = decodedValue; } } } }); } catch (e) { console.error('Error parsing content data:', e); } return content; } function parsePositionHistory(historyStr, pages, titles, snippets) { const history = []; if (!historyStr || historyStr.trim() === '') return history; try { const entries = historyStr.split(';'); entries.forEach(entry => { if (entry.trim()) { const parts = entry.split(':'); if (parts.length >= 3) { const day = parseInt(parts[0]); const position = parseInt(parts[1]); const pageId = parts[2]; const titleId = parts[3] || ''; const snippetId = parts[4] || ''; if (!isNaN(day) && !isNaN(position)) { history.push({ day: day, position: position, page: pages[pageId] || '', title: titles[titleId] || '', snippet: snippets[snippetId] || '' }); } } } }); history.sort((a, b) => a.day - b.day); } catch (e) { console.error('Error parsing position history:', e); } return history; } // Update table content with AJAX response function updateTableContent(engine, data) { const tbody = document.getElementById(`${engine}PhrasesBody`); if (!tbody) return; let tableRows = ''; const domain = document.getElementById('domain_name').value; data.forEach(item => { const pages = parsePageData(item.page_data); const titles = parseContentData(item.title_data); const snippets = parseContentData(item.snippet_data); const history = parsePositionHistory(item.position_history, pages, titles, snippets); const latestEntry = history.length > 0 ? history[history.length - 1] : null; const position = latestEntry ? latestEntry.position : null; const page = latestEntry ? latestEntry.page : ''; const title = latestEntry ? latestEntry.title : ''; const snippet = latestEntry ? latestEntry.snippet : ''; let change = null; if (history.length >= 2) { const previousEntry = history[history.length - 2]; if (previousEntry && position !== null && previousEntry.position !== null) { change = previousEntry.position - position; } } const badgeClass = getPositionBadgeClass(position); const positionHtml = position ? `<span class="badge ${badgeClass}">${position}</span>` : '<span class="badge">N/A</span>'; const changeHtml = change !== null && change !== 0 ? `<span class="position-change ${change > 0 ? 'positive' : 'negative'}">(${change > 0 ? '+' : ''}${change})</span>` : ''; const hasTitle = title && title.trim() !== ''; const hasSnippet = snippet && snippet.trim() !== ''; const showSnippet = hasTitle || hasSnippet; let snippetHtml = ''; if (showSnippet) { snippetHtml = ` <button class="show-snippet-btn" onclick="toggleSnippet(this)">Show snippet</button> <div class="snippet" style="display: none;"> <noindex><a href="https://${domain}${escapeHtml(page)}" rel="nofollow" target="_blank"> ${hasTitle ? `<strong>${escapeHtml(title)}</strong><br>` : ''} </a></noindex> ${hasSnippet ? escapeHtml(snippet) : ''} </div> `; } else { snippetHtml = '<span style="color: var(--gray);">-</span>'; } tableRows += ` <tr> <td>${positionHtml}${changeHtml}</td> <td><noindex><a href="/en/phrase/${encodeURIComponent(item.phrase)}/" rel="nofollow">${escapeHtml(item.phrase)}</a></noindex></td> <td>${escapeHtml(page)}</td> <td>${snippetHtml}</td> </tr> `; }); tbody.innerHTML = tableRows; } // Update pagination with AJAX response function updatePagination(engine, pagination) { const paginationContainer = document.getElementById(`${engine}Pagination`); if (!paginationContainer) return; const { current_page, total_pages, has_previous, has_next } = pagination; let paginationButtons = ''; if (total_pages > 1) { // Previous button paginationButtons += `<button ${!has_previous ? 'disabled' : ''} onclick="loadSearchPhrases('${engine}', ${current_page - 1})">«</button>`; // First page paginationButtons += `<button class="${current_page === 1 ? 'active' : ''}" ${current_page === 1 ? 'disabled' : ''} onclick="loadSearchPhrases('${engine}', 1)">1</button>`; // Calculate range let startPage = Math.max(2, current_page - 2); let endPage = Math.min(total_pages - 1, current_page + 2); if (current_page <= 3) { endPage = Math.min(total_pages - 1, 5); } if (current_page >= total_pages - 2) { startPage = Math.max(2, total_pages - 4); } if (startPage > 2) { paginationButtons += `<button disabled>...</button>`; } for (let i = startPage; i <= endPage; i++) { const activeClass = i === current_page ? 'active' : ''; const disabledAttr = i === current_page ? 'disabled' : ''; paginationButtons += `<button class="${activeClass}" ${disabledAttr} onclick="loadSearchPhrases('${engine}', ${i})">${i}</button>`; } if (endPage < total_pages - 1) { paginationButtons += `<button disabled>...</button>`; } if (total_pages > 1) { paginationButtons += `<button class="${current_page === total_pages ? 'active' : ''}" ${current_page === total_pages ? 'disabled' : ''} onclick="loadSearchPhrases('${engine}', ${total_pages})">${total_pages}</button>`; } // Next button paginationButtons += `<button ${!has_next ? 'disabled' : ''} onclick="loadSearchPhrases('${engine}', ${current_page + 1})">»</button>`; } paginationContainer.innerHTML = paginationButtons; } // Show error in table function showErrorInTable(engine, errorMessage) { const tbody = document.getElementById(`${engine}PhrasesBody`); if (tbody) { tbody.innerHTML = ` <tr> <td colspan="4" style="text-align: center; padding: 2rem; color: var(--danger);"> <div style="margin-bottom: 1rem;"> <span style="font-size: 2rem;">❌</span> </div> <p><strong>Error loading data:</strong></p> <p>${escapeHtml(errorMessage)}</p> <button class="toggle-btn" onclick="loadSearchPhrases('${engine}', 1)" style="margin-top: 1rem;"> Retry </button> </td> </tr> `; } } // Helper functions function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function getPositionBadgeClass(position) { if (!position) return 'badge'; if (position <= 3) return 'badge-success'; if (position <= 10) return 'badge-warning'; return 'badge-danger'; } function toggleSnippet(button) { const snippet = button.nextElementSibling; const isVisible = snippet.style.display === 'block'; snippet.style.display = isVisible ? 'none' : 'block'; button.textContent = isVisible ? 'Show snippet' : 'Hide snippet'; } // Filter functions function applyFilters(engine) { loadSearchPhrases(engine, 1); } function clearFilters(engine) { const phraseFilterEl = document.getElementById(`${engine}PhraseFilter`); const pageFilterEl = document.getElementById(`${engine}PageFilter`); if (phraseFilterEl) phraseFilterEl.value = ''; if (pageFilterEl) pageFilterEl.value = ''; loadSearchPhrases(engine, 1); } // Initialize filter events on page load document.addEventListener('DOMContentLoaded', function() { // Initialize enter key for filter inputs ['google', 'yandex'].forEach(engine => { const phraseFilter = document.getElementById(`${engine}PhraseFilter`); const pageFilter = document.getElementById(`${engine}PageFilter`); if (phraseFilter) { phraseFilter.addEventListener('keypress', function(e) { if (e.key === 'Enter') { applyFilters(engine); } }); } if (pageFilter) { pageFilter.addEventListener('keypress', function(e) { if (e.key === 'Enter') { applyFilters(engine); } }); } }); // Load Google Charts google.charts.load('current', {'packages':['corechart', 'line']}); // Initial chart drawing for server-side data google.charts.setOnLoadCallback(function() { // You might want to extract chart data from initial server-side render // or make an initial AJAX call to get chart data for page 1 }); }); // AJAX request function function sendRequest(url, data, callback) { const xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { try { const response = JSON.parse(xhr.responseText); callback(response); } catch (e) { console.error("JSON parse error:", e); callback({status: 'error', error: 'Invalid response from server'}); } } else { callback({status: 'error', error: 'Server error: ' + xhr.status}); } } }; xhr.send(JSON.stringify(data)); } </script> <script> (function() { 'use strict'; // --- Configuration --- const STORAGE_KEY = 'serpuls_domain_sections_state'; const SECTIONS_TO_COLLAPSE = [ { id: 'domain-main-info', title: 'Hauptinformationen', btnText: 'Info' }, { id: 'domain-server-info', title: 'Serverinformationen', btnText: 'Server' }, { id: 'domain-meta-tags', title: 'Meta-Tags-Liste', btnText: 'Meta' }, { id: 'domain-internal-links', title: 'Interne Links', btnText: 'Intern' }, { id: 'domain-external-links', title: 'Externe Links', btnText: 'Extern' }, { id: 'domain-whois-info', title: 'Whois-Informationen', btnText: 'Whois' }, { id: 'domain-seo-audit', title: 'SEO-Audit', btnText: 'SEO-Audit' } ]; // --- Helper Functions --- 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 const sections = []; // Add main sections from the page const sectionSelectors = [ { id: 'domain-header', title: 'Domäneninformationen', selector: '.domain-header' }, { id: 'domain-description', title: 'Website-Beschreibung', selector: '.card:has(.card-title:contains("Website-Beschreibung"))' }, { id: 'domain-backup', title: 'Domänensicherung', selector: '.card:has(.card-title:contains("Domänensicherung"))' }, { id: 'domain-seo-score', title: 'SEO-Score', selector: '.seo-score' }, { id: 'domain-main-info', title: 'Hauptinformationen', selector: '.card:has(.card-title:contains("Hauptinformationen"))' }, { id: 'domain-server-info', title: 'Serverinformationen', selector: '.card:has(.card-title:contains("Serverinformationen"))' }, { id: 'domain-meta-tags', title: 'Meta-Tags-Liste', selector: '.card:has(.card-title:contains("Meta-Tags-Liste"))' }, { id: 'domain-internal-links', title: 'Interne Links', selector: '.card:has(.card-title:contains("Interne Links"))' }, { id: 'domain-external-links', title: 'Externe Links', selector: '.card:has(.card-title:contains("Externe Links"))' }, { id: 'domain-whois-info', title: 'Whois-Informationen', selector: '.card:has(.card-title:contains("Whois-Informationen"))' }, { id: 'domain-whois-raw', title: 'Whois-Rohdaten', selector: '.card:has(.card-title:contains("Whois-Rohdaten"))' }, { id: 'domain-robots', title: 'Robots.txt', selector: '.card:has(.card-title:contains("Robots.txt"))' }, { id: 'domain-seo-audit', title: 'SEO-Audit', selector: '.card:has(.card-title:contains("SEO-Audit"))' }, { id: 'domain-google-positions', title: 'Positionen in Google', selector: '.chart-container:has(h2:contains("Google"))' }, { id: 'domain-google-phrases', title: 'Suchphrasen - Google', selector: '.card:has(.card-title:contains("Google"))' }, { id: 'domain-yandex-positions', title: 'Positionen in Yandex', selector: '.chart-container:has(h2:contains("Yandex"))' }, { id: 'domain-yandex-phrases', title: 'Suchphrasen - Yandex', selector: '.card:has(.card-title:contains("Yandex"))' }, { id: 'domain-additional-services', title: 'Zusätzliche Dienstleistungen', selector: '.card:has(.card-title:contains("Zusätzliche Dienstleistungen"))' } ]; sectionSelectors.forEach(section => { let element = null; // Try to find by selector if (section.selector) { if (section.selector.includes(':contains')) { // Simple contains selector const containsText = section.selector.match(/:contains\("([^"]+)"\)/); if (containsText) { const searchText = containsText[1]; const cards = document.querySelectorAll('.card, .chart-container, .seo-score, .domain-header'); for (const card of cards) { if (card.textContent.includes(searchText) && !card.id) { element = card; break; } } } } else { element = document.querySelector(section.selector); } } if (element && !element.id) { element.id = section.id; sections.push({ id: section.id, title: section.title, element: element }); } else if (element && element.id) { sections.push({ id: element.id, title: section.title, element: element }); } }); // Also add any card with an ID that wasn't captured document.querySelectorAll('.card[id], .chart-container[id], .seo-score[id]').forEach(el => { if (!sections.find(s => s.id === el.id)) { const titleEl = el.querySelector('.card-title, h2'); if (titleEl) { sections.push({ id: el.id, title: titleEl.innerText.trim(), element: el }); } } }); 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) { if (!section) continue; 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 ID or by title let targetCard = document.getElementById(section.id); if (!targetCard) { 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')) { content.style.display = ''; card.classList.remove('section-collapsed'); button.innerHTML = '➖'; saveSectionState(sectionId, false); } else { 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 --- 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); 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 --- function addCustomStyles() { const style = document.createElement('style'); style.textContent = ` .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-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-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); } @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; } } html { scroll-behavior: smooth; } .card .card-content { transition: display 0.2s ease; } [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 --- 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="1000" 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> <div class="form-group"> <label for="loginEmail">E-Mail:</label> <input type="email" id="loginEmail" class="form-control" autocomplete="email" 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" autocomplete="current-password" placeholder="Geben Sie Ihr Passwort ein"> <div class="error-message" id="loginPasswordError"></div> </div> <button class="cta-button" style="width:100%" onclick="processLogin()">anmelden</button> <div style="text-align:center;margin:1rem 0;color:var(--gray);border-top:1px solid #e9ecef;padding-top:1rem;">Oder fahren Sie fort mit</div> <button class="cta-button secondary" style="width:100%;margin-bottom:.6rem" onclick="startOAuth('google')">Google</button> <button class="cta-button secondary" style="width:100%;margin-bottom:.6rem" onclick="startOAuth('github')">GitHub</button> <button class="cta-button" style="background:#0088cc;width:100%" onclick="loginWithTelegram()">📱 Melden Sie sich mit Telegram an</button> <p style="margin-top:.5rem;color:var(--gray);font-size:.9rem">Geben Sie für die Telegram-Anmeldung zunächst die E-Mail-Adresse des oben mit Telegram verbundenen Serpulse-Kontos ein.</p> </div> <div class="auth-form" id="registerForm"> <h2 class="modal-title">Benutzerkonto erstellen</h2> <div class="form-group"><label for="registerName">Name:</label><input type="text" id="registerName" class="form-control" autocomplete="name" 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" autocomplete="email" required><div class="error-message" id="registerEmailError"></div></div> <div class="form-group"><label for="registerPassword">Passwort:</label><input type="password" id="registerPassword" class="form-control" autocomplete="new-password" minlength="8" required><div class="error-message" id="registerPasswordError"></div></div> <div class="form-group"><label for="registerConfirmPassword">Passwort bestätigen:</label><input type="password" id="registerConfirmPassword" class="form-control" autocomplete="new-password" minlength="8" required><div class="error-message" id="registerConfirmPasswordError"></div></div> <button class="cta-button" style="width:100%" onclick="processRegistration()">Benutzerkonto erstellen</button> <div style="text-align:center;margin:1rem 0;color:var(--gray);border-top:1px solid #e9ecef;padding-top:1rem;">Oder erstellen Sie ein Konto bei</div> <button class="cta-button secondary" style="width:100%;margin-bottom:.6rem" onclick="startOAuth('google')">Google</button> <button class="cta-button secondary" style="width:100%;margin-bottom:.6rem" onclick="startOAuth('github')">GitHub</button> <button class="cta-button" style="background:#0088cc;width:100%" onclick="startTelegramRegistration()">📱 Registrieren Sie sich bei Telegram</button> <p style="margin-top:1rem;color:var(--gray);font-size:.9rem">Für die Passwort- und Telegram-Registrierung ist eine E-Mail-Bestätigung erforderlich. Bei der Google- und GitHub-Registrierung wird die vom Anbieter zurückgegebene verifizierte E-Mail-Adresse verwendet.</p> </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">Bestätigen Sie Ihre E-Mail</h2> <p class="modal-text" id="emailVerifyMessage">Überprüfen Sie Ihren Posteingang und klicken Sie auf den Bestätigungslink, den wir Ihnen gesendet haben. Sie können sich nicht mit einem Passwort anmelden, bis Ihre E-Mail-Adresse bestätigt wurde.</p> <p id="emailVerifyAddress" style="font-weight:600;word-break:break-all"></p> <button id="resendEmailBtn" class="cta-button secondary" style="width:100%;margin-top:1rem" onclick="resendEmailVerification()">Bestätigungs-E-Mail erneut senden</button> <button class="cta-button" style="width:100%;margin-top:.75rem" onclick="closeEmailVerifyModal()">Schließen</button> </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 '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 showToast('Fehler: ' + response.error, '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; } } const serpulseCsrfToken = "0c76f160c1956bffb3bc216db0e4d126c0e7a82ee56dbd897cff6d1100874b77"; // 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.setRequestHeader('X-CSRF-Token', serpulseCsrfToken); 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) { showToast('Bitte geben Sie den Code aus dem Bild ein', 'error'); 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> // Unified authentication controller. All user-visible source strings are English and translated server-side. let telegramStatusInterval = null; let telegramTimerInterval = null; let qrcodeInstance = null; window.telegramBotUrl = null; function authText(key) { const messages = { registrationVerification: 'Konto erstellt. Überprüfen Sie Ihre E-Mails und klicken Sie auf den Bestätigungslink, bevor Sie sich anmelden.', loginSuccess: 'Erfolgreich angemeldet.', emailVerified: 'E-Mail bestätigt. Ihr Konto ist aktiv und Sie sind angemeldet.', emailTelegramPending: 'E-Mail bestätigt. Schließen Sie die Telegram-Verifizierung ab, um Ihr Konto zu aktivieren.', emailVerificationFailed: 'Dieser E-Mail-Bestätigungslink ist ungültig oder abgelaufen.', googleSuccess: 'Mit Google angemeldet.', githubSuccess: 'Mit GitHub angemeldet.', oauthCancelled: 'Die externe Anmeldung wurde abgebrochen.', oauthFailed: 'Die externe Anmeldung ist fehlgeschlagen. Bitte versuchen Sie es erneut.', telegramVerifiedEmailPending: 'Die Telegrammüberprüfung ist abgeschlossen. Überprüfen Sie Ihre E-Mails und klicken Sie auf den Bestätigungslink, um Ihr Konto zu aktivieren.', telegramRegistrationComplete: 'Registrierung abgeschlossen. Sie sind jetzt angemeldet.', telegramLoginSuccess: 'Mit Telegram angemeldet.', invalidResponse: 'Ungültige Antwort vom Server' }; return messages[key] || key; } function startOAuth(provider) { if (!['google', 'github'].includes(provider)) { showToast('Nicht unterstützter Anmeldeanbieter.', 'error'); return; } window.location.href = '/auth/' + provider + '.php?lang=' + encodeURIComponent(window.currentLang || 'en'); } function showEmailVerificationModal(email, message) { const normalizedEmail = (email || '').trim().toLowerCase(); if (normalizedEmail) localStorage.setItem('pending_verification_email', normalizedEmail); const address = document.getElementById('emailVerifyAddress'); const messageEl = document.getElementById('emailVerifyMessage'); if (address) address.textContent = normalizedEmail; if (messageEl) messageEl.textContent = message || 'Überprüfen Sie Ihren Posteingang und klicken Sie auf den Bestätigungslink, den wir Ihnen gesendet haben. Sie können sich nicht mit einem Passwort anmelden, bis Ihre E-Mail-Adresse bestätigt wurde.'; const modal = document.getElementById('emailVerifyModal'); if (modal) modal.style.display = 'flex'; } function closeEmailVerifyModal() { const modal = document.getElementById('emailVerifyModal'); if (modal) modal.style.display = 'none'; } function resendEmailVerification() { const email = (localStorage.getItem('pending_verification_email') || '').trim().toLowerCase(); if (!email) { showToast('Geben Sie Ihre E-Mail-Adresse erneut ein, um die Bestätigungsnachricht erneut zu senden.', 'error'); closeEmailVerifyModal(); return; } const button = document.getElementById('resendEmailBtn'); if (button) button.disabled = true; sendRequest('/ajax/', {type: 'resend_email_verification', email: email}, function(response) { if (button) button.disabled = false; if (response.status === 'ok') showToast(response.message, 'success'); else showToast(response.error || 'Wir konnten die Bestätigungs-E-Mail derzeit nicht senden. Bitte versuchen Sie es später noch einmal.', 'error'); }); } function processLogin() { const email = document.getElementById('loginEmail').value.trim().toLowerCase(); const password = document.getElementById('loginPassword').value; clearErrors(['loginEmail', 'loginPassword']); 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; sendRequest('/ajax/', {type: 'login', email: email, password: password}, function(response) { if (response.status === 'ok') { closeModal('authModal'); showToast(response.message || authText('loginSuccess'), 'success'); window.setTimeout(function(){ window.location.href = '/' + window.currentLang + '/profile/'; }, 250); return; } if (response.status === 'verification_required') { closeModal('authModal'); showEmailVerificationModal(response.email || email, response.message); showToast(response.message, 'info'); return; } showError('loginPassword', response.error || 'Ungültige E-Mail-Adresse oder ungültiges Passwort.'); }); } function processRegistration() { const name = document.getElementById('registerName').value.trim(); const email = document.getElementById('registerEmail').value.trim().toLowerCase(); const password = document.getElementById('registerPassword').value; const confirmPassword = document.getElementById('registerConfirmPassword').value; clearErrors(['registerName', 'registerEmail', 'registerPassword', 'registerConfirmPassword']); 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 < 8) { showError('registerPassword', 'Das Passwort muss mindestens 8 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; sendRequest('/ajax/', {type: 'registration', name: name, email: email, password: password}, function(response) { if (response.status === 'verification_required') { localStorage.setItem('pending_verification_email', response.email || email); closeModal('authModal'); showEmailVerificationModal(response.email || email, response.message || authText('registrationVerification')); showToast(response.message || authText('registrationVerification'), response.email_sent === false ? 'info' : 'success'); return; } if (response.status === 'ok') { showToast(response.message || authText('registrationVerification'), 'success'); return; } showToast(response.error || 'Die Registrierung konnte nicht abgeschlossen werden. Bitte versuchen Sie es erneut.', 'error'); }); } function clearRegistrationForm() { ['registerName','registerEmail','registerPassword','registerConfirmPassword'].forEach(function(id){ const el=document.getElementById(id); if (el) el.value=''; }); clearErrors(['registerName','registerEmail','registerPassword','registerConfirmPassword']); } function generateQRCode(url) { const container = document.getElementById('qrcode'); if (!container) return; container.innerHTML = ''; if (typeof QRCode === 'undefined') return; qrcodeInstance = new QRCode(container, {text: url, width: 200, height: 200, correctLevel: QRCode.CorrectLevel.M}); } function openTelegramBot() { if (window.telegramBotUrl) window.open(window.telegramBotUrl, '_blank', 'noopener'); } function refreshTelegramQR() { if (window.telegramBotUrl) generateQRCode(window.telegramBotUrl); } function clearTelegramTimers() { if (telegramTimerInterval) { clearInterval(telegramTimerInterval); telegramTimerInterval = null; } if (telegramStatusInterval) { clearInterval(telegramStatusInterval); telegramStatusInterval = null; } } function startTelegramTimer(duration) { clearInterval(telegramTimerInterval); let remaining = Math.max(0, parseInt(duration || 300, 10)); const timer = document.getElementById('timer'); function render() { if (!timer) return; const m = Math.floor(remaining / 60), s = remaining % 60; timer.textContent = String(m).padStart(2,'0') + ':' + String(s).padStart(2,'0'); } render(); telegramTimerInterval = setInterval(function(){ remaining -= 1; render(); if (remaining < 0) { clearTelegramTimers(); showToast('Der Telegram-Bestätigungslink ist abgelaufen. Bitte beginnen Sie erneut.', 'error'); } }, 1000); } function showTelegramVerifyModal(botUsername, telegramToken, deepLink, expiresRemaining) { window.telegramBotUrl = deepLink || ('https://t.me/' + botUsername + '?start=' + encodeURIComponent(telegramToken)); const title = document.querySelector('#telegramVerifyModal .modal-title'); const text = document.getElementById('telegramModalText'); if (title) title.textContent = 'Vollständige Registrierung im Telegram'; if (text) text.innerHTML = 'Telegram wurde in einem neuen Tab geöffnet.<br>Teilen Sie dem Bot Ihre eigene Telefonnummer mit und kehren Sie dann hierher zurück. Eine E-Mail-Bestätigung ist ebenfalls erforderlich.'; generateQRCode(window.telegramBotUrl); startTelegramTimer(expiresRemaining || 600); const modal = document.getElementById('telegramVerifyModal'); if (modal) modal.style.display='flex'; window.open(window.telegramBotUrl, '_blank', 'noopener'); } function closeTelegramVerifyModal() { clearTelegramTimers(); const modal = document.getElementById('telegramVerifyModal'); if (modal) modal.style.display='none'; } function startTelegramRegistration() { const name = document.getElementById('registerName').value.trim(); const email = document.getElementById('registerEmail').value.trim().toLowerCase(); clearErrors(['registerName','registerEmail']); 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; localStorage.setItem('pending_verification_email', email); sendRequest('/ajax/', {type:'telegram_registration_start',name:name,email:email}, function(response){ if (response.status === 'continue_email_verification') { closeModal('authModal'); showEmailVerificationModal(email, 'Telegram ist für dieses Konto bereits verifiziert. Bestätigen Sie Ihre E-Mail-Adresse, um das Konto zu aktivieren.'); showToast('Eine E-Mail-Bestätigung ist weiterhin erforderlich.', 'info'); return; } if (response.status !== 'ok') { showToast(response.error || 'Die Telegrammregistrierung konnte nicht gestartet werden.','error'); return; } localStorage.setItem('current_user_token', response.user_token); closeModal('authModal'); if (response.email_sent === false) { showToast(response.message || 'Die Bestätigungs-E-Mail konnte nicht gesendet werden. Bitte verwenden Sie die Bestätigungs-E-Mail erneut senden.','info'); } showTelegramVerifyModal(response.bot_username,response.telegram_token,response.deep_link,response.telegram_token_expires_remaining); startTelegramRegistrationStatusCheck(response.user_token, email); }); } function startTelegramRegistrationStatusCheck(userToken, email) { clearInterval(telegramStatusInterval); telegramStatusInterval=setInterval(function(){ checkTelegramVerificationStatus(userToken,email); }, 4000); checkTelegramVerificationStatus(userToken,email); } function checkTelegramVerificationStatus(userToken, email) { sendRequest('/ajax/', {type:'check_telegram_verification',user_token:userToken}, function(response){ if (response.status !== 'ok') return; if (response.account_verified) { clearTelegramTimers(); completeRegistration(); return; } if (response.telegram_verified && !response.email_verified) { clearTelegramTimers(); closeTelegramVerifyModal(); showEmailVerificationModal(email, authText('telegramVerifiedEmailPending')); showToast(authText('telegramVerifiedEmailPending'),'info'); } }, false); } function completeRegistration() { const token=localStorage.getItem('current_user_token') || ''; if (!token) return; sendRequest('/ajax/', {type:'complete_registration',user_token:token}, function(response){ if (response.status !== 'ok') { showToast(response.error || 'Die Registrierung konnte nicht abgeschlossen werden.','error'); return; } localStorage.removeItem('current_user_token'); localStorage.removeItem('pending_verification_email'); closeTelegramVerifyModal(); closeEmailVerifyModal(); showToast(response.message || authText('telegramRegistrationComplete'),'success'); window.setTimeout(function(){window.location.href='/' + window.currentLang + '/profile/';},250); }); } function loginWithTelegram() { const email=document.getElementById('loginEmail').value.trim().toLowerCase(); clearErrors(['loginEmail']); if (!email) { showError('loginEmail','Bitte geben Sie Ihre E-Mail-Adresse ein'); return; } if (!/\S+@\S+\.\S+/.test(email)) { showError('loginEmail','Bitte geben Sie eine gültige E-Mail-Adresse ein'); return; } generateTelegramLoginLink(email); } function generateTelegramLoginLink(email) { sendRequest('/ajax/', {type:'generate_telegram_login_link',email:email}, function(response){ if (response.status !== 'ok') { showToast(response.error || 'Die Telegram-Anmeldung kann nicht gestartet werden','error'); return; } localStorage.setItem('current_user_token',response.user_token); closeModal('authModal'); showTelegramLoginModal(response.bot_username,response.deep_link,response.telegram_token_expires_remaining); }); } function showTelegramLoginModal(botUsername,deepLink,expiresRemaining) { window.telegramBotUrl=deepLink; const title=document.querySelector('#telegramVerifyModal .modal-title'); const text=document.getElementById('telegramModalText'); if (title) title.textContent='Login per Telegram'; if (text) text.innerHTML='Öffnen Sie den Telegram-Bot, um Ihren Anmeldecode zu erhalten, und geben Sie den Code dann auf dieser Website ein.'; generateQRCode(window.telegramBotUrl); startTelegramTimer(expiresRemaining || 300); const modal=document.getElementById('telegramVerifyModal'); if (modal) modal.style.display='flex'; window.open(window.telegramBotUrl,'_blank','noopener'); startTelegramLoginStatusCheck(); } function startTelegramLoginStatusCheck() { const token=localStorage.getItem('current_user_token') || ''; clearInterval(telegramStatusInterval); telegramStatusInterval=setInterval(function(){checkTelegramLoginStatus(token);},3000); checkTelegramLoginStatus(token); } function checkTelegramLoginStatus(userToken) { if (!userToken) return; sendRequest('/ajax/', {type:'check_telegram_login_status',user_token:userToken}, function(response){ if (response.status==='ok' && response.login_code_sended) { clearTelegramTimers(); closeTelegramVerifyModal(); const codeModal=document.getElementById('telegramCodeModal'); if (codeModal) codeModal.style.display='flex'; startLoginCodeTimer(300); } },false); } function startLoginCodeTimer(duration) { let remaining=parseInt(duration||300,10); const timer=document.getElementById('loginTimer'); if (window.loginCodeTimer) clearInterval(window.loginCodeTimer); window.loginCodeTimer=setInterval(function(){ const m=Math.max(0,Math.floor(remaining/60)), s=Math.max(0,remaining%60); if (timer) timer.textContent=String(m).padStart(2,'0')+':'+String(s).padStart(2,'0'); remaining--; if (remaining<0) { clearInterval(window.loginCodeTimer); showToast('Code abgelaufen. Bitte fangen Sie von vorne an.','error'); } },1000); } function verifyTelegramLoginCode() { const token=localStorage.getItem('current_user_token') || ''; const input=document.getElementById('telegramLoginCode'); const code=input ? input.value.trim() : ''; clearErrors(['telegramLoginCode']); if (!/^\\d{7}$/.test(code)) { showError('telegramLoginCode','Der Code muss 7 Ziffern enthalten'); return; } sendRequest('/ajax/', {type:'verify_telegram_login_code',user_token:token,verify_code:code}, function(response){ if (response.status!=='ok') { showError('telegramLoginCode',response.error || 'Fehlerhafter Code oder abgelaufen'); return; } if (window.loginCodeTimer) clearInterval(window.loginCodeTimer); localStorage.removeItem('current_user_token'); closeModal('telegramCodeModal'); showToast(response.message || authText('telegramLoginSuccess'),'success'); window.setTimeout(function(){window.location.href='/' + window.currentLang + '/profile/';},250); }); } // Old registration-status probing is intentionally disabled: it leaked account state and duplicated the new verification flow. function checkRegistrationStatus() { return; } function closeVisibleAuthModal() { for (const id of ['telegramCodeModal','telegramVerifyModal','emailVerifyModal','authModal']) { const modal=document.getElementById(id); if (!modal || modal.style.display !== 'flex') continue; if (id === 'telegramVerifyModal') closeTelegramVerifyModal(); else if (id === 'emailVerifyModal') closeEmailVerifyModal(); else closeModal(id); return; } } document.addEventListener('click', function(event){ const target=event.target; if (!(target instanceof HTMLElement) || !target.classList.contains('modal')) return; const id=target.id; if (!['telegramCodeModal','telegramVerifyModal','emailVerifyModal'].includes(id)) return; if (id === 'telegramVerifyModal') closeTelegramVerifyModal(); else if (id === 'emailVerifyModal') closeEmailVerifyModal(); else closeModal(id); }); document.addEventListener('keydown', function(event){ if (event.key === 'Escape') closeVisibleAuthModal(); }); function handleAuthNotice() { const url=new URL(window.location.href); const notice=url.searchParams.get('auth'); if (!notice) return; const map={ email_verified:[authText('emailVerified'),'success'], email_verified_telegram_pending:[authText('emailTelegramPending'),'info'], email_verification_failed:[authText('emailVerificationFailed'),'error'], google_success:[authText('googleSuccess'),'success'], github_success:[authText('githubSuccess'),'success'], oauth_cancelled:[authText('oauthCancelled'),'info'], oauth_failed:[authText('oauthFailed'),'error'] }; if (map[notice]) showToast(map[notice][0],map[notice][1]); url.searchParams.delete('auth'); window.history.replaceState({},document.title,url.pathname+url.search+url.hash); } document.addEventListener('DOMContentLoaded',handleAuthNotice); </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; // 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>