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 +=
'
' +
'
 + ')
' +
'
';
}
});
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 += '
';
}
});
}
const num = String(idx + 1).padStart(2, '0');
const stepHtml =
'' +
'' +
'' + 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 =
'' +
'
![Enlarged image]()
' +
'
' +
'';
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, ''');
}