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

Ενεργή φράση
Ημερομηνία ενημέρωσης πληροφοριών: 2026/03/29

📊 Περίληψη: www html

📋
Συχνότητα αναζήτησης: 72 481
Γλώσσα: en
Δεν υπάρχουν διαθέσιμα μορφολογικά δεδομένα ή ορισμός για αυτήν τη φράση.

Θέσεις σε Google

Αναζήτηση φράσεων - Google

🔍
Θέση Πεδίο ορισμού Σελίδα Δράσεις
1 www.w3schools.com /html/
Πλήρης διεύθυνση URL
Τίτλος
HTML Tutorial
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
HTML Tutorial
HTML is the standard markup language for Web pages. With HTML you can create your own Website. HTML is easy to learn - You will enjoy it!
2 html.com /
Πλήρης διεύθυνση URL
Τίτλος
HTML For Beginners The Easy Way
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
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
Πλήρης διεύθυνση URL
Τίτλος
Как сменить document_root apache с /var/www/html на /var ...
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
Как сменить 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...
Τίτλος
HTML - MDN Web Docs - Mozilla
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
HTML - MDN Web Docs - Mozilla
15 апр. 2025 г. — HTML (HyperText Markup Language — «язык гипертекстовой разметки») — самый базовый строительный блок Веба. Он определяет содержание и ...
5 stackoverflow.com /questions/16197663/...
Τίτλος
What is /var/www/html? [closed] - php
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
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 /
Πλήρης διεύθυνση URL
Τίτλος
HTML Academy
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
HTML Academy
Готовим разработчиков напрямую для работодателей . Получите базовые навыки на тренажёрах, а затем пройдите серьёзные профессиональные программы для ...
7 ru.wikipedia.org /wiki/html;4107130
Πλήρης διεύθυνση URL
Τίτλος
HTML
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
HTML
HTML (от англ. HyperText Markup Language — «язык гипертекстовой разметки») — стандартизированный язык гипертекстовой разметки документов для просмотра веб- ...
8 www.linux.org.ru /forum/general/88885...
Πλήρης διεύθυνση URL
Τίτλος
Помогите разобраться с apache и /var/www/index.html
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
Помогите разобраться с apache и /var/www/index.html
24 февр. 2013 г. — вообщем главная страница в любом случае должна быть /var/www/index. html ? а остальные страницы где угодно, только чтоб в папке /var/www?
9 htmlbook.ru /
Πλήρης διεύθυνση URL
Τίτλος
Home | htmlbook.ru
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
Home | htmlbook.ru
Аудио и видео. Семантический HTML . Каждый урок сопровождается задачами, которые надо решать прямиком в редакторе. Курс бесплатный, для изучения требуется только ...
10 html.spec.whatwg.org /
Πλήρης διεύθυνση URL
Τίτλος
HTML Standard;5302519
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
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

Θέσεις σε Yandex

Αναζήτηση φράσεων - Yandex

🔍
Θέση Πεδίο ορισμού Σελίδα Δράσεις
1 inf.e-alekseev.ru /text/www_html.html
Πλήρης διεύθυνση URL
Τίτλος
13.5. WWW и HTML
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
13.5. WWW и HTML
HTML – это формат гипертекстовых документов, использующихся в WWW для предоставления информации.
2 schoolsw3.com /html/html_intro.php
Πλήρης διεύθυνση URL
Τίτλος
Введение в HTML за 5 минут — Как работает HTML ?
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
Введение в HTML за 5 минут — Как работает HTML ?
Простое объяснение HTML для новичков. Основные понятия, структура документа и примеры кода.
3 oslogic.ru /knowledge/75/osnovn...
Τίτλος
Основные понятия World Wide Web, или WWW
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
Основные понятия World Wide Web, или WWW
WWW — (World Wide Web) — это часть Internet, представляющая собой гигантский набор документов, хранящихся на компьютерах по всему миру.
4 frolov-lib.ru /books/bsp/v23/ch4_1...
Πλήρης διεύθυνση URL
Τίτλος
4 создание страниц www
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
4 создание страниц www
Практическое введение в Internet, E-Mail, FTP, WWW и HTML , программирование для Windows Sockets.
5 dzen.ru /a/zgzma5bzlrj7ofll
Πλήρης διεύθυνση URL
Τίτλος
Основы html - база для всех тех, кто мечтает стать... | Дзен
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
Основы html - база для всех тех, кто мечтает стать... | Дзен
Подписчиков
6 htmlacademy.ru /blog/html/introduct...
Τίτλος
Знакомство с HTML — журнал «Доктайп» | HTML Academy
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
Знакомство с HTML — журнал «Доктайп» | HTML Academy
Знакомство с HTML для тех, кто никогда не писал сайты.
7 help.sweb.ru /html-osnovy-yazyka-...
Τίτλος
HTML
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
HTML
Основы HTML - для чего используется этот язык разметки и зачем он нужен; как выглядит код и синтаксис HyperText Markup Language.
8 academy.kgtk.ru /it3/web-programming...
Τίτλος
Лекция 1. Основы World Wide Web ( WWW )
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
Лекция 1. Основы World Wide Web ( WWW )
В лекции рассмотрены общие принципы организации и функционирования сервиса WWW . Описаны структура и компоненты веб, включая протокол HTTP...
9 sky.pro /wiki/html/osnovy-ht...
Τίτλος
Основы HTML
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
Основы HTML
Изучите основы HTML и создайте свою первую веб-страницу! В этой статье вы узнаете о ключевых тегах и структуре HTML -документа, научитесь...
10 developer.mozilla.org /ru/docs/learn_web_d...
Τίτλος
Начало работы с HTML - Изучение веб-разработки | MDN
Τελευταία Ενημέρωση
N/A
Αρχή σελίδας
N/A
Κυκλοφορία: N/A
Επιστροφή συνδέσμους: N/A
Μερίδια κοινωνικής δικτύωσης: N/A
Χρόνος φόρτωσης: N/A
Προεπισκόπηση αποσπάσματος:
Начало работы с HTML - Изучение веб-разработки | MDN
В этой статье мы охватим азы HTML , необходимые для начала работы. Дадим определение «элементам», «атрибутам»...

Αρθρο

📝

Κατανόηση των βασικών στοιχείων του www HTML: Ένας ολοκληρωμένος οδηγός

Καλώς ήρθατε σε μια βαθιά κατάδυση στον κόσμο της ανάπτυξης ιστού, όπου κάθε γραμμή κώδικα μπορεί να μετατρέψει το απλό κείμενο σε δυναμικούς, διαδραστικούς ιστότοπους. Σήμερα, εστιάζουμε στη ραχοκοκαλιά του ιστού—www HTML. Είτε είστε εντελώς αρχάριος είτε εξελίσσετε τις δεξιότητές σας, αυτός ο οδηγός θα σας προσφέρει μια σταθερή βάση στην HTML και πώς ταιριάζει στη μεγαλύτερη εικόνα του σχεδιασμού ιστού.

Τι είναι η HTML;

