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

www html

Frase Ativa
Data de atualização das informações: 2026/03/29

📊 Resumo: www html

📋
Frequência de pesquisa: 72 481
Linguagem: en
Não há dados morfológicos ou definição disponível para esta frase.

Posições em Google

Frases de pesquisa - Google

🔍
Posição Domínio Página Ações
1 www.w3schools.com /html/
Título
HTML Tutorial
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
HTML Tutorial
HTML is the standard markup language for Web pages. With HTML you can create your own Website. HTML is easy to learn - You will enjoy it!
2 html.com /
URL completo
Título
HTML For Beginners The Easy Way
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
HTML For Beginners The Easy Way
Our step-by-step guide teaches you the basics of HTML and how to build your first website. That means how to layout an HTML page, how to add text and images , ...
3 qna.habr.com /q/695506
Título
Как сменить document_root apache с /var/www/html на /var ...
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
Как сменить document_root apache с /var/www/html на /var ...
23 дек. 2019 г. — Как сменить document_root apache с /var/ www/html на /var/www/test/ html /public? Есть vps, на нем apache и php. из различных статей пробовал ...
4 developer.mozilla.org /ru/docs/web/html;19...
Título
HTML - MDN Web Docs - Mozilla
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
HTML - MDN Web Docs - Mozilla
15 апр. 2025 г. — HTML (HyperText Markup Language — «язык гипертекстовой разметки») — самый базовый строительный блок Веба. Он определяет содержание и ...
5 stackoverflow.com /questions/16197663/...
Título
What is /var/www/html? [closed] - php
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
What is /var/www/html? [closed] - php
/ var/www/html is just the default root folder of the web server. You can change that to be whatever folder you want by editing your apache.conf file.
6 htmlacademy.ru /
Título
HTML Academy
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
HTML Academy
Готовим разработчиков напрямую для работодателей . Получите базовые навыки на тренажёрах, а затем пройдите серьёзные профессиональные программы для ...
7 ru.wikipedia.org /wiki/html;4107130
Título
HTML
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
HTML
HTML (от англ. HyperText Markup Language — «язык гипертекстовой разметки») — стандартизированный язык гипертекстовой разметки документов для просмотра веб- ...
8 www.linux.org.ru /forum/general/88885...
Título
Помогите разобраться с apache и /var/www/index.html
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
Помогите разобраться с apache и /var/www/index.html
24 февр. 2013 г. — вообщем главная страница в любом случае должна быть /var/www/index. html ? а остальные страницы где угодно, только чтоб в папке /var/www?
9 htmlbook.ru /
Título
Home | htmlbook.ru
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
Home | htmlbook.ru
Аудио и видео. Семантический HTML . Каждый урок сопровождается задачами, которые надо решать прямиком в редакторе. Курс бесплатный, для изучения требуется только ...
10 html.spec.whatwg.org /
Título
HTML Standard;5302519
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
HTML Standard;5302519
HTML is the World Wide Web's core markup language. Originally, HTML was primarily designed as a language for semantically describing scientific documents. Its ...;60549698

Posições em Yandex

Frases de pesquisa - Yandex

🔍
Posição Domínio Página Ações
1 inf.e-alekseev.ru /text/www_html.html
Título
13.5. WWW и HTML
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
13.5. WWW и HTML
HTML – это формат гипертекстовых документов, использующихся в WWW для предоставления информации.
2 schoolsw3.com /html/html_intro.php
Título
Введение в HTML за 5 минут — Как работает HTML ?
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
Введение в HTML за 5 минут — Как работает HTML ?
Простое объяснение HTML для новичков. Основные понятия, структура документа и примеры кода.
3 oslogic.ru /knowledge/75/osnovn...
Título
Основные понятия World Wide Web, или WWW
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
Основные понятия World Wide Web, или WWW
WWW — (World Wide Web) — это часть Internet, представляющая собой гигантский набор документов, хранящихся на компьютерах по всему миру.
4 frolov-lib.ru /books/bsp/v23/ch4_1...
Título
4 создание страниц www
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
4 создание страниц www
Практическое введение в Internet, E-Mail, FTP, WWW и HTML , программирование для Windows Sockets.
5 dzen.ru /a/zgzma5bzlrj7ofll
Título
Основы html - база для всех тех, кто мечтает стать... | Дзен
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
6 htmlacademy.ru /blog/html/introduct...
Título
Знакомство с HTML — журнал «Доктайп» | HTML Academy
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
Знакомство с HTML — журнал «Доктайп» | HTML Academy
Знакомство с HTML для тех, кто никогда не писал сайты.
7 help.sweb.ru /html-osnovy-yazyka-...
Título
HTML
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
HTML
Основы HTML - для чего используется этот язык разметки и зачем он нужен; как выглядит код и синтаксис HyperText Markup Language.
8 academy.kgtk.ru /it3/web-programming...
Título
Лекция 1. Основы World Wide Web ( WWW )
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
Лекция 1. Основы World Wide Web ( WWW )
В лекции рассмотрены общие принципы организации и функционирования сервиса WWW . Описаны структура и компоненты веб, включая протокол HTTP...
9 sky.pro /wiki/html/osnovy-ht...
Título
Основы HTML
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
Основы HTML
Изучите основы HTML и создайте свою первую веб-страницу! В этой статье вы узнаете о ключевых тегах и структуре HTML -документа, научитесь...
10 developer.mozilla.org /ru/docs/learn_web_d...
Título
Начало работы с HTML - Изучение веб-разработки | MDN
Última atualização
N / D
Autoridade da página
N / D
Tráfego: N / D
Backlinks: N / D
Ações Sociais: N / D
Tempo de carregamento: N / D
Visualização do trecho:
Начало работы с HTML - Изучение веб-разработки | MDN
В этой статье мы охватим азы HTML , необходимые для начала работы. Дадим определение «элементам», «атрибутам»...

Artigo

📝

Compreendendo os fundamentos do www HTML: um guia abrangente

Bem-vindo a um mergulho profundo no mundo do desenvolvimento web, onde cada linha de código pode transformar texto simples em sites dinâmicos e interativos. Hoje, estamos nos concentrando na espinha dorsal da web: www HTML. Quer você seja um novato ou esteja aprimorando suas habilidades, este guia fornecerá uma base sólida em HTML e como ele se encaixa no panorama geral do web design.

