Български | 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

Aktivna fraza
Datum ažuriranja informacija: 2026/03/29

📊 Sažetak: www html

📋
Učestalost pretraživanja: 72 481
Jezik: en
Nema dostupnih morfoloških podataka ili definicija za ovaj izraz.

Pozicije u Google

Fraze za pretraživanje - Google

🔍
Položaj Domena Stranica Radnje
1 www.w3schools.com /html/
Titula
HTML Tutorial
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
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 /
Titula
HTML For Beginners The Easy Way
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
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
Titula
Как сменить document_root apache с /var/www/html на /var ...
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
Как сменить 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...
Titula
HTML - MDN Web Docs - Mozilla
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
HTML - MDN Web Docs - Mozilla
15 апр. 2025 г. — HTML (HyperText Markup Language — «язык гипертекстовой разметки») — самый базовый строительный блок Веба. Он определяет содержание и ...
5 stackoverflow.com /questions/16197663/...
Titula
What is /var/www/html? [closed] - php
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
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 /
Titula
HTML Academy
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
HTML Academy
Готовим разработчиков напрямую для работодателей . Получите базовые навыки на тренажёрах, а затем пройдите серьёзные профессиональные программы для ...
7 ru.wikipedia.org /wiki/html;4107130
Titula
HTML
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
HTML
HTML (от англ. HyperText Markup Language — «язык гипертекстовой разметки») — стандартизированный язык гипертекстовой разметки документов для просмотра веб- ...
8 www.linux.org.ru /forum/general/88885...
Titula
Помогите разобраться с apache и /var/www/index.html
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
Помогите разобраться с apache и /var/www/index.html
24 февр. 2013 г. — вообщем главная страница в любом случае должна быть /var/www/index. html ? а остальные страницы где угодно, только чтоб в папке /var/www?
9 htmlbook.ru /
Titula
Home | htmlbook.ru
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
Home | htmlbook.ru
Аудио и видео. Семантический HTML . Каждый урок сопровождается задачами, которые надо решать прямиком в редакторе. Курс бесплатный, для изучения требуется только ...
10 html.spec.whatwg.org /
Titula
HTML Standard;5302519
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
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

Pozicije u Yandex

Fraze za pretraživanje - Yandex

🔍
Položaj Domena Stranica Radnje
1 inf.e-alekseev.ru /text/www_html.html
Titula
13.5. WWW и HTML
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
13.5. WWW и HTML
HTML – это формат гипертекстовых документов, использующихся в WWW для предоставления информации.
2 schoolsw3.com /html/html_intro.php
Titula
Введение в HTML за 5 минут — Как работает HTML ?
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
Введение в HTML за 5 минут — Как работает HTML ?
Простое объяснение HTML для новичков. Основные понятия, структура документа и примеры кода.
3 oslogic.ru /knowledge/75/osnovn...
Titula
Основные понятия World Wide Web, или WWW
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
Основные понятия World Wide Web, или WWW
WWW — (World Wide Web) — это часть Internet, представляющая собой гигантский набор документов, хранящихся на компьютерах по всему миру.
4 frolov-lib.ru /books/bsp/v23/ch4_1...
Titula
4 создание страниц www
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
4 создание страниц www
Практическое введение в Internet, E-Mail, FTP, WWW и HTML , программирование для Windows Sockets.
5 dzen.ru /a/zgzma5bzlrj7ofll
Titula
Основы html - база для всех тех, кто мечтает стать... | Дзен
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
6 htmlacademy.ru /blog/html/introduct...
Titula
Знакомство с HTML — журнал «Доктайп» | HTML Academy
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
Знакомство с HTML — журнал «Доктайп» | HTML Academy
Знакомство с HTML для тех, кто никогда не писал сайты.
7 help.sweb.ru /html-osnovy-yazyka-...
Titula
HTML
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
HTML
Основы HTML - для чего используется этот язык разметки и зачем он нужен; как выглядит код и синтаксис HyperText Markup Language.
8 academy.kgtk.ru /it3/web-programming...
Titula
Лекция 1. Основы World Wide Web ( WWW )
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
Лекция 1. Основы World Wide Web ( WWW )
В лекции рассмотрены общие принципы организации и функционирования сервиса WWW . Описаны структура и компоненты веб, включая протокол HTTP...
9 sky.pro /wiki/html/osnovy-ht...
Titula
Основы HTML
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
Основы HTML
Изучите основы HTML и создайте свою первую веб-страницу! В этой статье вы узнаете о ключевых тегах и структуре HTML -документа, научитесь...
10 developer.mozilla.org /ru/docs/learn_web_d...
Titula
Начало работы с HTML - Изучение веб-разработки | MDN
Zadnje ažurirano
N/A
Autoritet stranice
N/A
Promet: N/A
Povratne veze: N/A
Dionice na društvenim mrežama: N/A
Vrijeme učitavanja: N/A
Pregled isječka:
Начало работы с HTML - Изучение веб-разработки | MDN
В этой статье мы охватим азы HTML , необходимые для начала работы. Дадим определение «элементам», «атрибутам»...

