document.addEventListener('DOMContentLoaded', () => { loadComponents().then(() => { initSkillIssueTicker(); initCryptoCopier(); }); loadRecentPosts(); initTypingEffect(); initScrollReveal(); initScrollParallax(); if (document.getElementById('sec-grid')) { initSearchableGrid('sec', 'sec-grid', 'sec-search'); } if (document.getElementById('magic-grid')) { initSearchableGrid('magic', 'magic-grid', 'magic-search'); } }); function initScrollParallax() { if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; const root = document.documentElement; let scrollY = 0; let ticking = false; function update() { root.style.setProperty('--scroll', scrollY); ticking = false; } window.addEventListener('scroll', () => { scrollY = window.scrollY || window.pageYOffset || 0; if (!ticking) { requestAnimationFrame(update); ticking = true; } }, { passive: true }); } async function loadComponents() { try { const headerRes = await fetch('components/header.html'); if (headerRes.ok) { let headerHtml = await headerRes.text(); headerHtml = headerHtml.replaceAll('{{prefix}}', ''); const headerEl = document.querySelector('header'); if (headerEl) headerEl.innerHTML = headerHtml; } const footerRes = await fetch('components/footer.html'); if (footerRes.ok) { const footerEl = document.querySelector('footer'); if (footerEl) footerEl.innerHTML = await footerRes.text(); } } catch (e) { console.error('Component load error:', e); } } function initScrollReveal() { const sections = document.querySelectorAll('main > section'); sections.forEach(section => { if (!section.classList.contains('hero')) { section.classList.add('reveal'); } }); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('in-view'); observer.unobserve(entry.target); } }); }, { threshold: 0.12, rootMargin: '0px 0px -60px 0px' }); document.querySelectorAll('.reveal, .reveal-stagger, main > section').forEach(el => { observer.observe(el); }); } function attachStaggerObserver(root) { if (!root) return; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('in-view'); observer.unobserve(entry.target); } }); }, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' }); observer.observe(root); } function initTypingEffect() { const el = document.querySelector('.hero-typed'); if (!el) return; const text = "My Computer Science notes, mostly focused on trying to master cybersecurity."; let index = 0; function type() { if (index < text.length) { el.textContent += text[index]; index++; setTimeout(type, 28 + Math.random() * 35); } else { el.classList.add('done'); } } setTimeout(type, 700); } async function loadRecentPosts() { const container = document.getElementById('post-container'); if (!container) return; try { const response = await fetch('/api/collections/cases/records?sort=-created'); if (!response.ok) throw new Error('Status: ' + response.status); const data = await response.json(); const posts = (data.items || []).slice(0, 6); if (posts.length === 0) { container.innerHTML = '

No posts yet.

'; return; } container.classList.add('reveal-stagger'); container.innerHTML = posts.map(post => { const linkUrl = 'walkthroughs/view.html?id=' + post.id; const tagStyle = post.category === 'magic' ? 'color: #a855f7; background: rgba(168, 85, 247, 0.1);' : ''; const date = (post.created || '').split(' ')[0]; return ` ${escapeHtml(date)}

${escapeHtml(post.title || '')}

${escapeHtml(post.summary || '')}

`; }).join(''); attachStaggerObserver(container); } catch (e) { console.error('Failed to sync data:', e); container.innerHTML = '

Failed to load posts.

'; } } async function initSearchableGrid(category, gridId, searchId) { const grid = document.getElementById(gridId); const searchInput = document.getElementById(searchId); if (!grid) return; try { const response = await fetch('/api/collections/cases/records?sort=-created'); if (!response.ok) throw new Error('Status: ' + response.status); const data = await response.json(); const allPosts = data.items || []; const categoryPosts = allPosts.filter(p => p.category === category); const render = (items) => { grid.classList.add('reveal-stagger'); grid.classList.remove('in-view'); if (items.length === 0) { grid.classList.remove('reveal-stagger'); grid.innerHTML = '

No results found.

'; return; } grid.innerHTML = items.map(post => { let imageHtml = ''; let cardClass = 'sec-card'; const date = (post.created || '').split(' ')[0]; if (category === 'magic' && post.thumbnail) { imageHtml = `
`; cardClass += ' has-image'; } return ` ${imageHtml}

${escapeHtml(post.title || '')}

[${escapeHtml(post.tag || '')}] ${escapeHtml(date)}
`; }).join(''); requestAnimationFrame(() => grid.classList.add('in-view')); }; render(categoryPosts); if (searchInput) { searchInput.addEventListener('input', (e) => { const term = e.target.value.toLowerCase(); const filtered = categoryPosts.filter(post => (post.title || '').toLowerCase().includes(term) || (post.tag || '').toLowerCase().includes(term) || (post.summary || '').toLowerCase().includes(term) ); render(filtered); }); } } catch (e) { console.error('Grid error:', e); grid.innerHTML = '

Failed to load.

'; } } function initSkillIssueTicker() { const tickerContainer = document.getElementById('skill-issue-feed'); if (!tickerContainer) return; const messages = [ "Zero Days found: 0", "Imposter Syndrome kicking in", "Pigeons are liars", "CERTIFICATIONS: Trust me bro", "The S in IOT stand for Security.", "Must be a layer 8 issue.", "The best way to secure a computer is to turn it off and never power it on.", "And then?" ]; const content = [...messages, ...messages, ...messages] .map(msg => `${escapeHtml(msg)}`) .join(''); tickerContainer.innerHTML = content; } function initCryptoCopier() { document.querySelectorAll('.crypto-addr').forEach(addr => { addr.addEventListener('click', async () => { const originalText = addr.innerText; const cleanAddress = originalText.replace(/\s/g, ''); try { await navigator.clipboard.writeText(cleanAddress); addr.classList.add('copied'); addr.innerText = '[ ADDRESS COPIED ]'; setTimeout(() => { addr.classList.remove('copied'); addr.innerText = originalText; }, 1500); } catch (err) { console.error('Copy failed:', err); } }); }); } function escapeHtml(str) { if (str == null) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function escapeAttr(str) { if (str == null) return ''; return String(str).replace(/"/g, '"').replace(/'/g, '''); }