O que é HTML?

HTML, que significa HyperText Markup Language, é a principal linguagem usada para criar páginas da web. Ele fornece uma estrutura básica que os navegadores interpretam para exibir conteúdo na internet. Cada site que você visita começa como código HTML, o que o torna uma habilidade fundamental para qualquer pessoa interessada em desenvolvimento web.

O papel do www HTML

Você deve estar se perguntando por que mencionamos especificamente www HTML. Embora a parte “www” normalmente não seja necessária em endereços da web modernos, ela já foi crucial. A World Wide Web (WWW) é essencialmente um sistema de documentos de hipertexto interligados e acessados ​​​​via Internet. HTML é a linguagem usada para criar esses documentos.

No contexto atual, mencionar www HTML enfatiza o significado histórico e o papel fundamental do HTML na criação da web. Isso nos lembra que HTML é mais do que apenas código: é a linguagem da web.

Estrutura HTML básica

Vamos dar uma olhada na estrutura básica de um documento HTML:

Título da página

Meu primeiro título

Meu primeiro parágrafo.

  • : Declara o tipo de documento e a versão do HTML.
  • : o elemento raiz de uma página HTML.
  • : Contém metainformações sobre o documento HTML, como título e links para folhas de estilo.
  • </strong>: define o título da página da web, que aparece na guia do navegador.</li> <li><strong><body></strong>: contém o conteúdo do documento HTML que fica visível aos usuários.</li> <li><strong><h1></strong>: define um título de nível superior.</li> <li><strong><p></strong>: representa um parágrafo de texto.</li> </ul> <h2>Criando Elementos de Texto com www HTML</h2> <p>HTML fornece diversas tags para criar diferentes tipos de elementos de texto. Aqui estão alguns exemplos:</p> <ul> <li><strong><h1>-<h6></strong>: defina os títulos do maior para o menor.</li> <li><strong><p></strong>: cria parágrafos de texto.</li> <li><strong><a></strong>: insere hiperlinks para outras páginas ou recursos da web.</li> <li><strong><em></strong> e <strong><strong></strong>: enfatizam o texto, com <strong><em></strong> normalmente renderizado em itálico e <strong><strong></strong> em negrito.</li> <li><strong><br></strong>: insere uma quebra de linha.</li> </ul> <h3>Exemplo: Criando um Link Simples</h3> <pré> <a href="https://www.serpulse.com">Visite Serpulse</a> </pré> <p>Este código cria um link clicável para o site da Serpulse. O atributo <strong>href</strong> especifica o URL para o qual o link aponta.</p> <h2>Adicionando imagens com www HTML</h2> <p>A incorporação de imagens em suas páginas da web melhora o apelo visual e a experiência do usuário. Para adicionar uma imagem usando HTML, você usa a tag <strong><img></strong>:</p> <pré> <img src="caminho/para/image.jpg" alt="Descrição da imagem"> </pré> <ul> <li><strong>src</strong>: especifica o caminho para o arquivo de imagem.</li> <li><strong>alt</strong>: fornece texto alternativo para leitores de tela e quando a imagem não pode ser exibida.</li> </ul> <h2>Criando listas com www HTML</h2> <p>As listas são essenciais para organizar as informações do seu site. HTML suporta listas ordenadas e não ordenadas:</p> <h3>Lista não ordenada</h3> <pré> <ul> <li>Primeiro item</li> <li>Segundo item</li> <li>Terceiro item</li> </ul> </pré> <h3>Lista ordenada</h3> <pré> <ol> <li>Primeiro passo</li> <li>Segunda etapa</li> <li>Terceiro passo</li> </ol> </pré> <h2>Tabelas em www HTML</h2> <p>HTML também permite criar tabelas para exibir dados tabulares:</p> <pré> <tabela> <tr> <th>Cabeçalho 1</th> <th>Cabeçalho 2</th> </tr> <tr> <td>Linha 1, Célula 1</td> <td>Linha 1, Célula 2</td> </tr> <tr><td>Linha 2, Célula 1</td> <td>Linha 2, Célula 2</td> </tr> </tabela> </pré> <ul> <li><strong><table></strong>: Define a tabela.</li> <li><strong><tr></strong>: Define uma linha da tabela.</li> <li><strong><th></strong>: Define uma célula de cabeçalho em uma tabela.</li> <li><strong><td></strong>: define uma célula de dados padrão em uma tabela.</li> </ul> <h2>Formulários em www HTML</h2> <p>Os formulários são essenciais para coletar informações do usuário. Aqui está um exemplo básico de formulário HTML:</p> <pré> <form action="/submit-form" método="post"> <label for="name">Nome:</label> <input type="text" id="nome" nome="nome"><br><br> <label for="email">E-mail:</label> <input type="email" id="email" name="email"><br><br> <input type="submit" value="Enviar"> </form> </pré> <ul> <li><strong><form></strong>: cria um formulário HTML para entrada do usuário.</li> <li><strong>ação</strong>: especifica para onde enviar os dados do formulário quando enviados.</li> <li><strong>método</strong>: Define o método HTTP a ser usado (GET ou POST).</li> <li><strong><label></strong>: fornece rótulos para campos de entrada.</li> <li><strong><input></strong>: cria vários tipos de elementos de entrada, como campos de texto e botões.</li> </ul> <h2>Estilização com CSS</h2> <p>Enquanto o HTML fornece a estrutura das suas páginas web, o CSS (Cascading Style Sheets) é responsável pela sua aparência. Você pode vincular um arquivo CSS ao seu documento HTML usando a tag <strong><link></strong> na seção <strong><head></strong>:</p> <pré> <link rel="stylesheet" href="styles.css"> </pré> <p>Como alternativa, você pode incluir CSS diretamente no seu HTML usando a tag <strong><style></strong>:</p> <pré> <estilo> corpo { cor de fundo: azul claro; } h1 { cor: marinho; margem esquerda: 20px; } </estilo> </pré> <h2>JavaScript para interatividade</h2> <p>Para adicionar interatividade às suas páginas da web, você pode usar JavaScript. Embora não faça parte do HTML em si, o JavaScript funciona junto com HTML e CSS para criar experiências de usuário dinâmicas. Aqui está um exemplo simples de incorporação de JavaScript em um documento HTML:</p> <pré> <roteiro> function showMessage() { alerta("Olá, mundo!"); } </script> <button onclick="showMessage()">Clique em mim!</button> </pré> <h2>Conclusão</h2> <p>Neste guia completo, exploramos os fundamentos do <strong>www HTML</strong>, desde sua função na World Wide Web até a criação de elementos complexos, como tabelas e formulários. Ao compreender o HTML, você estará no caminho certo para criar sites com aparência profissional que envolvam e informem seu público.</p> <p>Lembre-se: aprender HTML é apenas o começo de sua jornada no desenvolvimento web. Ao dominar o HTML, não se esqueça de mergulhar no CSS para estilização e no JavaScript para interatividade. Boa codificação!</p> <p>Para mais recursos e tutoriais, visite <a href="https://www.serpulse.com">Serpulse</a>.</p> <p><em>Autor: Serpulse.com</em></p> <p><em>Palavras-chave: www html, noções básicas de HTML, desenvolvimento web, web design, estrutura HTML, tags HTML, elementos de texto HTML, imagens HTML, listas HTML, tabelas HTML, formulários HTML, CSS, JavaScript</em></p> ```redução Este artigo fornece uma visão geral detalhada do HTML, sua importância e como ele se encaixa no contexto mais amplo do desenvolvimento web. Abrange conceitos fundamentais e exemplos práticos, tornando-se um excelente recurso tanto para iniciantes quanto para desenvolvedores experientes que buscam atualizar seus conhecimentos. A inclusão de tags HTML específicas e trechos de código garante clareza e oportunidades de aprendizagem prática. ``` <p>Autor: serpuls.com</p> </div> </div> <!-- Words Definitions --> <div class="card"> <div class="card-header"> <h2 class="card-title">www html Palavras</h2> <div class="card-icon">📚</div> </div> <div class="card-content"> <div class="word-section" style="margin-bottom: 2rem;"> <h3 style="color: var(--primary); margin-bottom: 1rem; border-bottom: 1px solid #e9ecef; padding-bottom: 0.5rem;"> html </h3> <div class="info-grid"> <div class="info-item"> <strong>Perl:</strong> <div class="text-muted" style="font-size:0.85em; margin:4px 0;"> 📖 Естественные науки </div> <span>Perl - язык программирования для разработки CGI-скриптов. Программы, написанные на Perl, запускаются на стороне сервера. Perl не является коммерческим продуктом.</span> </div> </div> </div> </div> </div> <!-- Additional Services --> <div class="card" id="additional-services"> <div class="card-header"> <h2 class="card-title">Serviços Adicionais</h2> <div class="card-icon">💎</div> </div> <div class="card-content"> <div class="info-grid"> <div class="info-item"> <span class="popup-trigger" alt="Auditoria completa de SEO com recomendações detalhadas"> <button class="cta-button" data-modal="paymentModal" data-title="Auditoria SEO completa" data-text="Auditoria completa de SEO com recomendações detalhadas para melhorar as posições nos mecanismos de busca." data-price="99.99"> Auditoria SEO completa </button> <span class="popup-text"></span> </span> </div> <div class="info-item"> <span class="popup-trigger" alt="Monitoramento mensal de posição para 100 palavras-chave"> <button class="cta-button" data-modal="paymentModal" data-title="Monitoramento de posição" data-text="Monitoramento mensal da posição de 100 frases-chave no Google e Yandex." data-price="49.99"> Monitoramento de posição </button> <span class="popup-text"></span> </span> </div> <div class="info-item"> <span class="popup-trigger" alt="Análise da concorrência e identificação de suas estratégias de promoção"> <button class="cta-button" data-modal="paymentModal" data-title="Análise do Concorrente" data-text="Análise da concorrência e identificação das suas estratégias de promoção." data-price="79.99"> Análise do Concorrente </button> <span class="popup-text"></span> </span> </div> </div> </div> </div> <!-- JavaScript for AJAX Pagination --> <script> // Apply filters function function applyPhraseFilters(engine) { loadSearchDomains(engine, 1); } // Load search domains for phrase function loadSearchDomains(engine, page) { const domainFilter = document.getElementById(engine + 'DomainFilter').value; const pageFilter = document.getElementById(engine + 'PageFilter').value; const phrase_id = document.getElementById('phrase_id').value; const data = { type: 'search_domains', engine: engine, page: page, phrase_id: phrase_id, // Send the phrase domain_filter: domainFilter, page_filter: pageFilter }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { renderSearchDomains(engine, response.data); renderPagination(engine, response.pagination); } else { showToast('Erro' + ':'+response.error); } }); } // Render domain pagination specifically for phrase pages function renderDomainPagination(engine, pagination) { const paginationContainer = document.getElementById(engine + 'Pagination'); paginationContainer.innerHTML = ''; const { current_page, total_pages, has_previous, has_next } = pagination; // Previous button const prevButton = document.createElement('button'); prevButton.innerHTML = '«'; prevButton.disabled = !has_previous; prevButton.addEventListener('click', () => { if (has_previous) loadSearchDomains(engine, current_page - 1); }); paginationContainer.appendChild(prevButton); // Page buttons for (let i = 1; i <= total_pages; i++) { const pageButton = document.createElement('button'); pageButton.textContent = i; pageButton.classList.toggle('active', i === current_page); pageButton.addEventListener('click', () => { if (i !== current_page) loadSearchDomains(engine, i); }); paginationContainer.appendChild(pageButton); } // Next button const nextButton = document.createElement('button'); nextButton.innerHTML = '»'; nextButton.disabled = !has_next; nextButton.addEventListener('click', () => { if (has_next) loadSearchDomains(engine, current_page + 1); }); paginationContainer.appendChild(nextButton); } // Get badge class based on position function getPositionBadgeClass(position) { if (position <= 3) return 'badge-success'; if (position <= 10) return 'badge-warning'; return 'badge-danger'; } // Toggle snippet visibility function toggleSnippet(button) { const snippet = button.nextElementSibling; const isVisible = snippet.style.display === 'block'; snippet.style.display = isVisible ? 'none' : 'block'; button.textContent = isVisible ? 'Mostrar trecho' : 'Ocultar trecho'; } </script> <script> // Toggle page details visibility function togglePageDetails(button) { const row = button.closest('tr'); const detailsRow = row.nextElementSibling; const pageDetails = detailsRow.querySelector('.page-details'); // Close all other open page details document.querySelectorAll('.page-details').forEach(detail => { if (detail !== pageDetails) { detail.classList.remove('active'); } }); // Update all button texts document.querySelectorAll('.show-page-btn').forEach(btn => { if (btn !== button) { btn.textContent = 'Mostrar detalhes da página'; } }); // Toggle current page details if (pageDetails.classList.contains('active')) { pageDetails.classList.remove('active'); button.textContent = 'Mostrar detalhes da página'; } else { pageDetails.classList.add('active'); button.textContent = 'Ocultar detalhes da página'; } } // Close all page details when clicking outside document.addEventListener('click', function(e) { if (!e.target.closest('.show-page-btn') && !e.target.closest('.page-details')) { document.querySelectorAll('.page-details').forEach(detail => { detail.classList.remove('active'); }); document.querySelectorAll('.show-page-btn').forEach(btn => { btn.textContent = 'Mostrar detalhes da página'; }); } }); // Update the renderSearchDomains function to include show page buttons function renderSearchDomains(engine, data) { const tbody = document.getElementById(engine + 'PhrasesBody'); tbody.innerHTML = ''; data.forEach((item, index) => { const row = document.createElement('tr'); // Format position with change let positionHtml = `<span class="badge ${getPositionBadgeClass(item.position)}">${item.position}</span>`; if (item.change !== null && item.change !== 0) { const changeClass = item.change > 0 ? 'positive' : 'negative'; const changeSymbol = item.change > 0 ? '+' : ''; positionHtml += `<span class="position-change ${changeClass}">(${changeSymbol}${item.change})</span>`; } row.innerHTML = ` <td>${positionHtml}</td> <td><noindex><a href="/pt/domain/${item.domain}/" rel="nofollow">${item.domain}</a></noindex></td> <td>${item.page}</td> <td> <button class="show-page-btn" onclick="togglePageDetails(this)">Mostrar detalhes da página</button> </td> `; tbody.appendChild(row); // Add page details row const detailsRow = document.createElement('tr'); detailsRow.className = 'page-details-row'; detailsRow.innerHTML = ` <td colspan="4"> <div class="page-details" id="page-details-${engine}-${index}"> <div class="page-details-grid"> <div class="page-detail-item"> <div class="page-detail-label">Full URL</div> <div class="page-detail-value"> <a href="${item.full_url || '#'}" class="page-url" target="_blank" rel="nofollow"> ${item.full_url || 'N/A'} </a> </div> </div> <div class="page-detail-item"> <div class="page-detail-label">Title</div> <div class="page-detail-value">${item.page_title || 'N/A'}</div> </div> <div class="page-detail-item"> <div class="page-detail-label">Last Updated</div> <div class="page-detail-value">${item.last_updated || 'N/A'}</div> </div> <div class="page-detail-item"> <div class="page-detail-label">Page Authority</div> <div class="page-detail-value">${item.page_authority || 'N/A'}</div> </div> </div> <div class="page-metrics"> <div class="page-metric"> <span class="page-detail-label">Traffic:</span> <span class="page-metric-value">${item.traffic || 'N/A'}</span> </div> <div class="page-metric"> <span class="page-detail-label">Backlinks:</span> <span class="page-metric-value">${item.backlinks || 'N/A'}</span> </div> <div class="page-metric"> <span class="page-detail-label">Social Shares:</span> <span class="page-metric-value">${item.social_shares || 'N/A'}</span> </div> <div class="page-metric"> <span class="page-detail-label">Load Time:</span> <span class="page-metric-value">${item.load_time || 'N/A'}</span> </div> </div> <div class="snippet" style="margin-top: 1rem;"> <strong>Snippet Preview:</strong><br> ${item.snippet || 'No snippet available'} </div> </div> </td> `; tbody.appendChild(detailsRow); }); } </script> <script> (function() { 'use strict'; // --- Configuration --- const STORAGE_KEY = 'serpuls_phrase_sections_state'; const SECTIONS_TO_COLLAPSE = [ { id: 'morphological-lemmas', title: 'Lemas morfológicos', btnText: 'Lemas' }, { id: 'google-search-section', title: 'Frases de pesquisa - Google', btnText: 'Google' }, { id: 'yandex-search-section', title: 'Frases de pesquisa - Yandex', btnText: 'Yandex' }, { id: 'book-samples-section', title: 'Exemplos de livros com frase', btnText: 'Livros' }, { id: 'additional-services', title: 'Serviços Adicionais', btnText: 'Serviços' } ]; // --- Helper Functions --- function getSectionIdFromTitle(title) { if (title.includes('Lemas morfológicos')) return 'morphological-lemmas'; if (title.includes('Frases de pesquisa - Google')) return 'google-search-section'; if (title.includes('Frases de pesquisa - Yandex')) return 'yandex-search-section'; if (title.includes('Serviços Adicionais')) return 'additional-services'; return null; } function saveSectionState(sectionId, isCollapsed) { const savedState = localStorage.getItem(STORAGE_KEY); const state = savedState ? JSON.parse(savedState) : {}; state[sectionId] = isCollapsed; localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } function loadSectionState(sectionId) { const savedState = localStorage.getItem(STORAGE_KEY); if (!savedState) return false; const state = JSON.parse(savedState); return state[sectionId] || false; } // --- Create Floating TOC --- function createFloatingTOC() { // Find all major sections (cards with a card-header) const sections = Array.from(document.querySelectorAll('.card')).map(card => { const headerEl = card.querySelector('.card-header'); if (!headerEl) return null; const titleEl = headerEl.querySelector('.card-title'); if (!titleEl) return null; const title = titleEl.innerText.trim(); // Skip sections that are too small or not needed const skipTitles = [ ]; let shouldSkip = false; for (const skipTitle of skipTitles) { if (title.includes(skipTitle) || title === skipTitle) { shouldSkip = true; break; } } if (shouldSkip) return null; // Create a unique ID for the section if it doesn't have one let id = card.id; if (!id) { id = 'section-' + Math.random().toString(36).substr(2, 8); card.id = id; } return { id, title, element: card }; }).filter(s => s !== null); if (sections.length < 2) return; // Create TOC container const tocContainer = document.createElement('div'); tocContainer.id = 'floating-toc'; tocContainer.className = 'floating-toc'; tocContainer.innerHTML = ` <div class="floating-toc-header"> <span>📑 Índice</span> <button id="toc-toggle-btn" class="toc-toggle-btn" aria-label="Recolher índice">−</button> </div> <div id="toc-links-container" class="toc-links-container"> ${sections.map(section => `<a href="#${section.id}" class="toc-link" data-id="${section.id}">${escapeHtml(section.title)}</a>`).join('')} </div> `; document.body.appendChild(tocContainer); // Toggle TOC visibility const toggleBtn = document.getElementById('toc-toggle-btn'); const linksContainer = document.getElementById('toc-links-container'); let isTocExpanded = true; toggleBtn.addEventListener('click', () => { if (isTocExpanded) { linksContainer.style.display = 'none'; toggleBtn.textContent = '+'; tocContainer.classList.add('collapsed'); } else { linksContainer.style.display = 'block'; toggleBtn.textContent = '−'; tocContainer.classList.remove('collapsed'); } isTocExpanded = !isTocExpanded; }); // Highlight active section on scroll const tocLinks = document.querySelectorAll('.toc-link'); const sectionElements = sections.map(s => s.element); function updateActiveLink() { let current = ''; const scrollPosition = window.scrollY + 100; for (const section of sectionElements) { const sectionTop = section.offsetTop; const sectionBottom = sectionTop + section.offsetHeight; if (scrollPosition >= sectionTop && scrollPosition < sectionBottom) { current = section.id; break; } } tocLinks.forEach(link => { link.classList.remove('active'); if (link.getAttribute('data-id') === current) { link.classList.add('active'); } }); } window.addEventListener('scroll', updateActiveLink); updateActiveLink(); // Smooth scroll tocLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const targetId = link.getAttribute('data-id'); const targetElement = document.getElementById(targetId); if (targetElement) { targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); } // --- Add Collapsible Buttons to Sections --- function addCollapsibleButtons() { SECTIONS_TO_COLLAPSE.forEach(section => { // Find the card by its title let targetCard = null; const cards = document.querySelectorAll('.card'); for (const card of cards) { const titleEl = card.querySelector('.card-title'); if (titleEl && titleEl.innerText.trim() === section.title) { targetCard = card; break; } } if (!targetCard) return; // Set ID if not exists if (!targetCard.id) targetCard.id = section.id; // Create button const button = document.createElement('button'); button.className = 'collapse-section-btn'; button.setAttribute('data-section-id', section.id); button.setAttribute('aria-label', 'Recolher/expandir seção'); // Get initial state const isCollapsed = loadSectionState(section.id); if (isCollapsed) { button.innerHTML = '➕'; targetCard.classList.add('section-collapsed'); const content = targetCard.querySelector('.card-content'); if (content) content.style.display = 'none'; } else { button.innerHTML = '➖'; targetCard.classList.remove('section-collapsed'); const content = targetCard.querySelector('.card-content'); if (content) content.style.display = ''; } // Insert button into card-header const cardHeader = targetCard.querySelector('.card-header'); if (cardHeader) { const iconDiv = cardHeader.querySelector('.card-icon'); if (iconDiv) { cardHeader.insertBefore(button, iconDiv); } else { cardHeader.appendChild(button); } // Add click handler button.addEventListener('click', (e) => { e.stopPropagation(); const sectionId = button.getAttribute('data-section-id'); const card = document.getElementById(sectionId); if (!card) return; const content = card.querySelector('.card-content'); if (!content) return; if (card.classList.contains('section-collapsed')) { // Expand content.style.display = ''; card.classList.remove('section-collapsed'); button.innerHTML = '➖'; saveSectionState(sectionId, false); } else { // Collapse content.style.display = 'none'; card.classList.add('section-collapsed'); button.innerHTML = '➕'; saveSectionState(sectionId, true); } }); } }); } // --- Create Back to Top Button --- function createBackToTopButton() { const btn = document.createElement('button'); btn.id = 'back-to-top'; btn.innerHTML = '↑'; btn.setAttribute('aria-label', 'Voltar ao topo'); btn.className = 'back-to-top-btn'; document.body.appendChild(btn); window.addEventListener('scroll', () => { if (window.scrollY > 500) { btn.classList.add('visible'); } else { btn.classList.remove('visible'); } }); btn.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); } // --- Improve Table Responsiveness (add scroll hints) --- function improveTables() { const tables = document.querySelectorAll('.data-table, .tasks-table'); tables.forEach(table => { const container = table.closest('.table-responsive') || table.parentElement; if (container && !container.querySelector('.scroll-hint')) { const hint = document.createElement('div'); hint.className = 'scroll-hint'; hint.innerHTML = '← → Deslize para ver mais'; hint.style.cssText = ` display: none; text-align: center; font-size: 0.7rem; color: var(--gray); margin-top: 0.5rem; padding: 0.25rem; background: rgba(67, 97, 238, 0.05); border-radius: 12px; `; container.style.position = 'relative'; container.appendChild(hint); // Show hint if table is wider than container function checkScroll() { if (container.scrollWidth > container.clientWidth) { hint.style.display = 'block'; } else { hint.style.display = 'none'; } } checkScroll(); window.addEventListener('resize', checkScroll); container.addEventListener('scroll', () => { hint.style.opacity = '0.5'; setTimeout(() => { hint.style.opacity = '1'; }, 300); }); } }); } // --- Escape HTML helper --- function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // --- Add custom styles for new elements --- function addCustomStyles() { const style = document.createElement('style'); style.textContent = ` /* Floating TOC */ .floating-toc { position: fixed; top: 80px; right: 20px; z-index: 1000; background: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); border: 1px solid #e9ecef; width: 260px; max-width: calc(100% - 40px); transition: all 0.3s ease; backdrop-filter: blur(8px); background: rgba(255,255,255,0.95); } .floating-toc-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; background: linear-gradient(135deg, var(--primary), var(--secondary)); color: white; border-radius: 12px 12px 0 0; font-weight: 600; font-size: 0.9rem; cursor: pointer; } .toc-toggle-btn { background: none; border: none; color: white; font-size: 1.2rem; cursor: pointer; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: background 0.2s; } .toc-toggle-btn:hover { background: rgba(255,255,255,0.2); } .floating-toc.collapsed { width: auto; } .floating-toc.collapsed .floating-toc-header { border-radius: 12px; } .toc-links-container { max-height: 400px; overflow-y: auto; padding: 8px 0; } .toc-link { display: block; padding: 8px 15px; color: var(--dark); text-decoration: none; font-size: 0.85rem; transition: all 0.2s; border-left: 3px solid transparent; } .toc-link:hover { background: rgba(67, 97, 238, 0.05); color: var(--primary); padding-left: 20px; } .toc-link.active { background: rgba(67, 97, 238, 0.1); border-left-color: var(--primary); color: var(--primary); font-weight: 500; } /* Collapse button in cards */ .collapse-section-btn { background: none; border: none; font-size: 1.1rem; cursor: pointer; width: 28px; height: 28px; border-radius: 6px; display: inline-flex; align-items: center; justify-content: center; transition: all 0.2s; color: var(--gray); margin-right: 8px; } .collapse-section-btn:hover { background: rgba(67, 97, 238, 0.1); color: var(--primary); } .card.section-collapsed .card-header { border-bottom: none; } /* Back to Top Button */ .back-to-top-btn { position: fixed; bottom: 30px; right: 30px; width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, var(--primary), var(--secondary)); color: white; border: none; cursor: pointer; font-size: 1.5rem; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: all 0.3s ease; z-index: 999; box-shadow: 0 4px 12px rgba(67, 97, 238, 0.4); } .back-to-top-btn.visible { opacity: 1; visibility: visible; } .back-to-top-btn:hover { transform: translateY(-3px); box-shadow: 0 6px 16px rgba(67, 97, 238, 0.5); } /* Mobile adjustments */ @media (max-width: 768px) { .floating-toc { top: auto; bottom: 20px; right: 20px; left: auto; width: 48px; overflow: hidden; } .floating-toc .floating-toc-header span { display: none; } .floating-toc .floating-toc-header { justify-content: center; padding: 12px; } .floating-toc .toc-toggle-btn { margin: 0; } .floating-toc:not(.collapsed) { width: 260px; left: auto; right: 20px; bottom: 20px; top: auto; } .floating-toc:not(.collapsed) .floating-toc-header span { display: inline; } .floating-toc:not(.collapsed) .floating-toc-header { justify-content: space-between; } .back-to-top-btn { bottom: 80px; right: 20px; width: 40px; height: 40px; font-size: 1.2rem; } .scroll-hint { display: block !important; font-size: 0.65rem; } } /* Smooth scroll behavior */ html { scroll-behavior: smooth; } /* Better card transitions */ .card .card-content { transition: display 0.2s ease; } /* RTL support for languages like Arabic/Hebrew */ [dir="rtl"] .floating-toc { right: auto; left: 20px; } [dir="rtl"] .back-to-top-btn { right: auto; left: 30px; } [dir="rtl"] .collapse-section-btn { margin-right: 0; margin-left: 8px; } `; document.head.appendChild(style); } // --- Initialize everything when DOM is ready --- function init() { addCustomStyles(); createFloatingTOC(); addCollapsibleButtons(); createBackToTopButton(); improveTables(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })(); </script></div> <!-- Footer --> <footer> <div class="footer-container"> <div class="footer-content"> <div class="footer-section"> <h3>About serpuls.com</h3> <div class="footer-info"> <p>Ferramenta profissional para análise de mecanismos de busca, monitoramento de posição e otimização SEO.</p> <p style="margin-top: 1rem;"><strong>Skynet360 LP.</strong></p> <p><strong>Empresa Não:</strong> SL027564</p> <p><strong>Endereço:</strong> Suite 1 4 Queen Street, Edinburgh, Scotland, EH2 1JE</p> </div> </div> <div class="footer-section"> <h3>Links rápidos</h3> <ul class="footer-links"> <li><a href="/pt/">Lar</a></li> <li><a href="/pt/profile/">Perfil</a></li> <li><a href="/pt/tasks/">Tarefas de monitoramento</a></li> <li><a href="/pt/api_docs/">Documentação da API</a></li> <li><a href="/pt/team/">Nossa equipe</a></li> </ul> </div> <div class="footer-section"> <h3>Recursos</h3> <ul class="footer-links"> <li><a href="/pt/blogs/">Blogue</a></li> <li><a href="/pt/faq/">Perguntas frequentes</a></li> <li><a href="/pt/support/">Apoiar</a></li> </ul> </div> <div class="footer-section"> <h3>Contatos</h3> <div class="footer-info"> <p>E-mail: <a href="mailto:info@serpuls.com">info@serpuls.com</a></p> <p>Telegrama: <a href="https://t.me/serpulse_support">@serpulse_support</a></p> <div class="social-links"> <a href="#" class="social-link" title="Telegrama">📱</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. Todos os direitos reservados. Skynet360 LP.</p> <div class="footer-bottom-links"> <a href="/pt/privacy/">política de Privacidade</a> <a href="/pt/terms/">Termos de Uso</a> <a href="/pt/agreement/">Contrato do usuário</a> <a href="/pt/cookie/">Política de Cookies</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>Pagamento realizado com sucesso!</h3> <p>Obrigado pelo seu pedido. Entraremos em contato com você em breve.</p> <button class="cta-button" onclick="closeModal('paymentModal')">Fechar</button> </div> <form id="paymentForm" onsubmit="return processPayment(event)"> <h2 class="modal-title" id="modalTitle">Pagamento pelo serviço</h2> <p class="modal-text" id="modalText">Descrição do serviço</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">Insira o valor (USD):</label> <input type="number" id="manualPrice" class="form-control" placeholder="Insira o valor" 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)"> Insira o valor personalizado </label> </div> <div class="form-group"> <label for="modalName">Nome:</label> <input type="text" id="modalName" class="form-control" placeholder="Digite seu nome" required> <div class="error-message" id="modalNameError"></div> </div> <div class="form-group"> <label for="modalEmail">E-mail:</label> <input type="email" id="modalEmail" class="form-control" placeholder="Digite seu e-mail" required> <div class="error-message" id="modalEmailError"></div> </div> <p class="modal-price" id="modalPrice">Preço: $0.00</p> <button type="submit" class="cta-button" id="payButton">Pagar $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">Pagamento</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">Conecte-se</div> <div class="auth-tab" data-tab="register">Cadastre-se</div> </div> <div class="auth-form active" id="loginForm"> <h2 class="modal-title">Faça login na sua conta</h2> <div class="form-group"> <label for="loginEmail">E-mail:</label> <input type="email" id="loginEmail" class="form-control" autocomplete="email" placeholder="Digite seu e-mail"> <div class="error-message" id="loginEmailError"></div> </div> <div class="form-group"> <label for="loginPassword">Senha:</label> <input type="password" id="loginPassword" class="form-control" autocomplete="current-password" placeholder="Digite sua senha"> <div class="error-message" id="loginPasswordError"></div> </div> <button class="cta-button" style="width:100%" onclick="processLogin()">Entrar</button> <div style="text-align:center;margin:1rem 0;color:var(--gray);border-top:1px solid #e9ecef;padding-top:1rem;">Ou continue com</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()">📱 Faça login com Telegram</button> <p style="margin-top:.5rem;color:var(--gray);font-size:.9rem">Para entrar no Telegram, insira primeiro o e-mail da conta Serpulse conectada ao Telegram acima.</p> </div> <div class="auth-form" id="registerForm"> <h2 class="modal-title">Criar uma conta</h2> <div class="form-group"><label for="registerName">Nome:</label><input type="text" id="registerName" class="form-control" autocomplete="name" required><div class="error-message" id="registerNameError"></div></div> <div class="form-group"><label for="registerEmail">E-mail:</label><input type="email" id="registerEmail" class="form-control" autocomplete="email" required><div class="error-message" id="registerEmailError"></div></div> <div class="form-group"><label for="registerPassword">Senha:</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">Confirme sua senha:</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()">Criar uma conta</button> <div style="text-align:center;margin:1rem 0;color:var(--gray);border-top:1px solid #e9ecef;padding-top:1rem;">Ou crie uma conta com</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()">📱 Registre-se no Telegram</button> <p style="margin-top:1rem;color:var(--gray);font-size:.9rem">A verificação de e-mail é necessária para senha e registro no Telegram. O registro do Google e do GitHub usa o e-mail verificado retornado pelo provedor.</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">Código de verificação</h2> <p class="modal-text">O código foi enviado ao Telegram. Digite abaixo:</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()"> Confirmar login </button> <div class="timer-container" id="loginCodeTimer"> Código válido para: <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">Conclua o cadastro no Telegram</h2> <p class="modal-text" id="telegramModalText"> O Telegram foi aberto em uma nova aba.<br> Caso o bot não tenha aberto automaticamente, use o botão abaixo ou escaneie o código QR. </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;"> Digitalize o código QR para abrir o bot </p> </div> <!-- Timer and Status --> <div id="telegramTimer" class="timer-container"> Aguardando confirmação... <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"> 📱 Abra o Telegram novamente </button> <button class="cta-button secondary" onclick="refreshTelegramQR()" id="refreshQRBtn"> 🔄 Atualizar código QR </button> </div> <!-- Status Info --> <div class="info-box" style="margin-top: 1.5rem;"> <p style="margin: 0; font-size: 0.9rem;"> <strong>Como funciona:</strong><br> 1. Clique em Abrir Telegram ou escaneie o código QR<br> 2. No bot, clique em INICIAR e compartilhe seu número de telefone<br> 3. O status de confirmação será atualizado automaticamente </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">Verifique seu e-mail</h2> <p class="modal-text" id="emailVerifyMessage">Verifique sua caixa de entrada e clique no link de verificação que enviamos para você. Você não pode fazer login com uma senha até que seu e-mail seja verificado.</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()">Reenviar e-mail de verificação</button> <button class="cta-button" style="width:100%;margin-top:.75rem" onclick="closeEmailVerifyModal()">Fechar</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 || 'Perfil'; } // 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('Erro ao verificar o status de login:', 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 = 'Esconder'; } else { // Hide all beyond the first 5 items.forEach((el, index) => { if (index >= max_count) el.classList.add('hidden'); }); btn.textContent = 'Mostrar mais'; } } // Toggle raw data visibility function toggleRawData(elementId, btn) { const element = document.getElementById(elementId); if (element.style.maxHeight === '200px') { element.style.maxHeight = 'none'; btn.textContent = 'Ocultar dados completos'; } else { element.style.maxHeight = '200px'; btn.textContent = 'Mostrar dados completos'; } } </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 = 'Preço: $' + parseFloat(price).toFixed(2); payButton.textContent = 'Pagar $' + 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 = 'De $${paymentConfig.minAmount} para $${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 = 'Preço: de $${paymentConfig.minAmount} para $${paymentConfig.maxAmount}'; payButton.textContent = 'Valor inválido'; payButton.disabled = true; payButton.style.opacity = '0.6'; } else { modalPrice.textContent = 'Preço: $0.00'; payButton.textContent = 'Pagar $0.00'; payButton.disabled = true; payButton.style.opacity = '0.6'; } return; } const modalPrice = document.getElementById('modalPrice'); const payButton = document.getElementById('payButton'); modalPrice.textContent = 'Preço: $' + parseFloat(price).toFixed(2); payButton.textContent = 'Pagar $' + 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('Erro ao analisar opções de pagamento:', 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', 'O valor deve estar entre $${paymentConfig.minAmount} e $${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', 'Por favor digite seu nome'); hasErrors = true; } if (!email) { showError('modalEmail', 'Por favor insira seu endereço de e-mail'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('modalEmail', 'Por favor insira um endereço de e-mail válido'); 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('Erro: ' + 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 = "8985e60d52670f2a1997de6732ea0b78e85c20060aeec43f08c30c9ea7cb6fa0"; // 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: 'Resposta inválida do servidor'}); } if(is_good) callback(response); } else { callback({status: 'error', error: 'Erro no servidor: ' + 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">Confirme que você não é um robô</h2> <p class="modal-text">Digite o texto da imagem:</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()">Atualizar código</button> <input type="text" class="captcha-input" id="captchaInput" placeholder="Insira o código" maxlength="6"> </div> <button class="cta-button" onclick="submitAnalysis()" style="margin-top: 1rem;">Enviar para análise</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('Por favor insira o código da imagem', 'error'); return; } // Show loading state analysisStatus.textContent = 'Enviando solicitação...'; 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 = 'Erro: tipo é nenhum'; 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 = 'Sua solicitação foi enviada, aguarde a verificação do domínio'; analysisStatus.className = 'analysis-status success'; } else { // Show error message analysisStatus.textContent = 'Erro: ' + 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: 'Conta criada. Verifique seu e-mail e clique no link de verificação antes de fazer login.', loginSuccess: 'Login realizado com sucesso.', emailVerified: 'E-mail verificado. Sua conta está ativa e você está conectado.', emailTelegramPending: 'E-mail verificado. Conclua a verificação do Telegram para ativar sua conta.', emailVerificationFailed: 'Este link de verificação de e-mail é inválido ou expirou.', googleSuccess: 'Conectado com o Google.', githubSuccess: 'Conectado com GitHub.', oauthCancelled: 'O login externo foi cancelado.', oauthFailed: 'Falha no login externo. Por favor, tente novamente.', telegramVerifiedEmailPending: 'A verificação do telegrama foi concluída. Verifique seu e-mail e clique no link de verificação para ativar sua conta.', telegramRegistrationComplete: 'Cadastro concluído. Agora você está conectado.', telegramLoginSuccess: 'Conectado com Telegram.', invalidResponse: 'Resposta inválida do servidor' }; return messages[key] || key; } function startOAuth(provider) { if (!['google', 'github'].includes(provider)) { showToast('Provedor de login não compatível.', '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 || 'Verifique sua caixa de entrada e clique no link de verificação que enviamos para você. Você não pode fazer login com uma senha até que seu e-mail seja verificado.'; 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('Digite seu e-mail novamente para reenviar a mensagem de verificação.', '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 || 'Não foi possível enviar o e-mail de verificação no momento. Por favor, tente novamente mais tarde.', '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', 'Por favor insira seu e-mail'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('loginEmail', 'Por favor insira um endereço de e-mail válido'); hasErrors = true; } if (!password) { showError('loginPassword', 'Por favor digite sua senha'); 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 || 'E-mail ou senha inválidos.'); }); } 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', 'Por favor digite seu nome'); hasErrors = true; } if (!email) { showError('registerEmail', 'Por favor insira seu e-mail'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('registerEmail', 'Por favor insira um endereço de e-mail válido'); hasErrors = true; } if (!password) { showError('registerPassword', 'Por favor crie uma senha'); hasErrors = true; } else if (password.length < 8) { showError('registerPassword', 'A senha deve ter pelo menos 8 caracteres'); hasErrors = true; } if (!confirmPassword) { showError('registerConfirmPassword', 'Por favor confirme sua senha'); hasErrors = true; } else if (password !== confirmPassword) { showError('registerConfirmPassword', 'As senhas não coincidem'); 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 || 'Não foi possível concluir o registo. Por favor, tente novamente.', '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('O link de verificação do Telegram expirou. Por favor, comece de novo.', '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 = 'Conclua o cadastro no Telegram'; if (text) text.innerHTML = 'O Telegram foi aberto em uma nova aba.<br>Compartilhe seu próprio número de telefone com o bot e volte aqui. A verificação de e-mail também é necessária.'; 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','Por favor digite seu nome'); hasErrors=true; } if (!email) { showError('registerEmail','Por favor insira seu e-mail'); hasErrors=true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('registerEmail','Por favor insira um endereço de e-mail válido'); 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, 'O Telegram já está verificado para esta conta. Verifique seu e-mail para ativar a conta.'); showToast('A verificação de e-mail ainda é necessária.', 'info'); return; } if (response.status !== 'ok') { showToast(response.error || 'O registro do Telegram não pôde ser iniciado.','error'); return; } localStorage.setItem('current_user_token', response.user_token); closeModal('authModal'); if (response.email_sent === false) { showToast(response.message || 'O e-mail de verificação não pôde ser enviado. Use Reenviar e-mail de verificação.','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 || 'Não foi possível concluir o registo.','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','Por favor insira seu e-mail'); return; } if (!/\S+@\S+\.\S+/.test(email)) { showError('loginEmail','Por favor insira um endereço de e-mail válido'); return; } generateTelegramLoginLink(email); } function generateTelegramLoginLink(email) { sendRequest('/ajax/', {type:'generate_telegram_login_link',email:email}, function(response){ if (response.status !== 'ok') { showToast(response.error || 'Não foi possível iniciar o login do Telegram','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 telegrama'; if (text) text.innerHTML='Abra o bot do Telegram para receber seu código de login e insira o código neste site.'; 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('O código expirou. Por favor, comece de novo.','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','O código deve conter 7 dígitos'); return; } sendRequest('/ajax/', {type:'verify_telegram_login_code',user_token:token,verify_code:code}, function(response){ if (response.status!=='ok') { showError('telegramLoginCode',response.error || 'Código incorreto ou expirado'); 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;">🍪 Consentimento de cookies</h3> <p style="margin: 0; color: var(--gray, #6c757d); line-height: 1.5;"> Usamos cookies para melhorar sua experiência de navegação, fornecer conteúdo personalizado e analisar nosso tráfego. Ao clicar em Aceitar tudo, você concorda com o uso de cookies. Leia nosso <a href="/en/cookie/" style="color: var(--primary, #4361ee); text-decoration: underline;">Política de Cookies</a> e <a href="/en/privacy/" style="color: var(--primary, #4361ee); text-decoration: underline;">política de Privacidade</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);"> Aceitar tudo </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;"> Somente essencial </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;"> Personalizar </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);">Preferências de cookies</h3> </div> <div style="padding: 1.5rem;"> <p style="color: var(--gray, #6c757d); margin-bottom: 1.5rem;">Escolha quais cookies você permite. Você pode alterar essas configurações a qualquer momento.</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);">Cookies essenciais</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Necessário para o funcionamento do site. Não pode ser desativado.</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);">Cookies analíticos</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Ajude-nos a entender como os visitantes interagem com nosso 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);">Cookies Funcionais</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Lembre-se de suas preferências e configurações.</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);">Cookies de segmentação</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Usado para fornecer conteúdo e anúncios relevantes.</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;"> Salvar preferências </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;"> Cancelar </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('Erro ao analisar o consentimento do cookie:', e); } } // Try localStorage const stored = localStorage.getItem(COOKIE_CONSENT_KEY); if (stored) { try { return JSON.parse(stored); } catch (e) { console.error('Erro ao analisar o consentimento do localStorage:', 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', 'Configurações de cookies'); 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>