1
0
Files
test.br0tcraft.de/article.html
2026-08-20 20:36:47 +02:00

378 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BR0TCRAFT | Artikel</title>
<link rel="stylesheet" href="style.css">
<link
href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;600&family=Inter:wght@300;400;600;800&family=Orbitron:wght@700&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
</head>
<body>
<nav class="navbar">
<div class="brand">BR0TCRAFT</div>
<div class="nav-links">
<a href="index.html">Home</a>
<a href="projects.html">Projekte</a>
<a href="tutorials.html" class="active">Dev Notes</a>
<a href="about.html">About</a>
<a href="assets.html">Assets</a>
</div>
</nav>
<div class="sidebar-overlay" id="overlay"></div>
<button class="mobile-menu-btn" id="menuToggle">
<span></span>
</button>
<div class="container-docs">
<div class="docs-layout">
<aside class="docs-sidebar" id="sidebar">
<button class="sidebar-toggle-desktop" id="sidebarCollapseBtn">
<span>&larr;</span> Sidebar einklappen
</button>
<div class="sidebar-group">
<h4>Kapitel</h4>
<div id="auto-toc"></div>
</div>
</aside>
<button class="sidebar-collapsed-trigger" id="sidebarExpandBtn" title="Sidebar zeigen">
&rarr;
</button>
<main class="section">
<div id="markdown-content">
<p style="color: #444; font-family: var(--font-code); font-size: 0.85rem;">Lade Inhalt...</p>
</div>
<div id="doc-nav" class="doc-pagination"></div>
<div style="margin-top: 4rem; border-top: 1px solid #1e1e1e; padding-top: 2rem; display: flex; gap: 0.75rem;">
<a href="tutorials.html" class="btn">&larr; Dev Notes</a>
<a href="index.html" class="btn"
style="background: transparent; border: 1px solid #222; color: var(--text-muted);">Home</a>
</div>
</main>
</div>
</div>
<script>
const urlParams = new URLSearchParams(window.location.search);
const fileParam = urlParams.get('file');
const docParam = urlParams.get('doc');
const partParam = urlParams.get('part');
const contentDiv = document.getElementById('markdown-content');
const toc = document.getElementById('auto-toc');
let docManifest = null;
// --- 1. Tabs System ---
function initTabs() {
document.querySelectorAll('.code-tabs').forEach((group, gIdx) => {
const wrapper = document.createElement('div');
wrapper.className = 'tab-wrapper';
const headerRow = document.createElement('div');
headerRow.className = 'tab-header-row';
wrapper.appendChild(headerRow);
const children = Array.from(group.children);
let tabCount = 0;
children.forEach((child) => {
if (child.tagName.startsWith('H')) {
const btn = document.createElement('button');
btn.className = 'tab-trigger';
btn.textContent = child.textContent;
btn.dataset.target = `panel-${gIdx}-${tabCount}`;
if (tabCount === 0) btn.classList.add('active');
btn.onclick = (e) => {
wrapper.querySelectorAll('.tab-trigger').forEach(b => b.classList.remove('active'));
wrapper.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
e.target.classList.add('active');
wrapper.querySelector(`#${e.target.dataset.target}`).classList.add('active');
};
headerRow.appendChild(btn);
} else if (child.tagName === 'PRE') {
const panel = document.createElement('div');
panel.className = 'tab-panel';
panel.id = `panel-${gIdx}-${tabCount}`;
if (tabCount === 0) panel.classList.add('active');
panel.appendChild(child.cloneNode(true));
wrapper.appendChild(panel);
tabCount++;
}
});
group.parentNode.replaceChild(wrapper, group);
});
}
// --- 2. Code Headers & Copy Button ---
function enhanceCodeBlocks() {
hljs.highlightAll();
document.querySelectorAll('pre code').forEach((block) => {
const parent = block.parentElement;
if (parent.querySelector('.code-header')) return;
let lang = "CODE";
block.classList.forEach(c => { if (c.startsWith('language-')) lang = c.split('-')[1].toUpperCase(); });
const header = document.createElement('div');
header.className = 'code-header';
const copyBtn = document.createElement('button');
copyBtn.className = 'copy-btn';
copyBtn.innerHTML = `<span>Copy</span>`;
copyBtn.onclick = () => {
navigator.clipboard.writeText(block.innerText);
copyBtn.innerHTML = `<span style="color:var(--accent-primary)">Copied!</span>`;
setTimeout(() => copyBtn.innerHTML = `<span>Copy</span>`, 2000);
};
header.innerHTML = `<span class="lang-tag">${lang}</span>`;
header.appendChild(copyBtn);
parent.insertBefore(header, block);
});
}
// --- 3. Sidebar Generators ---
// Auto-generate TOC from headers (for single-file mode)
function generateAutoTOC() {
const headers = contentDiv.querySelectorAll('h2, h3');
headers.forEach(h => {
const link = document.createElement('a');
link.className = 'sidebar-link';
link.href = '#' + h.id;
link.textContent = h.textContent.replace('#', '').trim();
if (h.tagName === 'H3') link.style.paddingLeft = '25px';
toc.appendChild(link);
});
}
// Generate Sidebar from Document Manifest
function renderDocSidebar(chapters, container, depth = 0) {
chapters.forEach(chapter => {
if (chapter.children) {
const groupTitle = document.createElement('div');
groupTitle.className = 'sidebar-category';
groupTitle.textContent = chapter.title;
groupTitle.style.paddingLeft = (depth * 15) + 'px';
const childGroup = document.createElement('div');
childGroup.className = 'sidebar-child-group';
// Check if current part is in this group to keep it open
const hasActiveChild = (list) => list.some(c => c.id === partParam || (c.children && hasActiveChild(c.children)));
if (!hasActiveChild(chapter.children)) {
// groupTitle.classList.add('collapsed');
// childGroup.classList.add('collapsed');
}
groupTitle.onclick = () => {
groupTitle.classList.toggle('collapsed');
childGroup.classList.toggle('collapsed');
};
container.appendChild(groupTitle);
container.appendChild(childGroup);
renderDocSidebar(chapter.children, childGroup, depth + 1);
} else {
const link = document.createElement('a');
link.className = 'sidebar-link';
if (partParam === chapter.id || (!partParam && depth === 0 && chapters.indexOf(chapter) === 0)) {
link.classList.add('active');
}
link.href = `?doc=${docParam}&part=${chapter.id}`;
link.textContent = chapter.title;
link.style.paddingLeft = (depth * 15 + 15) + 'px';
container.appendChild(link);
}
});
}
// --- Anchor Links ---
function addAnchorLinks() {
contentDiv.querySelectorAll('h1, h2, h3').forEach(h => {
const slug = h.textContent.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)+/g, '');
h.id = slug;
const a = document.createElement('a');
a.className = 'anchor-link';
a.href = '#' + slug;
a.innerHTML = '#';
h.insertBefore(a, h.firstChild);
});
}
// --- Navigation ---
const menuBtn = document.getElementById('menuToggle');
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('overlay');
const docsLayout = document.querySelector('.docs-layout');
function toggleMenu() {
menuBtn.classList.toggle('active');
sidebar.classList.toggle('active');
overlay.classList.toggle('active');
}
menuBtn.addEventListener('click', toggleMenu);
overlay.addEventListener('click', toggleMenu);
// Desktop Toggle
document.getElementById('sidebarCollapseBtn')?.addEventListener('click', () => {
docsLayout.classList.add('sidebar-collapsed');
});
document.getElementById('sidebarExpandBtn')?.addEventListener('click', () => {
docsLayout.classList.remove('sidebar-collapsed');
});
document.addEventListener('click', (e) => {
if (e.target.classList.contains('sidebar-link') && window.innerWidth <= 1024) {
toggleMenu();
}
});
// --- Update page title ---
function updateTitle() {
const h1 = contentDiv.querySelector('h1');
if (h1) {
document.title = 'BR0TCRAFT | ' + h1.textContent.replace('#', '').trim();
}
}
// --- Loading Logic ---
function loadMarkdown(path) {
fetch(path)
.then(r => {
if (!r.ok) throw new Error('Datei nicht gefunden');
return r.text();
})
.then(md => {
contentDiv.innerHTML = marked.parse(md);
updateTitle();
addAnchorLinks();
initTabs();
enhanceCodeBlocks();
if (!docParam) generateAutoTOC();
window.scrollTo(0, 0);
})
.catch(() => {
contentDiv.innerHTML = `<p style="color:#555; font-family:var(--font-code); font-size:0.85rem;">
Inhalt konnte nicht geladen werden.
</p>`;
});
}
// --- Navigation Footer ---
function renderPagination() {
if (!docManifest) return;
const nav = document.getElementById('doc-nav');
nav.innerHTML = '';
// Flatten structure to get linear sequence
const flat = [];
const flatten = (list) => {
list.forEach(item => {
if (item.file) flat.push(item);
if (item.children) flatten(item.children);
});
};
flatten(docManifest.chapters);
const currentIndex = flat.findIndex(item => item.id === (partParam || flat[0].id));
if (currentIndex > 0) {
const prev = flat[currentIndex - 1];
const a = document.createElement('a');
a.href = `?doc=${docParam}&part=${prev.id}`;
a.className = 'nav-page-btn prev';
a.innerHTML = `<span class="nav-label">Zurück</span><span class="nav-title">${prev.title}</span>`;
nav.appendChild(a);
} else {
nav.appendChild(document.createElement('div')); // Spacer
}
if (currentIndex < flat.length - 1) {
const next = flat[currentIndex + 1];
const a = document.createElement('a');
a.href = `?doc=${docParam}&part=${next.id}`;
a.className = 'nav-page-btn next';
a.innerHTML = `<span class="nav-label">Weiter</span><span class="nav-title">${next.title}</span>`;
nav.appendChild(a);
}
}
if (docParam) {
const manifestPath = `content/${docParam}/index.json`;
fetch(manifestPath)
.then(r => {
if (!r.ok) throw new Error('Manifest nicht gefunden');
return r.json();
})
.then(data => {
docManifest = data;
document.querySelector('#sidebar h4').textContent = data.title || "Kapitel";
renderDocSidebar(data.chapters, toc);
let targetFile = null;
const findPart = (list) => {
for (const item of list) {
if (item.id === partParam) { targetFile = item.file; break; }
if (item.children) findPart(item.children);
if (targetFile) break;
}
};
if (partParam) {
findPart(data.chapters);
} else {
const first = data.chapters[0];
targetFile = first.children ? first.children[0].file : first.file;
}
if (targetFile) {
loadMarkdown(`content/${docParam}/${targetFile}`);
renderPagination();
}
})
.catch(err => {
contentDiv.innerHTML = `<div style="background: rgba(255,77,77,0.05); border: 1px solid var(--accent-primary); padding: 2rem; border-radius: 12px; margin-top: 2rem;">
<h3 style="color: var(--accent-primary); margin-top: 0;">Ladefehler</h3>
<p style="color:#aaa; font-size:0.9rem;">Die Dokumentation <b>${docParam}</b> konnte nicht geladen werden.</p>
<ul style="color:#777; font-size:0.8rem; margin-top: 1rem;">
<li>Prüfe, ob du die Seite über einen <b>Webserver</b> aufrufst (nicht nur lokal öffnen).</li>
<li>Prüfe, ob die Datei <code>content/${docParam}/index.json</code> existiert.</li>
</ul>
</div>`;
});
} else if (fileParam) {
loadMarkdown(fileParam);
} else {
contentDiv.innerHTML = `<p style="color:#555; font-family:var(--font-code); font-size:0.85rem;">
Kein Artikel angegeben.
</p>`;
}
</script>
</body>
</html>