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

maksim.by

Registered

Analysis of the site maksim.by: What can you learn from the main page? Based on the data provided from the main page of the site maksim.by, we can conclude that the resou...

Page analyze update date: 2026/06/03 22:24:49
Last whois update date: 2026/06/09 21:54:04
Domain Status
Registered
Paid till
13.08.2027
Available from
12.09.2027

Website Description

📝

Analysis of the site maksim.by: What can you learn from the main page?

Based on the data provided from the main page of the site maksim.by, we can conclude that the resource has minimal functionality and does not contain significant information necessary to understand its purpose.

Site theme

The website maksim.by is presented in an extremely limited form. The domain name indicates a personal nature - probably a personal project or the name of the owner. However, in the text data there is no mention of the activities, services, products or purposes of the site. The only text available is the phrase "Click me 18+", which may indicate content intended for an adult audience, however the lack of additional information makes this interpretation speculative.

The page description is listed as empty, which indicates that the description meta tag is empty. This negatively affects SEO optimization and reduces the chances of getting into search results. There is also a missing page title (</code> tag) - this is a serious technical error, since search engines cannot determine the theme of the site without it.</p> <h2>Output</h2> <p>Based on the data provided, the site <strong>maksim.by</strong> does not function as a full-fledged resource. It is either under development, a temporary stub, or not supported. The lack of content, description, title and links makes it unsuitable for use as an informational, commercial or professional site.</p> <p>We also cannot exclude the possibility that the domain was purchased for the purpose of future monetization, but at the moment it does not contain any activity or content.</p> <div class="faq-accordion"> <h2>Frequently asked questions</h2> <details> <summary>What does the phrase “Click me 18+” mean on the website maksim.by?</summary> <p>The phrase “Click me 18+” may indicate the presence of adult content, however, due to the complete lack of other data, it is impossible to confirm this with certainty. This could be test text, a stub, or part of an experimental project.</p> </details> <details> <summary>Why does the site have no title and description?</summary> <p>The absence of the <code><title></code> tag and the <code>description</code> meta tag indicates an error in the development of the site or its incomplete state. Such sites do not appear effectively in search results and may be perceived as broken.</p> </details> <details> <summary>Can the maksim.by website be considered working?</summary> <p>No. Based on the data provided, the site does not function correctly: key page elements such as title, description and content are missing. This indicates that the resource is either temporarily unavailable or is a technical stub.</p> </details> </div></p> </div> </div> <!-- Archive Section --> <!-- SEO Score --> <!-- Meta Tags --> <!-- Whois Information --> <div class="card"> <div class="card-header"> <h2 class="card-title">Whois Information</h2> <div class="card-icon">📄</div> </div> <div class="card-content"> <div class="info-grid"> <div class="info-item"><strong>domain_name:</strong> maksim.by</div><div class="info-item"><strong>creation_date:</strong> 2007-08-09</div><div class="info-item"><strong>creation_time:</strong> 1186617600</div><div class="info-item"><strong>expiration_date:</strong> 2027-08-13</div><div class="info-item"><strong>expiration_time:</strong> 1818115200</div><div class="info-item"><strong>registrar:</strong> Reliable Software, Ltd</div> </div> </div> </div> <!-- Whois Raw Data --> <div class="card"> <div class="card-header"> <h2 class="card-title">Whois Raw Data</h2> <div class="card-icon">📋</div> </div> <div class="card-content"> <pre id="whoisRaw" style="white-space: pre-wrap; background: #f8f9fa; padding: 1rem; border-radius: var(--border-radius); max-height: 200px; overflow-y: auto;"> Domain name: maksim.by<br />Registrar: Reliable Software, Ltd<br />Person: HIDDEN!<br />Email: HIDDEN! Details are available at https://whois.cctld.by<br />Name Server: justin.ns.cloudflare.com<br />Name Server: rita.ns.cloudflare.com<br />Update Date: 2026-04-11<br />Creation Date: 2007-08-09<br />Expiration Date: 2027-08-13<br />-------------------------------------------<br />Service provided by Belarusian Cloud Technologies LLC<br /> </pre> <button class="toggle-btn" onclick="toggleRawData('whoisRaw', this)">Show full data</button> </div> </div> <!-- SEO Audit --> <!-- 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">Positions 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">Search Phrases - 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="Filter by phrase"> </div> <div class="filter-group"> <label class="filter-label" for="googlePageFilter">Page:</label> <input type="text" id="googlePageFilter" class="filter-input" placeholder="Filter by page"> </div> <div class="filter-group"> <label class="filter-label"> </label> <button class="cta-button" onclick="applyFilters('google')">Apply Filter</button> </div> </div> <div class="table-responsive"> <table class="data-table" id="googlePhrasesTable"> <thead> <tr> <th>Position</th> <th>Phrase</th> <th>Page</th> <th>Snippet</th> </tr> </thead> <tbody id="googlePhrasesBody"> <tr><td><span class="badge badge-success">1</span></td><td><noindex><a href="/en/phrase/%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC-%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D0%B0-%D0%BF%D0%B5%D1%81%D0%BD%D0%B8-%D0%BA%D0%BE%D0%BB%D1%8B%D0%B1%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F/" rel="nofollow">максим певица песни колыбельная</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-success">1</span></td><td><noindex><a href="/en/phrase/%D0%BA%D0%BE%D0%BB%D1%8B%D0%B1%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F-%D0%BF%D0%B5%D1%81%D0%BD%D1%8F-%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D1%8B-%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC-%D0%BF%D1%80%D0%BE%D1%81%D0%BB%D1%83%D1%88%D0%B0%D1%82%D1%8C/" rel="nofollow">колыбельная песня певицы максим прослушать</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-success">1</span></td><td><noindex><a href="/en/phrase/%D0%BF%D0%B5%D1%81%D0%BD%D1%8F-%D0%BA%D0%BE%D0%BB%D1%8B%D0%B1%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F-%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D1%8B-%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC-%D1%81%D0%BA%D0%B0%D1%87%D0%B0%D1%82%D1%8C/" rel="nofollow">песня колыбельная певицы максим скачать</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-success">1</span></td><td><noindex><a href="/en/phrase/%D0%BA%D0%BE%D0%BB%D1%8B%D0%B1%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F-%D0%BF%D0%B5%D1%81%D0%BD%D1%8F-%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D1%8B-%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC/" rel="nofollow">колыбельная песня певицы максим</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-success">2</span></td><td><noindex><a href="/en/phrase/%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC-%D0%BA%D0%BE%D0%BB%D1%8B%D0%B1%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F-%D0%B2%D0%B8%D0%B4%D0%B5%D0%BE/" rel="nofollow">максим колыбельная видео</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-success">2</span></td><td><noindex><a href="/en/phrase/%D0%BA%D0%BB%D0%B8%D0%BF-%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC-%D0%BA%D0%BE%D0%BB%D1%8B%D0%B1%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F/" rel="nofollow">клип максим колыбельная</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-success">2</span></td><td><noindex><a href="/en/phrase/%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC-%D0%BA%D0%BE%D0%BB%D1%8B%D0%B1%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F-%D1%82%D0%B0%D1%80%D0%B0%D1%81-%D0%B1%D1%83%D0%BB%D1%8C%D0%B1%D0%B0-%D1%81%D0%BA%D0%B0%D1%87%D0%B0%D1%82%D1%8C/" rel="nofollow">максим колыбельная тарас бульба скачать</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-success">2</span></td><td><noindex><a href="/en/phrase/%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC-%D0%B0%D0%BA%D0%BA%D0%BE%D1%80%D0%B4%D1%8B-%D0%BF%D0%B5%D1%81%D0%B5%D0%BD/" rel="nofollow">максим аккорды песен</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-success">2</span></td><td><noindex><a href="/en/phrase/%D1%81%D0%BB%D1%83%D1%88%D0%B0%D1%82%D1%8C-%D0%BF%D0%B5%D1%81%D0%BD%D1%8E-%D0%BA%D0%BE%D0%BB%D1%8B%D0%B1%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F-%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC/" rel="nofollow">слушать песню колыбельная максим</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-success">2</span></td><td><noindex><a href="/en/phrase/%D1%82%D0%B5%D0%BA%D1%81%D1%82-%D0%BF%D0%B5%D1%81%D0%BD%D0%B8-%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D1%8B-%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC-%D0%BA%D0%BE%D0%BB%D1%8B%D0%B1%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F/" rel="nofollow">текст песни певицы максим колыбельная</a></noindex></td><td>/</td><td></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', 19)">19</button> <button class="" onclick="loadSearchPhrases('google', 20)">20</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', 'Day'); 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", "слушать песню колыбельная максим");data.addColumn("number", "текст песни певицы максим колыбельная"); data.addRows([ [new Date(2011, 1, 16),null,null,1,1,null,null,null,null,null,null,],[new Date(2011, 1, 3),1,null,null,null,2,2,2,null,null,null,],[new Date(2011, 1, 4),null,null,null,null,null,null,null,2,null,null,],[new Date(2011, 1, 7),null,1,null,null,null,null,null,null,2,2,], ]); var options = { chart: { title: 'Search phrases for which the domain is at the top of the google search engine: "maksim.by"', subtitle: 'Phrase position history' }, width: '100%', height: 400, curveType: 'function', legend: { position: 'bottom' }, pointSize: 5, pointsVisible: true, interpolateNulls: true, selectionMode: 'multiple', vAxis: { direction: '-1', title: 'Position', }, hAxis: { title: 'Date', }, }; var chart = new google.visualization.LineChart(document.getElementById("chart_div_google")); chart.draw(data, options); } </script> <!-- Yandex Positions --> <div class="chart-container"> <h2 class="chart-title">Positions in Yandex</h2> <div id="chart_div_yandex" style="width: 100%; height: 400px;"></div> </div> <!-- Yandex Search Phrases --> <div class="card"> <div class="card-header"> <h2 class="card-title">Search Phrases - Yandex</h2> <div class="card-icon">🔍</div> </div> <div class="card-content"> <div class="filters"> <div class="filter-group"> <label class="filter-label" for="yandexPhraseFilter">Phrase:</label> <input type="text" id="yandexPhraseFilter" class="filter-input" placeholder="Filter by phrase"> </div> <div class="filter-group"> <label class="filter-label" for="yandexPageFilter">Page:</label> <input type="text" id="yandexPageFilter" class="filter-input" placeholder="Filter by page"> </div> <div class="filter-group"> <label class="filter-label"> </label> <button class="cta-button" onclick="applyFilters('yandex')">Apply Filter</button> </div> </div> <div class="table-responsive"> <table class="data-table" id="yandexPhrasesTable"> <thead> <tr> <th>Position</th> <th>Phrase</th> <th>Page</th> <th>Snippet</th> </tr> </thead> <tbody id="yandexPhrasesBody"> <tr><td><span class="badge badge-warning">4</span></td><td><noindex><a href="/en/phrase/%D0%BC%D0%B0%D0%BAs%D0%B8%D0%BC/" rel="nofollow">макsим</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-warning">5</span></td><td><noindex><a href="/en/phrase/%D1%81%D0%BA%D0%B0%D1%87%D0%B0%D1%82%D1%8C-%D0%BF%D0%B5%D1%81%D0%BD%D1%8E-%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D1%8B-%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC/" rel="nofollow">скачать песню певицы максим</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-warning">5</span></td><td><noindex><a href="/en/phrase/%D1%81%D0%BA%D0%B0%D1%87%D0%B0%D1%82%D1%8C-%D0%BF%D0%B5%D1%81%D0%BD%D0%B8-%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D1%8B-%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC/" rel="nofollow">скачать песни певицы максим</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-warning">6</span></td><td><noindex><a href="/en/phrase/%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D0%B0-%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC-%D0%BF%D0%B5%D1%81%D0%BD%D0%B8-%D0%B1%D0%B5%D1%81%D0%BF%D0%BB%D0%B0%D1%82%D0%BD%D0%BE/" rel="nofollow">певица максим песни бесплатно</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-warning">6</span></td><td><noindex><a href="/en/phrase/%D1%84%D0%BE%D1%82%D0%BE-%D1%80%D0%B5%D0%B1%D0%B5%D0%BD%D0%BA%D0%B0-%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D1%8B-%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC/" rel="nofollow">фото ребенка певицы максим</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-warning">6</span></td><td><noindex><a href="/en/phrase/%D1%81%D0%BA%D0%B0%D1%87%D0%B0%D1%82%D1%8C-%D0%B1%D0%B5%D1%81%D0%BF%D0%BB%D0%B0%D1%82%D0%BD%D0%BE-%D0%BF%D0%B5%D1%81%D0%BD%D1%8E-%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D1%8B-%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC/" rel="nofollow">скачать бесплатно песню певицы максим</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-warning">6</span></td><td><noindex><a href="/en/phrase/%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC-%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D0%B0-%D0%BA%D0%BE%D0%BB%D1%8B%D0%B1%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F/" rel="nofollow">максим певица колыбельная</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-warning">7</span></td><td><noindex><a href="/en/phrase/%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC-%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D0%B0-%D1%81%D0%BA%D0%B0%D1%87%D0%B0%D1%82%D1%8C-%D0%B1%D0%B5%D1%81%D0%BF%D0%BB%D0%B0%D1%82%D0%BD%D0%BE/" rel="nofollow">максим певица скачать бесплатно</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-warning">10</span></td><td><noindex><a href="/en/phrase/%D0%BC%D1%83%D0%B7%D1%8B%D0%BA%D0%B0-%D0%BF%D0%B5%D0%B2%D0%B8%D1%86%D1%8B-%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC/" rel="nofollow">музыка певицы максим</a></noindex></td><td>/</td><td></td></tr><tr><td><span class="badge badge-danger">11</span></td><td><noindex><a href="/en/phrase/%D0%BC%D1%83%D0%B7-%D1%82%D0%B2-2008/" rel="nofollow">муз тв 2008</a></noindex></td><td>/</td><td></td></tr> </tbody> </table> </div> <div class="pagination" id="yandexPagination"> <button disabled onclick="loadSearchPhrases('yandex', 0)">«</button> <button class="active" onclick="loadSearchPhrases('yandex', 1)">1</button> <button class="" onclick="loadSearchPhrases('yandex', 2)">2</button> <button class="" onclick="loadSearchPhrases('yandex', 3)">3</button> <button class="" onclick="loadSearchPhrases('yandex', 4)">4</button> <button class="" onclick="loadSearchPhrases('yandex', 5)">5</button> <button onclick="loadSearchPhrases('yandex', 2)">»</button> </div> </div> </div> <script> // Google Charts for yandex google.charts.setOnLoadCallback(drawChart_yandex); function drawChart_yandex() { var data = new google.visualization.DataTable(); data.addColumn('date', 'Day'); data.addColumn("number", "макsим");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", "муз тв 2008"); data.addRows([ [new Date(2011, 1, 26),null,null,null,6,null,null,null,null,null,null,],[new Date(2011, 1, 27),null,null,null,null,6,null,null,null,null,11,],[new Date(2011, 2, 17),null,5,5,null,null,6,6,7,10,null,],[new Date(2011, 4, 10),4,null,null,null,null,null,null,null,null,null,], ]); var options = { chart: { title: 'Search phrases for which the domain is at the top of the yandex search engine: "maksim.by"', subtitle: 'Phrase position history' }, width: '100%', height: 400, curveType: 'function', legend: { position: 'bottom' }, pointSize: 5, pointsVisible: true, interpolateNulls: true, selectionMode: 'multiple', vAxis: { direction: '-1', title: 'Position', }, hAxis: { title: 'Date', }, }; var chart = new google.visualization.LineChart(document.getElementById("chart_div_yandex")); chart.draw(data, options); } </script> <!-- Additional Services --> <div class="card" id="domain-additional-services"> <div class="card-header"> <h2 class="card-title">Additional Services</h2> <div class="card-icon">💎</div> </div> <div class="card-content"> <div class="info-grid"> <div class="info-item"> <span class="popup-trigger" alt="Full SEO audit with detailed recommendations"> <button class="cta-button" data-modal="paymentModal" data-payment-type="domain_seo_pay" data-payment-item-id="484838" data-title="Full SEO Audit maksim.by" data-text="Full SEO audit with detailed recommendations for improving search engine positions." data-price="99.99"> Full SEO Audit </button> <span class="popup-text"></span> </span> </div> <div class="info-item"> <span class="popup-trigger" alt="Monthly position monitoring for 100 key phrases"> <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="484838" data-title="Position Monitoring maksim.by" data-text="Monthly position monitoring for 100 key phrases in Google and Yandex." data-price="49.99"> Position Monitoring </button> <span class="popup-text"></span> </span> </div> <div class="info-item"> <span class="popup-trigger" alt="Competitor analysis and identification of their promotion strategies"> <button class="cta-button" data-modal="paymentModal" data-payment-type="domain_analiz_pay" data-payment-item-id="484838" data-title="Competitor Analysis maksim.by" data-text="Competitor analysis and identification of their promotion strategies." data-price="79.99"> Competitor Analysis </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: 'Main Information', btnText: 'Info' }, { id: 'domain-server-info', title: 'Server Information', btnText: 'Server' }, { id: 'domain-meta-tags', title: 'Meta Tags List', btnText: 'Meta' }, { id: 'domain-internal-links', title: 'Internal Links', btnText: 'Internal' }, { id: 'domain-external-links', title: 'External Links', btnText: 'External' }, { id: 'domain-whois-info', title: 'Whois Information', 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: 'Domain Information', selector: '.domain-header' }, { id: 'domain-description', title: 'Website Description', selector: '.card:has(.card-title:contains("Website Description"))' }, { id: 'domain-backup', title: 'Domain Backup', selector: '.card:has(.card-title:contains("Domain Backup"))' }, { id: 'domain-seo-score', title: 'SEO Score', selector: '.seo-score' }, { id: 'domain-main-info', title: 'Main Information', selector: '.card:has(.card-title:contains("Main Information"))' }, { id: 'domain-server-info', title: 'Server Information', selector: '.card:has(.card-title:contains("Server Information"))' }, { id: 'domain-meta-tags', title: 'Meta Tags List', selector: '.card:has(.card-title:contains("Meta Tags List"))' }, { id: 'domain-internal-links', title: 'Internal Links', selector: '.card:has(.card-title:contains("Internal Links"))' }, { id: 'domain-external-links', title: 'External Links', selector: '.card:has(.card-title:contains("External Links"))' }, { id: 'domain-whois-info', title: 'Whois Information', selector: '.card:has(.card-title:contains("Whois Information"))' }, { id: 'domain-whois-raw', title: 'Whois Raw Data', selector: '.card:has(.card-title:contains("Whois Raw Data"))' }, { 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: 'Positions in Google', selector: '.chart-container:has(h2:contains("Google"))' }, { id: 'domain-google-phrases', title: 'Search Phrases - Google', selector: '.card:has(.card-title:contains("Google"))' }, { id: 'domain-yandex-positions', title: 'Positions in Yandex', selector: '.chart-container:has(h2:contains("Yandex"))' }, { id: 'domain-yandex-phrases', title: 'Search Phrases - Yandex', selector: '.card:has(.card-title:contains("Yandex"))' }, { id: 'domain-additional-services', title: 'Additional Services', selector: '.card:has(.card-title:contains("Additional Services"))' } ]; 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>📑 Table of Contents</span> <button id="toc-toggle-btn" class="toc-toggle-btn" aria-label="Collapse table of contents">−</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', 'Collapse/expand section'); // 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', 'Back to top'); 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 = '← → Swipe to view more'; 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>Professional tool for search engine analysis, position monitoring and SEO optimization.</p> <p style="margin-top: 1rem;"><strong>Skynet360 LP.</strong></p> <p><strong>Company No:</strong> SL027564</p> <p><strong>Address:</strong> Suite 1 4 Queen Street, Edinburgh, Scotland, EH2 1JE</p> </div> </div> <div class="footer-section"> <h3>Quick Links</h3> <ul class="footer-links"> <li><a href="/en/">Home</a></li> <li><a href="/en/profile/">Profile</a></li> <li><a href="/en/tasks/">Monitoring Tasks</a></li> <li><a href="/en/api_docs/">API Documentation</a></li> <li><a href="/en/team/">Our Team</a></li> </ul> </div> <div class="footer-section"> <h3>Resources</h3> <ul class="footer-links"> <li><a href="/en/blogs/">Blog</a></li> <li><a href="/en/faq/">FAQ</a></li> <li><a href="/en/support/">Support</a></li> </ul> </div> <div class="footer-section"> <h3>Contacts</h3> <div class="footer-info"> <p>Email: <a href="mailto:info@serpuls.com">info@serpuls.com</a></p> <p>Telegram: <a href="https://t.me/serpulse_support">@serpulse_support</a></p> <div class="social-links"> <a href="#" class="social-link" title="Telegram">📱</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. All rights reserved. Skynet360 LP.</p> <div class="footer-bottom-links"> <a href="/en/privacy/">Privacy Policy</a> <a href="/en/terms/">Terms of Use</a> <a href="/en/agreement/">User Agreement</a> <a href="/en/cookie/">Cookies Policy</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>Payment successful!</h3> <p>Thank you for your order. We will contact you shortly.</p> <button class="cta-button" onclick="closeModal('paymentModal')">Close</button> </div> <form id="paymentForm" onsubmit="return processPayment(event)"> <h2 class="modal-title" id="modalTitle">Payment for service</h2> <p class="modal-text" id="modalText">Service description</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">Enter amount (USD):</label> <input type="number" id="manualPrice" class="form-control" placeholder="Enter amount" 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)"> Enter custom amount </label> </div> <div class="form-group"> <label for="modalName">Name:</label> <input type="text" id="modalName" class="form-control" placeholder="Enter your name" required> <div class="error-message" id="modalNameError"></div> </div> <div class="form-group"> <label for="modalEmail">Email:</label> <input type="email" id="modalEmail" class="form-control" placeholder="Enter your email" required> <div class="error-message" id="modalEmailError"></div> </div> <p class="modal-price" id="modalPrice">Price: $0.00</p> <button type="submit" class="cta-button" id="payButton">Pay $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">Payment</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">Register</div> </div> <div class="auth-form active" id="loginForm"> <h2 class="modal-title">Sign in to your account</h2> <div class="form-group"> <label for="loginEmail">Email:</label> <input type="email" id="loginEmail" class="form-control" autocomplete="email" placeholder="Enter your email"> <div class="error-message" id="loginEmailError"></div> </div> <div class="form-group"> <label for="loginPassword">Password:</label> <input type="password" id="loginPassword" class="form-control" autocomplete="current-password" placeholder="Enter your password"> <div class="error-message" id="loginPasswordError"></div> </div> <button class="cta-button" style="width:100%" onclick="processLogin()">Sign in</button> <div style="text-align:center;margin:1rem 0;color:var(--gray);border-top:1px solid #e9ecef;padding-top:1rem;">Or continue with</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()">📱 Sign in with Telegram</button> <p style="margin-top:.5rem;color:var(--gray);font-size:.9rem">For Telegram sign-in, enter the email of the Serpulse account connected to Telegram above first.</p> </div> <div class="auth-form" id="registerForm"> <h2 class="modal-title">Create account</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">Email:</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">Password:</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">Confirm password:</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()">Create account</button> <div style="text-align:center;margin:1rem 0;color:var(--gray);border-top:1px solid #e9ecef;padding-top:1rem;">Or create an account with</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()">📱 Register with Telegram</button> <p style="margin-top:1rem;color:var(--gray);font-size:.9rem">Email verification is required for password and Telegram registration. Google and GitHub registration use the verified email returned by the provider.</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">Verification code</h2> <p class="modal-text">The code was sent to Telegram. Enter it below:</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()"> Confirm login </button> <div class="timer-container" id="loginCodeTimer"> Code valid for: <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">Complete registration in Telegram</h2> <p class="modal-text" id="telegramModalText"> Telegram was opened in a new tab.<br> If the bot did not open automatically, use the button below or scan the 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;"> Scan the QR code to open the bot </p> </div> <!-- Timer and Status --> <div id="telegramTimer" class="timer-container"> Waiting for confirmation... <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"> 📱 Open Telegram again </button> <button class="cta-button secondary" onclick="refreshTelegramQR()" id="refreshQRBtn"> 🔄 Refresh QR code </button> </div> <!-- Status Info --> <div class="info-box" style="margin-top: 1.5rem;"> <p style="margin: 0; font-size: 0.9rem;"> <strong>How it works:</strong><br> 1. Click Open Telegram or scan QR code<br> 2. In the bot click START and share your phone number<br> 3. Confirmation status will update automatically </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">Verify your email</h2> <p class="modal-text" id="emailVerifyMessage">Check your inbox and click the verification link we sent you. You cannot sign in with a password until your email is verified.</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()">Resend verification email</button> <button class="cta-button" style="width:100%;margin-top:.75rem" onclick="closeEmailVerifyModal()">Close</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 || 'Profile'; } // 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('Error checking login status:', 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 = 'Hide'; } else { // Hide all beyond the first 5 items.forEach((el, index) => { if (index >= max_count) el.classList.add('hidden'); }); btn.textContent = 'Show more'; } } // Toggle raw data visibility function toggleRawData(elementId, btn) { const element = document.getElementById(elementId); if (element.style.maxHeight === '200px') { element.style.maxHeight = 'none'; btn.textContent = 'Hide full data'; } else { element.style.maxHeight = '200px'; btn.textContent = 'Show full data'; } } </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 = 'Price: $' + parseFloat(price).toFixed(2); payButton.textContent = 'Pay $' + 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 = 'From $${paymentConfig.minAmount} to $${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 = 'Price: from $${paymentConfig.minAmount} to $${paymentConfig.maxAmount}'; payButton.textContent = 'Invalid amount'; payButton.disabled = true; payButton.style.opacity = '0.6'; } else { modalPrice.textContent = 'Price: $0.00'; payButton.textContent = 'Pay $0.00'; payButton.disabled = true; payButton.style.opacity = '0.6'; } return; } const modalPrice = document.getElementById('modalPrice'); const payButton = document.getElementById('payButton'); modalPrice.textContent = 'Price: $' + parseFloat(price).toFixed(2); payButton.textContent = 'Pay $' + 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('Error parsing payment options:', 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', 'Amount must be between $${paymentConfig.minAmount} and $${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', 'Please enter your name'); hasErrors = true; } if (!email) { showError('modalEmail', 'Please enter your email address'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('modalEmail', 'Please enter a valid email address'); 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('Error: ' + 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 = "26e7547173db90762bae72ea572f1926adc3844ff62ea3d139ca8147acac2c9d"; // 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: 'Invalid response from server'}); } if(is_good) callback(response); } else { callback({status: 'error', error: 'Server error: ' + 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">Confirm you are not a robot</h2> <p class="modal-text">Enter the text from the image:</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()">Refresh code</button> <input type="text" class="captcha-input" id="captchaInput" placeholder="Enter code" maxlength="6"> </div> <button class="cta-button" onclick="submitAnalysis()" style="margin-top: 1rem;">Submit for analysis</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('Please enter the code from the image', 'error'); return; } // Show loading state analysisStatus.textContent = 'Sending request...'; 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 = 'Error: type is none'; 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 = 'Your request has been sent, please wait for domain verification'; analysisStatus.className = 'analysis-status success'; } else { // Show error message analysisStatus.textContent = 'Error: ' + 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: 'Account created. Check your email and click the verification link before signing in.', loginSuccess: 'Signed in successfully.', emailVerified: 'Email verified. Your account is active and you are signed in.', emailTelegramPending: 'Email verified. Complete Telegram verification to activate your account.', emailVerificationFailed: 'This email verification link is invalid or expired.', googleSuccess: 'Signed in with Google.', githubSuccess: 'Signed in with GitHub.', oauthCancelled: 'External sign-in was cancelled.', oauthFailed: 'External sign-in failed. Please try again.', telegramVerifiedEmailPending: 'Telegram verification is complete. Check your email and click the verification link to activate your account.', telegramRegistrationComplete: 'Registration completed. You are now signed in.', telegramLoginSuccess: 'Signed in with Telegram.', invalidResponse: 'Invalid response from server' }; return messages[key] || key; } function startOAuth(provider) { if (!['google', 'github'].includes(provider)) { showToast('Unsupported sign-in provider.', '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 || 'Check your inbox and click the verification link we sent you. You cannot sign in with a password until your email is verified.'; 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('Enter your email again to resend the verification message.', '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 || 'We could not send the verification email right now. Please try again later.', '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', 'Please enter your email'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('loginEmail', 'Please enter a valid email address'); hasErrors = true; } if (!password) { showError('loginPassword', 'Please enter your password'); 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 || 'Invalid email or password.'); }); } 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', 'Please enter your name'); hasErrors = true; } if (!email) { showError('registerEmail', 'Please enter your email'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('registerEmail', 'Please enter a valid email address'); hasErrors = true; } if (!password) { showError('registerPassword', 'Please create a password'); hasErrors = true; } else if (password.length < 8) { showError('registerPassword', 'Password must be at least 8 characters'); hasErrors = true; } if (!confirmPassword) { showError('registerConfirmPassword', 'Please confirm your password'); hasErrors = true; } else if (password !== confirmPassword) { showError('registerConfirmPassword', 'Passwords do not match'); 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 || 'Registration could not be completed. Please try again.', '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('Telegram verification link expired. Please start again.', '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 = 'Complete registration in Telegram'; if (text) text.innerHTML = 'Telegram was opened in a new tab.<br>Share your own phone number with the bot, then return here. Email verification is also required.'; 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','Please enter your name'); hasErrors=true; } if (!email) { showError('registerEmail','Please enter your email'); hasErrors=true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('registerEmail','Please enter a valid email address'); 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 is already verified for this account. Verify your email to activate the account.'); showToast('Email verification is still required.', 'info'); return; } if (response.status !== 'ok') { showToast(response.error || 'Telegram registration could not be started.','error'); return; } localStorage.setItem('current_user_token', response.user_token); closeModal('authModal'); if (response.email_sent === false) { showToast(response.message || 'The verification email could not be sent. Please use Resend verification email.','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 || 'Registration could not be completed.','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','Please enter your email'); return; } if (!/\S+@\S+\.\S+/.test(email)) { showError('loginEmail','Please enter a valid email address'); return; } generateTelegramLoginLink(email); } function generateTelegramLoginLink(email) { sendRequest('/ajax/', {type:'generate_telegram_login_link',email:email}, function(response){ if (response.status !== 'ok') { showToast(response.error || 'Unable to start Telegram login','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 via Telegram'; if (text) text.innerHTML='Open the Telegram bot to receive your sign-in code, then enter the code on this website.'; 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 expired. Please start over.','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','The code must contain 7 digits'); return; } sendRequest('/ajax/', {type:'verify_telegram_login_code',user_token:token,verify_code:code}, function(response){ if (response.status!=='ok') { showError('telegramLoginCode',response.error || 'Bad code or expired'); 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 Consent</h3> <p style="margin: 0; color: var(--gray, #6c757d); line-height: 1.5;"> We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking Accept All, you consent to our use of cookies. Read our <a href="/en/cookie/" style="color: var(--primary, #4361ee); text-decoration: underline;">Cookie Policy</a> and <a href="/en/privacy/" style="color: var(--primary, #4361ee); text-decoration: underline;">Privacy Policy</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);"> Accept All </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;"> Essential Only </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;"> Customize </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 Preferences</h3> </div> <div style="padding: 1.5rem;"> <p style="color: var(--gray, #6c757d); margin-bottom: 1.5rem;">Choose which cookies you allow. You can change these settings at any time.</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);">Essential Cookies</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Required for the website to function. Cannot be disabled.</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);">Analytics Cookies</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Help us understand how visitors interact with our site.</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);">Functional Cookies</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Remember your preferences and settings.</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);">Used to deliver relevant content and ads.</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;"> Save Preferences </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;"> Cancel </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('Error parsing cookie consent:', e); } } // Try localStorage const stored = localStorage.getItem(COOKIE_CONSENT_KEY); if (stored) { try { return JSON.parse(stored); } catch (e) { console.error('Error parsing localStorage consent:', 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 Settings'); 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>