Članak

📝

Razumijevanje osnova www HTML-a: Opsežan vodič

Dobro došli u duboki zaron u svijet web razvoja, gdje svaka linija koda može transformirati običan tekst u dinamičke, interaktivne web stranice. Danas se fokusiramo na okosnicu weba—www HTML. Bez obzira na to jeste li potpuni početnik ili usavršavate svoje vještine, ovaj će vam vodič pružiti čvrste temelje u HTML-u i način na koji se on uklapa u širu sliku web-dizajna.

Što je HTML?

HTML, što je kratica za HyperText Markup Language, primarni je jezik koji se koristi za izradu web stranica. Pruža osnovnu strukturu koju preglednici tumače za prikaz sadržaja na internetu. Svaka web stranica koju posjetite počinje kao HTML kod, što je čini osnovnom vještinom za sve zainteresirane za web razvoj.

Uloga www HTML

Možda se pitate zašto posebno spominjemo www HTML. Iako dio "www" obično nije neophodan u modernim web adresama, nekad je bio ključan. Svjetska mreža (WWW) u biti je sustav međusobno povezanih hipertekstualnih dokumenata kojima se pristupa putem interneta. HTML je jezik koji se koristi za izradu ovih dokumenata.

U današnjem kontekstu, spominjanje www HTML naglašava povijesni značaj i temeljnu ulogu HTML-a u stvaranju weba. Podsjeća nas da je HTML više od običnog koda – to je jezik weba.

Osnovna HTML struktura

Pogledajmo osnovnu strukturu HTML dokumenta:



    
        Naslov stranice
    
    
        

Moj prvi naslov

