document.addEventListener('DOMContentLoaded', async () => { await loadComponents(); initScrollParallax(); initLightbox(); const urlParams = new URLSearchParams(window.location.search); const caseId = urlParams.get('id'); if (!caseId) { document.getElementById('main-content').innerHTML = '

Error: No case ID specified.

'; return; } await loadCaseData(caseId); attachLightboxToImages(); }); 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}}', '../'); document.querySelector('header').innerHTML = headerHtml; } const footerRes = await fetch('../components/footer.html'); if (footerRes.ok) { document.querySelector('footer').innerHTML = await footerRes.text(); } } catch (e) { console.error('Component load error', e); } } async function loadCaseData(id) { try { const response = await fetch('/api/collections/cases/records'); const data = await response.json(); const cases = data.items || []; const currentCase = cases.find(c => c.id === id); if (!currentCase) { document.getElementById('main-content').innerHTML = '

Error: Case not found.

'; return; } const backLink = document.getElementById('back-link'); if (backLink) { if (currentCase.category === 'magic') { backLink.href = '../magic.html'; backLink.innerHTML = '← BACK TO MAGIC'; } else { backLink.href = '../sec.html'; backLink.innerHTML = '← BACK TO SEC'; } } const titleElement = document.getElementById('case-title'); titleElement.innerText = currentCase.title || ''; if (currentCase.category === 'magic') { titleElement.classList.add('expert-title'); titleElement.classList.remove('text-gradient'); } document.title = (currentCase.title || 'Case Study') + ' | t3jfel SEC'; document.getElementById('case-tag').innerText = currentCase.tag || ''; const date = (currentCase.created || '').split(' ')[0]; document.getElementById('case-meta').innerText = 'DATE: ' + date + ' | ID: ' + currentCase.id; document.getElementById('case-intro').innerText = currentCase.intro || ''; const container = document.getElementById('steps-container'); container.innerHTML = ''; if (currentCase.category === 'magic' && currentCase.sections) { container.className = 'expert-container'; currentCase.sections.forEach(section => { let sectionHtml = '
' + '

' + escapeHtml(section.heading || '') + '

'; (section.content || []).forEach(block => { if (block.type === 'text') { sectionHtml += '

' + escapeHtml(block.value) + '

'; } else if (block.type === 'code') { const codeVal = Array.isArray(block.value) ? block.value.join('\n') : block.value; sectionHtml += '
' +
                            escapeHtml(codeVal) + '
'; } else if (block.type === 'image') { const imgPath = block.value.startsWith('http') ? block.value : '../' + block.value; sectionHtml += '
' + 'Documentation Image' + '
'; } }); sectionHtml += '
'; container.insertAdjacentHTML('beforeend', sectionHtml); }); } else { container.className = 'analysis-steps-container'; (currentCase.steps || []).forEach((step, idx) => { let bodyHtml = ''; if (step.body) { step.body.forEach(block => { if (block.type === 'text') { bodyHtml += '

' + escapeHtml(block.value) + '

'; } else if (block.type === 'code') { const codeVal = Array.isArray(block.value) ? block.value.join('\n') : block.value; bodyHtml += '
' + escapeHtml(codeVal) + '
'; } else if (block.type === 'image') { const imgPath = block.value.startsWith('http') ? block.value : '../' + block.value; bodyHtml += 'Step image'; } }); } const num = String(idx + 1).padStart(2, '0'); const stepHtml = '
' + '
' + 'Step ' + num + '' + '

' + escapeHtml(step.title || '') + '

' + '
' + '
' + bodyHtml + '
' + '
'; container.insertAdjacentHTML('beforeend', stepHtml); }); } } catch (e) { console.error(e); } } var lb = { scale: 1, tx: 0, ty: 0 }; function initLightbox() { const overlay = document.createElement('div'); overlay.id = 'lightbox-overlay'; overlay.innerHTML = '' + ''; document.body.appendChild(overlay); const img = document.getElementById('lightbox-img'); function applyTransform() { img.style.transform = 'translate(' + lb.tx + 'px, ' + lb.ty + 'px) scale(' + lb.scale + ')'; } overlay.addEventListener('click', function (e) { if (e.target === overlay || e.target.id === 'lightbox-content') { closeLightbox(); } }); document.getElementById('lightbox-close').addEventListener('click', closeLightbox); overlay.addEventListener('wheel', function (e) { e.preventDefault(); const rect = img.getBoundingClientRect(); const dx = e.clientX - rect.left; const dy = e.clientY - rect.top; const factor = e.deltaY < 0 ? 1.12 : 0.9; const newScale = Math.min(Math.max(lb.scale * factor, 0.15), 10); const ratio = newScale / lb.scale; lb.tx = lb.tx + dx * (1 - ratio); lb.ty = lb.ty + dy * (1 - ratio); lb.scale = newScale; applyTransform(); }, { passive: false }); document.addEventListener('keydown', function (e) { if (overlay.classList.contains('active') && e.key === 'Escape') { closeLightbox(); } }); } function openLightbox(src) { lb.scale = 1; lb.tx = 0; lb.ty = 0; const img = document.getElementById('lightbox-img'); img.src = src; img.style.transform = ''; document.getElementById('lightbox-overlay').classList.add('active'); document.body.style.overflow = 'hidden'; } function closeLightbox() { document.getElementById('lightbox-overlay').classList.remove('active'); document.body.style.overflow = ''; } function attachLightboxToImages() { document.querySelectorAll('.lightbox-trigger').forEach(function (img) { img.addEventListener('click', function () { openLightbox(img.src); }); }); } 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, '''); }