Η HTML, που σημαίνει HyperText Markup Language, είναι η κύρια γλώσσα που χρησιμοποιείται για τη δημιουργία ιστοσελίδων. Παρέχει μια βασική δομή που ερμηνεύουν τα προγράμματα περιήγησης για την εμφάνιση περιεχομένου στο διαδίκτυο. Κάθε ιστότοπος που επισκέπτεστε ξεκινά ως κώδικας HTML, καθιστώντας τον μια θεμελιώδη δεξιότητα για όποιον ενδιαφέρεται για την ανάπτυξη ιστού.

Ο ρόλος του www HTML

Μπορεί να αναρωτιέστε γιατί αναφέρουμε συγκεκριμένα το www HTML. Αν και το τμήμα "www" δεν είναι συνήθως απαραίτητο στις σύγχρονες διευθύνσεις ιστού, ήταν κάποτε σημαντικό. Ο Παγκόσμιος Ιστός (WWW) είναι ουσιαστικά ένα σύστημα διασυνδεδεμένων εγγράφων υπερκειμένου που προσπελάζονται μέσω του Διαδικτύου. Η HTML είναι η γλώσσα που χρησιμοποιείται για τη δημιουργία αυτών των εγγράφων.

Στο σημερινό πλαίσιο, η αναφορά του www HTML τονίζει την ιστορική σημασία και τον θεμελιώδη ρόλο του HTML στη δημιουργία του ιστού. Μας υπενθυμίζει ότι η HTML είναι κάτι περισσότερο από κώδικας—είναι η γλώσσα του ιστού.

Βασική δομή HTML

Ας ρίξουμε μια ματιά στη βασική δομή ενός εγγράφου HTML:

<προ> <κεφάλι> Τίτλος σελίδας <σώμα>

Η πρώτη μου επικεφαλίδα

