summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-03-26 11:02:48 +0100
committerDanilo M. <danix@danix.xyz>2026-03-26 11:02:48 +0100
commit7aed0eca7e62f6950cab85486d30b8f894f268c0 (patch)
treeec2ff915d3298b8b0d9f470bc08f93fcdd064c69
parentaab13e6564203c9ca435a003f5f2245f0d773c68 (diff)
downloaddanixme-7aed0eca7e62f6950cab85486d30b8f894f268c0.tar.gz
danixme-7aed0eca7e62f6950cab85486d30b8f894f268c0.zip
added new version of the danix.me hugo site, coauthored by claude-code
-rw-r--r--.gitignore24
-rw-r--r--content/en/_index.md17
-rw-r--r--content/it/_index.md17
-rw-r--r--data/certs.yaml34
-rw-r--r--data/contact.yaml23
-rw-r--r--data/education.yaml31
-rw-r--r--data/projects.yaml17
-rw-r--r--data/services.yaml64
-rw-r--r--data/skills.yaml35
-rw-r--r--hugo.toml41
-rw-r--r--static/api/composer.json5
-rw-r--r--static/api/composer.lock101
-rw-r--r--static/api/contact.php180
-rw-r--r--static/files/Danilo Macri - EN.pdfbin0 -> 136020 bytes
-rw-r--r--static/files/Danilo Macri - IT.pdfbin0 -> 115715 bytes
-rw-r--r--static/img/Danilo_profile.jpgbin0 -> 349152 bytes
-rw-r--r--static/img/certs/ejpt_badge.pngbin0 -> 44680 bytes
-rw-r--r--static/img/fav.pngbin0 -> 45033 bytes
-rw-r--r--static/img/lampD.pngbin0 -> 45033 bytes
-rw-r--r--static/img/linkedin-cover.jpgbin0 -> 99066 bytes
-rw-r--r--static/img/og-cover.jpgbin0 -> 72486 bytes
-rw-r--r--static/img/projects/passgen_h.jpgbin0 -> 192030 bytes
-rw-r--r--static/img/projects/sl-hack-ware.jpgbin0 -> 603288 bytes
-rw-r--r--themes/danixme/assets/css/main.css1750
-rw-r--r--themes/danixme/assets/js/main.js374
-rw-r--r--themes/danixme/i18n/en.toml149
-rw-r--r--themes/danixme/i18n/it.toml149
-rw-r--r--themes/danixme/layouts/_default/baseof.html11
-rw-r--r--themes/danixme/layouts/index.html10
-rw-r--r--themes/danixme/layouts/partials/about.html37
-rw-r--r--themes/danixme/layouts/partials/certs.html67
-rw-r--r--themes/danixme/layouts/partials/contact.html49
-rw-r--r--themes/danixme/layouts/partials/education.html28
-rw-r--r--themes/danixme/layouts/partials/footer.html22
-rw-r--r--themes/danixme/layouts/partials/head.html77
-rw-r--r--themes/danixme/layouts/partials/hero.html60
-rw-r--r--themes/danixme/layouts/partials/nav.html45
-rw-r--r--themes/danixme/layouts/partials/projects.html93
-rw-r--r--themes/danixme/layouts/partials/scripts.html11
-rw-r--r--themes/danixme/layouts/partials/services.html24
-rw-r--r--themes/danixme/layouts/partials/skills.html24
-rw-r--r--themes/danixme/layouts/robots.txt4
-rw-r--r--themes/danixme/theme.toml5
43 files changed, 3578 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 364fdec..8ec54af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,25 @@
+# Hugo build output
public/
+resources/
+.hugo_build.lock
+
+# PHP credentials — never commit
+mail-config.php
+
+# PHP dependencies — restore with: composer install
+static/api/vendor/
+
+# Archives
+*.tar.gz
+*.zip
+
+# Claude Code local settings
+.claude/
+
+# OS / editor
+.DS_Store
+Thumbs.db
+.idea/
+.vscode/
+*.swp
+*~
diff --git a/content/en/_index.md b/content/en/_index.md
new file mode 100644
index 0000000..eba8930
--- /dev/null
+++ b/content/en/_index.md
@@ -0,0 +1,17 @@
+---
+tagline: "I bridge the gap between complex technical vulnerabilities and actionable business security. From deep-tissue network assessments to infrastructure hardening, I help you secure what matters most - without the jargon."
+bio: |-
+ I am a Cybersecurity Specialist and certified eJPT pentester with a unique mission: making complex security simple.
+
+ With over 20 years of experience as a Linux power user, I help companies evaluate their security posture across networks, apps, and systems.
+
+ My background in high-pressure service management taught me that the best technical solution is useless if it isn’t communicated clearly. Whether I’m hardening a LAMP server or managing my Proxmox home lab, I focus on delivering actionable insights for technical and non-technical stakeholders alike.
+typed_phrases:
+ - "Cybersecurity Specialist"
+ - "Penetration Tester"
+ - "eJPT Certified"
+ - "Ethical Hacker"
+about_photo: "/img/Danilo_profile.jpg"
+description: "eJPT-certified Cybersecurity Specialist offering penetration testing, infrastructure hardening, and security awareness training in Italy and remotely."
+contact_sub: "Whether you have an opportunity, a security challenge, or just want to talk — my inbox is open."
+---
diff --git a/content/it/_index.md b/content/it/_index.md
new file mode 100644
index 0000000..e42186c
--- /dev/null
+++ b/content/it/_index.md
@@ -0,0 +1,17 @@
+---
+tagline: "Riduco la distanza tra le complessità tecniche della sicurezza e le necessità concrete del business. Dalle analisi profonde dell'infrastruttura di rete al consolidamento dei sistemi, ti aiuto a proteggere ciò che conta di più - senza inutili tecnicismi."
+bio: |-
+ Sono uno Specialista in Cybersecurity e pentester certificato eJPT con una missione precisa: rendere semplici le complessità della sicurezza informatica.
+
+ Con oltre 20 anni di esperienza come ‘power user’ Linux, aiuto le aziende a valutare la propria postura di sicurezza su reti, applicazioni e sistemi.
+
+ Il mio background nel service management ad alte prestazioni mi ha insegnato che la migliore soluzione tecnica è inutile se non viene comunicata in modo chiaro. Che si tratti di blindare un server LAMP o di gestire il mio home lab su Proxmox, il mio obiettivo è sempre fornire indicazioni concrete e attuabili, tanto per i team tecnici quanto per gli stakeholder non tecnici
+typed_phrases:
+ - "Specialista in Cybersecurity"
+ - "Penetration Tester"
+ - "Certificato eJPT"
+ - "Ethical Hacker"
+about_photo: "/img/Danilo_profile.jpg"
+description: "Specialista in Cybersecurity certificato eJPT. Penetration testing, hardening di infrastrutture e formazione sulla sicurezza in Italia e da remoto."
+contact_sub: "Che tu abbia un'opportunità, una sfida di sicurezza o voglia semplicemente parlare — sono sempre disponibile."
+---
diff --git a/data/certs.yaml b/data/certs.yaml
new file mode 100644
index 0000000..aade9c7
--- /dev/null
+++ b/data/certs.yaml
@@ -0,0 +1,34 @@
+- badge: "fa-solid fa-shield-halved"
+ name: "BSCP"
+ issuer: "PortSwigger"
+ desc_en: "Burp Suite Certified Professional — advanced web application penetration testing certification covering complex vulnerabilities and expert-level Burp Suite usage."
+ desc_it: "Burp Suite Certified Professional — certificazione avanzata di penetration testing su applicazioni web, con focus su vulnerabilità complesse e uso esperto di Burp Suite."
+ issuer_val: "PortSwigger"
+ level_en: "Advanced"
+ level_it: "Avanzato"
+ type_en: "Practical"
+ type_it: "Pratica"
+ in_progress: true
+ placeholder: false
+
+- image: "/img/certs/ejpt_badge.png"
+ name: "eJPT"
+ issuer: "eLearnSecurity · INE"
+ desc_en: "eLearnSecurity Junior Penetration Tester — entry-level certification validating practical penetration testing skills: network analysis, exploitation, and professional reporting."
+ desc_it: "eLearnSecurity Junior Penetration Tester — certificazione entry-level che valida competenze pratiche di penetration testing: analisi di rete, sfruttamento di vulnerabilità e redazione di report professionali."
+ issuer_val: "INE / eLS"
+ level_en: "Entry"
+ level_it: "Base"
+ type_en: "Practical"
+ type_it: "Pratica"
+ date: "03/2026"
+ placeholder: false
+
+- badge: "fa-solid fa-plus"
+ name_en: "Next Cert"
+ name_it: "Prossima Cert."
+ issuer_en: "In Progress…"
+ issuer_it: "In corso…"
+ desc_en: "Always learning, always leveling up. New certifications on the horizon."
+ desc_it: "In costante formazione e crescita. Nuove certificazioni all'orizzonte."
+ placeholder: true
diff --git a/data/contact.yaml b/data/contact.yaml
new file mode 100644
index 0000000..d20756e
--- /dev/null
+++ b/data/contact.yaml
@@ -0,0 +1,23 @@
+- icon: "fa-solid fa-envelope"
+ label: "Email"
+ email_user: "danilo.macri"
+ email_domain: "danix.me"
+ external: false
+
+- icon: "fa-brands fa-linkedin-in"
+ label: "LinkedIn"
+ value: "linkedin.com/in/danilo-macri-aka-danix"
+ href: "https://linkedin.com/in/danilo-macri-aka-danix"
+ external: true
+
+- icon: "fa-brands fa-github"
+ label: "GitHub"
+ value: "github.com/danixland"
+ href: "https://github.com/danixland"
+ external: true
+
+- icon: "fa-solid fa-flag"
+ label: "hackthebox"
+ value: "danix on HTB"
+ href: "https://profile.hackthebox.com/profile/019c9acf-058e-71a2-8680-bcee49bb3c31"
+ external: true
diff --git a/data/education.yaml b/data/education.yaml
new file mode 100644
index 0000000..7b0d38a
--- /dev/null
+++ b/data/education.yaml
@@ -0,0 +1,31 @@
+- period: "06/2025 – 01/2026"
+ degree_en: "Master CyberSecurity Specialist"
+ degree_it: "Master CyberSecurity Specialist"
+ institution_en: "Epicode School of Technology"
+ institution_it: "Epicode School of Technology"
+ desc_en: |-
+ Highly technical Professional Master's (250+ hours) specializing in dual-stack cybersecurity. I gained extensive experience in offensive security through system and web app penetration testing, while also mastering defensive operations by designing SOC workflows for vulnerability remediation and patch management.
+ - Offensive Security (**Red Teaming**): Gained specialized expertise in full-stack penetration testing, encompassing systems, complex network architectures, and web applications. Developed a methodical approach to identifying vulnerabilities, exploiting weaknesses, and documenting security gaps.
+ - Defensive Operations (**Blue Teaming**): Engineered and managed Security Operations Center (SOC) environments to monitor and defend critical infrastructure. Gained proficiency in vulnerability lifecycle management, including the deployment of patches and the implementation of robust mitigation strategies for both legacy systems and modern web applications.
+ desc_it: |-
+ Master Professionale ad Alto Contenuto Tecnico (250+ ore) con specializzazione in Cybersecurity Dual-Stack.
+
+ Ho maturato un'esperienza approfondita nella Offensive Security attraverso penetration test su sistemi e web app, consolidando al contempo le competenze in Defensive Operations tramite la progettazione di workflow SOC per la remediation delle vulnerabilità e il patch management.
+ - Offensive Security (**Red Team**): Competenza specialistica in penetration testing full-stack su sistemi, architetture di rete complesse e applicazioni web. Ho sviluppato un approccio metodico per identificare vulnerabilità, testare i punti deboli e documentare le falle di sicurezza.
+ - Defensive Operations (**Blue Team**): Progettazione e gestione di ambienti Security Operations Center (SOC) per il monitoraggio e la difesa di infrastrutture critiche. Esperienza avanzata nella gestione del ciclo di vita delle vulnerabilità, dall'implementazione delle patch alle strategie di mitigazione per sistemi legacy e moderne applicazioni web.
+ current: true
+
+- period: "2004"
+ degree_en: "High School Diploma"
+ degree_it: "Diploma di Maturità"
+ institution_en: "IIS \"Umberto Zanotti Bianco\" — Marina di Gioiosa Jonica (RC)"
+ institution_it: "IIS \"Umberto Zanotti Bianco\" — Marina di Gioiosa Jonica (RC)"
+ desc_en: |
+ Subject: Tourism
+
+ Grade: 77/100
+ desc_it: |
+ Indirizzo: Turismo
+
+ Voto: 77/100
+ current: false
diff --git a/data/projects.yaml b/data/projects.yaml
new file mode 100644
index 0000000..a88a126
--- /dev/null
+++ b/data/projects.yaml
@@ -0,0 +1,17 @@
+- title: "sl(HACK)ware"
+ subtitle_en: "Open Source pentesting · Slackware"
+ subtitle_it: "Pentesting open source · Slackware"
+ image: "/img/projects/sl-hack-ware.jpg"
+ desc_en: "Open Source pentesting suite of programs packaged for Slackware GNU/Linux."
+ desc_it: "Suite open source di programmi per pentesting pacchettizzati per Slackware GNU/Linux."
+ url: "https://github.com/danixland/Slackware-Pentesting-Suite"
+ tags: [Slackware, Pentesting, "Open Source", Linux]
+
+- title: "Password Generator"
+ subtitle_en: "Python · GNU/Linux"
+ subtitle_it: "Python · GNU/Linux"
+ image: "/img/projects/passgen_h.jpg"
+ desc_en: "A Python script that generates strong, easy to memorize passwords using random words from a dictionary file."
+ desc_it: "Uno script Python che genera password forti e facili da memorizzare usando parole casuali da un file dizionario."
+ url: "https://github.com/danixland/passgen"
+ tags: [Python, "GNU/Linux", Security]
diff --git a/data/services.yaml b/data/services.yaml
new file mode 100644
index 0000000..533b5d2
--- /dev/null
+++ b/data/services.yaml
@@ -0,0 +1,64 @@
+- icon: "fa-solid fa-bug"
+ title_en: "Vulnerabilities"
+ title_it: "Vulnerabilità"
+ subtitle_en: "Assessments & Penetration Testing"
+ subtitle_it: "Assessment & Penetration Testing"
+ desc_en: |-
+ A stress test for your digital infrastructure using eJPT methodology to identify security gaps before they are exploited.
+ - **Network Security**: Evaluating internal and external network entry points.
+ - **Web Application Testing**: Specialized assessments for custom apps, LAMP stacks, and WordPress environments.
+ - **Actionable Reporting**: You won't just get a list of bugs; you’ll get a roadmap for remediation tailored to your technical (or non-technical) team.
+
+ desc_it: |-
+ Uno stress test per la tua infrastruttura digitale usando la metodologia eJPT per identificare falle di sicurezza prima che vengano sfruttate.
+ - **Network Security**: Valutazione dei punti di accesso alla rete, sia interni che esterni.
+ - **Web Application Testing**: Assessment specializzati per applicazioni custom, stack LAMP e ambienti WordPress.
+ - **Reportistica Applicabile**: Non riceverai un semplice elenco di bug, ma una vera tabella di marcia per la risoluzione dei problemi, pensata su misura per il tuo team (tecnico o meno).
+
+- icon: "fa-solid fa-shield"
+ title_en: "Hardening"
+ title_it: "Hardening"
+ subtitle_en: "Network, Systems & Website Hardening"
+ subtitle_it: "Hardening di Reti, Sistemi e Siti Web"
+ desc_en: |-
+ Leverages 20+ years of Linux expertise to move organizations from default configurations to solid defensive postures.
+ - **Server Security**: Auditing and securing LAMP stacks on Slackware/Debian/Ubuntu/RHEL environments.
+ - **Container & Virtualization Security**: Securing Proxmox environments and Dockerized workflows to ensure isolation and least-privilege access.
+ - **System Automation**: Custom Python and Bash scripting to automate security monitoring and routine maintenance.
+
+ desc_it: |-
+ Sfrutta oltre 20 anni di esperienza con Linux per portare le organizzazioni da configurazioni predefinite a posture difensive solide.
+ - **Server Security**: Audit e messa in sicurezza di stack LAMP in ambienti Slackware, Debian, Ubuntu e RHEL.
+ - **Container & Virtualization Security**: Protezione di ambienti Proxmox e workflow Dockerizzati per garantire isolamento e accesso secondo il principio del *least-privilege*.
+ - **System Automation**: Scripting personalizzato in Python e Bash per automatizzare il monitoraggio della sicurezza e la manutenzione ordinaria.
+
+- icon: "fa-solid fa-chalkboard-user"
+ title_en: "Human Firewall"
+ title_it: "Firewall Umano"
+ subtitle_en: "Security Awareness Training"
+ subtitle_it: "Formazione sulla Sicurezza"
+ desc_en: |-
+ Uses management background to translate technical concepts into practical staff training.
+ - **Executive Briefings**: Explaining high-level risks and ROI on security investments to stakeholders.
+ - **Staff Workshops**: Teaching non-technical teams how to spot phishing, manage passwords (using mnemonic techniques), and maintain "security hygiene."
+
+ desc_it: |-
+ Sfrutta l'esperienza manageriale per tradurre concetti tecnici in formazione pratica per il personale.
+ - **Executive Briefing**: Spiego agli stakeholder i rischi di alto livello e il ROI (ritorno sull'investimento) delle soluzioni di sicurezza.
+ - **Workshop per lo Staff**: Insegno ai team non tecnici come riconoscere il phishing, gestire le password (anche tramite tecniche mnemoniche) e mantenere una corretta 'igiene digitale'.
+
+- icon: "fa-solid fa-clipboard-check"
+ title_en: "Security Check-ups"
+ title_it: "Check-up di Sicurezza"
+ subtitle_en: "Small Business Security Assessments"
+ subtitle_it: "Assessment per Piccole Imprese"
+ desc_en: |-
+ Tailored for SMEs without dedicated IT departments.
+ - **Security Posture Review**: A holistic look at your current tools, backup strategies, and password policies.
+ - **WordPress Hardening**: Specialized security audits for WordPress sites, including plugin/theme code reviews to prevent SQL injections and XSS attacks.
+
+ desc_it: |-
+ Pensato per le PMI senza un reparto IT dedicato.
+ - **Security Posture Review**: Un'analisi olistica dei tuoi strumenti attuali, delle strategie di backup e delle policy di gestione delle password.
+ - **WordPress Hardening**: Audit di sicurezza specializzati per siti WordPress, incluse revisioni del codice di plugin e temi per prevenire attacchi SQL injection e XSS.
+
diff --git a/data/skills.yaml b/data/skills.yaml
new file mode 100644
index 0000000..cbb7b75
--- /dev/null
+++ b/data/skills.yaml
@@ -0,0 +1,35 @@
+- icon: "fa-solid fa-crosshairs"
+ name_en: "Penetration Testing"
+ name_it: "Penetration Testing"
+ level: 0.75
+ tags: [Metasploit, "Burp Suite", nmap, SQLMap]
+
+- icon: "fa-solid fa-network-wired"
+ name_en: "Network Security"
+ name_it: "Sicurezza di Rete"
+ level: 0.68
+ tags: [Wireshark, "TCP/IP", Firewalls, VPN]
+
+- icon: "fa-brands fa-linux"
+ name_en: "Linux / Kali"
+ name_it: "Linux / Kali"
+ level: 0.95
+ tags: ["Kali Linux", Bash, "Priv. Esc.", "File System"]
+
+- icon: "fa-solid fa-user-secret"
+ name_en: "OSINT"
+ name_it: "OSINT"
+ level: 0.62
+ tags: [Maltego, Shodan, Recon-ng]
+
+- icon: "fa-solid fa-terminal"
+ name_en: "Scripting"
+ name_it: "Scripting"
+ level: 0.85
+ tags: [Python, Bash, PowerShell]
+
+- icon: "fa-solid fa-shield-halved"
+ name_en: "Web Security"
+ name_it: "Sicurezza Web"
+ level: 0.77
+ tags: ["OWASP Top 10", XSS, SQLi, IDOR]
diff --git a/hugo.toml b/hugo.toml
new file mode 100644
index 0000000..773b000
--- /dev/null
+++ b/hugo.toml
@@ -0,0 +1,41 @@
+baseURL = "https://danix.me/"
+title = "Danilo Macrì — Cybersecurity Specialist"
+theme = "danixme"
+enableRobotsTXT = true
+
+[minify]
+ minifyOutput = true
+
+defaultContentLanguage = "en"
+defaultContentLanguageInSubdir = false
+
+[params]
+ name = "Danilo Macrì"
+ handle = "danix"
+ location = "Italy"
+ cert = "eJPT"
+ focus = "Pen Testing"
+ favicon = "/img/fav.png" # path from static/ — supports .png, .ico, .svg
+ logo = "/img/lampD.png" # navbar logo image (leave empty to show text only)
+ description = "Cybersecurity Specialist and eJPT-certified Penetration Tester helping companies secure networks, apps, and systems."
+ og_image = "/img/og-cover.jpg" # 1200×630 social sharing image — create this file
+
+[languages]
+ [languages.en]
+ languageName = "EN"
+ weight = 1
+ contentDir = "content/en"
+ title = "Danilo Macrì — Cybersecurity Specialist"
+ [languages.en.params]
+ flag = "gb"
+ cv = "/files/Danilo Macri - EN.pdf"
+ og_locale = "en_GB"
+ [languages.it]
+ languageName = "IT"
+ weight = 2
+ contentDir = "content/it"
+ title = "Danilo Macrì — Specialista in Cybersecurity"
+ [languages.it.params]
+ flag = "it"
+ cv = "/files/Danilo Macri - IT.pdf"
+ og_locale = "it_IT"
diff --git a/static/api/composer.json b/static/api/composer.json
new file mode 100644
index 0000000..078861f
--- /dev/null
+++ b/static/api/composer.json
@@ -0,0 +1,5 @@
+{
+ "require": {
+ "phpmailer/phpmailer": "^7.0"
+ }
+}
diff --git a/static/api/composer.lock b/static/api/composer.lock
new file mode 100644
index 0000000..447fe18
--- /dev/null
+++ b/static/api/composer.lock
@@ -0,0 +1,101 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "826f515f5ef16946d3e3ee3e3205b25e",
+ "packages": [
+ {
+ "name": "phpmailer/phpmailer",
+ "version": "v7.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPMailer/PHPMailer.git",
+ "reference": "ebf1655bd5b99b3f97e1a3ec0a69e5f4cd7ea088"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/ebf1655bd5b99b3f97e1a3ec0a69e5f4cd7ea088",
+ "reference": "ebf1655bd5b99b3f97e1a3ec0a69e5f4cd7ea088",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-filter": "*",
+ "ext-hash": "*",
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "doctrine/annotations": "^1.2.6 || ^1.13.3",
+ "php-parallel-lint/php-console-highlighter": "^1.0.0",
+ "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "phpcompatibility/php-compatibility": "^10.0.0@dev",
+ "squizlabs/php_codesniffer": "^3.13.5",
+ "yoast/phpunit-polyfills": "^1.0.4"
+ },
+ "suggest": {
+ "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
+ "directorytree/imapengine": "For uploading sent messages via IMAP, see gmail example",
+ "ext-imap": "Needed to support advanced email address parsing according to RFC822",
+ "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
+ "ext-openssl": "Needed for secure SMTP sending and DKIM signing",
+ "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
+ "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
+ "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
+ "psr/log": "For optional PSR-3 debug logging",
+ "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
+ "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PHPMailer\\PHPMailer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-2.1-only"
+ ],
+ "authors": [
+ {
+ "name": "Marcus Bointon",
+ "email": "phpmailer@synchromedia.co.uk"
+ },
+ {
+ "name": "Jim Jagielski",
+ "email": "jimjag@gmail.com"
+ },
+ {
+ "name": "Andy Prevost",
+ "email": "codeworxtech@users.sourceforge.net"
+ },
+ {
+ "name": "Brent R. Matzelle"
+ }
+ ],
+ "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+ "support": {
+ "issues": "https://github.com/PHPMailer/PHPMailer/issues",
+ "source": "https://github.com/PHPMailer/PHPMailer/tree/v7.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Synchro",
+ "type": "github"
+ }
+ ],
+ "time": "2026-01-09T18:02:33+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {},
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {},
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
+}
diff --git a/static/api/contact.php b/static/api/contact.php
new file mode 100644
index 0000000..4689b99
--- /dev/null
+++ b/static/api/contact.php
@@ -0,0 +1,180 @@
+<?php
+/**
+ * Contact form handler for danix.me
+ *
+ * Spam protection:
+ * 1. Honeypot field — bots fill it, humans don't see it
+ * 2. Timing check — reject submissions faster than MIN_ELAPSED seconds
+ * 3. IP rate limit — one submission per IP per RATE_WINDOW seconds
+ */
+
+ob_start();
+header('Content-Type: application/json; charset=utf-8');
+header('X-Content-Type-Options: nosniff');
+header('X-Frame-Options: DENY');
+header('Referrer-Policy: strict-origin-when-cross-origin');
+header('Cache-Control: no-store');
+
+// ── Configuration ─────────────────────────────────────────────────────────────
+// Credentials are read from server environment variables — never hardcoded here.
+// Set them in your nginx fastcgi_param, Apache SetEnv, or hosting panel.
+const SENDER_DOMAIN = 'danix.xyz'; // used in From name and subject prefix
+const ALLOWED_ORIGIN = 'https://danix.me'; // CSRF: only accept from this origin
+const MIN_ELAPSED = 4; // seconds before submission is accepted
+const RATE_WINDOW = 300; // seconds between allowed submissions per IP
+const RATE_DIR = '/var/lib/danixme/cf_rate'; // owned by www-data:www-data, mode 0700
+
+// $cfg = [
+// 'recipient' => getenv('MAIL_RECIPIENT') ?: '',
+// 'smtp_host' => getenv('MAIL_SMTP_HOST') ?: '',
+// 'smtp_port' => (int)(getenv('MAIL_SMTP_PORT') ?: 587),
+// 'smtp_secure' => getenv('MAIL_SMTP_SECURE') ?: 'tls',
+// 'smtp_user' => getenv('MAIL_SMTP_USER') ?: '',
+// 'smtp_pass' => getenv('MAIL_SMTP_PASS') ?: '',
+// ];
+
+$cfg = require dirname(__DIR__, 2) . '/mail-config.php';
+
+if ($cfg['recipient'] === '' || $cfg['smtp_host'] === '' || $cfg['smtp_user'] === '') {
+ out(500, 'Mail not configured');
+}
+// ──────────────────────────────────────────────────────────────────────────────
+
+// Only accept POST
+if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+ out(405, 'Method not allowed');
+}
+
+// ── CSRF: verify request originates from our own domain ───────────────────────
+$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
+$referer = $_SERVER['HTTP_REFERER'] ?? '';
+
+if ($origin !== '') {
+ if ($origin !== ALLOWED_ORIGIN) {
+ out(403, 'Forbidden');
+ }
+} elseif (strpos($referer, ALLOWED_ORIGIN . '/') !== 0) {
+ out(403, 'Forbidden');
+}
+// ─────────────────────────────────────────────────────────────────────────────
+
+// ── Honeypot ──────────────────────────────────────────────────────────────────
+// If the hidden "website" field is filled, it's a bot — silently pretend success.
+if (!empty($_POST['website'])) {
+ out(200, null, true);
+}
+
+// ── Timing check ──────────────────────────────────────────────────────────────
+$loadedAt = (int) filter_input(INPUT_POST, '_t', FILTER_SANITIZE_NUMBER_INT);
+$elapsed = time() - $loadedAt;
+
+if ($loadedAt === 0 || $elapsed < MIN_ELAPSED || $elapsed > 7200) {
+ out(400, 'Invalid submission');
+}
+
+// ── Rate limiting ─────────────────────────────────────────────────────────────
+$ip = preg_replace('/[^a-f0-9:.]/', '', $_SERVER['REMOTE_ADDR'] ?? '');
+$lockDir = rtrim(RATE_DIR, '/');
+
+if (!is_dir($lockDir)) {
+ @mkdir($lockDir, 0700, true);
+}
+
+$lockFile = $lockDir . '/' . md5($ip) . '.lock';
+
+// Atomic check+write using exclusive lock — prevents TOCTOU race condition
+$fp = fopen($lockFile, 'c+');
+if ($fp === false || !flock($fp, LOCK_EX)) {
+ out(500, 'Server error');
+}
+$lastTime = (int) fread($fp, 20);
+if ($lastTime > 0 && (time() - $lastTime) < RATE_WINDOW) {
+ flock($fp, LOCK_UN);
+ fclose($fp);
+ out(429, 'Too many requests');
+}
+
+// ── Sanitize & validate ───────────────────────────────────────────────────────
+$name = clean(INPUT_POST, 'name', 100);
+$email = trim(filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL) ?? '');
+$subject = clean(INPUT_POST, 'subject', 200);
+$message = clean(INPUT_POST, 'message', 5000);
+
+if ($name === '' || $email === '' || $message === '') {
+ flock($fp, LOCK_UN); fclose($fp);
+ out(400, 'Missing required fields');
+}
+
+if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ flock($fp, LOCK_UN); fclose($fp);
+ out(400, 'Invalid email address');
+}
+
+// ── Build and send the email via PHPMailer ────────────────────────────────────
+require __DIR__ . '/vendor/autoload.php';
+
+use PHPMailer\PHPMailer\PHPMailer;
+use PHPMailer\PHPMailer\SMTP;
+use PHPMailer\PHPMailer\Exception as MailerException;
+
+$subjectLine = '[contact from danix.me] - ' . ($subject !== '' ? $subject : 'New message from ' . $name);
+
+$body = implode("\n", [
+ 'Name: ' . $name,
+ 'Email: ' . $email,
+ 'Subject: ' . ($subject ?: '—'),
+ '',
+ str_repeat('─', 48),
+ '',
+ $message,
+]);
+
+$mail = new PHPMailer(true);
+
+try {
+ $mail->isSMTP();
+ $mail->Host = $cfg['smtp_host'];
+ $mail->SMTPAuth = true;
+ $mail->Username = $cfg['smtp_user'];
+ $mail->Password = $cfg['smtp_pass'];
+ $mail->SMTPSecure = $cfg['smtp_secure'] === 'ssl' ? PHPMailer::ENCRYPTION_SMTPS : PHPMailer::ENCRYPTION_STARTTLS;
+ $mail->Port = $cfg['smtp_port'];
+ $mail->CharSet = 'UTF-8';
+
+ $mail->setFrom('danix@' . SENDER_DOMAIN, SENDER_DOMAIN);
+ $mail->addReplyTo($email, $name);
+ $mail->addAddress($cfg['recipient']);
+
+ $mail->Subject = $subjectLine;
+ $mail->Body = $body;
+
+ $mail->send();
+
+ rewind($fp);
+ ftruncate($fp, 0);
+ fwrite($fp, (string) time());
+ flock($fp, LOCK_UN);
+ fclose($fp);
+ out(200, null, true);
+} catch (MailerException $e) {
+ flock($fp, LOCK_UN);
+ fclose($fp);
+ out(500, 'Failed to send email');
+}
+
+// ── Helpers ───────────────────────────────────────────────────────────────────
+
+function clean(int $type, string $key, int $maxLen): string
+{
+ $val = filter_input($type, $key, FILTER_UNSAFE_RAW) ?? '';
+ $val = strip_tags(trim((string) $val));
+ return mb_substr($val, 0, $maxLen);
+}
+
+function out(int $code, ?string $error, bool $success = false): void
+{
+ ob_end_clean();
+ http_response_code($code);
+ echo json_encode(['success' => $success, 'error' => $error]);
+ exit;
+}
diff --git a/static/files/Danilo Macri - EN.pdf b/static/files/Danilo Macri - EN.pdf
new file mode 100644
index 0000000..a631de9
--- /dev/null
+++ b/static/files/Danilo Macri - EN.pdf
Binary files differ
diff --git a/static/files/Danilo Macri - IT.pdf b/static/files/Danilo Macri - IT.pdf
new file mode 100644
index 0000000..f231dd1
--- /dev/null
+++ b/static/files/Danilo Macri - IT.pdf
Binary files differ
diff --git a/static/img/Danilo_profile.jpg b/static/img/Danilo_profile.jpg
new file mode 100644
index 0000000..34c644e
--- /dev/null
+++ b/static/img/Danilo_profile.jpg
Binary files differ
diff --git a/static/img/certs/ejpt_badge.png b/static/img/certs/ejpt_badge.png
new file mode 100644
index 0000000..2fabcda
--- /dev/null
+++ b/static/img/certs/ejpt_badge.png
Binary files differ
diff --git a/static/img/fav.png b/static/img/fav.png
new file mode 100644
index 0000000..d348983
--- /dev/null
+++ b/static/img/fav.png
Binary files differ
diff --git a/static/img/lampD.png b/static/img/lampD.png
new file mode 100644
index 0000000..d348983
--- /dev/null
+++ b/static/img/lampD.png
Binary files differ
diff --git a/static/img/linkedin-cover.jpg b/static/img/linkedin-cover.jpg
new file mode 100644
index 0000000..332031a
--- /dev/null
+++ b/static/img/linkedin-cover.jpg
Binary files differ
diff --git a/static/img/og-cover.jpg b/static/img/og-cover.jpg
new file mode 100644
index 0000000..61c89cd
--- /dev/null
+++ b/static/img/og-cover.jpg
Binary files differ
diff --git a/static/img/projects/passgen_h.jpg b/static/img/projects/passgen_h.jpg
new file mode 100644
index 0000000..7829d3c
--- /dev/null
+++ b/static/img/projects/passgen_h.jpg
Binary files differ
diff --git a/static/img/projects/sl-hack-ware.jpg b/static/img/projects/sl-hack-ware.jpg
new file mode 100644
index 0000000..e24b7f7
--- /dev/null
+++ b/static/img/projects/sl-hack-ware.jpg
Binary files differ
diff --git a/themes/danixme/assets/css/main.css b/themes/danixme/assets/css/main.css
new file mode 100644
index 0000000..649adae
--- /dev/null
+++ b/themes/danixme/assets/css/main.css
@@ -0,0 +1,1750 @@
+/* ── Reset & Variables ──────────────────────────── */
+*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
+
+:root {
+ --bg: #060b10;
+ --bg2: #0c1520;
+ --surface: #101e2d;
+ --border: #182840;
+ --accent: #a855f7;
+ --accent2: #00ff88;
+ --accent-glow: rgba(168, 85, 247, 0.12);
+ --text: #c4d6e8;
+ --text-dim: #7a9bb8;
+ --muted: #304860;
+ --font-mono: 'JetBrains Mono', 'Courier New', monospace;
+ --font-head: 'Oxanium', sans-serif;
+}
+
+html { scroll-behavior: smooth; font-size: 17px; overflow-x: hidden; }
+
+body {
+ background-color: var(--bg);
+ color: var(--text);
+ font-family: var(--font-mono);
+ font-size: 1rem;
+ line-height: 1.8;
+ overflow-x: hidden;
+ -webkit-font-smoothing: antialiased;
+ overflow-wrap: break-word;
+}
+
+/* Dot grid */
+body::before {
+ content: '';
+ position: fixed;
+ inset: 0;
+ background-image: radial-gradient(circle, rgba(168,85,247,0.07) 1px, transparent 1px);
+ background-size: 30px 30px;
+ pointer-events: none;
+ z-index: 0;
+}
+
+/* ── Light Theme ────────────────────────────────── */
+/* Class applied by inline script before CSS renders — no flash. */
+/* Media query block is a no-JS fallback only. */
+
+html.theme-light {
+ --bg: #f0f4f8;
+ --bg2: #e2eaf4;
+ --surface: #d4dff0;
+ --border: #a8bdd8;
+ --accent: #7c3aed;
+ --accent2: #008f5a;
+ --accent-glow: rgba(124, 58, 237, 0.1);
+ --text: #0d1b2a;
+ --text-dim: #2e4a6a;
+ --muted: #6888a8;
+}
+html.theme-light body::before {
+ background-image: radial-gradient(circle, rgba(124,58,237,0.07) 1px, transparent 1px);
+}
+html.theme-light nav {
+ background: rgba(240, 244, 248, 0.92);
+ border-bottom-color: rgba(168, 189, 216, 0.6);
+}
+html.theme-light #matrix-canvas { opacity: 0.18; }
+html.theme-light #hero::after {
+ background: radial-gradient(ellipse, rgba(124,58,237,0.06) 0%, transparent 65%);
+}
+html.theme-light .hero-name {
+ text-shadow: 0 0 80px rgba(124,58,237,0.12), 0 0 120px rgba(124,58,237,0.05);
+}
+html.theme-light .hero-term {
+ background: rgba(10, 20, 35, 0.95);
+ border-color: rgba(124, 58, 237, 0.35);
+ box-shadow: 0 0 40px rgba(124,58,237,0.08), inset 0 0 30px rgba(0,0,0,0.5);
+ /* Reset colour variables to dark values so text is legible on dark bg */
+ --text: #c4d6e8;
+ --text-dim: #7a9bb8;
+ --muted: #304860;
+ --accent: #a855f7;
+ --accent2: #00ff88;
+ /* Re-apply color so it resolves against the local --text above */
+ color: var(--text);
+}
+html.theme-light #scroll-progress {
+ box-shadow: 0 0 8px rgba(124,58,237,0.45);
+}
+
+/* No-JS fallback via media query */
+@media (prefers-color-scheme: light) {
+ :root {
+ --bg: #f0f4f8;
+ --bg2: #e2eaf4;
+ --surface: #d4dff0;
+ --border: #a8bdd8;
+ --accent: #7c3aed;
+ --accent2: #008f5a;
+ --accent-glow: rgba(124, 58, 237, 0.1);
+ --text: #0d1b2a;
+ --text-dim: #2e4a6a;
+ --muted: #6888a8;
+ }
+ body::before {
+ background-image: radial-gradient(circle, rgba(124,58,237,0.07) 1px, transparent 1px);
+ }
+ nav { background: rgba(240,244,248,0.92); border-bottom-color: rgba(168,189,216,0.6); }
+ #matrix-canvas { opacity: 0.18; }
+ #hero::after { background: radial-gradient(ellipse, rgba(124,58,237,0.06) 0%, transparent 65%); }
+ .hero-name { text-shadow: 0 0 80px rgba(124,58,237,0.12), 0 0 120px rgba(124,58,237,0.05); }
+ .hero-term { background: rgba(10,20,35,0.95); border-color: rgba(124,58,237,0.35); box-shadow: 0 0 40px rgba(124,58,237,0.08), inset 0 0 30px rgba(0,0,0,0.5); --text: #c4d6e8; --text-dim: #7a9bb8; --muted: #304860; --accent: #a855f7; --accent2: #00ff88; color: var(--text); }
+ #scroll-progress { box-shadow: 0 0 8px rgba(124,58,237,0.45); }
+}
+
+/* Force dark even when system preference is light */
+html.theme-dark {
+ --bg: #060b10;
+ --bg2: #0c1520;
+ --surface: #101e2d;
+ --border: #182840;
+ --accent: #a855f7;
+ --accent2: #00ff88;
+ --accent-glow: rgba(168, 85, 247, 0.12);
+ --text: #c4d6e8;
+ --text-dim: #7a9bb8;
+ --muted: #304860;
+}
+html.theme-dark nav {
+ background: rgba(6,11,16,0.88);
+ border-bottom-color: rgba(24,40,64,0.5);
+}
+html.theme-dark body::before {
+ background-image: radial-gradient(circle, rgba(168,85,247,0.07) 1px, transparent 1px);
+}
+html.theme-dark #matrix-canvas { opacity: 0.13; }
+html.theme-dark #hero::after {
+ background: radial-gradient(ellipse, rgba(168,85,247,0.07) 0%, transparent 65%);
+}
+html.theme-dark .hero-name {
+ text-shadow: 0 0 80px rgba(168,85,247,0.18), 0 0 120px rgba(168,85,247,0.08);
+}
+html.theme-dark .hero-term {
+ background: rgba(6, 11, 16, 0.85);
+ border-color: rgba(168,85,247,0.3);
+ box-shadow: 0 0 40px rgba(168,85,247,0.1), inset 0 0 30px rgba(0,0,0,0.4);
+}
+html.theme-dark #scroll-progress {
+ box-shadow: 0 0 8px rgba(168,85,247,0.6);
+}
+
+/* ── Container ──────────────────────────────────── */
+.container {
+ max-width: 1080px;
+ margin: 0 auto;
+ padding: 0 1.2rem;
+ position: relative;
+ z-index: 1;
+}
+
+@media (min-width: 768px) {
+ .container { padding: 0 2rem; }
+}
+
+/* ── Scroll Progress ─────────────────────────────── */
+#scroll-progress {
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 2px;
+ width: 0%;
+ background: linear-gradient(to right, var(--accent), var(--accent2));
+ box-shadow: 0 0 8px rgba(168,85,247,0.6);
+ z-index: 9999;
+ pointer-events: none;
+}
+
+/* ── Navbar ─────────────────────────────────────── */
+nav {
+ position: fixed;
+ inset-block-start: 0;
+ inset-inline: 0;
+ z-index: 100;
+ padding: 1rem 1.2rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: rgba(6,11,16,0.88);
+ backdrop-filter: blur(14px);
+ border-bottom: 1px solid rgba(24,40,64,0.5);
+ transition: border-color 0.3s;
+}
+
+@media (min-width: 768px) {
+ nav { padding: 1.1rem 2rem; }
+}
+
+.nav-logo {
+ display: flex;
+ align-items: center;
+ gap: 0.55rem;
+ text-decoration: none;
+}
+.nav-logo-img {
+ height: 28px;
+ width: auto;
+ display: block;
+ flex-shrink: 0;
+}
+.nav-logo-text {
+ font-family: var(--font-head);
+ font-size: 1.05rem;
+ font-weight: 800;
+ color: var(--accent);
+ letter-spacing: -0.02em;
+}
+.nav-logo-tld { color: var(--muted); font-size: 0.85rem; }
+@media (max-width: 767px) {
+ .nav-logo-text { display: none; }
+}
+
+.nav-right {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+@media (min-width: 768px) {
+ .nav-right { gap: 2rem; }
+}
+
+/* Nav links: hidden on mobile, shown on desktop */
+.nav-links {
+ display: none;
+ list-style: none;
+}
+
+@media (min-width: 768px) {
+ .nav-links {
+ display: flex;
+ gap: 1.75rem;
+ }
+}
+
+.nav-links a {
+ color: var(--text-dim);
+ text-decoration: none;
+ font-size: 0.8rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ transition: color 0.2s;
+ position: relative;
+}
+.nav-links a::after {
+ content: '';
+ position: absolute;
+ bottom: -2px;
+ inset-inline: 0;
+ height: 1px;
+ background: var(--accent);
+ transform: scaleX(0);
+ transition: transform 0.2s;
+}
+.nav-links a:hover { color: var(--accent); }
+.nav-links a:hover::after { transform: scaleX(1); }
+
+/* Lang toggle */
+.lang-toggle {
+ display: flex;
+ background: var(--surface);
+ border: 1px solid var(--border);
+ overflow: hidden;
+}
+.lang-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.25rem;
+ cursor: pointer;
+ background: transparent;
+ border: 2px solid transparent;
+ border-radius: 4px;
+ transition: border-color 0.2s, opacity 0.2s;
+ line-height: 1;
+}
+.lang-btn .fi {
+ width: 1.4em;
+ height: 1.4em;
+ border-radius: 2px;
+ display: block;
+}
+.lang-btn.active {
+ border-color: var(--accent);
+}
+.lang-btn:not(.active) { opacity: 0.65; }
+.lang-btn:not(.active):hover { opacity: 1; }
+span.lang-btn { cursor: default; }
+a.lang-btn { text-decoration: none; }
+
+/* Theme toggle */
+.theme-toggle {
+ background: none;
+ border: none;
+ cursor: pointer;
+ color: var(--text-dim);
+ font-size: 1rem;
+ padding: 0.25rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ transition: color 0.2s;
+ line-height: 1;
+}
+.theme-toggle:hover { color: var(--accent); }
+
+/* Desktop toggle: hide on mobile */
+.theme-toggle--desktop {
+ display: none;
+}
+@media (min-width: 768px) {
+ .theme-toggle--desktop { display: flex; }
+}
+
+/* Mobile toggle item: hide on desktop */
+.nav-theme-item { display: none; }
+.nav-theme-item .theme-toggle {
+ font-size: 0.8rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ padding: 0;
+}
+
+/* Hamburger: shown on mobile, hidden on desktop */
+.hamburger {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ cursor: pointer;
+ background: none;
+ border: none;
+ padding: 4px;
+}
+.hamburger span {
+ display: block;
+ width: 22px;
+ height: 2px;
+ background: var(--text-dim);
+ transition: all 0.3s;
+}
+
+@media (min-width: 768px) {
+ .hamburger { display: none; }
+}
+
+/* ── Hero ───────────────────────────────────────── */
+#hero {
+ min-height: 100svh;
+ display: flex;
+ align-items: center;
+ padding-top: 60px;
+ position: relative;
+ overflow: hidden;
+ background: var(--bg);
+}
+
+@media (min-width: 768px) {
+ #hero { padding-top: 70px; }
+}
+
+/* Matrix canvas */
+#matrix-canvas {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0.13;
+ pointer-events: none;
+}
+
+/* Scanlines overlay */
+#hero::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: repeating-linear-gradient(
+ 0deg,
+ transparent,
+ transparent 2px,
+ rgba(0,0,0,0.07) 2px,
+ rgba(0,0,0,0.07) 3px
+ );
+ pointer-events: none;
+ z-index: 1;
+}
+
+/* Ambient radial glow */
+#hero::after {
+ content: '';
+ position: absolute;
+ top: 35%;
+ left: 30%;
+ transform: translate(-50%, -50%);
+ width: 900px;
+ height: 600px;
+ background: radial-gradient(ellipse, rgba(168,85,247,0.07) 0%, transparent 65%);
+ pointer-events: none;
+ z-index: 0;
+}
+
+/* Hero inner: flex column — name full-width, then bottom row */
+.hero-inner {
+ padding: 3rem 0;
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ position: relative;
+ z-index: 2;
+}
+
+@media (min-width: 768px) {
+ .hero-inner { padding: 4rem 0; }
+}
+
+/* Bottom row: content + terminal side-by-side on desktop */
+.hero-bottom {
+ display: flex;
+ align-items: center;
+ gap: 3rem;
+}
+
+.hero-content { flex: 1; min-width: 0; }
+
+/* ── Prompt ── */
+.hero-prompt {
+ font-size: 0.8rem;
+ color: var(--accent2);
+ letter-spacing: 0.1em;
+ margin-bottom: 1.25rem;
+ opacity: 0;
+ animation: fadeUp 0.6s ease forwards 0.2s;
+}
+.hero-prompt::before { content: '$ '; color: var(--muted); }
+
+/* ── Name with glitch ── */
+.hero-name {
+ font-family: var(--font-head);
+ font-size: clamp(3rem, 12vw, 7rem);
+ font-weight: 800;
+ line-height: 0.95;
+ letter-spacing: -0.04em;
+ color: var(--text);
+ margin-bottom: 0;
+ opacity: 0;
+ animation: fadeUp 0.6s ease forwards 0.4s;
+ display: inline-block;
+ position: relative;
+ text-shadow: 0 0 80px rgba(168,85,247,0.18), 0 0 120px rgba(168,85,247,0.08);
+}
+
+/* Chromatic aberration layers */
+.hero-name::before,
+.hero-name::after {
+ content: attr(data-text);
+ position: absolute;
+ top: 0; left: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0;
+ pointer-events: none;
+ overflow: hidden;
+ font-family: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+ letter-spacing: inherit;
+ line-height: inherit;
+}
+.hero-name::before { color: #ff2b6d; }
+.hero-name::after { color: #00e5ff; }
+
+.hero-name.is-glitching::before {
+ opacity: 0.8;
+ animation: glitch-red 0.45s steps(3) forwards;
+}
+.hero-name.is-glitching::after {
+ opacity: 0.8;
+ animation: glitch-cyn 0.45s steps(3) forwards;
+}
+
+/* ── Role ── */
+.hero-role {
+ font-size: clamp(0.85rem, 3vw, 1rem);
+ color: var(--accent);
+ margin-bottom: 1.5rem;
+ opacity: 0;
+ animation: fadeUp 0.6s ease forwards 0.6s;
+ min-height: 1.75em;
+}
+
+.cursor {
+ display: inline-block;
+ width: 2px;
+ height: 1em;
+ background: var(--accent);
+ margin-left: 1px;
+ vertical-align: text-bottom;
+ animation: blink 1s step-end infinite;
+}
+
+/* ── Tagline ── */
+.hero-tagline {
+ font-size: 0.93rem;
+ color: var(--text-dim);
+ max-width: 500px;
+ margin-bottom: 2.5rem;
+ opacity: 0;
+ animation: fadeUp 0.6s ease forwards 0.8s;
+ line-height: 1.95;
+}
+
+/* ── CTA buttons ── */
+.hero-cta {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ opacity: 0;
+ animation: fadeUp 0.6s ease forwards 1s;
+}
+
+@media (min-width: 480px) {
+ .hero-cta { flex-direction: row; flex-wrap: wrap; }
+}
+
+/* ── Terminal panel ── */
+.hero-term {
+ display: none;
+}
+
+@media (min-width: 900px) {
+ .hero-term {
+ display: flex;
+ flex-direction: column;
+ width: 320px;
+ flex-shrink: 0;
+ background: rgba(6, 11, 16, 0.85);
+ border: 1px solid rgba(168,85,247,0.3);
+ border-radius: 10px;
+ overflow: hidden;
+ box-shadow: 0 0 40px rgba(168,85,247,0.1), inset 0 0 30px rgba(0,0,0,0.4);
+ opacity: 0;
+ animation: fadeUp 0.6s ease forwards 0.5s;
+ backdrop-filter: blur(8px);
+ }
+}
+
+.hero-term-bar {
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+ padding: 0.6rem 0.9rem;
+ background: rgba(255,255,255,0.04);
+ border-bottom: 1px solid rgba(168,85,247,0.15);
+}
+
+.hero-term-dot {
+ width: 11px;
+ height: 11px;
+ border-radius: 50%;
+ background: var(--c, #888);
+ flex-shrink: 0;
+}
+
+.hero-term-title {
+ margin-left: 0.4rem;
+ font-size: 0.65rem;
+ color: var(--muted);
+ letter-spacing: 0.06em;
+ font-family: var(--font-mono);
+}
+
+.hero-term-body {
+ padding: 1rem 1.1rem;
+ font-family: var(--font-mono);
+ font-size: 0.78rem;
+ line-height: 1.7;
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+}
+
+/* Terminal line fade-in sequence */
+.tl {
+ opacity: 0;
+ animation: fadeIn 0.25s ease forwards;
+}
+.hero-term-body .tl { white-space: nowrap; }
+.tl-d1 { animation-delay: 0.9s; }
+.tl-d2 { animation-delay: 1.5s; }
+.tl-d3 { animation-delay: 1.9s; }
+.tl-d4 { animation-delay: 2.1s; }
+.tl-d5 { animation-delay: 2.3s; }
+.tl-d6 { animation-delay: 2.5s; }
+.tl-d7 { animation-delay: 2.7s; }
+.tl-d8 { animation-delay: 2.9s; }
+.tl-d9 { animation-delay: 3.1s; }
+
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+.tc-dim { color: var(--muted); }
+.tc-ok { color: var(--accent2); }
+.tc-val { color: var(--text); }
+.tc-key { color: var(--muted); }
+.tc-blink { animation: blink 1s step-end infinite; color: var(--accent2); }
+
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ padding: 0.65rem 1.4rem;
+ font-family: var(--font-mono);
+ font-size: 0.8rem;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ text-decoration: none;
+ cursor: pointer;
+ border: none;
+ transition: all 0.22s;
+}
+
+@media (min-width: 480px) {
+ .btn { justify-content: flex-start; }
+}
+
+.btn-primary {
+ background: var(--accent);
+ color: var(--bg);
+ font-weight: 700;
+}
+.btn-primary:hover {
+ background: #fff;
+ box-shadow: 0 0 24px rgba(168,85,247,0.45);
+}
+.btn-outline {
+ background: transparent;
+ color: var(--text);
+ border: 1px solid var(--border);
+}
+.btn-outline:hover {
+ border-color: var(--accent);
+ color: var(--accent);
+ box-shadow: inset 0 0 16px rgba(168,85,247,0.06);
+}
+.btn-cv {
+ background: transparent;
+ color: var(--muted);
+ border: 1px dashed var(--border);
+ gap: 0.45rem;
+}
+.btn-cv:hover {
+ border-color: var(--accent);
+ border-style: solid;
+ color: var(--accent);
+}
+
+.scroll-indicator {
+ position: absolute;
+ bottom: 2rem;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.5rem;
+ color: var(--muted);
+ font-size: 0.7rem;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ opacity: 0;
+ animation: fadeUp 0.6s ease forwards 1.4s;
+}
+.scroll-line {
+ width: 1px;
+ height: 44px;
+ background: linear-gradient(to bottom, var(--accent), transparent);
+ animation: scanDown 2s ease-in-out infinite;
+}
+
+/* ── Sections ───────────────────────────────────── */
+section {
+ padding: 4rem 0;
+ position: relative;
+ z-index: 1;
+}
+
+@media (min-width: 768px) {
+ section { padding: 6rem 0; }
+}
+
+.section-eyebrow {
+ font-size: 0.75rem;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: var(--accent);
+ margin-bottom: 0.6rem;
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+}
+.section-eyebrow::before { content: '//'; color: var(--muted); }
+
+.section-title {
+ font-family: var(--font-head);
+ font-size: clamp(1.7rem, 6vw, 3rem);
+ font-weight: 800;
+ color: var(--text);
+ letter-spacing: -0.035em;
+ margin-bottom: 2rem;
+ line-height: 1.05;
+}
+
+@media (min-width: 768px) {
+ .section-title { margin-bottom: 3rem; }
+}
+
+/* ── About ──────────────────────────────────────── */
+
+/* Mobile: single column */
+.about-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 2.5rem;
+ align-items: start;
+}
+
+/* Desktop: two columns */
+@media (min-width: 768px) {
+ .about-grid {
+ grid-template-columns: 1.1fr 0.9fr;
+ gap: 4rem;
+ }
+}
+
+.about-bio p {
+ color: var(--text-dim);
+ font-size: 0.95rem;
+ line-height: 1.95;
+ margin-bottom: 1rem;
+ hyphens: auto;
+}
+.about-bio p:last-child { margin-bottom: 0; }
+
+/* About photo (floated inside bio column) */
+.about-photo {
+ float: left;
+ width: 120px;
+ height: 120px;
+ object-fit: cover;
+ object-position: center top;
+ border: 1px solid var(--border);
+ margin: 0.2rem 1.25rem 0.75rem 0;
+ filter: grayscale(15%);
+ transition: filter 0.4s;
+ flex-shrink: 0;
+}
+
+.about-photo:hover {
+ filter: grayscale(0%);
+}
+
+.about-bio { overflow: hidden; }
+
+/* Terminal window */
+.terminal {
+ background: var(--bg2);
+ border: 1px solid var(--border);
+}
+.terminal-bar {
+ padding: 0.55rem 1rem;
+ background: var(--surface);
+ border-bottom: 1px solid var(--border);
+ display: flex;
+ align-items: center;
+ gap: 0.45rem;
+}
+.dot { width: 10px; height: 10px; border-radius: 50%; }
+.dot-r { background: #ff5f57; }
+.dot-y { background: #ffbd2e; }
+.dot-g { background: #28c840; }
+.t-file { font-size: 0.68rem; color: var(--muted); margin-left: 0.4rem; }
+
+.terminal-body {
+ padding: 1.4rem 1.5rem;
+ font-size: 0.84rem;
+ line-height: 1.9;
+ overflow-x: auto;
+}
+.tl { display: flex; gap: 0.6rem; }
+.t-ps { color: var(--accent2); user-select: none; flex-shrink: 0; }
+.t-cm { color: var(--accent); }
+.t-out { color: var(--text-dim); padding-left: 1rem; }
+.t-k { color: var(--muted); }
+.t-v { color: var(--text); }
+.t-s { color: var(--accent2); }
+
+/* ── Services ───────────────────────────────────── */
+.services-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 1.5rem;
+}
+
+@media (min-width: 600px) {
+ .services-grid { grid-template-columns: repeat(2, 1fr); }
+}
+
+.service-card {
+ border: 1px solid var(--border);
+ background: var(--bg2);
+ padding: 1.75rem;
+ position: relative;
+ overflow: hidden;
+ transition: border-color 0.3s, background 0.2s;
+}
+.service-card::before {
+ content: '';
+ position: absolute;
+ top: 0; inset-inline: 0;
+ height: 2px;
+ background: linear-gradient(90deg, var(--accent), var(--accent2));
+}
+.service-card:hover {
+ border-color: rgba(168,85,247,0.28);
+ background: var(--surface);
+}
+
+.service-icon {
+ font-size: 1.6rem;
+ margin-bottom: 1rem;
+ color: var(--accent);
+ display: block;
+}
+.service-title {
+ font-family: var(--font-head);
+ font-size: 1.1rem;
+ font-weight: 800;
+ color: var(--text);
+ letter-spacing: -0.02em;
+ margin-bottom: 0.2rem;
+}
+.service-subtitle {
+ font-size: 0.76rem;
+ color: var(--accent);
+ letter-spacing: 0.06em;
+ margin-bottom: 0.9rem;
+}
+.service-desc {
+ font-size: 0.88rem;
+ color: var(--text-dim);
+ line-height: 1.85;
+}
+
+/* ── Education ──────────────────────────────────── */
+#education { background: var(--bg2); }
+
+.edu-list {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ padding-left: 1rem;
+}
+.edu-list::before {
+ content: '';
+ position: absolute;
+ left: 0.44rem;
+ top: 0.75rem;
+ bottom: 0;
+ width: 1px;
+ background: linear-gradient(to bottom, var(--accent), transparent);
+}
+
+.edu-item {
+ display: grid;
+ grid-template-columns: 1.5rem 1fr;
+ gap: 1.5rem;
+ padding-bottom: 2.5rem;
+}
+.edu-item:last-child { padding-bottom: 0; }
+
+.edu-dot {
+ width: 0.9rem;
+ height: 0.9rem;
+ border-radius: 50%;
+ border: 2px solid var(--accent);
+ background: var(--bg2);
+ flex-shrink: 0;
+ margin-top: 0.3rem;
+ position: relative;
+ z-index: 1;
+ transition: background 0.2s;
+}
+.edu-dot.current {
+ background: var(--accent);
+ box-shadow: 0 0 10px rgba(168,85,247,0.5);
+}
+
+.edu-period {
+ font-size: 0.72rem;
+ color: var(--accent);
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ margin-bottom: 0.35rem;
+}
+.edu-degree {
+ font-family: var(--font-head);
+ font-size: 1.05rem;
+ font-weight: 800;
+ color: var(--text);
+ letter-spacing: -0.02em;
+ margin-bottom: 0.2rem;
+}
+.edu-institution {
+ font-size: 0.78rem;
+ color: var(--muted);
+ letter-spacing: 0.03em;
+ margin-bottom: 0.6rem;
+}
+.edu-desc {
+ font-size: 0.88rem;
+ color: var(--text-dim);
+ line-height: 1.85;
+}
+
+/* ── Skills ─────────────────────────────────────── */
+#skills { background: var(--bg2); }
+#skills::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image: radial-gradient(circle, rgba(168,85,247,0.04) 1px, transparent 1px);
+ background-size: 28px 28px;
+}
+
+/* Mobile: single column; wider: auto-fill grid */
+.skills-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ background: var(--border);
+ gap: 1px;
+ border: 1px solid var(--border);
+}
+
+@media (min-width: 480px) {
+ .skills-grid { grid-template-columns: repeat(2, 1fr); }
+}
+
+@media (min-width: 900px) {
+ .skills-grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
+}
+
+.skill-card {
+ background: var(--bg2);
+ padding: 1.5rem;
+ transition: background 0.2s;
+}
+.skill-card:hover { background: var(--surface); }
+
+.skill-head {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ margin-bottom: 0.6rem;
+}
+.skill-icon {
+ width: 34px;
+ height: 34px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--accent-glow);
+ border: 1px solid rgba(168,85,247,0.18);
+ font-size: 1rem;
+ flex-shrink: 0;
+ color: var(--accent);
+}
+.skill-name {
+ font-size: 0.85rem;
+ font-weight: 700;
+ color: var(--text);
+ letter-spacing: 0.01em;
+}
+.skill-track {
+ height: 2px;
+ background: var(--border);
+ margin-top: 0.75rem;
+ overflow: hidden;
+}
+.skill-fill {
+ height: 100%;
+ background: linear-gradient(90deg, var(--accent), var(--accent2));
+ transform-origin: left;
+ transform: scaleX(0);
+ transition: transform 1.1s cubic-bezier(0.16,1,0.3,1);
+}
+.skill-fill.go { transform: scaleX(var(--w, 1)); }
+
+.skill-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.35rem;
+ margin-top: 0.75rem;
+}
+.tag {
+ font-size: 0.7rem;
+ padding: 0.18rem 0.45rem;
+ background: rgba(168,85,247,0.05);
+ border: 1px solid rgba(168,85,247,0.13);
+ color: var(--text-dim);
+ letter-spacing: 0.04em;
+}
+
+/* ── Certifications ─────────────────────────────── */
+
+/* Mobile: single column; wider: auto-fill */
+.cert-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 1.5rem;
+}
+
+@media (min-width: 640px) {
+ .cert-grid { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
+}
+
+.cert-card {
+ border: 1px solid var(--border);
+ background: var(--bg2);
+ padding: 1.5rem;
+ position: relative;
+ overflow: hidden;
+ transition: border-color 0.3s, box-shadow 0.3s;
+}
+
+.cert-date {
+ position: absolute;
+ top: 1.5rem;
+ right: 1.5rem;
+ font-size: 0.72rem;
+ color: var(--muted);
+ letter-spacing: 0.06em;
+}
+
+.cert-wip {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ font-size: 0.72rem;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ padding: 0.2rem 0.55rem;
+ border-radius: 999px;
+ background: color-mix(in srgb, var(--accent2, #00ff88) 12%, transparent);
+ color: var(--accent2, #00ff88);
+ border: 1px solid color-mix(in srgb, var(--accent2, #00ff88) 35%, transparent);
+ margin-bottom: 0.75rem;
+}
+
+.cert-wip::before {
+ content: "";
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--accent2, #00ff88);
+ animation: pulse-dot 1.4s ease-in-out infinite;
+}
+
+@keyframes pulse-dot {
+ 0%, 100% { opacity: 1; transform: scale(1); }
+ 50% { opacity: 0.4; transform: scale(0.7); }
+}
+
+@media (min-width: 640px) {
+ .cert-card { padding: 2rem; }
+}
+
+.cert-card::before {
+ content: '';
+ position: absolute;
+ top: 0; inset-inline: 0;
+ height: 2px;
+ background: linear-gradient(90deg, var(--accent), var(--accent2));
+}
+.cert-card:hover {
+ border-color: rgba(168,85,247,0.28);
+ box-shadow: 0 0 40px rgba(168,85,247,0.04);
+}
+.cert-card.placeholder {
+ opacity: 0.38;
+ border-style: dashed;
+}
+.cert-card.placeholder::before { display: none; }
+
+.cert-badge {
+ width: 72px;
+ height: 72px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 1.2rem;
+ position: relative;
+}
+.cert-badge--img {
+ background: transparent;
+}
+.cert-badge--img img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+.cert-badge--placeholder {
+ background: var(--accent-glow);
+ border: 2px dashed var(--muted);
+ font-size: 1.6rem;
+ color: var(--muted);
+ width: 52px;
+ height: 52px;
+}
+
+.cert-name {
+ font-family: var(--font-head);
+ font-size: 1.35rem;
+ font-weight: 800;
+ color: var(--text);
+ letter-spacing: -0.02em;
+ margin-bottom: 0.2rem;
+}
+.cert-issuer {
+ font-size: 0.78rem;
+ color: var(--accent);
+ letter-spacing: 0.06em;
+ margin-bottom: 0.9rem;
+}
+.cert-desc {
+ font-size: 0.88rem;
+ color: var(--text-dim);
+ line-height: 1.85;
+}
+.cert-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem 1.5rem;
+ margin-top: 1.2rem;
+ padding-top: 1rem;
+ border-top: 1px solid var(--border);
+}
+.cert-meta-item { font-size: 0.76rem; }
+.cert-meta-label { color: var(--muted); letter-spacing: 0.06em; display: block; margin-bottom: 0.1rem; }
+.cert-meta-val { color: var(--text-dim); font-weight: 700; }
+
+/* ── Carousel ───────────────────────────────────── */
+.carousel-wrap { position: relative; }
+
+/* Use double-class specificity to beat section-level grid rules */
+.cert-grid.is-carousel,
+.projects-grid.is-carousel {
+ display: flex;
+ overflow-x: auto;
+ scroll-snap-type: x mandatory;
+ -webkit-overflow-scrolling: touch;
+ scrollbar-width: none;
+ gap: 1.5rem;
+ padding-bottom: 0.25rem;
+}
+.cert-grid.is-carousel::-webkit-scrollbar,
+.projects-grid.is-carousel::-webkit-scrollbar { display: none; }
+
+/* Card sizing — narrow: ~1 card */
+.is-carousel .cert-card,
+.is-carousel .project-card {
+ flex: 0 0 min(300px, 80vw);
+ scroll-snap-align: start;
+}
+
+/* Tablet: ~2 cards */
+@media (min-width: 640px) {
+ .is-carousel .cert-card,
+ .is-carousel .project-card {
+ flex: 0 0 calc(50% - 0.75rem);
+ }
+}
+
+/* Wide: revert to grid if not is-wide-carousel */
+@media (min-width: 900px) {
+ .cert-grid.is-carousel:not(.is-wide-carousel),
+ .projects-grid.is-carousel:not(.is-wide-carousel) {
+ display: grid;
+ overflow: visible;
+ scroll-snap-type: none;
+ }
+ .cert-grid.is-carousel:not(.is-wide-carousel) .cert-card,
+ .projects-grid.is-carousel:not(.is-wide-carousel) .project-card {
+ flex: none;
+ scroll-snap-align: none;
+ }
+ /* Wide carousel: 3 cards per view */
+ .is-wide-carousel .cert-card,
+ .is-wide-carousel .project-card {
+ flex: 0 0 calc(33.333% - 1rem);
+ scroll-snap-align: start;
+ }
+}
+
+/* Controls: visible on narrow always, on wide only if is-wide-carousel */
+.carousel-controls {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 1rem;
+ margin-top: 1.5rem;
+}
+
+@media (min-width: 900px) {
+ .carousel-wrap:not(:has(.is-wide-carousel)) .carousel-controls {
+ display: none;
+ }
+}
+
+.carousel-btn {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ border: 1px solid var(--border);
+ background: var(--bg2);
+ color: var(--text-dim);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.75rem;
+ flex-shrink: 0;
+ transition: border-color 0.2s, color 0.2s;
+}
+.carousel-btn:hover { border-color: var(--accent); color: var(--accent); }
+
+.carousel-dots {
+ display: flex;
+ gap: 0.45rem;
+ align-items: center;
+}
+
+.carousel-dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ border: none;
+ padding: 0;
+ background: var(--border);
+ cursor: pointer;
+ transition: background 0.2s, transform 0.2s;
+}
+.carousel-dot.active { background: var(--accent); transform: scale(1.4); }
+
+/* ── Projects ───────────────────────────────────── */
+.projects-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 1.5rem;
+}
+
+@media (min-width: 640px) {
+ .projects-grid { grid-template-columns: repeat(2, 1fr); }
+}
+
+@media (min-width: 900px) {
+ .projects-grid { grid-template-columns: repeat(3, 1fr); }
+}
+
+.project-card {
+ background: var(--bg2);
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ transition: border-color 0.25s, transform 0.25s;
+}
+
+.project-card:hover {
+ border-color: var(--accent);
+ transform: translateY(-3px);
+}
+
+.project-img-wrap {
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ overflow: hidden;
+ background: var(--bg3, var(--bg2));
+}
+
+.project-img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ transition: transform 0.4s;
+}
+
+.project-card:hover .project-img { transform: scale(1.04); }
+
+.project-body {
+ padding: 1.25rem;
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ gap: 0.6rem;
+}
+
+.project-title {
+ font-size: 1rem;
+ font-weight: 700;
+ color: var(--text);
+ margin: 0;
+}
+
+.project-subtitle {
+ font-size: 0.8rem;
+ color: var(--accent);
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ margin: 0;
+}
+
+.project-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+}
+
+.project-tag {
+ font-size: 0.75rem;
+ padding: 0.2rem 0.55rem;
+ border-radius: 999px;
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
+ color: var(--accent);
+ border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
+ letter-spacing: 0.04em;
+}
+
+.project-desc {
+ font-size: 0.88rem;
+ color: var(--text-dim);
+ line-height: 1.7;
+ margin: 0;
+ flex: 1;
+}
+
+.project-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 0.6rem;
+ padding-top: 0.75rem;
+ border-top: 1px solid var(--border);
+ gap: 0.5rem;
+}
+
+.project-link {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ font-size: 0.84rem;
+ font-weight: 600;
+ color: var(--accent);
+ text-decoration: none;
+ transition: opacity 0.2s;
+}
+
+.project-link:hover { opacity: 0.75; }
+
+.project-share {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.share-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border-radius: 6px;
+ background: color-mix(in srgb, var(--text-dim) 8%, transparent);
+ color: var(--text-dim);
+ text-decoration: none;
+ font-size: 0.78rem;
+ transition: background 0.2s, color 0.2s;
+}
+
+.share-btn:hover {
+ background: color-mix(in srgb, var(--accent) 15%, transparent);
+ color: var(--accent);
+}
+
+.project-card--collab {
+ border-style: dashed;
+ border-color: color-mix(in srgb, var(--accent) 35%, transparent);
+ background: color-mix(in srgb, var(--accent) 4%, transparent);
+ justify-content: center;
+ min-height: 220px;
+}
+
+.project-card--collab:hover {
+ border-style: dashed;
+ border-color: var(--accent);
+ transform: translateY(-3px);
+}
+
+.project-collab-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 2rem;
+ color: color-mix(in srgb, var(--accent) 45%, transparent);
+ padding: 1.5rem 1.25rem 0;
+ transition: color 0.25s;
+}
+
+.project-card--collab:hover .project-collab-icon {
+ color: var(--accent);
+}
+
+/* ── Contact ────────────────────────────────────── */
+#contact { background: var(--bg2); }
+
+.contact-sub {
+ font-size: 0.93rem;
+ color: var(--text-dim);
+ max-width: 520px;
+ margin-bottom: 2rem;
+ line-height: 1.95;
+}
+
+@media (min-width: 768px) {
+ .contact-sub { margin-bottom: 2.5rem; }
+}
+
+.contact-list {
+ border: 1px solid var(--border);
+ max-width: 580px;
+}
+.contact-row {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 0.9rem 1.2rem;
+ text-decoration: none;
+ cursor: pointer;
+ color: var(--text);
+ border-bottom: 1px solid var(--border);
+ transition: background 0.2s, color 0.2s;
+}
+
+@media (min-width: 480px) {
+ .contact-row { padding: 1rem 1.4rem; }
+}
+
+.contact-row:last-child { border-bottom: none; }
+.contact-row:hover { background: var(--accent-glow); }
+
+.contact-ico {
+ width: 34px;
+ height: 34px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--surface);
+ border: 1px solid var(--border);
+ flex-shrink: 0;
+ font-size: 1rem;
+ color: var(--text-dim);
+ transition: border-color 0.2s, color 0.2s;
+}
+.contact-row:hover .contact-ico { color: var(--accent); }
+.contact-row:hover .contact-ico { border-color: rgba(168,85,247,0.35); }
+.contact-lbl { font-size: 0.72rem; color: var(--muted); letter-spacing: 0.06em; display: block; }
+.contact-val { font-size: 0.88rem; color: var(--text); }
+.contact-row:hover .contact-val { color: var(--accent); }
+
+/* Contact form max width when standalone */
+.contact-form { max-width: 640px; }
+
+/* ── Contact form ───────────────────────────────── */
+.contact-form {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 1.1rem;
+}
+
+/* Honeypot — visually hidden, not display:none so bots still see it */
+.cf-honey {
+ position: absolute;
+ left: -9999px;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+}
+
+.cf-row {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 1.1rem;
+}
+
+@media (min-width: 480px) {
+ .cf-row { grid-template-columns: 1fr 1fr; }
+}
+
+.cf-field {
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+}
+
+.cf-field label {
+ font-size: 0.76rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--muted);
+}
+
+.cf-req { color: var(--accent); }
+
+.cf-field input,
+.cf-field textarea {
+ background: var(--surface);
+ border: 1px solid var(--border);
+ color: var(--text);
+ font-family: var(--font-mono);
+ font-size: 0.88rem;
+ padding: 0.65rem 0.9rem;
+ outline: none;
+ transition: border-color 0.2s, box-shadow 0.2s;
+ resize: vertical;
+ width: 100%;
+}
+
+.cf-field input:focus,
+.cf-field textarea:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px rgba(168,85,247,0.1);
+}
+
+.cf-invalid {
+ border-color: #ff5f7e !important;
+ box-shadow: 0 0 0 3px rgba(255,95,126,0.1) !important;
+}
+
+.cf-field input::placeholder,
+.cf-field textarea::placeholder {
+ color: var(--muted);
+}
+
+.cf-footer {
+ display: flex;
+ align-items: center;
+ gap: 1.2rem;
+ flex-wrap: wrap;
+}
+
+.cf-submit { min-width: 160px; }
+.cf-submit.loading ~ .cf-clear { visibility: hidden; }
+
+.cf-submit.loading span { display: none; }
+.cf-submit.loading::after {
+ content: '';
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ border: 2px solid currentColor;
+ border-top-color: transparent;
+ border-radius: 50%;
+ animation: spin 0.7s linear infinite;
+}
+
+@keyframes spin { to { transform: rotate(360deg); } }
+
+.cf-status {
+ font-size: 0.84rem;
+ line-height: 1.4;
+}
+
+.cf-status.ok { color: var(--accent2); }
+.cf-status.err { color: #ff5f7e; }
+
+/* ── Footer ─────────────────────────────────────── */
+footer {
+ padding: 1.5rem 1.2rem;
+ text-align: center;
+ font-size: 0.75rem;
+ color: var(--muted);
+ letter-spacing: 0.07em;
+ border-top: 1px solid var(--border);
+ position: relative;
+ z-index: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+}
+
+@media (min-width: 768px) {
+ footer { padding: 2rem; }
+}
+
+.footer-top { color: var(--text-dim); }
+.footer-copy { color: var(--muted); font-size: 0.7rem; }
+
+.footer-social {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.6rem;
+}
+
+.footer-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ border: 1px solid var(--border);
+ color: var(--muted);
+ font-size: 0.8rem;
+ text-decoration: none;
+ cursor: pointer;
+ transition: border-color 0.2s, color 0.2s;
+}
+
+.footer-icon:hover {
+ border-color: var(--accent);
+ color: var(--accent);
+}
+
+/* ── Scroll Reveal ──────────────────────────────── */
+.reveal {
+ opacity: 0;
+ transform: translateY(28px);
+ transition: opacity 0.75s cubic-bezier(0.16,1,0.3,1),
+ transform 0.75s cubic-bezier(0.16,1,0.3,1);
+}
+.reveal.visible { opacity: 1; transform: translateY(0); }
+
+/* ── Keyframes ──────────────────────────────────── */
+@keyframes fadeUp {
+ from { opacity: 0; transform: translateY(22px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes blink {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0; }
+}
+
+@keyframes scanDown {
+ 0%, 100% { opacity: 0.25; transform: scaleY(0.5); transform-origin: top; }
+ 50% { opacity: 1; transform: scaleY(1); }
+}
+
+@keyframes glitch {
+ 0% { transform: translate(0); clip-path: none; }
+ 20% { transform: translate(-3px, 2px); clip-path: inset(25% 0 35% 0); }
+ 40% { transform: translate(3px, -2px); clip-path: inset(55% 0 15% 0); }
+ 60% { transform: translate(-2px, 1px); clip-path: inset(10% 0 65% 0); }
+ 80% { transform: translate(2px, -1px); clip-path: inset(75% 0 8% 0); }
+ 100% { transform: translate(0); clip-path: none; }
+}
+
+@keyframes glitch-red {
+ 0% { transform: translate(-5px, 1px); clip-path: inset(8% 0 78% 0); }
+ 33% { transform: translate(4px, -2px); clip-path: inset(42% 0 38% 0); }
+ 66% { transform: translate(-3px, 2px); clip-path: inset(68% 0 12% 0); }
+ 100% { transform: translate(0); clip-path: inset(0 0 100% 0); opacity: 0; }
+}
+
+@keyframes glitch-cyn {
+ 0% { transform: translate(5px, -1px); clip-path: inset(22% 0 62% 0); }
+ 33% { transform: translate(-4px, 2px); clip-path: inset(55% 0 28% 0); }
+ 66% { transform: translate(3px, -2px); clip-path: inset(15% 0 70% 0); }
+ 100% { transform: translate(0); clip-path: inset(0 0 100% 0); opacity: 0; }
+}
+
+/* ── Accessibility: Focus Styles ────────────────── */
+.nav-links a:focus-visible,
+.btn:focus-visible,
+.lang-btn:focus-visible,
+.footer-icon:focus-visible,
+.carousel-btn:focus-visible,
+.carousel-dot:focus-visible,
+.hamburger:focus-visible,
+.project-link:focus-visible,
+.share-btn:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 3px;
+}
+
+/* ── Accessibility: Reduced Motion ─────────────── */
+@media (prefers-reduced-motion: reduce) {
+ html { scroll-behavior: auto; }
+
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
+
+ /* Reveal elements should be visible immediately */
+ .reveal {
+ opacity: 1;
+ transform: none;
+ transition: none;
+ }
+
+ /* Hero animated elements should start visible */
+ .hero-prompt,
+ .hero-name,
+ .hero-role,
+ .hero-tagline,
+ .hero-cta,
+ .hero-term,
+ .scroll-indicator {
+ opacity: 1;
+ animation: none;
+ }
+
+ /* Terminal lines should be visible immediately */
+ .tl { opacity: 1; animation: none; }
+
+ /* Stop blinking cursors */
+ .cursor,
+ .tc-blink { animation: none; opacity: 1; }
+
+ /* Hide matrix canvas */
+ #matrix-canvas { display: none; }
+}
diff --git a/themes/danixme/assets/js/main.js b/themes/danixme/assets/js/main.js
new file mode 100644
index 0000000..7d28689
--- /dev/null
+++ b/themes/danixme/assets/js/main.js
@@ -0,0 +1,374 @@
+/* ── Matrix Rain ─────────────────────────────────── */
+(function () {
+ const canvas = document.getElementById('matrix-canvas');
+ if (!canvas) return;
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
+
+ const ctx = canvas.getContext('2d');
+ const CHARS = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789ABCDEF<>/\\|{}[]$#@!';
+ const FS = 14;
+ let cols, drops, raf;
+
+ function init() {
+ canvas.width = canvas.offsetWidth;
+ canvas.height = canvas.offsetHeight;
+ cols = Math.floor(canvas.width / FS) + 1;
+ drops = Array.from({ length: cols }, () => Math.random() * -(canvas.height / FS));
+ }
+
+ function tick() {
+ const light = document.documentElement.classList.contains('theme-light');
+ ctx.fillStyle = light ? 'rgba(240,244,248,0.07)' : 'rgba(6,11,16,0.055)';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ ctx.font = `${FS}px "JetBrains Mono", monospace`;
+
+ for (let i = 0; i < cols; i++) {
+ const char = CHARS[Math.floor(Math.random() * CHARS.length)];
+ // Occasional bright green "head", otherwise purple
+ ctx.fillStyle = Math.random() > 0.96
+ ? (light ? '#008f5a' : '#00ff88')
+ : (light ? '#7c3aed' : '#a855f7');
+ ctx.fillText(char, i * FS, drops[i] * FS);
+
+ if (drops[i] * FS > canvas.height && Math.random() > 0.975) {
+ drops[i] = Math.random() * -20;
+ }
+ drops[i] += 0.5;
+ }
+ raf = requestAnimationFrame(tick);
+ }
+
+ init();
+ window.addEventListener('resize', () => { cancelAnimationFrame(raf); init(); tick(); }, { passive: true });
+ document.addEventListener('visibilitychange', () => {
+ if (document.hidden) cancelAnimationFrame(raf); else tick();
+ });
+ tick();
+})();
+
+/* ── Hero Glitch ─────────────────────────────────── */
+(function () {
+ const name = document.querySelector('.hero-name');
+ if (!name) return;
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
+
+ function glitch() {
+ name.classList.add('is-glitching');
+ setTimeout(() => name.classList.remove('is-glitching'), 500);
+ setTimeout(glitch, 4000 + Math.random() * 7000);
+ }
+ setTimeout(glitch, 3500);
+})();
+
+/* ── CV Download ─────────────────────────────────── */
+document.querySelectorAll('[data-cv]').forEach(el => {
+ const trigger = (e) => {
+ e.preventDefault();
+ const a = document.createElement('a');
+ a.href = atob(el.dataset.cv);
+ a.download = '';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ };
+ el.addEventListener('click', trigger);
+ el.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') trigger(e); });
+});
+
+/* ── Email Assembly ──────────────────────────────── */
+document.querySelectorAll('[data-eu]').forEach(el => {
+ const addr = el.dataset.eu + '@' + el.dataset.ed;
+ el.href = 'mailto:' + addr;
+ const val = el.querySelector('.contact-val');
+ if (val) val.textContent = addr;
+});
+
+/* ── Typing Animation ────────────────────────────── */
+const typedEl = document.getElementById('typed');
+const phrases = window.SITE_PHRASES || [];
+
+let pIdx = 0, cIdx = 0, deleting = false, timer;
+
+function type() {
+ const word = phrases[pIdx % phrases.length];
+ if (!word) return;
+
+ if (!deleting) {
+ typedEl.textContent = word.slice(0, ++cIdx);
+ if (cIdx === word.length) { deleting = true; timer = setTimeout(type, 2200); return; }
+ } else {
+ typedEl.textContent = word.slice(0, --cIdx);
+ if (cIdx === 0) { deleting = false; pIdx++; timer = setTimeout(type, 350); return; }
+ }
+ timer = setTimeout(type, deleting ? 38 : 68);
+}
+
+function resetTyping() {
+ clearTimeout(timer);
+ pIdx = 0; cIdx = 0; deleting = false;
+ typedEl.textContent = '';
+ type();
+}
+
+resetTyping();
+
+/* ── Scroll Reveal ───────────────────────────────── */
+const revealObs = new IntersectionObserver((entries) => {
+ entries.forEach((e, i) => {
+ if (e.isIntersecting) {
+ setTimeout(() => e.target.classList.add('visible'), i * 90);
+ revealObs.unobserve(e.target);
+ }
+ });
+}, { threshold: 0.1 });
+
+document.querySelectorAll('.reveal').forEach(el => revealObs.observe(el));
+
+/* ── Skill Bar Animation ─────────────────────────── */
+const skillsObs = new IntersectionObserver((entries) => {
+ entries.forEach(e => {
+ if (e.isIntersecting) {
+ e.target.querySelectorAll('.skill-fill').forEach((bar, i) =>
+ setTimeout(() => bar.classList.add('go'), i * 110)
+ );
+ skillsObs.unobserve(e.target);
+ }
+ });
+}, { threshold: 0.2 });
+
+const sg = document.getElementById('skillsGrid');
+if (sg) skillsObs.observe(sg);
+
+/* ── Carousel ────────────────────────────────────── */
+document.querySelectorAll('[data-carousel]').forEach(wrap => {
+ const track = wrap.querySelector('.is-carousel');
+ const prevBtn = wrap.querySelector('.carousel-btn--prev');
+ const nextBtn = wrap.querySelector('.carousel-btn--next');
+ const dotsWrap = wrap.querySelector('.carousel-dots');
+ if (!track) return;
+
+ const cards = Array.from(track.children);
+
+ // Scroll position of card i relative to the track's scrollable content
+ function cardScrollLeft(i) {
+ const trackRect = track.getBoundingClientRect();
+ const cardRect = cards[i].getBoundingClientRect();
+ return track.scrollLeft + (cardRect.left - trackRect.left);
+ }
+
+ // Build dots
+ const dots = cards.map((_, i) => {
+ const dot = document.createElement('button');
+ dot.className = 'carousel-dot';
+ dot.setAttribute('aria-label', `${i + 1}`);
+ dot.addEventListener('click', () => {
+ track.scrollTo({ left: cardScrollLeft(i), behavior: 'smooth' });
+ });
+ dotsWrap.appendChild(dot);
+ return dot;
+ });
+
+ function activeDot() {
+ const trackLeft = track.getBoundingClientRect().left;
+ let closest = 0, minDist = Infinity;
+ cards.forEach((card, i) => {
+ const dist = Math.abs(card.getBoundingClientRect().left - trackLeft);
+ if (dist < minDist) { minDist = dist; closest = i; }
+ });
+ dots.forEach((d, i) => d.classList.toggle('active', i === closest));
+ }
+
+ function cardWidth() {
+ const gap = parseFloat(getComputedStyle(track).columnGap) || 24;
+ return cards[0].offsetWidth + gap;
+ }
+
+ prevBtn.addEventListener('click', () => track.scrollBy({ left: -cardWidth(), behavior: 'smooth' }));
+ nextBtn.addEventListener('click', () => track.scrollBy({ left: cardWidth(), behavior: 'smooth' }));
+
+ track.addEventListener('scroll', activeDot, { passive: true });
+ activeDot();
+});
+
+/* ── Contact Form ────────────────────────────────── */
+(function () {
+ const form = document.getElementById('contact-form');
+ if (!form) return;
+
+ // Stamp load time into hidden field — used by PHP timing check
+ const timeField = document.getElementById('cf-time');
+ if (timeField) timeField.value = Math.floor(Date.now() / 1000);
+
+ const btn = form.querySelector('.cf-submit');
+ const status = form.querySelector('.cf-status');
+ const i18n = window.FORM_I18N || {};
+
+ form.addEventListener('submit', async (e) => {
+ e.preventDefault();
+
+ // Basic client-side required check
+ const required = form.querySelectorAll('[required]');
+ let valid = true;
+ required.forEach(el => {
+ el.classList.toggle('cf-invalid', !el.value.trim());
+ if (!el.value.trim()) valid = false;
+ });
+ if (!valid) {
+ setStatus('err', i18n.error_fields);
+ return;
+ }
+
+ btn.classList.add('loading');
+ btn.disabled = true;
+ setStatus('', '');
+
+ try {
+ const res = await fetch('/api/contact.php', {
+ method: 'POST',
+ body: new FormData(form),
+ });
+ const data = await res.json();
+
+ if (data.success) {
+ setStatus('ok', i18n.success);
+ form.reset();
+ // Re-stamp time after reset
+ if (timeField) timeField.value = Math.floor(Date.now() / 1000);
+ } else {
+ setStatus('err', i18n.error);
+ }
+ } catch {
+ setStatus('err', i18n.error);
+ } finally {
+ btn.classList.remove('loading');
+ btn.disabled = false;
+ }
+ });
+
+ // Clear invalid state on input
+ form.querySelectorAll('input, textarea').forEach(el => {
+ el.addEventListener('input', () => el.classList.remove('cf-invalid'));
+ });
+
+ // On reset: clear status message, invalid states, and re-stamp time
+ form.addEventListener('reset', () => {
+ setStatus('', '');
+ form.querySelectorAll('.cf-invalid').forEach(el => el.classList.remove('cf-invalid'));
+ if (timeField) setTimeout(() => { timeField.value = Math.floor(Date.now() / 1000); }, 0);
+ });
+
+ function setStatus(type, msg) {
+ status.className = 'cf-status' + (type ? ' ' + type : '');
+ status.textContent = msg;
+ }
+})();
+
+/* ── Scroll Progress Bar ─────────────────────────── */
+(function () {
+ const bar = document.getElementById('scroll-progress');
+ if (!bar) return;
+ window.addEventListener('scroll', () => {
+ const scrolled = document.documentElement.scrollTop;
+ const total = document.documentElement.scrollHeight - document.documentElement.clientHeight;
+ const pct = total > 0 ? (scrolled / total) * 100 : 0;
+ bar.style.width = pct + '%';
+ bar.setAttribute('aria-valuenow', Math.round(pct));
+ }, { passive: true });
+})();
+
+/* ── Nav Border on Scroll ────────────────────────── */
+const navEl = document.getElementById('nav');
+const isLight = () => document.documentElement.classList.contains('theme-light');
+window.addEventListener('scroll', () => {
+ navEl.style.borderBottomColor = scrollY > 60
+ ? (isLight() ? 'rgba(168,189,216,0.9)' : 'rgba(24,40,64,0.9)')
+ : (isLight() ? 'rgba(168,189,216,0.5)' : 'rgba(24,40,64,0.5)');
+}, { passive: true });
+
+/* ── Mobile Menu ─────────────────────────────────── */
+const burger = document.querySelector('.hamburger');
+const navLinks = document.querySelector('.nav-links');
+let menuOpen = false;
+
+function menuBg() {
+ return isLight()
+ ? { bg: 'rgba(240,244,248,0.97)', border: '1px solid rgba(168,189,216,0.8)' }
+ : { bg: 'rgba(6,11,16,0.97)', border: '1px solid rgba(24,40,64,0.9)' };
+}
+
+function openMenu() {
+ menuOpen = true;
+ burger.setAttribute('aria-expanded', true);
+ const { bg, border } = menuBg();
+ Object.assign(navLinks.style, {
+ display: 'flex',
+ flexDirection: 'column',
+ position: 'absolute',
+ top: '100%',
+ left: '0',
+ right: '0',
+ background: bg,
+ padding: '1rem 2rem',
+ borderBottom: border,
+ gap: '1rem'
+ });
+ navLinks.querySelector('.nav-theme-item').style.display = 'flex';
+}
+
+function closeMenu() {
+ menuOpen = false;
+ burger.setAttribute('aria-expanded', false);
+ navLinks.style.display = 'none';
+ navLinks.querySelector('.nav-theme-item').style.display = '';
+}
+
+burger.addEventListener('click', (e) => {
+ e.stopPropagation();
+ menuOpen ? closeMenu() : openMenu();
+});
+
+document.addEventListener('click', (e) => {
+ if (menuOpen && !navEl.contains(e.target)) closeMenu();
+});
+
+document.addEventListener('touchstart', (e) => {
+ if (menuOpen && !navEl.contains(e.target)) closeMenu();
+}, { passive: true });
+
+/* ── Theme Toggle ────────────────────────────────── */
+(function () {
+ const html = document.documentElement;
+ const toggles = document.querySelectorAll('.theme-toggle');
+
+ function currentTheme() {
+ return html.classList.contains('theme-light') ? 'light' : 'dark';
+ }
+
+ function applyTheme(theme) {
+ html.classList.remove('theme-light', 'theme-dark');
+ html.classList.add('theme-' + theme);
+ localStorage.setItem('theme', theme);
+ updateIcons();
+ // Refresh open mobile menu background
+ if (menuOpen) {
+ const { bg, border } = menuBg();
+ navLinks.style.background = bg;
+ navLinks.style.borderBottom = border;
+ }
+ }
+
+ function updateIcons() {
+ const light = currentTheme() === 'light';
+ toggles.forEach(btn => {
+ btn.querySelector('i').className = light ? 'fa-solid fa-moon' : 'fa-solid fa-sun';
+ const lbl = btn.querySelector('.theme-toggle-label');
+ if (lbl) lbl.textContent = light ? 'Dark mode' : 'Light mode';
+ });
+ }
+
+ toggles.forEach(btn => btn.addEventListener('click', () => {
+ applyTheme(currentTheme() === 'light' ? 'dark' : 'light');
+ }));
+
+ updateIcons();
+})();
diff --git a/themes/danixme/i18n/en.toml b/themes/danixme/i18n/en.toml
new file mode 100644
index 0000000..8a7ffd8
--- /dev/null
+++ b/themes/danixme/i18n/en.toml
@@ -0,0 +1,149 @@
+[nav_about]
+other = "About"
+
+[nav_services]
+other = "Services"
+
+[nav_education]
+other = "Education"
+
+[nav_skills]
+other = "Skills"
+
+[nav_certs]
+other = "Certs"
+
+[nav_contact]
+other = "Contact"
+
+[btn_contact]
+other = "↗ Get in Touch"
+
+[btn_cv]
+other = "Download CV"
+
+[btn_more]
+other = "→ Learn More"
+
+[scroll]
+other = "scroll"
+
+[hero_prompt]
+other = "whoami"
+
+[section_about]
+other = "About"
+
+[section_skills]
+other = "Skills"
+
+[section_certs]
+other = "Certifications"
+
+[section_contact]
+other = "Contact"
+
+[about_title]
+other = "Background"
+
+[section_services]
+other = "Services"
+
+[services_title]
+other = "What I Do"
+
+[section_education]
+other = "Education"
+
+[education_title]
+other = "Academic Path"
+
+[skills_title]
+other = "Toolkit"
+
+[certs_title]
+other = "Credentials"
+
+[contact_title]
+other = "Let's Connect"
+
+[terminal_role]
+other = "CyberSecurity Specialist"
+
+[terminal_status]
+other = "open to work"
+
+[cert_in_progress]
+other = "Studying"
+
+[cert_issuer_label]
+other = "Issuer"
+
+[cert_level_label]
+other = "Level"
+
+[cert_type_label]
+other = "Type"
+
+[cert_date_label]
+other = "Obtained"
+
+[nav_projects]
+other = "Projects"
+
+[section_projects]
+other = "Projects"
+
+[projects_title]
+other = "My Work"
+
+[project_visit]
+other = "View Project"
+
+[carousel_prev]
+other = "Previous"
+
+[carousel_next]
+other = "Next"
+
+[project_collab_title]
+other = "Next Project"
+
+[project_collab_desc]
+other = "Got an exciting idea? I'm always open to collaborating on something new. Let's build something great together."
+
+[project_collab_cta]
+other = "Get in Touch"
+
+[form_name]
+other = "Name"
+
+[form_email]
+other = "Email"
+
+[form_subject]
+other = "Subject"
+
+[form_message]
+other = "Message"
+
+[form_send]
+other = "Send Message"
+
+[form_clear]
+other = "Clear"
+
+[form_success]
+other = "Message sent! I'll get back to you soon."
+
+[form_error]
+other = "Something went wrong. Please try again."
+
+[form_error_fields]
+other = "Please fill in all required fields."
+
+[footer1]
+other = 'Made with a mix of <i class="fa-solid fa-heart"></i> <i class="fa-solid fa-mug-saucer"></i> and a general lack of <i class="fa-solid fa-bed"></i>, plus some mad <i class="fa-solid fa-terminal"></i> skills'
+
+[footer2]
+other = "<em>This site is what my 15 years old self wanted to build</em>"
diff --git a/themes/danixme/i18n/it.toml b/themes/danixme/i18n/it.toml
new file mode 100644
index 0000000..943119d
--- /dev/null
+++ b/themes/danixme/i18n/it.toml
@@ -0,0 +1,149 @@
+[nav_about]
+other = "Chi Sono"
+
+[nav_services]
+other = "Servizi"
+
+[nav_education]
+other = "Formazione"
+
+[nav_skills]
+other = "Competenze"
+
+[nav_certs]
+other = "Certificazioni"
+
+[nav_contact]
+other = "Contatti"
+
+[btn_contact]
+other = "↗ Contattami"
+
+[btn_cv]
+other = "Scarica CV"
+
+[btn_more]
+other = "→ Scopri di Più"
+
+[scroll]
+other = "scorri"
+
+[hero_prompt]
+other = "chi_sono"
+
+[section_about]
+other = "Chi Sono"
+
+[section_skills]
+other = "Competenze"
+
+[section_certs]
+other = "Certificazioni"
+
+[section_contact]
+other = "Contatti"
+
+[about_title]
+other = "Background"
+
+[section_services]
+other = "Servizi"
+
+[services_title]
+other = "di cosa mi occupo"
+
+[section_education]
+other = "Formazione"
+
+[education_title]
+other = "Percorso Accademico"
+
+[skills_title]
+other = "Strumenti"
+
+[certs_title]
+other = "Credenziali"
+
+[contact_title]
+other = "Connettiti"
+
+[terminal_role]
+other = "Specialista Cybersecurity"
+
+[terminal_status]
+other = "disponibile"
+
+[cert_in_progress]
+other = "In corso"
+
+[cert_issuer_label]
+other = "Ente"
+
+[cert_level_label]
+other = "Livello"
+
+[cert_type_label]
+other = "Tipo"
+
+[cert_date_label]
+other = "Ottenuta"
+
+[nav_projects]
+other = "Progetti"
+
+[section_projects]
+other = "Progetti"
+
+[projects_title]
+other = "I Miei Lavori"
+
+[project_visit]
+other = "Vedi Progetto"
+
+[carousel_prev]
+other = "Precedente"
+
+[carousel_next]
+other = "Successivo"
+
+[project_collab_title]
+other = "Prossimo Progetto"
+
+[project_collab_desc]
+other = "Hai un'idea interessante? Sono sempre aperto a collaborare su qualcosa di nuovo. Costruiamo qualcosa di grande insieme."
+
+[project_collab_cta]
+other = "Contattami"
+
+[form_name]
+other = "Nome"
+
+[form_email]
+other = "Email"
+
+[form_subject]
+other = "Oggetto"
+
+[form_message]
+other = "Messaggio"
+
+[form_send]
+other = "Invia Messaggio"
+
+[form_clear]
+other = "Cancella"
+
+[form_success]
+other = "Messaggio inviato! Ti risponderò presto."
+
+[form_error]
+other = "Qualcosa è andato storto. Riprova."
+
+[form_error_fields]
+other = "Compila tutti i campi obbligatori."
+
+[footer1]
+other = 'Realizzato con un misto di <i class="fa-solid fa-heart"></i> <i class="fa-solid fa-mug-saucer"></i> e una mancanza di <i class="fa-solid fa-bed"></i>, oltre alle mie capacità da <i class="fa-solid fa-terminal"></i>'
+
+[footer2]
+other = "<em>Questo è il sito che sognavo a 15 anni.</em>"
diff --git a/themes/danixme/layouts/_default/baseof.html b/themes/danixme/layouts/_default/baseof.html
new file mode 100644
index 0000000..f01a9f2
--- /dev/null
+++ b/themes/danixme/layouts/_default/baseof.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="{{ .Site.Language.Lang }}">
+{{ partial "head.html" . }}
+<body>
+<div id="scroll-progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"></div>
+{{ partial "nav.html" . }}
+{{ block "main" . }}{{ end }}
+{{ partial "footer.html" . }}
+{{ partial "scripts.html" . }}
+</body>
+</html>
diff --git a/themes/danixme/layouts/index.html b/themes/danixme/layouts/index.html
new file mode 100644
index 0000000..01e2f90
--- /dev/null
+++ b/themes/danixme/layouts/index.html
@@ -0,0 +1,10 @@
+{{ define "main" }}
+{{ partial "hero.html" . }}
+{{ partial "about.html" . }}
+{{ partial "services.html" . }}
+{{ partial "skills.html" . }}
+{{ partial "education.html" . }}
+{{ partial "certs.html" . }}
+{{ partial "projects.html" . }}
+{{ partial "contact.html" . }}
+{{ end }}
diff --git a/themes/danixme/layouts/partials/about.html b/themes/danixme/layouts/partials/about.html
new file mode 100644
index 0000000..68cb6e8
--- /dev/null
+++ b/themes/danixme/layouts/partials/about.html
@@ -0,0 +1,37 @@
+<section id="about">
+ <div class="container">
+ <div class="section-eyebrow reveal">{{ i18n "section_about" }}</div>
+ <h2 class="section-title reveal">{{ i18n "about_title" }}</h2>
+
+ <div class="about-grid">
+ <div class="about-bio reveal">
+ {{ with .Params.about_photo }}
+ <img class="about-photo" src="{{ . }}" alt="{{ $.Site.Params.name }}" loading="lazy">
+ {{ end }}
+ <div>{{ .Params.bio | markdownify }}</div>
+ </div>
+
+ <div class="terminal reveal">
+ <div class="terminal-bar">
+ <div class="dot dot-r"></div>
+ <div class="dot dot-y"></div>
+ <div class="dot dot-g"></div>
+ <span class="t-file">profile.json</span>
+ </div>
+ <div class="terminal-body">
+ <div class="tl"><span class="t-ps">~</span><span class="t-cm">cat profile.json</span></div>
+ <div style="margin-top:.5rem">
+ <div class="tl"><span class="t-out">{</span></div>
+ <div class="tl"><span class="t-out">&nbsp;&nbsp;<span class="t-k">"name":</span>&nbsp;<span class="t-v">"{{ .Site.Params.name }}",</span></span></div>
+ <div class="tl"><span class="t-out">&nbsp;&nbsp;<span class="t-k">"role":</span>&nbsp;<span class="t-v">"{{ i18n "terminal_role" }}",</span></span></div>
+ <div class="tl"><span class="t-out">&nbsp;&nbsp;<span class="t-k">"cert":</span>&nbsp;<span class="t-v">"{{ .Site.Params.cert }}",</span></span></div>
+ <div class="tl"><span class="t-out">&nbsp;&nbsp;<span class="t-k">"focus":</span>&nbsp;<span class="t-v">"{{ .Site.Params.focus }}",</span></span></div>
+ <div class="tl"><span class="t-out">&nbsp;&nbsp;<span class="t-k">"location":</span>&nbsp;<span class="t-v">"{{ .Site.Params.location }}",</span></span></div>
+ <div class="tl"><span class="t-out">&nbsp;&nbsp;<span class="t-k">"status":</span>&nbsp;<span class="t-s">"{{ i18n "terminal_status" }}"</span></span></div>
+ <div class="tl"><span class="t-out">}</span></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</section>
diff --git a/themes/danixme/layouts/partials/certs.html b/themes/danixme/layouts/partials/certs.html
new file mode 100644
index 0000000..41e672a
--- /dev/null
+++ b/themes/danixme/layouts/partials/certs.html
@@ -0,0 +1,67 @@
+<section id="certifications">
+ <div class="container">
+ <div class="section-eyebrow reveal">{{ i18n "section_certs" }}</div>
+ <h2 class="section-title reveal">{{ i18n "certs_title" }}</h2>
+
+ {{ $lang := .Site.Language.Lang }}
+ {{ $count := len hugo.Data.certs }}
+ {{ $wide := gt $count 3 }}
+
+ <div class="carousel-wrap" data-carousel>
+ <div class="cert-grid is-carousel{{ if $wide }} is-wide-carousel{{ end }}">
+ {{ range hugo.Data.certs }}
+ {{ $ph := .placeholder }}
+ {{ $ip := index . "in_progress" }}
+ <div class="cert-card{{ if $ph }} placeholder{{ end }} reveal">
+ {{ if $ip }}<span class="cert-wip">{{ i18n "cert_in_progress" }}</span>{{ end }}
+ {{ with .date }}<span class="cert-date">{{ . }}</span>{{ end }}
+ {{ $img := index . "image" }}
+ {{ if and $img (not $ph) }}
+ <div class="cert-badge cert-badge--img">
+ <img src="{{ $img }}" alt="{{ .name }}">
+ </div>
+ {{ else }}
+ <div class="cert-badge cert-badge--placeholder"><i class="{{ .badge }}"></i></div>
+ {{ end }}
+
+ <div class="cert-name"{{ if $ph }} style="color:var(--muted)"{{ end }}>
+ {{ if .name }}{{ .name }}{{ else if eq $lang "it" }}{{ .name_it }}{{ else }}{{ .name_en }}{{ end }}
+ </div>
+
+ <div class="cert-issuer"{{ if $ph }} style="color:var(--muted)"{{ end }}>
+ {{ if .issuer }}{{ .issuer }}{{ else if eq $lang "it" }}{{ .issuer_it }}{{ else }}{{ .issuer_en }}{{ end }}
+ </div>
+
+ <p class="cert-desc">{{ if eq $lang "it" }}{{ .desc_it }}{{ else }}{{ .desc_en }}{{ end }}</p>
+
+ {{ if not $ph }}
+ <div class="cert-meta">
+ <div class="cert-meta-item">
+ <span class="cert-meta-label">{{ i18n "cert_issuer_label" }}</span>
+ <span class="cert-meta-val">{{ .issuer_val }}</span>
+ </div>
+ <div class="cert-meta-item">
+ <span class="cert-meta-label">{{ i18n "cert_level_label" }}</span>
+ <span class="cert-meta-val">{{ if eq $lang "it" }}{{ .level_it }}{{ else }}{{ .level_en }}{{ end }}</span>
+ </div>
+ <div class="cert-meta-item">
+ <span class="cert-meta-label">{{ i18n "cert_type_label" }}</span>
+ <span class="cert-meta-val">{{ if eq $lang "it" }}{{ .type_it }}{{ else }}{{ .type_en }}{{ end }}</span>
+ </div>
+ </div>
+ {{ end }}
+ </div>
+ {{ end }}
+ </div>
+ <div class="carousel-controls">
+ <button class="carousel-btn carousel-btn--prev" aria-label="{{ i18n "carousel_prev" }}">
+ <i class="fa-solid fa-chevron-left"></i>
+ </button>
+ <div class="carousel-dots"></div>
+ <button class="carousel-btn carousel-btn--next" aria-label="{{ i18n "carousel_next" }}">
+ <i class="fa-solid fa-chevron-right"></i>
+ </button>
+ </div>
+ </div>{{/* end carousel-wrap */}}
+ </div>
+</section>
diff --git a/themes/danixme/layouts/partials/contact.html b/themes/danixme/layouts/partials/contact.html
new file mode 100644
index 0000000..9584c74
--- /dev/null
+++ b/themes/danixme/layouts/partials/contact.html
@@ -0,0 +1,49 @@
+<section id="contact">
+ <div class="container">
+ <div class="section-eyebrow reveal">{{ i18n "section_contact" }}</div>
+ <h2 class="section-title reveal">{{ i18n "contact_title" }}</h2>
+
+ <p class="contact-sub reveal">{{ .Params.contact_sub }}</p>
+
+ <form id="contact-form" class="contact-form reveal" novalidate>
+ <!-- Honeypot: hidden from humans, filled by bots -->
+ <div class="cf-honey" aria-hidden="true">
+ <label for="cf-website">Website</label>
+ <input type="text" id="cf-website" name="website" tabindex="-1" autocomplete="off">
+ </div>
+ <input type="hidden" name="_t" id="cf-time">
+
+ <div class="cf-row">
+ <div class="cf-field">
+ <label for="cf-name">{{ i18n "form_name" }} <span class="cf-req">*</span></label>
+ <input type="text" id="cf-name" name="name" required autocomplete="name" maxlength="100">
+ </div>
+ <div class="cf-field">
+ <label for="cf-email">{{ i18n "form_email" }} <span class="cf-req">*</span></label>
+ <input type="email" id="cf-email" name="email" required autocomplete="email" maxlength="254">
+ </div>
+ </div>
+
+ <div class="cf-field">
+ <label for="cf-subject">{{ i18n "form_subject" }}</label>
+ <input type="text" id="cf-subject" name="subject" maxlength="200">
+ </div>
+
+ <div class="cf-field">
+ <label for="cf-message">{{ i18n "form_message" }} <span class="cf-req">*</span></label>
+ <textarea id="cf-message" name="message" required maxlength="5000" rows="5"></textarea>
+ </div>
+
+ <div class="cf-footer">
+ <button type="submit" class="btn btn-primary cf-submit">
+ <i class="fa-solid fa-paper-plane"></i>
+ <span>{{ i18n "form_send" }}</span>
+ </button>
+ <button type="reset" class="btn btn-outline cf-clear">
+ {{ i18n "form_clear" }}
+ </button>
+ <p class="cf-status" aria-live="polite"></p>
+ </div>
+ </form>
+ </div>
+</section>
diff --git a/themes/danixme/layouts/partials/education.html b/themes/danixme/layouts/partials/education.html
new file mode 100644
index 0000000..74b1374
--- /dev/null
+++ b/themes/danixme/layouts/partials/education.html
@@ -0,0 +1,28 @@
+<section id="education">
+ <div class="container">
+ <div class="section-eyebrow reveal">{{ i18n "section_education" }}</div>
+ <h2 class="section-title reveal">{{ i18n "education_title" }}</h2>
+
+ <div class="edu-list">
+ {{ $lang := .Site.Language.Lang }}
+ {{ range hugo.Data.education }}
+ {{ $current := index . "current" }}
+ <div class="edu-item reveal">
+ <div class="edu-dot{{ if $current }} current{{ end }}"></div>
+ <div class="edu-body">
+ <div class="edu-period">{{ .period }}</div>
+ <div class="edu-degree">
+ {{ if eq $lang "it" }}{{ .degree_it }}{{ else }}{{ .degree_en }}{{ end }}
+ </div>
+ <div class="edu-institution">
+ {{ if eq $lang "it" }}{{ .institution_it }}{{ else }}{{ .institution_en }}{{ end }}
+ </div>
+ <div class="edu-desc">
+ {{ if eq $lang "it" }}{{ .desc_it | markdownify }}{{ else }}{{ .desc_en | markdownify }}{{ end }}
+ </div>
+ </div>
+ </div>
+ {{ end }}
+ </div>
+ </div>
+</section>
diff --git a/themes/danixme/layouts/partials/footer.html b/themes/danixme/layouts/partials/footer.html
new file mode 100644
index 0000000..ad9d015
--- /dev/null
+++ b/themes/danixme/layouts/partials/footer.html
@@ -0,0 +1,22 @@
+<footer>
+ <div class="footer-social">
+ {{ range hugo.Data.contact }}
+ {{ $eu := index . "email_user" }}
+ {{ $ed := index . "email_domain" }}
+ {{ if $eu }}
+ <a class="footer-icon" data-eu="{{ $eu }}" data-ed="{{ $ed }}" role="link" tabindex="0" title="{{ .label }}">
+ <i class="{{ .icon }}"></i>
+ </a>
+ {{ else }}
+ <a href="{{ .href }}" class="footer-icon"{{ if .external }} target="_blank" rel="noopener"{{ end }} title="{{ .label }}">
+ <i class="{{ .icon }}"></i>
+ </a>
+ {{ end }}
+ {{ end }}
+ </div>
+
+ <div class="footer-top">{{ i18n "footer1" | safeHTML }}</div>
+ {{ with i18n "footer2" }}<div class="footer-top">{{ . | safeHTML }}</div>{{ end }}
+
+ <div class="footer-copy">&copy; {{ now.Year }} {{ .Site.Params.name }}</div>
+</footer>
diff --git a/themes/danixme/layouts/partials/head.html b/themes/danixme/layouts/partials/head.html
new file mode 100644
index 0000000..ce87286
--- /dev/null
+++ b/themes/danixme/layouts/partials/head.html
@@ -0,0 +1,77 @@
+<head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <script>(function(){var s=localStorage.getItem('theme')||(window.matchMedia('(prefers-color-scheme: light)').matches?'light':'dark');document.documentElement.classList.add('theme-'+s);})();</script>
+
+ {{/* ── Primary Meta ──────────────────────────────── */}}
+ <title>{{ .Site.Title }}</title>
+ {{ $desc := .Params.description | default .Site.Params.description }}
+ <meta name="description" content="{{ $desc }}" />
+ <meta name="author" content="{{ .Site.Params.name }}" />
+ <meta name="robots" content="index, follow" />
+ <link rel="canonical" href="{{ .Permalink }}" />
+
+ {{/* ── Hreflang (multilingual) ───────────────────── */}}
+ {{ range .Site.Home.AllTranslations }}
+ <link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}" />
+ {{ end }}
+ <link rel="alternate" hreflang="x-default" href="{{ .Site.Home.Permalink }}" />
+
+ {{/* ── Open Graph ───────────────────────────────── */}}
+ {{ $ogImage := .Params.og_image | default .Site.Params.og_image }}
+ <meta property="og:type" content="website" />
+ <meta property="og:url" content="{{ .Permalink }}" />
+ <meta property="og:title" content="{{ .Site.Title }}" />
+ <meta property="og:description" content="{{ $desc }}" />
+ {{ with $ogImage }}<meta property="og:image" content="{{ . | absURL }}" />{{ end }}
+ <meta property="og:locale" content="{{ .Site.Language.Params.og_locale | default .Site.Language.Lang }}" />
+ {{ range .Site.Home.AllTranslations }}{{ if ne .Language.Lang $.Site.Language.Lang }}
+ <meta property="og:locale:alternate" content="{{ .Language.Params.og_locale | default .Language.Lang }}" />
+ {{ end }}{{ end }}
+
+ {{/* ── Twitter / X Card ────────────────────────── */}}
+ <meta name="twitter:card" content="summary_large_image" />
+ <meta name="twitter:title" content="{{ .Site.Title }}" />
+ <meta name="twitter:description" content="{{ $desc }}" />
+ {{ with $ogImage }}<meta name="twitter:image" content="{{ . | absURL }}" />{{ end }}
+
+ {{/* ── Geo (local SEO — Italy) ────────────────── */}}
+ <meta name="geo.region" content="IT" />
+ <meta name="geo.placename" content="Italy" />
+
+ {{/* ── Favicon & icons ─────────────────────────── */}}
+ {{ with .Site.Params.favicon }}<link rel="icon" href="{{ . }}">{{ end }}
+ <link rel="apple-touch-icon" href="{{ .Site.Params.favicon | default "/img/fav.png" }}" />
+ <meta name="theme-color" content="#06080b" media="(prefers-color-scheme: dark)" />
+ <meta name="theme-color" content="#f0f4f8" media="(prefers-color-scheme: light)" />
+
+ {{/* ── JSON-LD: Person schema ───────────────────── */}}
+ {{- $photo := .Params.about_photo | default "/img/Danilo_profile.jpg" -}}
+ {{- $links := slice -}}
+ {{- range hugo.Data.contact -}}{{- with .href -}}{{- $links = $links | append . -}}{{- end -}}{{- end -}}
+ <script type="application/ld+json">
+ {
+ "@context": "https://schema.org",
+ "@type": "Person",
+ "name": {{ .Site.Params.name | jsonify }},
+ "url": {{ .Site.BaseURL | strings.TrimSuffix "/" | jsonify }},
+ "image": {{ $photo | absURL | jsonify }},
+ "jobTitle": {{ i18n "terminal_role" | jsonify }},
+ "description": {{ $desc | jsonify }},
+ "sameAs": [{{ range $i, $link := $links }}{{ if $i }},{{ end }}{{ $link | jsonify }}{{ end }}]
+ }
+ </script>
+
+ {{/* ── Preconnect ───────────────────────────────── */}}
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+ <link rel="preconnect" href="https://cdnjs.cloudflare.com" />
+
+ {{/* ── Stylesheets ──────────────────────────────── */}}
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,300;0,400;0,500;0,700;1,300&family=Oxanium:wght@700;800&display=swap" rel="stylesheet" />
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" integrity="sha384-nRgPTkuX86pH8yjPJUAFuASXQSSl2/bBUiNV47vSYpKFxHJhbcrGnmlYpYJMeD7a" crossorigin="anonymous" />
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.2.3/css/flag-icons.min.css" integrity="sha384-aQuvIWWIbpu/mSqULLDiUveyYiPoJzPKAWjUmGJ+Elm+N/LJhzfZqsutsfw870JS" crossorigin="anonymous" />
+ {{ $css := resources.Get "css/main.css" }}
+ {{ if hugo.IsProduction }}{{ $css = $css | minify | fingerprint }}{{ end }}
+ <link rel="stylesheet" href="{{ $css.RelPermalink }}" />
+</head>
diff --git a/themes/danixme/layouts/partials/hero.html b/themes/danixme/layouts/partials/hero.html
new file mode 100644
index 0000000..aa5ab0f
--- /dev/null
+++ b/themes/danixme/layouts/partials/hero.html
@@ -0,0 +1,60 @@
+<section id="hero">
+ <canvas id="matrix-canvas" aria-hidden="true"></canvas>
+
+ <div class="container">
+ <div class="hero-inner">
+
+ <div class="hero-prompt">{{ i18n "hero_prompt" }}</div>
+ <h1 class="hero-name" data-text="{{ .Site.Params.name }}">{{ .Site.Params.name }}</h1>
+
+ <div class="hero-bottom">
+ <div class="hero-content">
+ <div class="hero-role">
+ <span id="typed"></span><span class="cursor"></span>
+ </div>
+
+ <div class="hero-tagline">{{ .Params.tagline | markdownify }}</div>
+
+ <div class="hero-cta">
+ <a href="#contact" class="btn btn-primary">{{ i18n "btn_contact" }}</a>
+ <a href="#about" class="btn btn-outline">{{ i18n "btn_more" }}</a>
+ {{ with .Site.Language.Params.cv }}
+ <a class="btn btn-cv" data-cv="{{ . | base64Encode }}" role="button" tabindex="0">
+ <i class="fa-solid fa-file-arrow-down"></i>
+ {{ i18n "btn_cv" }}
+ </a>
+ {{ end }}
+ </div>
+ </div>
+
+ <div class="hero-term" aria-hidden="true">
+ <div class="hero-term-bar">
+ <span class="hero-term-dot" style="background:#ff5f57"></span>
+ <span class="hero-term-dot" style="background:#febc2e"></span>
+ <span class="hero-term-dot" style="background:#28c840"></span>
+ <span class="hero-term-title">root@{{ .Site.Params.handle }}</span>
+ </div>
+ <div class="hero-term-body">
+ <div class="tl"><span class="tc-dim">$</span> connect <span class="tc-val">{{ .Site.Params.handle }}</span></div>
+ <div class="tl tl-d1"><span class="tc-dim">></span> establishing secure channel<span class="tc-blink">_</span></div>
+ <div class="tl tl-d2"><span class="tc-ok">✔</span> connected <span class="tc-dim">[TLS 1.3]</span></div>
+ <div class="tl tl-d3 tc-dim">──────────────────────────</div>
+ <div class="tl tl-d4"><span class="tc-key">USER </span><span class="tc-val">{{ .Site.Params.name }}</span></div>
+ <div class="tl tl-d5"><span class="tc-key">CERT </span><span class="tc-val">{{ .Site.Params.cert }}</span></div>
+ <div class="tl tl-d6"><span class="tc-key">FOCUS </span><span class="tc-val">{{ .Site.Params.focus }}</span></div>
+ <div class="tl tl-d7"><span class="tc-key">STATUS </span><span class="tc-ok">{{ i18n "terminal_status" }}</span></div>
+ <div class="tl tl-d8 tc-dim">──────────────────────────</div>
+ <div class="tl tl-d9"><span class="tc-dim">$</span> <span class="tc-blink">█</span></div>
+ </div>
+ </div>
+
+ </div>{{/* end hero-bottom */}}
+
+ </div>{{/* end hero-inner */}}
+ </div>
+
+ <div class="scroll-indicator">
+ {{ i18n "scroll" }}
+ <div class="scroll-line"></div>
+ </div>
+</section>
diff --git a/themes/danixme/layouts/partials/nav.html b/themes/danixme/layouts/partials/nav.html
new file mode 100644
index 0000000..8a045d0
--- /dev/null
+++ b/themes/danixme/layouts/partials/nav.html
@@ -0,0 +1,45 @@
+<nav id="nav">
+ <a href="#hero" class="nav-logo">
+ {{- with .Site.Params.logo -}}
+ <img src="{{ . }}" alt="{{ $.Site.Params.handle }}" class="nav-logo-img">
+ {{- end -}}
+ <span class="nav-logo-text">{{ .Site.Params.handle }}<span class="nav-logo-tld">.me</span></span>
+ </a>
+
+ <div class="nav-right">
+ <ul class="nav-links">
+ <li><a href="#about">{{ i18n "nav_about" }}</a></li>
+ <li><a href="#services">{{ i18n "nav_services" }}</a></li>
+ <li><a href="#skills">{{ i18n "nav_skills" }}</a></li>
+ <li><a href="#education">{{ i18n "nav_education" }}</a></li>
+ <li><a href="#certifications">{{ i18n "nav_certs" }}</a></li>
+ <li><a href="#projects">{{ i18n "nav_projects" }}</a></li>
+ <li><a href="#contact">{{ i18n "nav_contact" }}</a></li>
+ <li class="nav-theme-item">
+ <button class="theme-toggle" aria-label="Toggle theme">
+ <i class="fa-solid fa-sun"></i>
+ <span class="theme-toggle-label"></span>
+ </button>
+ </li>
+ </ul>
+
+ <button class="theme-toggle theme-toggle--desktop" aria-label="Toggle theme">
+ <i class="fa-solid fa-sun"></i>
+ </button>
+
+ <div class="lang-toggle" role="group" aria-label="Language">
+ <span class="lang-btn active" title="{{ .Site.Language.LanguageName }}">
+ <span class="fi fi-{{ .Site.Language.Params.flag }}"></span>
+ </span>
+ {{- range .Translations -}}
+ <a href="{{ .RelPermalink }}" class="lang-btn" title="{{ .Language.LanguageName }}">
+ <span class="fi fi-{{ .Language.Params.flag }}"></span>
+ </a>
+ {{- end -}}
+ </div>
+
+ <button class="hamburger" aria-label="Toggle menu" aria-expanded="false">
+ <span></span><span></span><span></span>
+ </button>
+ </div>
+</nav>
diff --git a/themes/danixme/layouts/partials/projects.html b/themes/danixme/layouts/partials/projects.html
new file mode 100644
index 0000000..d9bc7d4
--- /dev/null
+++ b/themes/danixme/layouts/partials/projects.html
@@ -0,0 +1,93 @@
+<section id="projects">
+ <div class="container">
+ <div class="section-eyebrow reveal">{{ i18n "section_projects" }}</div>
+ <h2 class="section-title reveal">{{ i18n "projects_title" }}</h2>
+
+ {{ $lang := .Site.Language.Lang }}
+ {{ $count := add (len hugo.Data.projects) 1 }}{{/* +1 for the collab card */}}
+ {{ $wide := gt $count 3 }}
+
+ <div class="carousel-wrap" data-carousel>
+ <div class="projects-grid is-carousel{{ if $wide }} is-wide-carousel{{ end }}">
+ {{ range hugo.Data.projects }}
+ {{ $p := . }}
+ <div class="project-card reveal">
+ {{ with .image }}
+ <div class="project-img-wrap">
+ <img src="{{ . }}" alt="{{ $p.title }}" class="project-img">
+ </div>
+ {{ end }}
+
+ <div class="project-body">
+ <h3 class="project-title">{{ .title }}</h3>
+ <p class="project-subtitle">{{ if eq $lang "it" }}{{ .subtitle_it }}{{ else }}{{ .subtitle_en }}{{ end }}</p>
+
+ {{ with .tags }}
+ <div class="project-tags">
+ {{ range . }}<span class="project-tag">{{ . }}</span>{{ end }}
+ </div>
+ {{ end }}
+
+ <p class="project-desc">{{ if eq $lang "it" }}{{ .desc_it }}{{ else }}{{ .desc_en }}{{ end }}</p>
+
+ <div class="project-footer">
+ {{ with .url }}
+ <a href="{{ . }}" class="project-link" target="_blank" rel="noopener">
+ <i class="fa-solid fa-arrow-up-right-from-square"></i>
+ {{ i18n "project_visit" }}
+ </a>
+ {{ end }}
+
+ <div class="project-share">
+ {{ $title := .title }}
+ {{ $url := .url }}
+ {{ with $url }}
+ <a href="https://www.linkedin.com/sharing/share-offsite/?url={{ $url | urlize }}"
+ class="share-btn" target="_blank" rel="noopener" aria-label="Share on LinkedIn">
+ <i class="fa-brands fa-linkedin-in"></i>
+ </a>
+ <a href="https://x.com/intent/tweet?text={{ $title | urlize }}&url={{ $url | urlize }}"
+ class="share-btn" target="_blank" rel="noopener" aria-label="Share on X">
+ <i class="fa-brands fa-x-twitter"></i>
+ </a>
+ <a href="mailto:?subject={{ $title | urlize }}&body={{ $url | urlize }}"
+ class="share-btn" aria-label="Share via Email">
+ <i class="fa-solid fa-envelope"></i>
+ </a>
+ {{ end }}
+ </div>
+ </div>
+ </div>
+ </div>
+ {{ end }}
+
+ {{/* ── Collaboration placeholder card ── */}}
+ <div class="project-card project-card--collab reveal">
+ <div class="project-collab-icon">
+ <i class="fa-solid fa-handshake-angle"></i>
+ </div>
+ <div class="project-body">
+ <h3 class="project-title">{{ i18n "project_collab_title" }}</h3>
+ <p class="project-desc">{{ i18n "project_collab_desc" }}</p>
+ <div class="project-footer">
+ <a href="#contact" class="project-link">
+ <i class="fa-solid fa-paper-plane"></i>
+ {{ i18n "project_collab_cta" }}
+ </a>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ <div class="carousel-controls">
+ <button class="carousel-btn carousel-btn--prev" aria-label="{{ i18n "carousel_prev" }}">
+ <i class="fa-solid fa-chevron-left"></i>
+ </button>
+ <div class="carousel-dots"></div>
+ <button class="carousel-btn carousel-btn--next" aria-label="{{ i18n "carousel_next" }}">
+ <i class="fa-solid fa-chevron-right"></i>
+ </button>
+ </div>
+ </div>{{/* end carousel-wrap */}}
+ </div>
+</section>
diff --git a/themes/danixme/layouts/partials/scripts.html b/themes/danixme/layouts/partials/scripts.html
new file mode 100644
index 0000000..77e4583
--- /dev/null
+++ b/themes/danixme/layouts/partials/scripts.html
@@ -0,0 +1,11 @@
+<script>
+window.SITE_PHRASES = {{ .Params.typed_phrases | jsonify | safeJS }};
+window.FORM_I18N = {
+ success: {{ i18n "form_success" | jsonify | safeJS }},
+ error: {{ i18n "form_error" | jsonify | safeJS }},
+ error_fields: {{ i18n "form_error_fields" | jsonify | safeJS }}
+};
+</script>
+{{ $js := resources.Get "js/main.js" }}
+{{ if hugo.IsProduction }}{{ $js = $js | minify | fingerprint }}{{ end }}
+<script src="{{ $js.RelPermalink }}"></script>
diff --git a/themes/danixme/layouts/partials/services.html b/themes/danixme/layouts/partials/services.html
new file mode 100644
index 0000000..538fb29
--- /dev/null
+++ b/themes/danixme/layouts/partials/services.html
@@ -0,0 +1,24 @@
+<section id="services">
+ <div class="container">
+ <div class="section-eyebrow reveal">{{ i18n "section_services" }}</div>
+ <h2 class="section-title reveal">{{ i18n "services_title" }}</h2>
+
+ <div class="services-grid">
+ {{ $lang := .Site.Language.Lang }}
+ {{ range hugo.Data.services }}
+ <div class="service-card reveal">
+ <div class="service-icon"><i class="{{ .icon }}"></i></div>
+ <div class="service-title">
+ {{ if eq $lang "it" }}{{ .title_it }}{{ else }}{{ .title_en }}{{ end }}
+ </div>
+ <div class="service-subtitle">
+ {{ if eq $lang "it" }}{{ .subtitle_it }}{{ else }}{{ .subtitle_en }}{{ end }}
+ </div>
+ <div class="service-desc">
+ {{ if eq $lang "it" }}{{ .desc_it | markdownify }}{{ else }}{{ .desc_en | markdownify }}{{ end }}
+ </div>
+ </div>
+ {{ end }}
+ </div>
+ </div>
+</section>
diff --git a/themes/danixme/layouts/partials/skills.html b/themes/danixme/layouts/partials/skills.html
new file mode 100644
index 0000000..790bea3
--- /dev/null
+++ b/themes/danixme/layouts/partials/skills.html
@@ -0,0 +1,24 @@
+<section id="skills">
+ <div class="container">
+ <div class="section-eyebrow reveal">{{ i18n "section_skills" }}</div>
+ <h2 class="section-title reveal">{{ i18n "skills_title" }}</h2>
+
+ <div class="skills-grid reveal" id="skillsGrid">
+ {{ $lang := .Site.Language.Lang }}
+ {{ range hugo.Data.skills }}
+ <div class="skill-card">
+ <div class="skill-head">
+ <div class="skill-icon"><i class="{{ .icon }}"></i></div>
+ <div class="skill-name">
+ {{ if eq $lang "it" }}{{ .name_it }}{{ else }}{{ .name_en }}{{ end }}
+ </div>
+ </div>
+ <div class="skill-track"><div class="skill-fill" style="--w:{{ .level }}"></div></div>
+ <div class="skill-tags">
+ {{ range .tags }}<span class="tag">{{ . }}</span>{{ end }}
+ </div>
+ </div>
+ {{ end }}
+ </div>
+ </div>
+</section>
diff --git a/themes/danixme/layouts/robots.txt b/themes/danixme/layouts/robots.txt
new file mode 100644
index 0000000..da0727f
--- /dev/null
+++ b/themes/danixme/layouts/robots.txt
@@ -0,0 +1,4 @@
+User-agent: *
+Allow: /
+
+Sitemap: {{ .Site.BaseURL }}sitemap.xml
diff --git a/themes/danixme/theme.toml b/themes/danixme/theme.toml
new file mode 100644
index 0000000..738686a
--- /dev/null
+++ b/themes/danixme/theme.toml
@@ -0,0 +1,5 @@
+name = "danixme"
+license = "MIT"
+description = "Personal portfolio theme for danix.me"
+tags = ["portfolio", "cybersecurity", "minimal"]
+min_version = "0.120.0"