Moj prvi odlomak.

  • : Deklariše vrstu dokumenta i verziju HTML-a.
  • : korijenski element HTML stranice.
  • : Sadrži meta-informacije o HTML dokumentu, kao što su njegov naslov i veze na tablice stilova.
  • </strong>: Postavlja naslov web-stranice koja se pojavljuje na kartici preglednika.</li> <li><strong><body></strong>: Sadrži sadržaj HTML dokumenta koji je vidljiv korisnicima.</li> <li><strong><h1></strong>: Definira naslov najviše razine.</li> <li><strong><p></strong>: Predstavlja odlomak teksta.</li> </ul> <h2>Stvaranje tekstualnih elemenata pomoću www HTML</h2> <p>HTML nudi različite oznake za stvaranje različitih vrsta tekstualnih elemenata. Evo nekoliko primjera:</p> <ul> <li><strong><h1>-<h6></strong>: Definirajte naslove od najvećih prema najmanjim.</li> <li><strong><p></strong>: Stvara odlomke teksta.</li> <li><strong><a></strong>: Umeće hiperveze na druge web stranice ili resurse.</li> <li><strong><em></strong> i <strong><strong></strong>: Naglasite tekst, pri čemu se <strong><em></strong> obično ispisuje u kurzivu, a <strong><strong></strong> podebljano.</li> <li><strong><br></strong>: Umeće prijelom retka.</li> </ul> <h3>Primjer: Stvaranje jednostavne veze</h3> <pre> <a href="https://www.serpulse.com">Posjetite Serpulse</a> </pre> <p>Ovaj kod stvara poveznicu na koju se može kliknuti na web mjesto Serpulse. Atribut <strong>href</strong> navodi URL na koji vodi veza.</p> <h2>Dodavanje slika s www HTML</h2> <p>Uključivanje slika u vaše web stranice poboljšava njihovu vizualnu privlačnost i korisničko iskustvo. Za dodavanje slike pomoću HTML-a koristite oznaku <strong><img></strong>:</p> <pre> <img src="path/to/image.jpg" alt="Opis slike"> </pre> <ul> <li><strong>src</strong>: Određuje stazu do slikovne datoteke.</li> <li><strong>alt</strong>: pruža alternativni tekst za čitače zaslona i kada se slika ne može prikazati.</li> </ul> <h2>Stvaranje popisa s www HTML</h2> <p>Popisi su ključni za organiziranje informacija na vašoj web stranici. HTML podržava i uređene i neuređene popise:</p> <h3>Neuređeni popis</h3> <pre> <ul> <li>Prva stavka</li> <li>Druga stavka</li> <li>Treća stavka</li> </ul> </pre> <h3>Uređeni popis</h3> <pre> <ol> <li>Prvi korak</li> <li>Drugi korak</li> <li>Treći korak</li> </ol> </pre> <h2>Tablice u www HTML</h2> <p>HTML vam također omogućuje stvaranje tablica za prikaz tabličnih podataka:</p> <pre> <stol> <tr> <th>Zaglavlje 1</th> <th>Zaglavlje 2</th> </tr> <tr> <td>Redak 1, ćelija 1</td> <td>Redak 1, ćelija 2</td> </tr> <tr><td>Redak 2, ćelija 1</td> <td>Redak 2, ćelija 2</td> </tr> </table> </pre> <ul> <li><strong><table></strong>: Definira tablicu.</li> <li><strong><tr></strong>: Definira redak tablice.</li> <li><strong><th></strong>: Definira ćeliju zaglavlja u tablici.</li> <li><strong><td></strong>: Definira standardnu podatkovnu ćeliju u tablici.</li> </ul> <h2>Obrasci u www HTML</h2> <p>Obrasci su neophodni za prikupljanje unosa korisnika. Evo osnovnog primjera HTML obrasca:</p> <pre> <form action="/submit-form" method="post"> <label for="name">Ime:</label> <input type="text" id="name" name="name"><br><br> <label for="email">E-pošta:</label> <input type="email" id="email" name="email"><br><br> <input type="submit" value="Pošalji"> </form> </pre> <ul> <li><strong><form></strong>: Stvara HTML obrazac za korisnički unos.</li> <li><strong>radnja</strong>: Određuje kamo poslati podatke iz obrasca nakon slanja.</li> <li><strong>metoda</strong>: Definira HTTP metodu za korištenje (GET ili POST).</li> <li><strong><label></strong>: Pruža oznake za polja za unos.</li> <li><strong><input></strong>: Stvara različite vrste elemenata za unos, kao što su tekstualna polja i gumbi.</li> </ul> <h2>Stiliziranje pomoću CSS-a</h2> <p>Dok HTML osigurava strukturu vaših web stranica, CSS (Cascading Style Sheets) odgovoran je za njihov izgled. Možete povezati CSS datoteku sa svojim HTML dokumentom pomoću oznake <strong><link></strong> unutar odjeljka <strong><head></strong>:</p> <pre> <link rel="stylesheet" href="styles.css"> </pre> <p>Alternativno, CSS možete uključiti izravno u svoj HTML pomoću oznake <strong><style></strong>:</p> <pre> <stil> tijelo { boja pozadine: svijetloplava; } h1 { boja: tamnoplava; margin-lijevo: 20px; } </style> </pre> <h2>JavaScript za interaktivnost</h2> <p>Da biste dodali interaktivnost svojim web stranicama, možete koristiti JavaScript. Iako nije dio samog HTML-a, JavaScript radi uz HTML i CSS za stvaranje dinamičnog korisničkog iskustva. Evo jednostavnog primjera ugrađivanja JavaScripta u HTML dokument:</p> <pre> <skripta> funkcija showMessage() { upozorenje("Zdravo, svijete!"); } </script> <button onclick="showMessage()">Klikni me!</button> </pre> <h2>Zaključak</h2> <p>U ovom opsežnom vodiču istražili smo osnove <strong>www HTML-a</strong>, od njegove uloge na World Wide Webu do stvaranja složenih elemenata poput tablica i obrazaca. Razumijevanjem HTML-a bit ćete na dobrom putu za izradu web stranica profesionalnog izgleda koje privlače i informiraju vašu publiku.</p> <p>Zapamtite, učenje HTML-a samo je početak vašeg putovanja u web razvoj. Dok svladavate HTML, ne zaboravite uroniti u CSS za stiliziranje i JavaScript za interaktivnost. Sretno kodiranje!</p> <p>Za više izvora i vodiča posjetite <a href="https://www.serpulse.com">Serpulse</a>.</p> <p><em>Autor: Serpulse.com</em></p> <p><em>Ključne riječi: www html, osnove HTML-a, web razvoj, web dizajn, HTML struktura, HTML oznake, HTML tekstualni elementi, HTML slike, HTML popisi, HTML tablice, HTML obrasci, CSS, JavaScript</em></p> ```umanjivanje Ovaj članak pruža detaljan pregled HTML-a, njegovu važnost i kako se uklapa u širi kontekst web razvoja. Pokriva temeljne koncepte i praktične primjere, što ga čini izvrsnim resursom za početnike i iskusne programere koji žele osvježiti svoje znanje. Uključivanje specifičnih HTML oznaka i isječaka koda osigurava jasnoću i mogućnosti praktičnog učenja. ``` <p>Autor: serpuls.com</p> </div> </div> <!-- Words Definitions --> <div class="card"> <div class="card-header"> <h2 class="card-title">www html riječi</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">Dodatne usluge</h2> <div class="card-icon">💎</div> </div> <div class="card-content"> <div class="info-grid"> <div class="info-item"> <span class="popup-trigger" alt="Potpuni SEO audit s detaljnim preporukama"> <button class="cta-button" data-modal="paymentModal" data-title="Kompletna SEO revizija" data-text="Kompletan SEO audit s detaljnim preporukama za poboljšanje pozicija na tražilicama." data-price="99.99"> Kompletna SEO revizija </button> <span class="popup-text"></span> </span> </div> <div class="info-item"> <span class="popup-trigger" alt="Mjesečno praćenje položaja za 100 ključnih izraza"> <button class="cta-button" data-modal="paymentModal" data-title="Praćenje položaja" data-text="Mjesečno praćenje pozicije za 100 ključnih izraza u Google i Yandex." data-price="49.99"> Praćenje položaja </button> <span class="popup-text"></span> </span> </div> <div class="info-item"> <span class="popup-trigger" alt="Analiza konkurenata i identifikacija njihovih strategija promocije"> <button class="cta-button" data-modal="paymentModal" data-title="Analiza konkurenta" data-text="Analiza konkurenata i identifikacija njihovih strategija promocije." data-price="79.99"> Analiza konkurenta </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('Greška' + ':'+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 ? 'Prikaži isječak' : 'Sakrij isječak'; } </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 = 'Prikaži detalje stranice'; } }); // Toggle current page details if (pageDetails.classList.contains('active')) { pageDetails.classList.remove('active'); button.textContent = 'Prikaži detalje stranice'; } else { pageDetails.classList.add('active'); button.textContent = 'Sakrij detalje stranice'; } } // 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 = 'Prikaži detalje stranice'; }); } }); // 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="/hr/domain/${item.domain}/" rel="nofollow">${item.domain}</a></noindex></td> <td>${item.page}</td> <td> <button class="show-page-btn" onclick="togglePageDetails(this)">Prikaži detalje stranice</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: 'Morfološke leme', btnText: 'Leme' }, { id: 'google-search-section', title: 'Fraze za pretraživanje - Google', btnText: 'Google' }, { id: 'yandex-search-section', title: 'Fraze za pretraživanje - Yandex', btnText: 'Yandex' }, { id: 'book-samples-section', title: 'Knjižni primjeri s frazom', btnText: 'knjige' }, { id: 'additional-services', title: 'Dodatne usluge', btnText: 'Usluge' } ]; // --- Helper Functions --- function getSectionIdFromTitle(title) { if (title.includes('Morfološke leme')) return 'morphological-lemmas'; if (title.includes('Fraze za pretraživanje - Google')) return 'google-search-section'; if (title.includes('Fraze za pretraživanje - Yandex')) return 'yandex-search-section'; if (title.includes('Dodatne usluge')) 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>📑 Sadržaj</span> <button id="toc-toggle-btn" class="toc-toggle-btn" aria-label="Sažmi sadržaj">−</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', 'Sažmi/proširi odjeljak'); // 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', 'Povratak na vrh'); 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 = '← → Prijeđite prstom da vidite više'; 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>Profesionalni alat za analizu tražilica, praćenje pozicije i SEO optimizaciju.</p> <p style="margin-top: 1rem;"><strong>Skynet360 LP.</strong></p> <p><strong>Tvrtka br:</strong> SL027564</p> <p><strong>Adresa:</strong> Suite 1 4 Queen Street, Edinburgh, Scotland, EH2 1JE</p> </div> </div> <div class="footer-section"> <h3>Brze veze</h3> <ul class="footer-links"> <li><a href="/hr/">Dom</a></li> <li><a href="/hr/profile/">Profil</a></li> <li><a href="/hr/tasks/">Zadaci praćenja</a></li> <li><a href="/hr/api_docs/">API dokumentacija</a></li> <li><a href="/hr/team/">Naš tim</a></li> </ul> </div> <div class="footer-section"> <h3>Resursi</h3> <ul class="footer-links"> <li><a href="/hr/blogs/">Blog</a></li> <li><a href="/hr/faq/">FAQ</a></li> <li><a href="/hr/support/">podrška</a></li> </ul> </div> <div class="footer-section"> <h3>Kontakti</h3> <div class="footer-info"> <p>E-mail: <a href="mailto:info@serpuls.com">info@serpuls.com</a></p> <p>Telegram: <a href="https://t.me/serpulse_support">@serpulse_support</a></p> <div class="social-links"> <a href="#" class="social-link" title="Telegram">📱</a> <a href="https://github.com/SkyNetJose/serpuls" class="social-link" title="GitHub">💻</a> <a href="#" class="social-link" title="Cvrkut">🐦</a> </div> </div> </div> </div> <div class="footer-bottom"> <p class="copyright">© 2026 serpuls.com. Sva prava pridržana. Skynet360 LP.</p> <div class="footer-bottom-links"> <a href="/hr/privacy/">Politika privatnosti</a> <a href="/hr/terms/">Uvjeti korištenja</a> <a href="/hr/agreement/">Korisnički ugovor</a> <a href="/hr/cookie/">Politika kolačića</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>Plaćanje uspješno!</h3> <p>Hvala vam na narudžbi. Uskoro ćemo Vas kontaktirati.</p> <button class="cta-button" onclick="closeModal('paymentModal')">Zatvoriti</button> </div> <form id="paymentForm" onsubmit="return processPayment(event)"> <h2 class="modal-title" id="modalTitle">Plaćanje usluge</h2> <p class="modal-text" id="modalText">Opis usluge</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">Unesite iznos (USD):</label> <input type="number" id="manualPrice" class="form-control" placeholder="Unesite iznos" 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)"> Unesite prilagođeni iznos </label> </div> <div class="form-group"> <label for="modalName">Ime:</label> <input type="text" id="modalName" class="form-control" placeholder="Unesite svoje ime" required> <div class="error-message" id="modalNameError"></div> </div> <div class="form-group"> <label for="modalEmail">Email:</label> <input type="email" id="modalEmail" class="form-control" placeholder="Unesite svoju e-poštu" required> <div class="error-message" id="modalEmailError"></div> </div> <p class="modal-price" id="modalPrice">Cijena: $0.00</p> <button type="submit" class="cta-button" id="payButton">Platiti $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">Plaćanje</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">Prijava</div> <div class="auth-tab" data-tab="register">Registar</div> </div> <div class="auth-form active" id="loginForm"> <h2 class="modal-title">Prijavite se na svoj račun</h2> <div class="form-group"> <label for="loginEmail">Email:</label> <input type="email" id="loginEmail" class="form-control" autocomplete="email" placeholder="Unesite svoju e-poštu"> <div class="error-message" id="loginEmailError"></div> </div> <div class="form-group"> <label for="loginPassword">Lozinka:</label> <input type="password" id="loginPassword" class="form-control" autocomplete="current-password" placeholder="Unesite svoju lozinku"> <div class="error-message" id="loginPasswordError"></div> </div> <button class="cta-button" style="width:100%" onclick="processLogin()">Prijavite se</button> <div style="text-align:center;margin:1rem 0;color:var(--gray);border-top:1px solid #e9ecef;padding-top:1rem;">Ili nastavite s</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()">📱 Prijavite se s Telegramom</button> <p style="margin-top:.5rem;color:var(--gray);font-size:.9rem">Za prijavu na Telegram prvo unesite e-mail Serpulse računa povezanog s Telegramom.</p> </div> <div class="auth-form" id="registerForm"> <h2 class="modal-title">Napravi račun</h2> <div class="form-group"><label for="registerName">Ime:</label><input type="text" id="registerName" class="form-control" autocomplete="name" required><div class="error-message" id="registerNameError"></div></div> <div class="form-group"><label for="registerEmail">Email:</label><input type="email" id="registerEmail" class="form-control" autocomplete="email" required><div class="error-message" id="registerEmailError"></div></div> <div class="form-group"><label for="registerPassword">Lozinka:</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">Potvrdite lozinku:</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()">Napravi račun</button> <div style="text-align:center;margin:1rem 0;color:var(--gray);border-top:1px solid #e9ecef;padding-top:1rem;">Ili otvorite račun s</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()">📱 Registrirajte se na Telegramu</button> <p style="margin-top:1rem;color:var(--gray);font-size:.9rem">Za lozinku i registraciju na Telegram potrebna je potvrda e-pošte. Google i GitHub registracija koristi potvrđenu e-poštu koju je vratio pružatelj.</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">Kontrolni kod</h2> <p class="modal-text">Šifra je poslana na Telegram. Unesite ga ispod:</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()"> Potvrdite prijavu </button> <div class="timer-container" id="loginCodeTimer"> Kod vrijedi za: <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">Završite registraciju u Telegramu</h2> <p class="modal-text" id="telegramModalText"> Telegram je otvoren u novoj kartici.<br> Ako se bot nije otvorio automatski, upotrijebite gumb ispod ili skenirajte QR kod. </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;"> Skenirajte QR kod da otvorite bot </p> </div> <!-- Timer and Status --> <div id="telegramTimer" class="timer-container"> Čeka se potvrda... <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"> 📱 Ponovno otvorite Telegram </button> <button class="cta-button secondary" onclick="refreshTelegramQR()" id="refreshQRBtn"> 🔄 Osvježi QR kod </button> </div> <!-- Status Info --> <div class="info-box" style="margin-top: 1.5rem;"> <p style="margin: 0; font-size: 0.9rem;"> <strong>Kako radi:</strong><br> 1. Pritisnite Otvori Telegram ili skenirajte QR kod<br> 2. U botu kliknite START i podijelite svoj broj telefona<br> 3. Status potvrde ažurirat će se automatski </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">Potvrdite svoju e-poštu</h2> <p class="modal-text" id="emailVerifyMessage">Provjerite svoju pristiglu poštu i kliknite vezu za potvrdu koju smo vam poslali. Ne možete se prijaviti lozinkom dok se vaša e-pošta ne potvrdi.</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()">Ponovno pošalji e-poruku za potvrdu</button> <button class="cta-button" style="width:100%;margin-top:.75rem" onclick="closeEmailVerifyModal()">Zatvoriti</button> </div> </div> </div> <!-- Image reusable Modal --> <div class="modal" id="imgModal"> <div class="modal-content"> <span class="close-btn">×</span> <img id="modalImg" src="" style="max-width:90%; max-height:90%; margin-top:40px; border-radius:8px;"> </div> </div> <!--Loading overlay --> <div class="loading-overlay" id="loadingOverlay"> <div class="loading-spinner"></div> </div> <div class="toast" id="toast"></div> <script> // Get language from URL path like /en/about or /ru/products function getLangFromUrl() { const path = window.location.pathname; const match = path.match(/^\/([a-z]{2})(\/|$)/i); return match ? match[1] : 'en'; // default to 'en' if no language code found } // Store in global variable window.currentLang = getLangFromUrl(); // Show loading overlay function showLoading() { document.getElementById('loadingOverlay').classList.add('active'); } // Hide loading overlay function hideLoading() { document.getElementById('loadingOverlay').classList.remove('active'); } // Show toast notification function showToast(message, type = 'info') { const toast = document.getElementById('toast'); toast.textContent = message; toast.className = 'toast ' + type; toast.classList.add('show'); setTimeout(() => { toast.classList.remove('show'); }, 3000); } // Check user login status on page load document.addEventListener('DOMContentLoaded', function() { checkUserLoginStatus(); }); // Function to check if user is logged in function checkUserLoginStatus() { const userInfoElement = document.getElementById('userInfo'); const guestButtonsElement = document.getElementById('guestButtons'); const userNameElement = document.getElementById('userName'); // Show loading state or keep defaults while checking if (guestButtonsElement) { guestButtonsElement.style.display = 'flex'; } if (userInfoElement) { userInfoElement.style.display = 'none'; } const data = { type: 'check_user_logined' }; sendRequest('/ajax/', data, function(response) { if (response.status === 'ok') { if (response.user_logined === 1) { // User is logged in if (userInfoElement) { userInfoElement.style.display = 'block'; } if (guestButtonsElement) { guestButtonsElement.style.display = 'none'; } if (userNameElement) { // Use logined_user_name if available, otherwise show default userNameElement.textContent = response.logined_user_name || 'Profil'; } // Optionally store minimal info in localStorage for quick access // but don't rely on it for auth state if (response.logined_user_id) { localStorage.setItem('user_id', response.logined_user_id); } if (response.logined_user_name) { localStorage.setItem('user_name', response.logined_user_name); } } else { // User is not logged in if (userInfoElement) { userInfoElement.style.display = 'none'; } if (guestButtonsElement) { guestButtonsElement.style.display = 'flex'; } // Clear any previously stored user data localStorage.removeItem('user_id'); localStorage.removeItem('user_name'); localStorage.removeItem('user_info'); } } else { console.error('Pogreška pri provjeri statusa prijave:', 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 = 'Sakriti'; } else { // Hide all beyond the first 5 items.forEach((el, index) => { if (index >= max_count) el.classList.add('hidden'); }); btn.textContent = 'Prikaži više'; } } // Toggle raw data visibility function toggleRawData(elementId, btn) { const element = document.getElementById(elementId); if (element.style.maxHeight === '200px') { element.style.maxHeight = 'none'; btn.textContent = 'Sakrij sve podatke'; } else { element.style.maxHeight = '200px'; btn.textContent = 'Prikaži sve podatke'; } } </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 = 'Cijena: $' + parseFloat(price).toFixed(2); payButton.textContent = 'Platiti $' + 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 = 'Iz $${paymentConfig.minAmount} do $${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 = 'Cijena: iz $${paymentConfig.minAmount} do $${paymentConfig.maxAmount}'; payButton.textContent = 'Nevažeći iznos'; payButton.disabled = true; payButton.style.opacity = '0.6'; } else { modalPrice.textContent = 'Cijena: $0.00'; payButton.textContent = 'Platiti $0.00'; payButton.disabled = true; payButton.style.opacity = '0.6'; } return; } const modalPrice = document.getElementById('modalPrice'); const payButton = document.getElementById('payButton'); modalPrice.textContent = 'Cijena: $' + parseFloat(price).toFixed(2); payButton.textContent = 'Platiti $' + 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('Pogreška prilikom analize opcija plaćanja:', 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', 'Iznos mora biti između $${paymentConfig.minAmount} i $${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', 'Molimo unesite svoje ime'); hasErrors = true; } if (!email) { showError('modalEmail', 'Molimo unesite svoju email adresu'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('modalEmail', 'Unesite važeću adresu e-pošte'); 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('Greška: ' + 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 = "02a5aa90ec1a48e0772eed0c6a3e7487c20eae5b0e5c1e58c3ada7b176517639"; // 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: 'Nevažeći odgovor poslužitelja'}); } if(is_good) callback(response); } else { callback({status: 'error', error: 'Greška poslužitelja: ' + 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">Potvrdite da niste robot</h2> <p class="modal-text">Unesite tekst sa slike:</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()">Osvježi kôd</button> <input type="text" class="captcha-input" id="captchaInput" placeholder="Unesite kod" maxlength="6"> </div> <button class="cta-button" onclick="submitAnalysis()" style="margin-top: 1rem;">Pošaljite na analizu</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('Molimo unesite kod sa slike', 'error'); return; } // Show loading state analysisStatus.textContent = 'Slanje zahtjeva...'; 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 = 'Greška: tip je nijedan'; 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 = 'Vaš zahtjev je poslan, molimo pričekajte potvrdu domene'; analysisStatus.className = 'analysis-status success'; } else { // Show error message analysisStatus.textContent = 'Greška: ' + 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: 'Račun kreiran. Provjerite svoju e-poštu i kliknite vezu za potvrdu prije prijave.', loginSuccess: 'Uspješno ste prijavljeni.', emailVerified: 'Email potvrđen. Vaš račun je aktivan i prijavljeni ste.', emailTelegramPending: 'Email potvrđen. Dovršite provjeru Telegrama da biste aktivirali svoj račun.', emailVerificationFailed: 'Ova poveznica za potvrdu e-pošte nije važeća ili je istekla.', googleSuccess: 'Prijavljeni putem Googlea.', githubSuccess: 'Prijavljeni ste putem GitHuba.', oauthCancelled: 'Vanjska prijava je otkazana.', oauthFailed: 'Vanjska prijava nije uspjela. Molimo pokušajte ponovo.', telegramVerifiedEmailPending: 'Provjera telegrama je završena. Provjerite svoju e-poštu i kliknite vezu za potvrdu kako biste aktivirali svoj račun.', telegramRegistrationComplete: 'Registracija završena. Sada ste prijavljeni.', telegramLoginSuccess: 'Prijavljeni ste putem Telegrama.', invalidResponse: 'Nevažeći odgovor poslužitelja' }; return messages[key] || key; } function startOAuth(provider) { if (!['google', 'github'].includes(provider)) { showToast('Nepodržani davatelj usluga prijave.', '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 || 'Provjerite svoju pristiglu poštu i kliknite vezu za potvrdu koju smo vam poslali. Ne možete se prijaviti lozinkom dok se vaša e-pošta ne potvrdi.'; 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('Ponovno unesite svoju e-poštu kako biste ponovno poslali poruku za potvrdu.', '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 || 'Trenutno ne možemo poslati e-poruku za potvrdu. Pokušajte ponovno kasnije.', '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', 'Molimo unesite svoju e-poštu'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('loginEmail', 'Unesite važeću adresu e-pošte'); hasErrors = true; } if (!password) { showError('loginPassword', 'Molimo unesite svoju lozinku'); 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 || 'Nevažeći email ili lozinka.'); }); } 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', 'Molimo unesite svoje ime'); hasErrors = true; } if (!email) { showError('registerEmail', 'Molimo unesite svoju e-poštu'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('registerEmail', 'Unesite važeću adresu e-pošte'); hasErrors = true; } if (!password) { showError('registerPassword', 'Molimo kreirajte lozinku'); hasErrors = true; } else if (password.length < 8) { showError('registerPassword', 'Lozinka mora imati najmanje 8 znakova'); hasErrors = true; } if (!confirmPassword) { showError('registerConfirmPassword', 'Molimo potvrdite svoju lozinku'); hasErrors = true; } else if (password !== confirmPassword) { showError('registerConfirmPassword', 'Lozinke se ne podudaraju'); 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 || 'Registracija nije mogla biti dovršena. Molimo pokušajte ponovo.', '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('Link za potvrdu Telegrama je istekao. Molim vas počnite ispočetka.', '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 = 'Završite registraciju u Telegramu'; if (text) text.innerHTML = 'Telegram je otvoren u novoj kartici.<br>Podijelite svoj telefonski broj s botom, a zatim se vratite ovamo. Potrebna je i potvrda e-pošte.'; 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','Molimo unesite svoje ime'); hasErrors=true; } if (!email) { showError('registerEmail','Molimo unesite svoju e-poštu'); hasErrors=true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('registerEmail','Unesite važeću adresu e-pošte'); hasErrors=true; } if (hasErrors) return; localStorage.setItem('pending_verification_email', email); sendRequest('/ajax/', {type:'telegram_registration_start',name:name,email:email}, function(response){ if (response.status === 'continue_email_verification') { closeModal('authModal'); showEmailVerificationModal(email, 'Telegram je već potvrđen za ovaj račun. Potvrdite svoju e-poštu kako biste aktivirali račun.'); showToast('Još uvijek je potrebna potvrda e-pošte.', 'info'); return; } if (response.status !== 'ok') { showToast(response.error || 'Nije moguće pokrenuti registraciju Telegrama.','error'); return; } localStorage.setItem('current_user_token', response.user_token); closeModal('authModal'); if (response.email_sent === false) { showToast(response.message || 'E-pošta za potvrdu nije mogla biti poslana. Upotrijebite ponovno slanje e-pošte za potvrdu.','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 || 'Registracija nije mogla biti dovršena.','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','Molimo unesite svoju e-poštu'); return; } if (!/\S+@\S+\.\S+/.test(email)) { showError('loginEmail','Unesite važeću adresu e-pošte'); return; } generateTelegramLoginLink(email); } function generateTelegramLoginLink(email) { sendRequest('/ajax/', {type:'generate_telegram_login_link',email:email}, function(response){ if (response.status !== 'ok') { showToast(response.error || 'Nije moguće pokrenuti prijavu na 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='Prijavite se putem Telegrama'; if (text) text.innerHTML='Otvorite Telegram bot da primite svoj kod za prijavu, zatim unesite kod na ovoj web stranici.'; 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('Kod je istekao. Molim te počni ispočetka.','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','Šifra mora sadržavati 7 znamenki'); return; } sendRequest('/ajax/', {type:'verify_telegram_login_code',user_token:token,verify_code:code}, function(response){ if (response.status!=='ok') { showError('telegramLoginCode',response.error || 'Loš kod ili je istekao'); 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;">🍪 Suglasnost za kolačiće</h3> <p style="margin: 0; color: var(--gray, #6c757d); line-height: 1.5;"> Koristimo kolačiće za poboljšanje vašeg iskustva pregledavanja, posluživanje personaliziranog sadržaja i analizu našeg prometa. Klikom na Prihvati sve pristajete na našu upotrebu kolačića. Pročitajte naše <a href="/en/cookie/" style="color: var(--primary, #4361ee); text-decoration: underline;">Politika kolačića</a> i <a href="/en/privacy/" style="color: var(--primary, #4361ee); text-decoration: underline;">Politika privatnosti</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);"> Prihvati sve </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;"> Samo neophodno </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;"> Prilagodite </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);">Postavke kolačića</h3> </div> <div style="padding: 1.5rem;"> <p style="color: var(--gray, #6c757d); margin-bottom: 1.5rem;">Odaberite koje kolačiće dopuštate. Ove postavke možete promijeniti u bilo kojem trenutku.</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);">Osnovni kolačići</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Potrebno za funkcioniranje web stranice. Ne može se onemogućiti.</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);">Kolačići analitike</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Pomozite nam da shvatimo kako posjetitelji stupaju u interakciju s našom stranicom.</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);">Funkcionalni kolačići</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Zapamtite svoje postavke i postavke.</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);">Ciljanje kolačića</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Koristi se za isporuku relevantnog sadržaja i oglasa.</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;"> Spremi postavke </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;"> Otkazati </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('Pogreška pri analizi suglasnosti za kolačiće:', e); } } // Try localStorage const stored = localStorage.getItem(COOKIE_CONSENT_KEY); if (stored) { try { return JSON.parse(stored); } catch (e) { console.error('Pogreška prilikom analize pristanka za lokalnu pohranu:', 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', 'Postavke kolačića'); 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>