Η πρώτη μου παράγραφος.

  • : Δηλώνει τον τύπο και την έκδοση του εγγράφου HTML.
  • : Το ριζικό στοιχείο μιας σελίδας HTML.
  • : Περιέχει μετα-πληροφορίες σχετικά με το έγγραφο HTML, όπως τον τίτλο του και συνδέσμους σε φύλλα στυλ.
  • </strong>: Ορίζει τον τίτλο της ιστοσελίδας, ο οποίος εμφανίζεται στην καρτέλα του προγράμματος περιήγησης.</li> <li><strong><body></strong>: Περιέχει το περιεχόμενο του εγγράφου HTML που είναι ορατό στους χρήστες.</li> <li><strong><h1></strong>: Ορίζει μια επικεφαλίδα ανώτατου επιπέδου.</li> <li><strong><p></strong>: Αντιπροσωπεύει μια παράγραφο κειμένου.</li> </ul> <h2>Δημιουργία στοιχείων κειμένου με www HTML</h2> <p>Η HTML παρέχει διάφορες ετικέτες για τη δημιουργία διαφορετικών τύπων στοιχείων κειμένου. Ακολουθούν ορισμένα παραδείγματα:</p> <ul> <li><strong><h1>-<h6></strong>: Ορίστε τις επικεφαλίδες από τη μεγαλύτερη προς τη μικρότερη.</li> <li><strong><p></strong>: Δημιουργεί παραγράφους κειμένου.</li> <li><strong><a></strong>: Εισάγει υπερσυνδέσμους προς άλλες ιστοσελίδες ή πόρους.</li> <li><strong><em></strong> και <strong><strong></strong>: Δώστε έμφαση στο κείμενο, με το <strong><em></strong> να αποδίδεται συνήθως με πλάγιους χαρακτήρες και <strong><strong></strong> με έντονη γραφή.</li> <li><strong><br></strong>: Εισάγει μια αλλαγή γραμμής.</li> </ul> <h3>Παράδειγμα: Δημιουργία απλού συνδέσμου</h3> <προ> <a href="https://www.serpulse.com">Επισκεφτείτε το Serpulse</a> </pre> <p>Αυτός ο κώδικας δημιουργεί έναν σύνδεσμο με δυνατότητα κλικ στον ιστότοπο Serpulse. Το χαρακτηριστικό <strong>href</strong> καθορίζει τη διεύθυνση URL στην οποία οδηγεί ο σύνδεσμος.</p> <h2>Προσθήκη εικόνων με www HTML</h2> <p>Η ενσωμάτωση εικόνων στις ιστοσελίδες σας βελτιώνει την οπτική τους έλξη και την εμπειρία χρήστη. Για να προσθέσετε μια εικόνα χρησιμοποιώντας HTML, χρησιμοποιείτε την ετικέτα <strong><img></strong>:</p> <προ> <img src="path/to/image.jpg" alt="Περιγραφή εικόνας"> </pre> <ul> <li><strong>src</strong>: Καθορίζει τη διαδρομή προς το αρχείο εικόνας.</li> <li><strong>alt</strong>: Παρέχει εναλλακτικό κείμενο για προγράμματα ανάγνωσης οθόνης και όταν η εικόνα δεν μπορεί να εμφανιστεί.</li> </ul> <h2>Δημιουργία λιστών με www HTML</h2> <p>Οι λίστες είναι απαραίτητες για την οργάνωση πληροφοριών στον ιστότοπό σας. Η HTML υποστηρίζει τόσο ταξινομημένες όσο και μη ταξινομημένες λίστες:</p> <h3>Μη ταξινομημένη λίστα</h3> <προ> <ul> <li>Πρώτο στοιχείο</li> <li>Δεύτερο στοιχείο</li> <li>Τρίτο στοιχείο</li> </ul> </pre> <h3>Παραγγελία λίστας</h3> <προ> <ol> <li>Πρώτο βήμα</li> <li>Δεύτερο βήμα</li> <li>Τρίτο βήμα</li> </ol> </pre> <h2>Πίνακες σε www HTML</h2> <p>Η HTML σάς επιτρέπει επίσης να δημιουργείτε πίνακες για την εμφάνιση δεδομένων σε πίνακα:</p> <προ> <πίνακας> <tr> <th>Κεφαλίδα 1</th> <th>Κεφαλίδα 2</th> </tr> <tr> <td>Σειρά 1, κελί 1</td> <td>Σειρά 1, κελί 2</td> </tr> <tr><td>Σειρά 2, κελί 1</td> <td>Σειρά 2, κελί 2</td> </tr> </table> </pre> <ul> <li><strong><table></strong>: Καθορίζει τον πίνακα.</li> <li><strong><tr></strong>: Ορίζει μια σειρά πίνακα.</li> <li><strong><th></strong>: Ορίζει ένα κελί κεφαλίδας σε έναν πίνακα.</li> <li><strong><td></strong>: Ορίζει ένα τυπικό κελί δεδομένων σε έναν πίνακα.</li> </ul> <h2>Φόρμες σε www HTML</h2> <p>Οι φόρμες είναι απαραίτητες για τη συλλογή στοιχείων χρήστη. Ακολουθεί ένα βασικό παράδειγμα μιας φόρμας HTML:</p> <προ> <form action="/submit-form" method="post"> <label for="name">Όνομα:</label> <input type="text" id="name" name="name"><br><br> <label for="email">Ηλεκτρονικό ταχυδρομείο:</label> <input type="email" id="email" name="email"><br><br> <input type="submit" value="Submit"> </form> </pre> <ul> <li><strong><form></strong>: Δημιουργεί μια φόρμα HTML για εισαγωγή χρήστη.</li> <li><strong>ενέργεια</strong>: Καθορίζει πού θα αποστέλλονται τα δεδομένα της φόρμας όταν υποβάλλονται.</li> <li><strong>μέθοδος</strong>: Καθορίζει τη μέθοδο HTTP που θα χρησιμοποιηθεί (GET ή POST).</li> <li><strong><label></strong>: Παρέχει ετικέτες για πεδία εισαγωγής.</li> <li><strong><input></strong>: Δημιουργεί διάφορους τύπους στοιχείων εισαγωγής, όπως πεδία κειμένου και κουμπιά.</li> </ul> <h2>Styling με CSS</h2> <p>Ενώ η HTML παρέχει τη δομή των ιστοσελίδων σας, το CSS (Cascading Style Sheets) είναι υπεύθυνο για την εμφάνισή τους. Μπορείτε να συνδέσετε ένα αρχείο CSS στο έγγραφό σας HTML χρησιμοποιώντας την ετικέτα <strong><link></strong> στην ενότητα <strong><head></strong>:</p> <προ> <link rel="stylesheet" href="styles.css"> </pre> <p>Εναλλακτικά, μπορείτε να συμπεριλάβετε το CSS απευθείας στο HTML σας χρησιμοποιώντας την ετικέτα <strong><style></strong>:</p> <προ> <στυλ> σώμα { Χρώμα φόντου: ανοιχτό μπλε; } h1 { χρώμα: ναυτικό; περιθώριο-αριστερά: 20 εικονοστοιχεία; } </style> </pre> <h2>JavaScript για διαδραστικότητα</h2> <p>Για να προσθέσετε διαδραστικότητα στις ιστοσελίδες σας, μπορείτε να χρησιμοποιήσετε JavaScript. Αν και δεν αποτελεί μέρος της ίδιας της HTML, η JavaScript λειτουργεί παράλληλα με την HTML και το CSS για τη δημιουργία δυναμικών εμπειριών χρήστη. Ακολουθεί ένα απλό παράδειγμα ενσωμάτωσης JavaScript σε ένα έγγραφο HTML:</p> <προ> <σενάριο> συνάρτηση showMessage() { alert ("Γεια, κόσμο!"); } </script> <button onclick="showMessage()">Κάντε κλικ σε εμένα!</button> </pre> <h2>Συμπέρασμα</h2> <p>Σε αυτόν τον περιεκτικό οδηγό, εξερευνήσαμε τα βασικά στοιχεία του <strong>www HTML</strong>, από τον ρόλο του στον Παγκόσμιο Ιστό έως τη δημιουργία πολύπλοκων στοιχείων όπως πίνακες και φόρμες. Κατανοώντας την HTML, θα είστε σε καλό δρόμο για τη δημιουργία ιστότοπων με επαγγελματική εμφάνιση που θα προσελκύουν και θα ενημερώνουν το κοινό σας.</p> <p>Θυμηθείτε, η εκμάθηση HTML είναι μόνο η αρχή του ταξιδιού σας στην ανάπτυξη ιστού. Καθώς κατακτάτε την HTML, μην ξεχάσετε να βουτήξετε στο CSS για στυλ και JavaScript για διαδραστικότητα. Καλή κωδικοποίηση!</p> <p>Για περισσότερους πόρους και οδηγούς, επισκεφτείτε το <a href="https://www.serpulse.com">Serpulse</a>.</p> <p><em>Συγγραφέας: Serpulse.com</em></p> <p><em>Λέξεις-κλειδιά: www html, βασικά στοιχεία HTML, ανάπτυξη ιστού, σχεδίαση ιστοσελίδων, δομή HTML, ετικέτες HTML, στοιχεία κειμένου HTML, εικόνες HTML, λίστες HTML, πίνακες HTML, φόρμες HTML, CSS, JavaScript</em></p> ``` σήμανση Αυτό το άρθρο παρέχει μια λεπτομερή επισκόπηση του HTML, της σημασίας του και του τρόπου με τον οποίο εντάσσεται στο ευρύτερο πλαίσιο της ανάπτυξης ιστού. Καλύπτει θεμελιώδεις έννοιες και πρακτικά παραδείγματα, καθιστώντας το μια εξαιρετική πηγή τόσο για αρχάριους όσο και για έμπειρους προγραμματιστές που θέλουν να ανανεώσουν τις γνώσεις τους. Η συμπερίληψη συγκεκριμένων ετικετών HTML και αποσπασμάτων κώδικα εξασφαλίζει σαφήνεια και ευκαιρίες πρακτικής εκμάθησης. ``` <p>Συγγραφέας: serpuls.com</p> </div> </div> <!-- Words Definitions --> <div class="card"> <div class="card-header"> <h2 class="card-title">www html Λόγια</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">Πρόσθετες Υπηρεσίες</h2> <div class="card-icon">💎</div> </div> <div class="card-content"> <div class="info-grid"> <div class="info-item"> <span class="popup-trigger" alt="Πλήρης έλεγχος SEO με λεπτομερείς συστάσεις"> <button class="cta-button" data-modal="paymentModal" data-title="Πλήρης έλεγχος SEO" data-text="Πλήρης έλεγχος SEO με λεπτομερείς συστάσεις για τη βελτίωση των θέσεων στις μηχανές αναζήτησης." data-price="99.99"> Πλήρης έλεγχος SEO </button> <span class="popup-text"></span> </span> </div> <div class="info-item"> <span class="popup-trigger" alt="Μηνιαία παρακολούθηση θέσης για 100 φράσεις κλειδιά"> <button class="cta-button" data-modal="paymentModal" data-title="Παρακολούθηση θέσης" data-text="Μηνιαία παρακολούθηση θέσης για 100 φράσεις κλειδιά στο Google και το Yandex." data-price="49.99"> Παρακολούθηση θέσης </button> <span class="popup-text"></span> </span> </div> <div class="info-item"> <span class="popup-trigger" alt="Ανάλυση ανταγωνιστών και προσδιορισμός των στρατηγικών προώθησής τους"> <button class="cta-button" data-modal="paymentModal" data-title="Ανάλυση Ανταγωνιστή" data-text="Ανάλυση ανταγωνιστών και προσδιορισμός των στρατηγικών προώθησής τους." data-price="79.99"> Ανάλυση Ανταγωνιστή </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('Σφάλμα' + ':'+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 ? 'Εμφάνιση αποσπάσματος' : 'Απόκρυψη αποσπάσματος'; } </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 = 'Εμφάνιση λεπτομερειών σελίδας'; } }); // Toggle current page details if (pageDetails.classList.contains('active')) { pageDetails.classList.remove('active'); button.textContent = 'Εμφάνιση λεπτομερειών σελίδας'; } else { pageDetails.classList.add('active'); button.textContent = 'Απόκρυψη λεπτομερειών σελίδας'; } } // 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 = 'Εμφάνιση λεπτομερειών σελίδας'; }); } }); // 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="/el/domain/${item.domain}/" rel="nofollow">${item.domain}</a></noindex></td> <td>${item.page}</td> <td> <button class="show-page-btn" onclick="togglePageDetails(this)">Εμφάνιση λεπτομερειών σελίδας</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: 'Μορφολογικά λήμματα', btnText: 'Λήμματα' }, { id: 'google-search-section', title: 'Αναζήτηση φράσεων - Google', btnText: 'Google' }, { id: 'yandex-search-section', title: 'Αναζήτηση φράσεων - Yandex', btnText: 'Yandex' }, { id: 'book-samples-section', title: 'Παραδείγματα βιβλίων με φράση', btnText: 'Βιβλία' }, { id: 'additional-services', title: 'Πρόσθετες Υπηρεσίες', btnText: 'Υπηρεσίες' } ]; // --- Helper Functions --- function getSectionIdFromTitle(title) { if (title.includes('Μορφολογικά λήμματα')) return 'morphological-lemmas'; if (title.includes('Αναζήτηση φράσεων - Google')) return 'google-search-section'; if (title.includes('Αναζήτηση φράσεων - Yandex')) return 'yandex-search-section'; if (title.includes('Πρόσθετες Υπηρεσίες')) 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>📑 Πίνακας περιεχομένων</span> <button id="toc-toggle-btn" class="toc-toggle-btn" aria-label="Σύμπτυξη πίνακα περιεχομένων">−</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', 'Σύμπτυξη/ανάπτυξη ενότητας'); // 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', 'Επιστροφή στην κορυφή'); 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 = '← → Σύρετε για να δείτε περισσότερα'; 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>Επαγγελματικό εργαλείο για ανάλυση μηχανών αναζήτησης, παρακολούθηση θέσης και βελτιστοποίηση SEO.</p> <p style="margin-top: 1rem;"><strong>Skynet360 LP.</strong></p> <p><strong>Εταιρεία Αρ:</strong> SL027564</p> <p><strong>Διεύθυνση:</strong> Suite 1 4 Queen Street, Edinburgh, Scotland, EH2 1JE</p> </div> </div> <div class="footer-section"> <h3>Γρήγοροι Σύνδεσμοι</h3> <ul class="footer-links"> <li><a href="/el/">Σπίτι</a></li> <li><a href="/el/profile/">Προφίλ</a></li> <li><a href="/el/tasks/">Εργασίες παρακολούθησης</a></li> <li><a href="/el/api_docs/">Τεκμηρίωση API</a></li> <li><a href="/el/team/">Η Ομάδα μας</a></li> </ul> </div> <div class="footer-section"> <h3>Πόροι</h3> <ul class="footer-links"> <li><a href="/el/blogs/">Ιστολόγιο</a></li> <li><a href="/el/faq/">FAQ</a></li> <li><a href="/el/support/">Υποστήριξη</a></li> </ul> </div> <div class="footer-section"> <h3>Επαφές</h3> <div class="footer-info"> <p>E-mail: <a href="mailto:info@serpuls.com">info@serpuls.com</a></p> <p>Τηλεγράφημα: <a href="https://t.me/serpulse_support">@serpulse_support</a></p> <div class="social-links"> <a href="#" class="social-link" title="Τηλεγράφημα">📱</a> <a href="https://github.com/SkyNetJose/serpuls" class="social-link" title="GitHub">💻</a> <a href="#" class="social-link" title="Κελάδημα">🐦</a> </div> </div> </div> </div> <div class="footer-bottom"> <p class="copyright">© 2026 serpuls.com. Με την επιφύλαξη παντός δικαιώματος. Skynet360 LP.</p> <div class="footer-bottom-links"> <a href="/el/privacy/">Πολιτική Απορρήτου</a> <a href="/el/terms/">Όροι Χρήσης</a> <a href="/el/agreement/">Συμφωνία χρήστη</a> <a href="/el/cookie/">Πολιτική cookies</a> </div> </div> </div> </footer> <!-- Updated Payment Modal HTML --> <div class="modal" id="paymentModal"> <div class="modal-content"> <span class="close-btn">×</span> <div class="modal-success" id="paymentSuccess"> <div class="success-icon">✓</div> <h3>Η πληρωμή επιτυχής!</h3> <p>Σας ευχαριστούμε για την παραγγελία σας. Θα επικοινωνήσουμε μαζί σας σύντομα.</p> <button class="cta-button" onclick="closeModal('paymentModal')">Κοντά</button> </div> <form id="paymentForm" onsubmit="return processPayment(event)"> <h2 class="modal-title" id="modalTitle">Πληρωμή για την υπηρεσία</h2> <p class="modal-text" id="modalText">Περιγραφή υπηρεσίας</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">Εισαγάγετε το ποσό (USD):</label> <input type="number" id="manualPrice" class="form-control" placeholder="Εισαγάγετε το ποσό" 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)"> Εισαγάγετε προσαρμοσμένο ποσό </label> </div> <div class="form-group"> <label for="modalName">Ονομα:</label> <input type="text" id="modalName" class="form-control" placeholder="Εισαγάγετε το όνομά σας" required> <div class="error-message" id="modalNameError"></div> </div> <div class="form-group"> <label for="modalEmail">E-mail:</label> <input type="email" id="modalEmail" class="form-control" placeholder="Εισαγάγετε το email σας" required> <div class="error-message" id="modalEmailError"></div> </div> <p class="modal-price" id="modalPrice">Τιμή: $0.00</p> <button type="submit" class="cta-button" id="payButton">Πληρωμή $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">Πληρωμή</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">Σύνδεση</div> <div class="auth-tab" data-tab="register">Μητρώο</div> </div> <div class="auth-form active" id="loginForm"> <h2 class="modal-title">Συνδεθείτε στον λογαριασμό σας</h2> <div class="form-group"> <label for="loginEmail">E-mail:</label> <input type="email" id="loginEmail" class="form-control" autocomplete="email" placeholder="Εισαγάγετε το email σας"> <div class="error-message" id="loginEmailError"></div> </div> <div class="form-group"> <label for="loginPassword">Σύνθημα:</label> <input type="password" id="loginPassword" class="form-control" autocomplete="current-password" placeholder="Εισαγάγετε τον κωδικό πρόσβασής σας"> <div class="error-message" id="loginPasswordError"></div> </div> <button class="cta-button" style="width:100%" onclick="processLogin()">Συνδεθείτε</button> <div style="text-align:center;margin:1rem 0;color:var(--gray);border-top:1px solid #e9ecef;padding-top:1rem;">Ή συνεχίστε με</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()">📱 Συνδεθείτε με το Telegram</button> <p style="margin-top:.5rem;color:var(--gray);font-size:.9rem">Για είσοδο στο Telegram, εισαγάγετε πρώτα το email του λογαριασμού Serpulse που είναι συνδεδεμένος στο Telegram παραπάνω.</p> </div> <div class="auth-form" id="registerForm"> <h2 class="modal-title">Δημιουργία λογαριασμού</h2> <div class="form-group"><label for="registerName">Ονομα:</label><input type="text" id="registerName" class="form-control" autocomplete="name" required><div class="error-message" id="registerNameError"></div></div> <div class="form-group"><label for="registerEmail">E-mail:</label><input type="email" id="registerEmail" class="form-control" autocomplete="email" required><div class="error-message" id="registerEmailError"></div></div> <div class="form-group"><label for="registerPassword">Σύνθημα:</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">Επιβεβαίωση κωδικού πρόσβασης:</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()">Δημιουργία λογαριασμού</button> <div style="text-align:center;margin:1rem 0;color:var(--gray);border-top:1px solid #e9ecef;padding-top:1rem;">Ή δημιουργήστε έναν λογαριασμό με</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()">📱 Εγγραφείτε στο Telegram</button> <p style="margin-top:1rem;color:var(--gray);font-size:.9rem">Απαιτείται επαλήθευση μέσω email για εγγραφή κωδικού πρόσβασης και Telegram. Η εγγραφή Google και GitHub χρησιμοποιούν το επαληθευμένο email που επιστρέφει ο πάροχος.</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">Κωδικός επαλήθευσης</h2> <p class="modal-text">Ο κωδικός στάλθηκε στο Telegram. Εισαγάγετε το παρακάτω:</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()"> Επιβεβαιώστε τη σύνδεση </button> <div class="timer-container" id="loginCodeTimer"> Ο κωδικός ισχύει για: <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">Ολοκληρωμένη εγγραφή στο Telegram</h2> <p class="modal-text" id="telegramModalText"> Το Telegram άνοιξε σε νέα καρτέλα.<br> Εάν το bot δεν άνοιξε αυτόματα, χρησιμοποιήστε το κουμπί παρακάτω ή σαρώστε τον κωδικό QR. </p> <!-- QR Code Container --> <div id="qrCodeContainer" style="margin: 1.5rem 0; text-align: center;"> <div id="qrcode" style="display: inline-block;"></div> <p style="font-size: 0.9rem; color: var(--gray); margin-top: 0.5rem;"> Σαρώστε τον κωδικό QR για να ανοίξετε το bot </p> </div> <!-- Timer and Status --> <div id="telegramTimer" class="timer-container"> Αναμονή για επιβεβαίωση... <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"> 📱 Ανοίξτε ξανά το Telegram </button> <button class="cta-button secondary" onclick="refreshTelegramQR()" id="refreshQRBtn"> 🔄 Ανανέωση κωδικού QR </button> </div> <!-- Status Info --> <div class="info-box" style="margin-top: 1.5rem;"> <p style="margin: 0; font-size: 0.9rem;"> <strong>Πώς λειτουργεί:</strong><br> 1. Κάντε κλικ στο Open Telegram ή σαρώστε τον κωδικό QR<br> 2. Στο bot κάντε κλικ στο ΕΝΑΡΞΗ και μοιραστείτε τον αριθμό τηλεφώνου σας<br> 3. Η κατάσταση επιβεβαίωσης θα ενημερωθεί αυτόματα </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">Επαληθεύστε το email σας</h2> <p class="modal-text" id="emailVerifyMessage">Ελέγξτε τα εισερχόμενά σας και κάντε κλικ στον σύνδεσμο επαλήθευσης που σας στείλαμε. Δεν μπορείτε να συνδεθείτε με κωδικό πρόσβασης μέχρι να επαληθευτεί το email σας.</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()">Επανάληψη αποστολής email επαλήθευσης</button> <button class="cta-button" style="width:100%;margin-top:.75rem" onclick="closeEmailVerifyModal()">Κοντά</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 || 'Προφίλ'; } // 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('Σφάλμα κατά τον έλεγχο της κατάστασης σύνδεσης:', 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 = 'Κρύβω'; } else { // Hide all beyond the first 5 items.forEach((el, index) => { if (index >= max_count) el.classList.add('hidden'); }); btn.textContent = 'Εμφάνιση περισσότερων'; } } // Toggle raw data visibility function toggleRawData(elementId, btn) { const element = document.getElementById(elementId); if (element.style.maxHeight === '200px') { element.style.maxHeight = 'none'; btn.textContent = 'Απόκρυψη πλήρων δεδομένων'; } else { element.style.maxHeight = '200px'; btn.textContent = 'Εμφάνιση πλήρων δεδομένων'; } } </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 = 'Τιμή: $' + parseFloat(price).toFixed(2); payButton.textContent = 'Πληρωμή $' + 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 = 'Από $${paymentConfig.minAmount} να $${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 = 'Τιμή: από $${paymentConfig.minAmount} να $${paymentConfig.maxAmount}'; payButton.textContent = 'Μη έγκυρο ποσό'; payButton.disabled = true; payButton.style.opacity = '0.6'; } else { modalPrice.textContent = 'Τιμή: $0.00'; payButton.textContent = 'Πληρωμή $0.00'; payButton.disabled = true; payButton.style.opacity = '0.6'; } return; } const modalPrice = document.getElementById('modalPrice'); const payButton = document.getElementById('payButton'); modalPrice.textContent = 'Τιμή: $' + parseFloat(price).toFixed(2); payButton.textContent = 'Πληρωμή $' + 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('Σφάλμα ανάλυσης των επιλογών πληρωμής:', 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', 'Το ποσό πρέπει να είναι μεταξύ $${paymentConfig.minAmount} και $${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', 'Παρακαλώ εισάγετε το όνομά σας'); hasErrors = true; } if (!email) { showError('modalEmail', 'Εισαγάγετε τη διεύθυνση email σας'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('modalEmail', 'Εισαγάγετε μια έγκυρη διεύθυνση email'); 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('Σφάλμα: ' + 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 = "572effd268f7b975311e4f9de7a68dcc6b51fc5b841f6ee92b519c0eb034b621"; // 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: 'Μη έγκυρη απάντηση από τον διακομιστή'}); } if(is_good) callback(response); } else { callback({status: 'error', error: 'Σφάλμα διακομιστή: ' + xhr.status}); } } }; xhr.send(JSON.stringify(data)); } // Close redirect modal document.getElementById('redirectModal').querySelector('.close-btn').addEventListener('click', function() { document.getElementById('redirectModal').style.display = 'none'; }); // Mock chart data (in a real implementation, you would use Google Charts) function initCharts() { // This is a placeholder for actual chart implementation //console.log('Charts would be initialized here with Google Charts API'); } // Image modal functionality document.addEventListener('DOMContentLoaded', function() { // Image modal const imgModal = document.getElementById('imgModal'); const modalImg = document.getElementById('modalImg'); const imgCloseBtn = imgModal.querySelector('.close-btn'); // Open image modal document.querySelectorAll('[data-modal="imgModal"]').forEach(img => { img.addEventListener('click', function() { modalImg.src = this.src; imgModal.style.display = 'flex'; }); }); // Close image modal imgCloseBtn.addEventListener('click', function() { imgModal.style.display = 'none'; }); // Close on outside click window.addEventListener('click', function(e) { if (e.target === imgModal) { imgModal.style.display = 'none'; } }); }); </script> <script> //console.log("herei"); // Analysis functionality document.addEventListener('DOMContentLoaded', function() { //console.log("DOMContentLoaded"); const analyzeBtn = document.getElementById('analyzeNowBtn'); const analysisStatus = document.getElementById('analysisStatus'); if (analyzeBtn) { analyzeBtn.addEventListener('click', function() { //console.log("click"); // Show captcha modal showCaptchaModal(); }); } }); function showCaptchaModal() { //console.log("showCaptchaModal"); // Create modal for captcha const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'captchaModal'; modal.style.display = 'flex'; modal.innerHTML = ` <div class="modal-content"> <span class="close-btn" onclick="closeCaptchaModal()">×</span> <h2 class="modal-title">Επιβεβαιώστε ότι δεν είστε ρομπότ</h2> <p class="modal-text">Εισαγάγετε το κείμενο από την εικόνα:</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()">Ανανέωση κωδικού</button> <input type="text" class="captcha-input" id="captchaInput" placeholder="Εισαγάγετε κωδικό" maxlength="6"> </div> <button class="cta-button" onclick="submitAnalysis()" style="margin-top: 1rem;">Υποβολή για ανάλυση</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('Εισαγάγετε τον κωδικό από την εικόνα', 'error'); return; } // Show loading state analysisStatus.textContent = 'Αποστολή αιτήματος...'; 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 = 'Σφάλμα: ο τύπος δεν είναι κανένας'; 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 = 'Το αίτημά σας έχει σταλεί, περιμένετε για επαλήθευση τομέα'; analysisStatus.className = 'analysis-status success'; } else { // Show error message analysisStatus.textContent = 'Σφάλμα: ' + 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: 'Ο λογαριασμός δημιουργήθηκε. Ελέγξτε το email σας και κάντε κλικ στον σύνδεσμο επαλήθευσης πριν συνδεθείτε.', loginSuccess: 'Η σύνδεση έγινε με επιτυχία.', emailVerified: 'Το email επαληθεύτηκε. Ο λογαριασμός σας είναι ενεργός και είστε συνδεδεμένοι.', emailTelegramPending: 'Το email επαληθεύτηκε. Ολοκληρώστε την επαλήθευση Telegram για να ενεργοποιήσετε τον λογαριασμό σας.', emailVerificationFailed: 'Αυτός ο σύνδεσμος επαλήθευσης ηλεκτρονικού ταχυδρομείου δεν είναι έγκυρος ή έχει λήξει.', googleSuccess: 'Συνδεθείτε με την Google.', githubSuccess: 'Συνδεθήκατε με το GitHub.', oauthCancelled: 'Η εξωτερική είσοδος ακυρώθηκε.', oauthFailed: 'Η εξωτερική σύνδεση απέτυχε. Δοκιμάστε ξανά.', telegramVerifiedEmailPending: 'Η επαλήθευση τηλεγραφήματος ολοκληρώθηκε. Ελέγξτε το email σας και κάντε κλικ στον σύνδεσμο επαλήθευσης για να ενεργοποιήσετε τον λογαριασμό σας.', telegramRegistrationComplete: 'Η εγγραφή ολοκληρώθηκε. Είστε πλέον συνδεδεμένοι.', telegramLoginSuccess: 'Συνδεθήκατε με το Telegram.', invalidResponse: 'Μη έγκυρη απάντηση από τον διακομιστή' }; return messages[key] || key; } function startOAuth(provider) { if (!['google', 'github'].includes(provider)) { showToast('Μη υποστηριζόμενος πάροχος σύνδεσης.', '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 || 'Ελέγξτε τα εισερχόμενά σας και κάντε κλικ στον σύνδεσμο επαλήθευσης που σας στείλαμε. Δεν μπορείτε να συνδεθείτε με κωδικό πρόσβασης μέχρι να επαληθευτεί το email σας.'; 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('Εισαγάγετε ξανά το email σας για να στείλετε ξανά το μήνυμα επαλήθευσης.', '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 || 'Δεν ήταν δυνατή η αποστολή του email επαλήθευσης αυτήν τη στιγμή. Δοκιμάστε ξανά αργότερα.', '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', 'Παρακαλώ εισάγετε το email σας'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('loginEmail', 'Εισαγάγετε μια έγκυρη διεύθυνση email'); hasErrors = true; } if (!password) { showError('loginPassword', 'Εισαγάγετε τον κωδικό πρόσβασής σας'); 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 || 'Μη έγκυρο email ή κωδικός πρόσβασης.'); }); } 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', 'Παρακαλώ εισάγετε το όνομά σας'); hasErrors = true; } if (!email) { showError('registerEmail', 'Παρακαλώ εισάγετε το email σας'); hasErrors = true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('registerEmail', 'Εισαγάγετε μια έγκυρη διεύθυνση email'); hasErrors = true; } if (!password) { showError('registerPassword', 'Δημιουργήστε έναν κωδικό πρόσβασης'); hasErrors = true; } else if (password.length < 8) { showError('registerPassword', 'Ο κωδικός πρόσβασης πρέπει να αποτελείται από τουλάχιστον 8 χαρακτήρες'); hasErrors = true; } if (!confirmPassword) { showError('registerConfirmPassword', 'Επιβεβαιώστε τον κωδικό πρόσβασής σας'); hasErrors = true; } else if (password !== confirmPassword) { showError('registerConfirmPassword', 'Οι κωδικοί πρόσβασης δεν ταιριάζουν'); 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 || 'Δεν ήταν δυνατή η ολοκλήρωση της εγγραφής. Δοκιμάστε ξανά.', '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('Ο σύνδεσμος επαλήθευσης τηλεγραφήματος έληξε. Παρακαλώ ξεκινήστε ξανά.', '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 = 'Ολοκληρωμένη εγγραφή στο Telegram'; if (text) text.innerHTML = 'Το Telegram άνοιξε σε νέα καρτέλα.<br>Μοιραστείτε τον δικό σας αριθμό τηλεφώνου με το bot και, στη συνέχεια, επιστρέψτε εδώ. Απαιτείται επίσης επαλήθευση μέσω email.'; 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','Παρακαλώ εισάγετε το όνομά σας'); hasErrors=true; } if (!email) { showError('registerEmail','Παρακαλώ εισάγετε το email σας'); hasErrors=true; } else if (!/\S+@\S+\.\S+/.test(email)) { showError('registerEmail','Εισαγάγετε μια έγκυρη διεύθυνση email'); 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 έχει ήδη επαληθευτεί για αυτόν τον λογαριασμό. Επαληθεύστε το email σας για να ενεργοποιήσετε τον λογαριασμό.'); showToast('Η επαλήθευση μέσω email εξακολουθεί να απαιτείται.', 'info'); return; } if (response.status !== 'ok') { showToast(response.error || 'Δεν ήταν δυνατή η έναρξη της εγγραφής στο Telegram.','error'); return; } localStorage.setItem('current_user_token', response.user_token); closeModal('authModal'); if (response.email_sent === false) { showToast(response.message || 'Δεν ήταν δυνατή η αποστολή του email επαλήθευσης. Παρακαλούμε χρησιμοποιήστε Επαναποστολή email επαλήθευσης.','info'); } showTelegramVerifyModal(response.bot_username,response.telegram_token,response.deep_link,response.telegram_token_expires_remaining); startTelegramRegistrationStatusCheck(response.user_token, email); }); } function startTelegramRegistrationStatusCheck(userToken, email) { clearInterval(telegramStatusInterval); telegramStatusInterval=setInterval(function(){ checkTelegramVerificationStatus(userToken,email); }, 4000); checkTelegramVerificationStatus(userToken,email); } function checkTelegramVerificationStatus(userToken, email) { sendRequest('/ajax/', {type:'check_telegram_verification',user_token:userToken}, function(response){ if (response.status !== 'ok') return; if (response.account_verified) { clearTelegramTimers(); completeRegistration(); return; } if (response.telegram_verified && !response.email_verified) { clearTelegramTimers(); closeTelegramVerifyModal(); showEmailVerificationModal(email, authText('telegramVerifiedEmailPending')); showToast(authText('telegramVerifiedEmailPending'),'info'); } }, false); } function completeRegistration() { const token=localStorage.getItem('current_user_token') || ''; if (!token) return; sendRequest('/ajax/', {type:'complete_registration',user_token:token}, function(response){ if (response.status !== 'ok') { showToast(response.error || 'Δεν ήταν δυνατή η ολοκλήρωση της εγγραφής.','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','Παρακαλώ εισάγετε το email σας'); return; } if (!/\S+@\S+\.\S+/.test(email)) { showError('loginEmail','Εισαγάγετε μια έγκυρη διεύθυνση email'); return; } generateTelegramLoginLink(email); } function generateTelegramLoginLink(email) { sendRequest('/ajax/', {type:'generate_telegram_login_link',email:email}, function(response){ if (response.status !== 'ok') { showToast(response.error || 'Δεν είναι δυνατή η έναρξη της σύνδεσης στο 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='Είσοδος μέσω Telegram'; if (text) text.innerHTML='Ανοίξτε το bot του Telegram για να λάβετε τον κωδικό σύνδεσής σας και, στη συνέχεια, εισαγάγετε τον κωδικό σε αυτόν τον ιστότοπο.'; 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('Ο κωδικός έληξε. Παρακαλώ ξεκινήστε από την αρχή.','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','Ο κωδικός πρέπει να περιέχει 7 ψηφία'); return; } sendRequest('/ajax/', {type:'verify_telegram_login_code',user_token:token,verify_code:code}, function(response){ if (response.status!=='ok') { showError('telegramLoginCode',response.error || 'Λανθασμένος κωδικός ή ληγμένος'); return; } if (window.loginCodeTimer) clearInterval(window.loginCodeTimer); localStorage.removeItem('current_user_token'); closeModal('telegramCodeModal'); showToast(response.message || authText('telegramLoginSuccess'),'success'); window.setTimeout(function(){window.location.href='/' + window.currentLang + '/profile/';},250); }); } // Old registration-status probing is intentionally disabled: it leaked account state and duplicated the new verification flow. function checkRegistrationStatus() { return; } function closeVisibleAuthModal() { for (const id of ['telegramCodeModal','telegramVerifyModal','emailVerifyModal','authModal']) { const modal=document.getElementById(id); if (!modal || modal.style.display !== 'flex') continue; if (id === 'telegramVerifyModal') closeTelegramVerifyModal(); else if (id === 'emailVerifyModal') closeEmailVerifyModal(); else closeModal(id); return; } } document.addEventListener('click', function(event){ const target=event.target; if (!(target instanceof HTMLElement) || !target.classList.contains('modal')) return; const id=target.id; if (!['telegramCodeModal','telegramVerifyModal','emailVerifyModal'].includes(id)) return; if (id === 'telegramVerifyModal') closeTelegramVerifyModal(); else if (id === 'emailVerifyModal') closeEmailVerifyModal(); else closeModal(id); }); document.addEventListener('keydown', function(event){ if (event.key === 'Escape') closeVisibleAuthModal(); }); function handleAuthNotice() { const url=new URL(window.location.href); const notice=url.searchParams.get('auth'); if (!notice) return; const map={ email_verified:[authText('emailVerified'),'success'], email_verified_telegram_pending:[authText('emailTelegramPending'),'info'], email_verification_failed:[authText('emailVerificationFailed'),'error'], google_success:[authText('googleSuccess'),'success'], github_success:[authText('githubSuccess'),'success'], oauth_cancelled:[authText('oauthCancelled'),'info'], oauth_failed:[authText('oauthFailed'),'error'] }; if (map[notice]) showToast(map[notice][0],map[notice][1]); url.searchParams.delete('auth'); window.history.replaceState({},document.title,url.pathname+url.search+url.hash); } document.addEventListener('DOMContentLoaded',handleAuthNotice); </script> <!-- Add this JavaScript before closing body tag --> <script> // Animation on scroll document.addEventListener('DOMContentLoaded', function() { const fadeElements = document.querySelectorAll('.fade-in'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); } }); }, { threshold: 0.1 }); fadeElements.forEach(element => { observer.observe(element); }); // Add click handlers for project CTAs document.querySelectorAll('.project-cta').forEach(button => { button.addEventListener('click', function() { const projectTitle = this.closest('.project-card').querySelector('.project-title').textContent; // In production, this would navigate to the appropriate page }); }); }); </script> <script> // Cookie consent management (function() { // Configuration const COOKIE_CONSENT_KEY = 'serpuls_cookie_consent'; const COOKIE_EXPIRY_DAYS = 365; // Create cookie consent banner if not already accepted function initCookieConsent() { // Check if user has already made a choice const consent = getCookieConsent(); if (consent !== null) { // User already made a choice, apply preferences applyCookiePreferences(consent); return; } // Create banner createCookieBanner(); } // Create the cookie banner HTML function createCookieBanner() { const banner = document.createElement('div'); banner.id = 'cookie-consent-banner'; banner.style.cssText = ` position: fixed; bottom: 0; left: 0; right: 0; background: white; box-shadow: 0 -4px 20px rgba(0,0,0,0.15); padding: 1.5rem; z-index: 9999; border-top: 3px solid var(--primary, #4361ee); animation: slideUp 0.5s ease; `; // Add animation style if not exists if (!document.getElementById('cookie-animation-style')) { const style = document.createElement('style'); style.id = 'cookie-animation-style'; style.textContent = ` @keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } } `; document.head.appendChild(style); } banner.innerHTML = ` <div style="max-width: 1200px; margin: 0 auto; display: flex; flex-wrap: wrap; gap: 2rem; align-items: center; justify-content: space-between;"> <div style="flex: 2; min-width: 280px;"> <h3 style="margin: 0 0 0.5rem 0; color: var(--dark, #212529); font-size: 1.25rem;">🍪 Συναίνεση για cookie</h3> <p style="margin: 0; color: var(--gray, #6c757d); line-height: 1.5;"> Χρησιμοποιούμε cookies για να βελτιώσουμε την εμπειρία περιήγησής σας, να εξυπηρετήσουμε εξατομικευμένο περιεχόμενο και να αναλύσουμε την επισκεψιμότητά μας. Κάνοντας κλικ στην Αποδοχή όλων, συναινείτε στη χρήση των cookies από εμάς. Διαβάστε το δικό μας <a href="/en/cookie/" style="color: var(--primary, #4361ee); text-decoration: underline;">Πολιτική cookie</a> και <a href="/en/privacy/" style="color: var(--primary, #4361ee); text-decoration: underline;">Πολιτική Απορρήτου</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);"> Αποδοχή όλων </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;"> Απαραίτητο μόνο </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;"> Προσαρμογή </button> </div> </div> `; document.body.appendChild(banner); // Add event listeners document.getElementById('cookie-accept-all').addEventListener('click', function() { acceptAllCookies(); }); document.getElementById('cookie-accept-essential').addEventListener('click', function() { acceptEssentialCookies(); }); document.getElementById('cookie-settings').addEventListener('click', function() { showCookieSettings(); }); } // Show cookie settings modal function showCookieSettings() { // Remove existing modal if any const existingModal = document.getElementById('cookie-settings-modal'); if (existingModal) existingModal.remove(); const modal = document.createElement('div'); modal.id = 'cookie-settings-modal'; modal.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 10000; animation: fadeIn 0.3s ease; `; modal.innerHTML = ` <div style="background: white; border-radius: var(--border-radius, 8px); max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; box-shadow: 0 10px 25px rgba(0,0,0,0.2);"> <div style="padding: 1.5rem; border-bottom: 1px solid #e9ecef;"> <h3 style="margin: 0; color: var(--dark, #212529);">Προτιμήσεις cookie</h3> </div> <div style="padding: 1.5rem;"> <p style="color: var(--gray, #6c757d); margin-bottom: 1.5rem;">Επιλέξτε ποια cookies επιτρέπετε. Μπορείτε να αλλάξετε αυτές τις ρυθμίσεις ανά πάσα στιγμή.</p> <div style="margin-bottom: 1rem; padding: 1rem; background: #f8f9fa; border-radius: var(--border-radius, 8px);"> <label style="display: flex; align-items: center; gap: 0.75rem; cursor: not-allowed;"> <input type="checkbox" checked disabled style="width: 18px; height: 18px;"> <div> <strong style="color: var(--dark, #212529);">Βασικά cookies</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Απαιτείται για τη λειτουργία του ιστότοπου. Δεν μπορεί να απενεργοποιηθεί.</p> </div> </label> </div> <div style="margin-bottom: 1rem; padding: 1rem; background: #f8f9fa; border-radius: var(--border-radius, 8px);"> <label style="display: flex; align-items: center; gap: 0.75rem; cursor: pointer;"> <input type="checkbox" id="cookie-pref-analytics" checked style="width: 18px; height: 18px;"> <div> <strong style="color: var(--dark, #212529);">Cookies Analytics</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Βοηθήστε μας να κατανοήσουμε πώς αλληλεπιδρούν οι επισκέπτες με τον ιστότοπό μας.</p> </div> </label> </div> <div style="margin-bottom: 1rem; padding: 1rem; background: #f8f9fa; border-radius: var(--border-radius, 8px);"> <label style="display: flex; align-items: center; gap: 0.75rem; cursor: pointer;"> <input type="checkbox" id="cookie-pref-functional" checked style="width: 18px; height: 18px;"> <div> <strong style="color: var(--dark, #212529);">Λειτουργικά cookies</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Θυμηθείτε τις προτιμήσεις και τις ρυθμίσεις σας.</p> </div> </label> </div> <div style="margin-bottom: 1rem; padding: 1rem; background: #f8f9fa; border-radius: var(--border-radius, 8px);"> <label style="display: flex; align-items: center; gap: 0.75rem; cursor: pointer;"> <input type="checkbox" id="cookie-pref-targeting" checked style="width: 18px; height: 18px;"> <div> <strong style="color: var(--dark, #212529);">Cookies στόχευσης</strong> <p style="margin: 0.25rem 0 0; font-size: 0.9rem; color: var(--gray, #6c757d);">Χρησιμοποιείται για την παροχή σχετικού περιεχομένου και διαφημίσεων.</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;"> Αποθήκευση Προτιμήσεων </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;"> Ματαίωση </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('Σφάλμα ανάλυσης της συγκατάθεσης cookie:', e); } } // Try localStorage const stored = localStorage.getItem(COOKIE_CONSENT_KEY); if (stored) { try { return JSON.parse(stored); } catch (e) { console.error('Σφάλμα ανάλυσης της συναίνεσης localStorage:', e); } } return null; } // Apply cookie preferences (enable/disable features) function applyCookiePreferences(preferences) { if (!preferences) return; // Example: Disable analytics if not consented if (!preferences.analytics) { // Disable Google Analytics if present if (typeof window.gtag !== 'undefined') { window.gtag('consent', 'default', { 'analytics_storage': 'denied' }); } // Optionally remove existing analytics cookies // This is handled by the analytics script itself usually } // Disable targeting cookies if (!preferences.targeting) { // Disable ad-related tracking // Remove Facebook pixel, etc. } // Store preferences in data attribute for other scripts to check document.documentElement.setAttribute('data-cookie-consent', JSON.stringify(preferences)); // Dispatch event for other scripts to react window.dispatchEvent(new CustomEvent('cookieConsentChanged', { detail: preferences })); } // Remove cookie banner function removeCookieBanner() { const banner = document.getElementById('cookie-consent-banner'); if (banner) { banner.style.animation = 'slideDown 0.5s ease forwards'; setTimeout(() => { if (banner.parentNode) banner.remove(); }, 500); // Add slideDown animation if not exists if (!document.getElementById('cookie-animation-slidedown')) { const style = document.createElement('style'); style.id = 'cookie-animation-slidedown'; style.textContent = ` @keyframes slideDown { from { transform: translateY(0); } to { transform: translateY(100%); } } `; document.head.appendChild(style); } } } // Add a floating button to reopen cookie settings (optional) function addCookieSettingsButton() { const btn = document.createElement('button'); btn.id = 'cookie-settings-button'; btn.innerHTML = '🍪'; btn.setAttribute('aria-label', 'Ρυθμίσεις cookie'); 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>