summaryrefslogtreecommitdiffstats
path: root/themes/danix-xyz-hacker/assets/js
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-04-22 12:43:22 +0200
committerDanilo M. <danix@danix.xyz>2026-04-22 12:43:22 +0200
commit5b476f8905f411768e23cb01d577a60e5a5fd725 (patch)
tree0a08cc83d809dbea714f52826e822501ee7c0165 /themes/danix-xyz-hacker/assets/js
parent082e9246ffe453031894d32d3cee9d5d1bf2b67a (diff)
downloaddanixxyz-5b476f8905f411768e23cb01d577a60e5a5fd725.tar.gz
danixxyz-5b476f8905f411768e23cb01d577a60e5a5fd725.zip
chore: extract theme into git submodule (danix2-hugo-theme)
Diffstat (limited to 'themes/danix-xyz-hacker/assets/js')
-rw-r--r--themes/danix-xyz-hacker/assets/js/article-lazy.js34
-rw-r--r--themes/danix-xyz-hacker/assets/js/code-copy.js79
-rw-r--r--themes/danix-xyz-hacker/assets/js/contact-form.js45
-rw-r--r--themes/danix-xyz-hacker/assets/js/form-components.js127
-rw-r--r--themes/danix-xyz-hacker/assets/js/fortune.js9
-rw-r--r--themes/danix-xyz-hacker/assets/js/matrix-rain.js157
-rw-r--r--themes/danix-xyz-hacker/assets/js/menu.js112
-rw-r--r--themes/danix-xyz-hacker/assets/js/not-found-page.js9
-rw-r--r--themes/danix-xyz-hacker/assets/js/reading-progress.js29
-rw-r--r--themes/danix-xyz-hacker/assets/js/search.js134
-rw-r--r--themes/danix-xyz-hacker/assets/js/tag-cloud-spiral.js122
-rw-r--r--themes/danix-xyz-hacker/assets/js/theme-toggle.js53
12 files changed, 0 insertions, 910 deletions
diff --git a/themes/danix-xyz-hacker/assets/js/article-lazy.js b/themes/danix-xyz-hacker/assets/js/article-lazy.js
deleted file mode 100644
index 64ca862..0000000
--- a/themes/danix-xyz-hacker/assets/js/article-lazy.js
+++ /dev/null
@@ -1,34 +0,0 @@
-document.addEventListener('DOMContentLoaded', function () {
- var timeline = document.querySelector('ol.timeline');
- if (!timeline) return;
-
- // Progressive enhancement: activates CSS hidden state
- timeline.classList.add('js-lazy-timeline');
-
- var items = Array.prototype.slice.call(
- timeline.querySelectorAll('.timeline-item')
- );
-
- function reveal(item) {
- item.classList.add('is-visible');
- }
-
- var observer = new IntersectionObserver(
- function (entries) {
- entries.forEach(function (entry) {
- if (entry.isIntersecting) {
- reveal(entry.target);
- observer.unobserve(entry.target);
- }
- });
- },
- {
- rootMargin: '-80px 0px 0px 0px',
- threshold: 0.12,
- }
- );
-
- items.forEach(function (item) {
- observer.observe(item);
- });
-});
diff --git a/themes/danix-xyz-hacker/assets/js/code-copy.js b/themes/danix-xyz-hacker/assets/js/code-copy.js
deleted file mode 100644
index 8591436..0000000
--- a/themes/danix-xyz-hacker/assets/js/code-copy.js
+++ /dev/null
@@ -1,79 +0,0 @@
-(function () {
- var LANG_NAMES = {
- bash: 'Shell', sh: 'Shell', shell: 'Shell', zsh: 'Shell',
- js: 'JavaScript', javascript: 'JavaScript',
- ts: 'TypeScript', typescript: 'TypeScript',
- go: 'Go',
- py: 'Python', python: 'Python',
- rs: 'Rust', rust: 'Rust',
- html: 'HTML',
- css: 'CSS',
- toml: 'TOML',
- yaml: 'YAML', yml: 'YAML',
- json: 'JSON',
- sql: 'SQL',
- md: 'Markdown', markdown: 'Markdown',
- c: 'C',
- cpp: 'C++', 'c++': 'C++',
- java: 'Java',
- php: 'PHP',
- ruby: 'Ruby', rb: 'Ruby',
- swift: 'Swift',
- kotlin: 'Kotlin', kt: 'Kotlin',
- dockerfile: 'Dockerfile',
- makefile: 'Makefile',
- text: 'Text', txt: 'Text',
- };
-
- function prettyName(lang) {
- if (!lang) return '';
- var key = lang.toLowerCase();
- return LANG_NAMES[key] || (lang.charAt(0).toUpperCase() + lang.slice(1));
- }
-
- function getCodeText(wrapper) {
- var el = wrapper.querySelector('.lntd:last-child code')
- || wrapper.querySelector('.code-body code')
- || wrapper.querySelector('.code-body pre');
- return el ? el.innerText : '';
- }
-
- function initBlock(wrapper) {
- var header = wrapper.querySelector('.code-header');
- if (header) {
- var label = wrapper.querySelector('.code-lang-label');
- if (label) label.textContent = prettyName(header.getAttribute('data-lang') || '');
- }
-
- var btn = wrapper.querySelector('[data-copy-target]');
- if (!btn) return;
-
- btn.addEventListener('click', function () {
- var text = getCodeText(wrapper);
- if (!text) return;
-
- navigator.clipboard.writeText(text).then(function () {
- var copyIcon = btn.querySelector('.icon-copy');
- var checkIcon = btn.querySelector('.icon-check');
- var liveRegion = wrapper.querySelector('.code-copy-status');
- if (copyIcon) copyIcon.classList.add('hidden');
- if (checkIcon) checkIcon.classList.remove('hidden');
- btn.classList.add('is-copied');
- if (liveRegion) liveRegion.textContent = 'Code copied to clipboard.';
-
- setTimeout(function () {
- if (copyIcon) copyIcon.classList.remove('hidden');
- if (checkIcon) checkIcon.classList.add('hidden');
- btn.classList.remove('is-copied');
- if (liveRegion) liveRegion.textContent = '';
- }, 2000);
- }).catch(function () {
- // silent fail for insecure contexts
- });
- });
- }
-
- document.addEventListener('DOMContentLoaded', function () {
- document.querySelectorAll('.code-block-wrapper').forEach(initBlock);
- });
-})();
diff --git a/themes/danix-xyz-hacker/assets/js/contact-form.js b/themes/danix-xyz-hacker/assets/js/contact-form.js
deleted file mode 100644
index 4fa8f55..0000000
--- a/themes/danix-xyz-hacker/assets/js/contact-form.js
+++ /dev/null
@@ -1,45 +0,0 @@
-document.addEventListener('alpine:init', () => {
- Alpine.data('contactForm', () => ({
- formData: {
- name: '',
- email: '',
- message: ''
- },
- isSubmitting: false,
- statusMessage: '',
- statusClass: '',
-
- async submitContactForm() {
- this.isSubmitting = true;
- this.statusMessage = '';
- this.statusClass = '';
-
- try {
- const response = await fetch('/contact.php', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(this.formData)
- });
-
- const data = await response.json();
-
- if (response.ok) {
- this.statusMessage = 'Message sent successfully!';
- this.statusClass = 'bg-green-100 text-green-800 border border-green-300';
- this.formData = { name: '', email: '', message: '' };
- } else {
- this.statusMessage = data.error || 'An error occurred. Please try again.';
- this.statusClass = 'bg-red-100 text-red-800 border border-red-300';
- }
- } catch (error) {
- this.statusMessage = 'An error occurred. Please try again.';
- this.statusClass = 'bg-red-100 text-red-800 border border-red-300';
- console.error('Form submission error:', error);
- } finally {
- this.isSubmitting = false;
- }
- }
- }));
-});
diff --git a/themes/danix-xyz-hacker/assets/js/form-components.js b/themes/danix-xyz-hacker/assets/js/form-components.js
deleted file mode 100644
index ffa4260..0000000
--- a/themes/danix-xyz-hacker/assets/js/form-components.js
+++ /dev/null
@@ -1,127 +0,0 @@
-// Form component utilities and Alpine.js data
-
-export function formComponentsData() {
- return {
- // Modal states
- showAlertModal: false,
- showConfirmModal: false,
- showContentModal: false,
-
- // Toast notification state
- toasts: [],
-
- // Handle confirm modal action
- handleConfirm() {
- this.showConfirmModal = false;
- this.showToast('success', 'Action confirmed!');
- },
-
- // Show toast notification
- showToast(type = 'success', message = null) {
- const messages = {
- success: 'Operation completed successfully!',
- error: 'An error occurred. Please try again.',
- info: 'Here is some information.',
- warning: 'Please be careful with this action.'
- };
-
- const toastMessage = message || messages[type] || messages.success;
- const toastId = Date.now();
-
- // Add toast to list
- this.toasts.push({
- id: toastId,
- type: type,
- message: toastMessage
- });
-
- // Auto-remove after 5 seconds
- setTimeout(() => {
- this.toasts = this.toasts.filter(t => t.id !== toastId);
- }, 5000);
- },
-
- // Remove toast manually
- removeToast(id) {
- this.toasts = this.toasts.filter(t => t.id !== id);
- },
-
- // Form validation utilities
- validateEmail(email) {
- const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- return regex.test(email);
- },
-
- validatePassword(password) {
- return password.length >= 8;
- },
-
- // Auto-expand textarea
- autoExpandTextarea(event) {
- const textarea = event.target;
- textarea.style.height = 'auto';
- textarea.style.height = (textarea.scrollHeight) + 'px';
- }
- };
-}
-
-// Toast container component for Alpine.js
-export function renderToastContainer(Alpine) {
- if (!Alpine) return;
-
- // This can be used in templates via Alpine
- window.formUtils = {
- formatCharCount(current, max) {
- if (max) {
- return `${current}/${max}`;
- }
- return current;
- },
-
- isCharCountWarning(current, max) {
- if (!max) return false;
- return current > (max * 0.8);
- },
-
- isCharCountError(current, max) {
- if (!max) return false;
- return current >= max;
- }
- };
-}
-
-// Focus Trap for Modals - Week 5
-function createFocusTrap(modalElement) {
- const focusableElements = modalElement.querySelectorAll(
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
- );
-
- if (focusableElements.length === 0) return;
-
- const firstElement = focusableElements[0];
- const lastElement = focusableElements[focusableElements.length - 1];
-
- modalElement.addEventListener('keydown', (e) => {
- if (e.key !== 'Tab') return;
-
- if (e.shiftKey) {
- // Shift + Tab
- if (document.activeElement === firstElement) {
- e.preventDefault();
- lastElement.focus();
- }
- } else {
- // Tab
- if (document.activeElement === lastElement) {
- e.preventDefault();
- firstElement.focus();
- }
- }
- });
-
- // Set initial focus
- firstElement.focus();
-}
-
-// Export for use in Alpine.js
-window.createFocusTrap = createFocusTrap;
diff --git a/themes/danix-xyz-hacker/assets/js/fortune.js b/themes/danix-xyz-hacker/assets/js/fortune.js
deleted file mode 100644
index d4f981b..0000000
--- a/themes/danix-xyz-hacker/assets/js/fortune.js
+++ /dev/null
@@ -1,9 +0,0 @@
-(function() {
- const el = document.getElementById('fortune-quote');
- if (!el) return;
- const quotes = JSON.parse(el.dataset.quotes);
- if (!quotes || quotes.length === 0) return;
- const q = quotes[Math.floor(Math.random() * quotes.length)];
- el.querySelector('.fortune-text').textContent = '"' + q.text + '"';
- el.querySelector('.fortune-author').textContent = '— ' + q.author;
-})();
diff --git a/themes/danix-xyz-hacker/assets/js/matrix-rain.js b/themes/danix-xyz-hacker/assets/js/matrix-rain.js
deleted file mode 100644
index 53c55d8..0000000
--- a/themes/danix-xyz-hacker/assets/js/matrix-rain.js
+++ /dev/null
@@ -1,157 +0,0 @@
-// Matrix rain background effect
-(function() {
- // Bail out if user prefers reduced motion
- if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
- return;
- }
-
- // Canvas and context
- let canvas = document.getElementById('matrix-rain');
- if (!canvas) return;
- const ctx = canvas.getContext('2d');
-
- // State
- let columns = [];
- let frameCount = 0;
- let colors = { accent: '#a855f7', accent2: '#00ff88', bg: '#060b10', head: '#ffffff' };
-
- // Character set: 30% ASCII, 70% katakana
- const ASCII = Array.from({ length: 94 }, (_, i) => String.fromCharCode(33 + i));
- const KATA = Array.from({ length: 96 }, (_, i) => String.fromCodePoint(0x30a0 + i));
- const CHARS = shuffle([...ASCII, ...KATA, ...KATA, ...KATA]);
-
- // Utility: shuffle array
- function shuffle(arr) {
- for (let i = arr.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [arr[i], arr[j]] = [arr[j], arr[i]];
- }
- return arr;
- }
-
- // Utility: convert hex or rgb color to rgba string
- function hexToRgba(color, alpha) {
- const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
- if (rgbMatch) {
- return `rgba(${rgbMatch[1]}, ${rgbMatch[2]}, ${rgbMatch[3]}, ${alpha})`;
- }
- const hex = color.replace('#', '');
- const r = parseInt(hex.substring(0, 2), 16);
- const g = parseInt(hex.substring(2, 4), 16);
- const b = parseInt(hex.substring(4, 6), 16);
- return `rgba(${r}, ${g}, ${b}, ${alpha})`;
- }
-
- // Sample CSS variables based on current theme
- function sampleColors() {
- const style = getComputedStyle(document.documentElement);
- const isDark = document.documentElement.classList.contains('theme-dark');
- colors.accent = style.getPropertyValue('--accent').trim();
- colors.accent2 = style.getPropertyValue('--accent2').trim();
- colors.bg = style.getPropertyValue('--bg').trim();
- // Head char: bright white in dark mode, deep purple-black in light mode
- colors.head = isDark ? '#ffffff' : '#1a0533';
- }
-
- // Resize canvas to window dimensions
- function resizeCanvas() {
- canvas.width = window.innerWidth;
- canvas.height = window.innerHeight;
- ctx.font = '14px "JetBrains Mono", monospace';
- ctx.textBaseline = 'top';
- initColumns();
- }
-
- // Initialize columns for the current canvas width
- function initColumns() {
- columns = [];
- const columnWidth = 14;
- const columnCount = Math.floor(canvas.width / columnWidth);
-
- for (let i = 0; i < columnCount; i++) {
- columns.push({
- x: i * columnWidth,
- y: -Math.floor(Math.random() * 40), // stagger start above viewport
- speed: 2 + Math.floor(Math.random() * 3), // 2-4 frames between drops
- color: Math.random() < 0.6 ? 'accent2' : 'accent', // 60% green, 40% purple
- charIndex: Math.floor(Math.random() * CHARS.length),
- length: 8 + Math.floor(Math.random() * 13), // trail length 8-20
- });
- }
- }
-
- // Set up MutationObserver for theme switching
- function setupThemeObserver() {
- const observer = new MutationObserver(function(mutations) {
- for (const m of mutations) {
- if (m.attributeName === 'class') {
- sampleColors();
- break;
- }
- }
- });
- observer.observe(document.documentElement, {
- attributes: true,
- attributeFilter: ['class'],
- });
- }
-
- // Main animation loop
- function drawFrame() {
- frameCount++;
-
- // Fade layer: semi-transparent background fill
- ctx.fillStyle = hexToRgba(colors.bg, 0.085);
- ctx.fillRect(0, 0, canvas.width, canvas.height);
-
- // Draw each column
- for (const col of columns) {
- // Skip if not time to drop yet (per-column throttle)
- if (frameCount % col.speed !== 0) continue;
-
- // Draw explicit trail in column color
- ctx.fillStyle = colors[col.color];
- for (let i = 1; i <= col.length; i++) {
- const trailY = (col.y - i) * 14;
- if (trailY < 0) continue;
- const trailCharIndex = (col.charIndex - i + CHARS.length) % CHARS.length;
- ctx.fillText(CHARS[trailCharIndex], col.x, trailY);
- }
-
- // Draw head character (bright)
- ctx.fillStyle = colors.head;
- const headCharIndex = col.charIndex % CHARS.length;
- ctx.fillText(CHARS[headCharIndex], col.x, col.y * 14);
-
- // Advance column
- col.y++;
- col.charIndex = (col.charIndex + 1) % CHARS.length;
-
- // Reset when scrolled off screen
- if (col.y * 14 > canvas.height + col.length * 14) {
- col.y = -Math.floor(Math.random() * 20);
- col.charIndex = Math.floor(Math.random() * CHARS.length);
- col.color = Math.random() < 0.6 ? 'accent2' : 'accent';
- }
- }
-
- requestAnimationFrame(drawFrame);
- }
-
- // Initialize
- sampleColors();
- resizeCanvas();
- setupThemeObserver();
-
- // Debounced resize handler
- let resizeTimer;
- window.addEventListener('resize', function() {
- clearTimeout(resizeTimer);
- resizeTimer = setTimeout(resizeCanvas, 150);
- });
-
- // Start animation when fonts are ready
- document.fonts.ready.then(function() {
- requestAnimationFrame(drawFrame);
- });
-})();
diff --git a/themes/danix-xyz-hacker/assets/js/menu.js b/themes/danix-xyz-hacker/assets/js/menu.js
deleted file mode 100644
index 3f32642..0000000
--- a/themes/danix-xyz-hacker/assets/js/menu.js
+++ /dev/null
@@ -1,112 +0,0 @@
-document.addEventListener('DOMContentLoaded', () => {
- const menuToggle = document.getElementById('menu-toggle');
- const menuOverlay = document.getElementById('menu-overlay');
- const menuPanel = document.getElementById('hamburger-menu');
-
- function openMenu() {
- if (!menuOverlay || !menuPanel) return;
-
- // Show overlay
- menuOverlay.classList.remove('opacity-0');
- menuOverlay.classList.remove('invisible');
-
- // Slide menu in
- menuPanel.classList.remove('translate-x-full');
-
- // Manage accessibility
- menuToggle.setAttribute('aria-expanded', 'true');
- menuPanel.removeAttribute('aria-hidden');
-
- // Control body overflow
- document.body.style.overflow = 'hidden';
-
- // Focus first focusable element in menu
- const firstFocusable = menuPanel.querySelector('a, button');
- if (firstFocusable) {
- setTimeout(() => firstFocusable.focus(), 50);
- }
- }
-
- function closeMenu() {
- if (!menuOverlay || menuOverlay.classList.contains('opacity-0')) return;
-
- // Hide overlay
- menuOverlay.classList.add('opacity-0');
- menuOverlay.classList.add('invisible');
-
- // Slide menu out
- menuPanel.classList.add('translate-x-full');
-
- // Manage accessibility
- menuToggle.setAttribute('aria-expanded', 'false');
- menuPanel.setAttribute('aria-hidden', 'true');
-
- // Restore body overflow
- document.body.style.overflow = '';
-
- // Return focus to toggle button
- menuToggle.focus();
- }
-
- function toggleMenu() {
- if (menuOverlay && menuOverlay.classList.contains('opacity-0')) {
- openMenu();
- } else {
- closeMenu();
- }
- }
-
- // Toggle menu when clicking the hamburger button
- if (menuToggle) {
- menuToggle.addEventListener('click', toggleMenu);
- }
-
- // Close menu when clicking on the overlay
- if (menuOverlay) {
- menuOverlay.addEventListener('click', (e) => {
- if (e.target === menuOverlay) {
- closeMenu();
- }
- });
- }
-
- // Close menu when clicking menu items
- const menuLinks = document.querySelectorAll('#hamburger-menu a, #hamburger-menu button');
- menuLinks.forEach(link => {
- link.addEventListener('click', closeMenu);
- });
-
- // Close menu on Escape key
- document.addEventListener('keydown', (e) => {
- if (e.key === 'Escape' && menuOverlay && !menuOverlay.classList.contains('opacity-0')) {
- closeMenu();
- }
- });
-
- // Focus trap: keep tab within menu when open
- if (menuPanel) {
- menuPanel.addEventListener('keydown', (e) => {
- if (e.key !== 'Tab') return;
-
- const focusableElements = menuPanel.querySelectorAll('a, button, [tabindex]:not([tabindex="-1"])');
- if (focusableElements.length === 0) return;
-
- const firstElement = focusableElements[0];
- const lastElement = focusableElements[focusableElements.length - 1];
- const isMenuOpen = !menuOverlay.classList.contains('opacity-0');
-
- if (!isMenuOpen) return;
-
- // Shift+Tab on first element: move to last
- if (e.shiftKey && document.activeElement === firstElement) {
- e.preventDefault();
- lastElement.focus();
- }
- // Tab on last element: move to first
- else if (!e.shiftKey && document.activeElement === lastElement) {
- e.preventDefault();
- firstElement.focus();
- }
- });
- }
-});
diff --git a/themes/danix-xyz-hacker/assets/js/not-found-page.js b/themes/danix-xyz-hacker/assets/js/not-found-page.js
deleted file mode 100644
index 2b4f676..0000000
--- a/themes/danix-xyz-hacker/assets/js/not-found-page.js
+++ /dev/null
@@ -1,9 +0,0 @@
-// 404 page: initialize shared notFoundPage Alpine component
-document.addEventListener('alpine:init', () => {
- // Ensure search index is preloaded on 404 page
- const notFoundElement = document.querySelector('[x-data*="notFoundPage"]');
- if (notFoundElement && notFoundElement.__x) {
- notFoundElement.__x.$data.init();
- }
- console.log('404 page initialized with shared search functionality');
-});
diff --git a/themes/danix-xyz-hacker/assets/js/reading-progress.js b/themes/danix-xyz-hacker/assets/js/reading-progress.js
deleted file mode 100644
index ee1192f..0000000
--- a/themes/danix-xyz-hacker/assets/js/reading-progress.js
+++ /dev/null
@@ -1,29 +0,0 @@
-// Reading progress bar for single pages/articles
-(function() {
- const progressBar = document.getElementById('reading-progress');
-
- if (!progressBar) return;
-
- function updateProgress() {
- const windowHeight = window.innerHeight;
- const documentHeight = document.documentElement.scrollHeight - windowHeight;
- const scrollProgress = documentHeight > 0 ? (window.scrollY / documentHeight) * 100 : 0;
- progressBar.style.width = scrollProgress + '%';
- }
-
- // Throttle the scroll event for better performance
- let ticking = false;
-
- window.addEventListener('scroll', function() {
- if (!ticking) {
- window.requestAnimationFrame(function() {
- updateProgress();
- ticking = false;
- });
- ticking = true;
- }
- }, false);
-
- // Initial call
- updateProgress();
-})();
diff --git a/themes/danix-xyz-hacker/assets/js/search.js b/themes/danix-xyz-hacker/assets/js/search.js
deleted file mode 100644
index 8fb6262..0000000
--- a/themes/danix-xyz-hacker/assets/js/search.js
+++ /dev/null
@@ -1,134 +0,0 @@
-// Lazy-load search index from JSON file (language-aware)
-async function loadSearchIndex() {
- if (window.searchIndex) {
- return window.searchIndex;
- }
- try {
- // Detect current language from URL
- const isItalian = window.location.pathname.startsWith('/it/');
- const indexPath = isItalian ? '/it/search-index.json' : '/search-index.json';
-
- const response = await fetch(indexPath);
- if (!response.ok) throw new Error('Failed to load search index');
- window.searchIndex = await response.json();
- return window.searchIndex;
- } catch (error) {
- console.error('Error loading search index:', error);
- return [];
- }
-}
-
-// Filter articles by query (case-insensitive, max 5 results)
-function filterArticles(query, articles) {
- if (!query.trim()) {
- return [];
- }
- const lowerQuery = query.toLowerCase();
- return articles
- .filter(article =>
- article.title.toLowerCase().includes(lowerQuery) ||
- article.summary.toLowerCase().includes(lowerQuery)
- )
- .slice(0, 5);
-}
-
-// Register Alpine.js components
-document.addEventListener('alpine:init', () => {
- // Desktop search modal component
- Alpine.data('searchOverlay', () => ({
- isOpen: false,
- searchQuery: '',
- filteredArticles: [],
- allArticles: [],
- indexLoaded: false,
-
- async open() {
- this.isOpen = true;
- await this.ensureIndexLoaded();
- this.$nextTick(() => {
- const input = this.$el.querySelector('#search-input-desktop');
- if (input) input.focus();
- });
- },
-
- close() {
- this.isOpen = false;
- this.searchQuery = '';
- this.filteredArticles = [];
- },
-
- async ensureIndexLoaded() {
- if (!this.indexLoaded) {
- this.allArticles = await loadSearchIndex();
- this.indexLoaded = true;
- }
- },
-
- filterArticles(query) {
- this.searchQuery = query;
- this.filteredArticles = filterArticles(query, this.allArticles);
- },
-
- handleEscape(event) {
- if (event.key === 'Escape') {
- this.close();
- }
- }
- }));
-
- // Mobile search component (integrated into hamburger menu)
- Alpine.data('mobileSearch', () => ({
- searchQuery: '',
- filteredArticles: [],
- allArticles: [],
- indexLoaded: false,
-
- async ensureIndexLoaded() {
- if (!this.indexLoaded) {
- this.allArticles = await loadSearchIndex();
- this.indexLoaded = true;
- }
- },
-
- filterArticles(query) {
- this.searchQuery = query;
- this.filteredArticles = filterArticles(query, this.allArticles);
- }
- }));
-
- // Refactored 404 page component
- Alpine.data('notFoundPage', () => ({
- showEasterEgg: false,
- searchQuery: '',
- filteredArticles: [],
- allArticles: [],
- indexLoaded: false,
-
- async init() {
- await this.ensureIndexLoaded();
- },
-
- async ensureIndexLoaded() {
- if (!this.indexLoaded) {
- this.allArticles = await loadSearchIndex();
- this.indexLoaded = true;
- }
- },
-
- filterArticles(query) {
- this.searchQuery = query;
- this.filteredArticles = filterArticles(query, this.allArticles);
- },
-
- toggleEasterEgg() {
- this.showEasterEgg = !this.showEasterEgg;
- },
-
- goToRandomArticle() {
- if (this.allArticles.length > 0) {
- const randomArticle = this.allArticles[Math.floor(Math.random() * this.allArticles.length)];
- window.location.href = randomArticle.url;
- }
- }
- }));
-});
diff --git a/themes/danix-xyz-hacker/assets/js/tag-cloud-spiral.js b/themes/danix-xyz-hacker/assets/js/tag-cloud-spiral.js
deleted file mode 100644
index bed4645..0000000
--- a/themes/danix-xyz-hacker/assets/js/tag-cloud-spiral.js
+++ /dev/null
@@ -1,122 +0,0 @@
-document.addEventListener('DOMContentLoaded', function () {
- var containers = document.querySelectorAll('[data-tag-cloud]');
- if (!containers.length) return;
-
- Array.prototype.forEach.call(containers, function (container) {
- if (container.offsetWidth < 400) return;
-
- var links = Array.prototype.slice.call(
- container.querySelectorAll('.tag-cloud-link')
- );
- if (!links.length) return;
-
- // Sort descending by weight (biggest first = placed near center)
- links.sort(function (a, b) {
- return parseFloat(b.dataset.weight) - parseFloat(a.dataset.weight);
- });
-
- // String hash → deterministic angle seed (0..2π)
- function hashAngle(str) {
- var h = 0;
- for (var i = 0; i < str.length; i++) {
- h = (h * 31 + str.charCodeAt(i)) & 0xffffffff;
- }
- return ((h >>> 0) / 0xffffffff) * 2 * Math.PI;
- }
-
- // AABB collision check
- function overlaps(a, b) {
- return !(
- a.right < b.left ||
- a.left > b.right ||
- a.bottom < b.top ||
- a.top > b.bottom
- );
- }
-
- var placed = [];
- var containerWidth = container.offsetWidth;
- var cx = containerWidth / 2;
-
- // Measure each tag before repositioning
- var sizes = links.map(function (link) {
- var rect = link.getBoundingClientRect();
- return { w: rect.width, h: rect.height };
- });
-
- // Switch container to relative positioning and remove flex layout
- container.style.position = 'relative';
- container.style.display = 'block';
- container.classList.remove('flex', 'flex-wrap');
-
- var padding = -2; // px gap between tags (negative allows ~2px edge overlap)
- var aStep = 0.2; // radians per spiral step
- var rScale = (containerWidth * 0.013); // spiral tightness
-
- var minTop = Infinity, maxBottom = -Infinity;
-
- links.forEach(function (link, i) {
- var w = sizes[i].w;
- var h = sizes[i].h;
- var seed = hashAngle(link.href);
- var theta = seed;
- var placed_rect;
-
- // Step along spiral until no collision
- for (var attempt = 0; attempt < 3000; attempt++) {
- var r = rScale * theta;
- var x = cx + r * Math.cos(theta) - w / 2;
- var y = r * Math.sin(theta) - h / 2;
-
- var candidate = { left: x, top: y, right: x + w, bottom: y + h };
- var collision = false;
-
- for (var j = 0; j < placed.length; j++) {
- var p = placed[j];
- var padded = {
- left: p.left - padding,
- top: p.top - padding,
- right: p.right + padding,
- bottom: p.bottom + padding
- };
- if (overlaps(candidate, padded)) {
- collision = true;
- break;
- }
- }
-
- if (!collision) {
- placed_rect = candidate;
- break;
- }
- theta += aStep;
- }
-
- if (!placed_rect) {
- // Fallback: just append to flow if spiral exhausted
- link.style.position = 'static';
- return;
- }
-
- placed.push(placed_rect);
-
- link.style.position = 'absolute';
- link.style.left = Math.round(placed_rect.left) + 'px';
- link.style.top = Math.round(placed_rect.top) + 'px';
-
- if (placed_rect.top < minTop) minTop = placed_rect.top;
- if (placed_rect.bottom > maxBottom) maxBottom = placed_rect.bottom;
- });
-
- // Normalize: shift all tags so topmost is at y=16px
- var offset = 16 - minTop;
- links.forEach(function (link) {
- if (link.style.position === 'absolute') {
- link.style.top = (parseInt(link.style.top) + offset) + 'px';
- }
- });
-
- // Set container height to fit all tags + 2rem bottom padding (32px)
- container.style.height = (maxBottom - minTop + 48) + 'px';
- });
-});
diff --git a/themes/danix-xyz-hacker/assets/js/theme-toggle.js b/themes/danix-xyz-hacker/assets/js/theme-toggle.js
deleted file mode 100644
index bb95b2a..0000000
--- a/themes/danix-xyz-hacker/assets/js/theme-toggle.js
+++ /dev/null
@@ -1,53 +0,0 @@
-document.addEventListener('DOMContentLoaded', function() {
- const themeToggle = document.getElementById('theme-toggle');
- const sunIcon = document.getElementById('theme-icon-sun');
- const moonIcon = document.getElementById('theme-icon-moon');
-
- function updateThemeIcon() {
- const isDark = document.documentElement.classList.contains('theme-dark');
- if (sunIcon && moonIcon) {
- if (isDark) {
- sunIcon.style.display = 'block';
- moonIcon.style.display = 'none';
- } else {
- sunIcon.style.display = 'none';
- moonIcon.style.display = 'block';
- }
- }
- }
-
- // Update icon on initial load
- if (sunIcon && moonIcon) {
- updateThemeIcon();
- }
-
- if (!themeToggle) {
- return;
- }
-
- themeToggle.addEventListener('click', function(e) {
- e.preventDefault();
-
- // Get current theme from html element
- const htmlElement = document.documentElement;
- const isDark = htmlElement.classList.contains('theme-dark');
- const newTheme = isDark ? 'light' : 'dark';
-
- // Remove both theme classes
- htmlElement.classList.remove('theme-light', 'theme-dark');
-
- // Add the new theme class
- htmlElement.classList.add(`theme-${newTheme}`);
-
- // Persist to localStorage
- localStorage.setItem('theme', newTheme);
-
- // Update icon display
- updateThemeIcon();
-
- // Update Feather Icons if available
- if (window.feather) {
- window.feather.replace();
- }
- });
-});